Skip to content

Commit 168fe4a

Browse files
authored
Merge pull request github#18543 from owen-mc/go/misc-improvements-rs-cors
Go: miscellaneous improvements rs cors models
2 parents 54efb0a + d472dfe commit 168fe4a

File tree

7 files changed

+188
-87
lines changed

7 files changed

+188
-87
lines changed

go/ql/lib/semmle/go/frameworks/GinCors.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ module GinCors {
128128

129129
GinConfig() {
130130
this = v.getBaseVariable().getSourceVariable() and
131-
exists(Type t | t.hasQualifiedName(packagePath(), "Config") | v.getType() = t)
131+
v.getType().hasQualifiedName(packagePath(), "Config")
132132
}
133133

134134
/**
Lines changed: 27 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,52 @@
1-
/**
2-
* Provides classes for modeling the `github.com/rs/cors` package.
3-
*/
1+
/** Provides classes for modeling the `github.com/rs/cors` package. */
42

53
import go
64

7-
/**
8-
* An abstract class for modeling the Go CORS handler model origin write.
9-
*/
5+
/** An abstract class for modeling the Go CORS handler model origin write. */
106
abstract class UniversalOriginWrite extends DataFlow::ExprNode {
11-
/**
12-
* Get config variable holding header values
13-
*/
7+
/** Gets the config variable holding header values. */
148
abstract DataFlow::Node getBase();
159

16-
/**
17-
* Get config variable holding header values
18-
*/
10+
/** Gets the config variable holding header values. */
1911
abstract Variable getConfig();
2012
}
2113

2214
/**
23-
* An abstract class for modeling the Go CORS handler model allow all origins write.
15+
* An abstract class for modeling the Go CORS handler model allow all origins
16+
* write.
2417
*/
2518
abstract class UniversalAllowAllOriginsWrite extends DataFlow::ExprNode {
26-
/**
27-
* Get config variable holding header values
28-
*/
19+
/** Gets the config variable holding header values. */
2920
abstract DataFlow::Node getBase();
3021

31-
/**
32-
* Get config variable holding header values
33-
*/
22+
/** Gets the config variable holding header values. */
3423
abstract Variable getConfig();
3524
}
3625

3726
/**
38-
* An abstract class for modeling the Go CORS handler model allow credentials write.
27+
* An abstract class for modeling the Go CORS handler model allow credentials
28+
* write.
3929
*/
4030
abstract class UniversalAllowCredentialsWrite extends DataFlow::ExprNode {
41-
/**
42-
* Get config struct holding header values
43-
*/
31+
/** Gets the config struct holding header values. */
4432
abstract DataFlow::Node getBase();
4533

46-
/**
47-
* Get config variable holding header values
48-
*/
34+
/** Gets the config variable holding header values. */
4935
abstract Variable getConfig();
5036
}
5137

52-
/**
53-
* Provides classes for modeling the `github.com/rs/cors` package.
54-
*/
38+
/** Provides classes for modeling the `github.com/rs/cors` package. */
5539
module RsCors {
5640
/** Gets the package name `github.com/gin-gonic/gin`. */
5741
string packagePath() { result = package("github.com/rs/cors", "") }
5842

59-
/**
60-
* A new function create a new rs Handler
61-
*/
43+
/** The `New` function that creates a new rs Handler. */
6244
class New extends Function {
6345
New() { exists(Function f | f.hasQualifiedName(packagePath(), "New") | this = f) }
6446
}
6547

6648
/**
67-
* A write to the value of Access-Control-Allow-Credentials header
49+
* A write to the value of Access-Control-Allow-Credentials header.
6850
*/
6951
class AllowCredentialsWrite extends UniversalAllowCredentialsWrite {
7052
DataFlow::Node base;
@@ -77,14 +59,10 @@ module RsCors {
7759
)
7860
}
7961

80-
/**
81-
* Get options struct holding header values
82-
*/
62+
/** Gets the options struct holding header values. */
8363
override DataFlow::Node getBase() { result = base }
8464

85-
/**
86-
* Get options variable holding header values
87-
*/
65+
/** Gets the options variable holding header values. */
8866
override RsOptions getConfig() {
8967
exists(RsOptions gc |
9068
(
@@ -97,9 +75,7 @@ module RsCors {
9775
}
9876
}
9977

100-
/**
101-
* A write to the value of Access-Control-Allow-Origins header
102-
*/
78+
/** A write to the value of Access-Control-Allow-Origins header. */
10379
class AllowOriginsWrite extends UniversalOriginWrite {
10480
DataFlow::Node base;
10581

@@ -111,14 +87,10 @@ module RsCors {
11187
)
11288
}
11389

114-
/**
115-
* Get options struct holding header values
116-
*/
90+
/** Gets the options struct holding header values. */
11791
override DataFlow::Node getBase() { result = base }
11892

119-
/**
120-
* Get options variable holding header values
121-
*/
93+
/** Gets the options variable holding header values. */
12294
override RsOptions getConfig() {
12395
exists(RsOptions gc |
12496
(
@@ -132,7 +104,8 @@ module RsCors {
132104
}
133105

134106
/**
135-
* A write to the value of Access-Control-Allow-Origins of value "*", overriding AllowOrigins
107+
* A write to the value of Access-Control-Allow-Origins of value "*",
108+
* overriding `AllowOrigins`.
136109
*/
137110
class AllowAllOriginsWrite extends UniversalAllowAllOriginsWrite {
138111
DataFlow::Node base;
@@ -145,14 +118,10 @@ module RsCors {
145118
)
146119
}
147120

148-
/**
149-
* Get options struct holding header values
150-
*/
121+
/** Gets the options struct holding header values. */
151122
override DataFlow::Node getBase() { result = base }
152123

153-
/**
154-
* Get options variable holding header values
155-
*/
124+
/** Gets options variable holding header values. */
156125
override RsOptions getConfig() {
157126
exists(RsOptions gc |
158127
(
@@ -165,20 +134,16 @@ module RsCors {
165134
}
166135
}
167136

168-
/**
169-
* A variable of type Options that holds the headers to be set.
170-
*/
137+
/** A variable of type Options that holds the headers to be set. */
171138
class RsOptions extends Variable {
172139
SsaWithFields v;
173140

174141
RsOptions() {
175142
this = v.getBaseVariable().getSourceVariable() and
176-
exists(Type t | t.hasQualifiedName(packagePath(), "Options") | v.getType() = t)
143+
v.getType().hasQualifiedName(packagePath(), "Options")
177144
}
178145

179-
/**
180-
* Get variable declaration of Options
181-
*/
146+
/** Gets the SSA variable declaration of Options. */
182147
SsaWithFields getV() { result = v }
183148
}
184149
}

go/ql/test/experimental/CWE-942/CorsGin.go

Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ import (
88
"github.com/gin-gonic/gin"
99
)
1010

11-
/*
12-
** Function is vulnerable due to AllowAllOrigins = true aka Access-Control-Allow-Origin: null
13-
*/
14-
func vunlnerable() {
11+
// Function is vulnerable due to AllowAllOrigins = true aka Access-Control-Allow-Origin: null
12+
func vulnerable1() {
1513
router := gin.Default()
1614
// CORS for https://foo.com and null
1715
// - PUT and PATCH methods
@@ -25,17 +23,38 @@ func vunlnerable() {
2523
AllowCredentials: true,
2624
MaxAge: 12 * time.Hour,
2725
}
28-
config_vulnerable.AllowOrigins = []string{"null", "https://foo.com"}
26+
config_vulnerable.AllowOrigins = []string{"null", "https://foo.com"} // $ Alert
2927
router.Use(cors.New(config_vulnerable))
3028
router.GET("/", func(c *gin.Context) {
3129
c.String(http.StatusOK, "hello world")
3230
})
3331
router.Run()
3432
}
3533

36-
/*
37-
** Function is safe due to hardcoded origin and AllowCredentials: true
38-
*/
34+
// Function is vulnerable due to AllowAllOrigins = true aka Access-Control-Allow-Origin: null
35+
func vulnerable2() {
36+
router := gin.Default()
37+
// CORS for https://foo.com and null
38+
// - PUT and PATCH methods
39+
// - Origin header
40+
// - Credentials share
41+
// - Preflight requests cached for 12 hours
42+
config_vulnerable := cors.Config{
43+
AllowMethods: []string{"PUT", "PATCH"},
44+
AllowHeaders: []string{"Origin"},
45+
ExposeHeaders: []string{"Content-Length"},
46+
AllowCredentials: true,
47+
AllowOrigins: []string{"null", "https://foo.com"}, // $ Alert
48+
MaxAge: 12 * time.Hour,
49+
}
50+
router.Use(cors.New(config_vulnerable))
51+
router.GET("/", func(c *gin.Context) {
52+
c.String(http.StatusOK, "hello world")
53+
})
54+
router.Run()
55+
}
56+
57+
// Function is safe due to hardcoded origin and AllowCredentials: true
3958
func safe() {
4059
router := gin.Default()
4160
// CORS for https://foo.com origin, allowing:
@@ -58,10 +77,8 @@ func safe() {
5877
router.Run()
5978
}
6079

61-
/*
62-
** Function is safe due to AllowAllOrigins = true aka Access-Control-Allow-Origin: *
63-
*/
64-
func AllowAllTrue() {
80+
// Function is safe due to AllowAllOrigins = true aka Access-Control-Allow-Origin: *
81+
func AllowAllTrue1() {
6582
router := gin.Default()
6683
// CORS for "*" origin, allowing:
6784
// - PUT and PATCH methods
@@ -84,6 +101,30 @@ func AllowAllTrue() {
84101
router.Run()
85102
}
86103

104+
// Function is safe due to AllowAllOrigins = true aka Access-Control-Allow-Origin: *
105+
func AllowAllTrue2() {
106+
router := gin.Default()
107+
// CORS for "*" origin, allowing:
108+
// - PUT and PATCH methods
109+
// - Origin header
110+
// - Credentials share
111+
// - Preflight requests cached for 12 hours
112+
config_allowall := cors.Config{
113+
AllowMethods: []string{"PUT", "PATCH"},
114+
AllowHeaders: []string{"Origin"},
115+
ExposeHeaders: []string{"Content-Length"},
116+
AllowAllOrigins: true,
117+
AllowCredentials: true,
118+
MaxAge: 12 * time.Hour,
119+
}
120+
config_allowall.AllowOrigins = []string{"null"}
121+
router.Use(cors.New(config_allowall))
122+
router.GET("/", func(c *gin.Context) {
123+
c.String(http.StatusOK, "hello world")
124+
})
125+
router.Run()
126+
}
127+
87128
func NoVariableVulnerable() {
88129
router := gin.Default()
89130
// CORS for https://foo.com origin, allowing:
@@ -95,7 +136,7 @@ func NoVariableVulnerable() {
95136
AllowMethods: []string{"GET", "POST"},
96137
AllowHeaders: []string{"Origin"},
97138
ExposeHeaders: []string{"Content-Length"},
98-
AllowOrigins: []string{"null", "https://foo.com"},
139+
AllowOrigins: []string{"null", "https://foo.com"}, // $ Alert
99140
AllowCredentials: true,
100141
MaxAge: 12 * time.Hour,
101142
}))
@@ -104,3 +145,39 @@ func NoVariableVulnerable() {
104145
})
105146
router.Run()
106147
}
148+
149+
var global_config1 = cors.Config{
150+
AllowMethods: []string{"PUT", "PATCH"},
151+
AllowHeaders: []string{"Origin"},
152+
ExposeHeaders: []string{"Content-Length"},
153+
AllowCredentials: true,
154+
AllowOrigins: []string{"null", "https://foo.com"}, // $ Alert
155+
MaxAge: 12 * time.Hour,
156+
}
157+
158+
func vulnerableGlobal1() {
159+
router := gin.Default()
160+
router.Use(cors.New(global_config1))
161+
router.GET("/", func(c *gin.Context) {
162+
c.String(http.StatusOK, "hello world")
163+
})
164+
router.Run()
165+
}
166+
167+
var global_config2 = cors.Config{
168+
AllowMethods: []string{"PUT", "PATCH"},
169+
AllowHeaders: []string{"Origin"},
170+
ExposeHeaders: []string{"Content-Length"},
171+
AllowCredentials: true,
172+
MaxAge: 12 * time.Hour,
173+
}
174+
175+
func vulnerableGlobal2() {
176+
router := gin.Default()
177+
global_config2.AllowOrigins = []string{"null", "https://foo.com"} // $ MISSING: Alert
178+
router.Use(cors.New(global_config2))
179+
router.GET("/", func(c *gin.Context) {
180+
c.String(http.StatusOK, "hello world")
181+
})
182+
router.Run()
183+
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
| CorsGin.go:28:35:28:69 | slice literal | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` |
2-
| CorsGin.go:98:21:98:55 | slice literal | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` |
1+
| CorsGin.go:26:35:26:69 | slice literal | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` |
2+
| CorsGin.go:47:21:47:55 | slice literal | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` |
3+
| CorsGin.go:139:21:139:55 | slice literal | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` |
4+
| CorsGin.go:154:20:154:54 | slice literal | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` |
35
| CorsMisconfiguration.go:26:4:26:56 | call to Set | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` |
46
| CorsMisconfiguration.go:32:4:32:42 | call to Set | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` |
57
| CorsMisconfiguration.go:53:4:53:44 | call to Set | access-control-allow-origin header is set to a user-defined value, and access-control-allow-credentials is set to `true` |
68
| CorsMisconfiguration.go:60:4:60:56 | call to Set | access-control-allow-origin header is set to a user-defined value, and access-control-allow-credentials is set to `true` |
79
| CorsMisconfiguration.go:67:5:67:57 | call to Set | access-control-allow-origin header is set to a user-defined value, and access-control-allow-credentials is set to `true` |
810
| RsCors.go:11:21:11:59 | slice literal | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` |
11+
| RsCors.go:31:23:31:61 | slice literal | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` |
12+
| RsCors.go:59:20:59:58 | slice literal | access-control-allow-origin header is set to `null`, and access-control-allow-credentials is set to `true` |

go/ql/test/experimental/CWE-942/CorsMisconfiguration.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ func main() {
2323
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
2424
// BAD: 'null' origin is allowed,
2525
// and Access-Control-Allow-Credentials is set to 'true'.
26-
w.Header().Set("Access-Control-Allow-Origin", "null")
26+
w.Header().Set("Access-Control-Allow-Origin", "null") // $ Alert
2727
w.Header().Set("Access-Control-Allow-Credentials", "true")
2828
})
2929
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
3030
// BAD: 'null' origin is allowed,
3131
// and `Access-Control-Allow-Credentials` is set to 'true':
32-
w.Header().Set(HeaderAllowOrigin, Null)
32+
w.Header().Set(HeaderAllowOrigin, Null) // $ Alert
3333
w.Header().Set("Access-Control-Allow-Credentials", "true")
3434
})
3535
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
@@ -50,21 +50,21 @@ func main() {
5050
// BAD: the `Access-Control-Allow-Origin` header is set using a user-defined value,
5151
// and `Access-Control-Allow-Credentials` is set to 'true':
5252
origin := req.Header.Get("origin")
53-
w.Header().Set(HeaderAllowOrigin, origin)
53+
w.Header().Set(HeaderAllowOrigin, origin) // $ Alert
5454
w.Header().Set("Access-Control-Allow-Credentials", "true")
5555
})
5656
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
5757
// BAD: the `Access-Control-Allow-Origin` header is set using a user-defined value,
5858
// and `Access-Control-Allow-Credentials` is set to 'true':
5959
origin := req.Header.Get("origin")
60-
w.Header().Set("Access-Control-Allow-Origin", origin)
60+
w.Header().Set("Access-Control-Allow-Origin", origin) // $ Alert
6161
w.Header().Set(HeaderAllowCredentials, "true")
6262
})
6363
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
6464
// BAD: the `Access-Control-Allow-Origin` header is set using a user-defined value,
6565
// and `Access-Control-Allow-Credentials` is set to 'true':
6666
if origin := req.Header.Get("Origin"); origin != "" {
67-
w.Header().Set("Access-Control-Allow-Origin", origin)
67+
w.Header().Set("Access-Control-Allow-Origin", origin) // $ Alert
6868
}
6969
w.Header().Set(HeaderAllowCredentials, "true")
7070
})
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
experimental/CWE-942/CorsMisconfiguration.ql
1+
query: experimental/CWE-942/CorsMisconfiguration.ql
2+
postprocess: utils/test/InlineExpectationsTestQuery.ql

0 commit comments

Comments
 (0)