Skip to content

Commit cddd27c

Browse files
committed
V1
1 parent c6bc1a3 commit cddd27c

2 files changed

Lines changed: 287 additions & 0 deletions

File tree

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/** Provides models of commonly used functions and types in the fasthttp packages. */
2+
3+
import go
4+
private import semmle.go.security.RequestForgeryCustomizations
5+
private import semmle.go.security.SafeUrlFlowCustomizations
6+
import semmle.go.security.Xss
7+
8+
/** Provides models of commonly used functions and types in the fasthttp packages. */
9+
module Fasthttp {
10+
bindingset[this]
11+
abstract class AdditionalStep extends string {
12+
/**
13+
* Holds if `pred` to `succ` is an additional taint-propagating step for this query.
14+
*/
15+
abstract predicate hasTaintStep(DataFlow::Node pred, DataFlow::Node succ);
16+
}
17+
18+
string fasthttpPackage() { result = "github.com/valyala/fasthttp" }
19+
20+
module Functions {
21+
private class Redirect extends Http::Redirect::Range, DataFlow::CallNode {
22+
Redirect() {
23+
exists(DataFlow::Function f |
24+
f.hasQualifiedName("github.com/valyala/fasthttp", "DoRedirects") and this = f.getACall()
25+
)
26+
}
27+
28+
override DataFlow::Node getUrl() { result = this.getArgument(0) }
29+
30+
override Http::ResponseWriter getResponseWriter() { result.getANode() = this.getArgument(1) }
31+
}
32+
33+
private class HtmlQuoteSanitizer extends SharedXss::Sanitizer {
34+
HtmlQuoteSanitizer() {
35+
exists(DataFlow::CallNode c |
36+
c.getTarget()
37+
.hasQualifiedName("github.com/valyala/fasthttp",
38+
["AppendHTMLEscape", "AppendHTMLEscapeBytes", "AppendQuotedArg"])
39+
|
40+
this = c.getArgument(1)
41+
)
42+
}
43+
}
44+
45+
class SSRFSink extends RequestForgery::Sink {
46+
SSRFSink() {
47+
exists(DataFlow::Function f |
48+
f.hasQualifiedName("github.com/valyala/fasthttp",
49+
[
50+
"DialDualStack", "Dial", "DialTimeout", "DialDualStackTimeout", "Get", "GetDeadline",
51+
"GetTimeout", "Post", "Do", "DoDeadline", "DoTimeout", "Write", "Write", "Write",
52+
"Write", "Write"
53+
]) and
54+
this = f.getACall().getArgument(0)
55+
)
56+
}
57+
58+
override DataFlow::Node getARequest() { result = this }
59+
60+
override string getKind() { result = "URL" }
61+
}
62+
}
63+
64+
module RequestHeader {
65+
class UntrustedFlowSource extends UntrustedFlowSource::Range instanceof DataFlow::Node {
66+
UntrustedFlowSource() {
67+
exists(DataFlow::Method m |
68+
m.hasQualifiedName("github.com/valyala/fasthttp.RequestHeader",
69+
[
70+
"Header", "TrailerHeader", "RequestURI", "Host", "UserAgent", "ContentEncoding",
71+
"ContentType", "Cookie", "CookieBytes", "MultipartFormBoundary", "Peek", "PeekAll",
72+
"PeekBytes", "PeekKeys", "PeekTrailerKeys", "Referer", "RawHeaders"
73+
]) and
74+
this = m.getACall()
75+
or
76+
m.hasQualifiedName("github.com/valyala/fasthttp.RequestHeader", ["Write"]) and
77+
this = m.getACall().getArgument(0)
78+
)
79+
}
80+
}
81+
}
82+
83+
module URI {
84+
class URIAdditionalStep extends AdditionalStep {
85+
URIAdditionalStep() { this = "URI additioanl steps" }
86+
87+
override predicate hasTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
88+
exists(DataFlow::MethodCallNode m, DataFlow::Variable frn |
89+
m.getTarget()
90+
.hasQualifiedName("github.com/valyala/fasthttp.URI", ["SetHost", "SetHostBytes"]) and
91+
pred = m.getArgument(0) and
92+
frn.getARead() = m.getReceiver() and
93+
succ = frn.getARead()
94+
)
95+
}
96+
}
97+
98+
class UntrustedFlowSource extends UntrustedFlowSource::Range instanceof DataFlow::Node {
99+
UntrustedFlowSource() {
100+
exists(DataFlow::Method m |
101+
m.hasQualifiedName("github.com/valyala/fasthttp.URI",
102+
["Path", "PathOriginal", "LastPathSegment", "FullURI", "QueryString", "String"]) and
103+
this = m.getACall()
104+
or
105+
m.hasQualifiedName("github.com/valyala/fasthttp.URI", "WriteTo") and
106+
this = m.getACall().getArgument(0)
107+
)
108+
}
109+
}
110+
}
111+
112+
module Args {
113+
class UntrustedFlowSource extends UntrustedFlowSource::Range instanceof DataFlow::Node {
114+
UntrustedFlowSource() {
115+
exists(DataFlow::Method m |
116+
m.hasQualifiedName("github.com/valyala/fasthttp.Args",
117+
["Peek", "PeekBytes", "PeekMulti", "PeekMultiBytes", "QueryString", "String"]) and
118+
this = m.getACall()
119+
or
120+
m.hasQualifiedName("github.com/valyala/fasthttp.Args", "WriteTo") and
121+
this = m.getACall().getArgument(0)
122+
)
123+
}
124+
}
125+
}
126+
127+
module TCPDialer {
128+
class SSRFSink extends RequestForgery::Sink {
129+
SSRFSink() {
130+
exists(DataFlow::Method m |
131+
m.hasQualifiedName("github.com/valyala/fasthttp.TCPDialer",
132+
["Dial", "DialTimeout", "DialDualStack", "DialDualStackTimeout"]) and
133+
this = m.getACall().getArgument(0)
134+
)
135+
}
136+
137+
override DataFlow::Node getARequest() { result = this }
138+
139+
override string getKind() { result = "Host" }
140+
}
141+
}
142+
143+
module Client {
144+
class SSRFSink extends RequestForgery::Sink {
145+
SSRFSink() {
146+
exists(DataFlow::Method m |
147+
m.hasQualifiedName("github.com/valyala/fasthttp.Client",
148+
["Get", "GetDeadline", "GetTimeout", "Post", "Do", "DoDeadline", "DoTimeout"]) and
149+
this = m.getACall().getArgument(0)
150+
)
151+
}
152+
153+
override DataFlow::Node getARequest() { result = this }
154+
155+
override string getKind() { result = "URL" }
156+
}
157+
}
158+
159+
module Request {
160+
class RequestAdditionalStep extends AdditionalStep {
161+
RequestAdditionalStep() { this = "Request additioanl steps" }
162+
163+
override predicate hasTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
164+
exists(DataFlow::MethodCallNode m, DataFlow::Variable frn |
165+
m.getTarget()
166+
.hasQualifiedName("github.com/valyala/fasthttp.Request",
167+
["SetRequestURI", "SetRequestURIBytes", "SetURI", "SetHost", "SetHostBytes"]) and
168+
pred = m.getArgument(0) and
169+
frn.getARead() = m.getReceiver() and
170+
succ = frn.getARead()
171+
)
172+
}
173+
}
174+
175+
class UntrustedFlowSource extends UntrustedFlowSource::Range instanceof DataFlow::Node {
176+
UntrustedFlowSource() {
177+
exists(DataFlow::Method m |
178+
m.hasQualifiedName("github.com/valyala/fasthttp.Request",
179+
[
180+
"Host", "RequestURI", "Body", "BodyGunzip", "BodyInflate", "BodyUnbrotli",
181+
"BodyStream", "BodyUncompressed"
182+
]) and
183+
this = m.getACall()
184+
or
185+
m.hasQualifiedName("github.com/valyala/fasthttp.Request",
186+
[
187+
"BodyWriteTo", "WriteTo", "ReadBody", "ReadLimitBody", "ContinueReadBodyStream",
188+
"ContinueReadBody"
189+
]) and
190+
this = m.getACall().getArgument(0)
191+
)
192+
}
193+
}
194+
}
195+
196+
module Response {
197+
class HttpResponseBodySink extends SharedXss::Sink {
198+
HttpResponseBodySink() {
199+
exists(DataFlow::Method m |
200+
m.hasQualifiedName("github.com/valyala/fasthttp.Response",
201+
[
202+
"AppendBody", "AppendBodyString", "SetBody", "SetBodyString", "SetBodyRaw",
203+
"SetBodyStream"
204+
]) and
205+
this = m.getACall().getArgument(0)
206+
)
207+
}
208+
}
209+
}
210+
211+
module RequestCtx {
212+
private class Redirect extends Http::Redirect::Range, DataFlow::CallNode {
213+
Redirect() {
214+
exists(DataFlow::Function f |
215+
f.hasQualifiedName("github.com/valyala/fasthttp", ["Redirect", "RedirectBytes"]) and
216+
this = f.getACall()
217+
)
218+
}
219+
220+
override DataFlow::Node getUrl() { result = this.getArgument(0) }
221+
222+
override Http::ResponseWriter getResponseWriter() { none() }
223+
}
224+
225+
class UntrustedFlowSource extends UntrustedFlowSource::Range instanceof DataFlow::Node {
226+
UntrustedFlowSource() {
227+
exists(DataFlow::Method m |
228+
m.hasQualifiedName("github.com/valyala/fasthttp.RequestCtx",
229+
["Path", "Referer", "PostBody", "RequestBodyStream", "RequestURI", "UserAgent", "Host"]) and
230+
this = m.getACall()
231+
)
232+
}
233+
}
234+
}
235+
}

go/ql/src/experimental/test.ql

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @name Uncontrolled data used in network request
3+
* @description Sending network requests with user-controlled data allows for request forgery attacks.
4+
* @id go/ssrf
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision high
8+
* @tags security
9+
* experimental
10+
* external/cwe/cwe-918
11+
*/
12+
13+
import go
14+
15+
module Config implements DataFlow::ConfigSig {
16+
predicate isSource(DataFlow::Node source) {
17+
exists(DataFlow::MethodCallNode m |
18+
m.getTarget().hasQualifiedName("github.com/valyala/fasthttp.URI", ["SetHost", "SetHostBytes"]) and
19+
source = m.getArgument(0)
20+
)
21+
}
22+
23+
predicate isSink(DataFlow::Node sink) { any() }
24+
25+
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
26+
exists(DataFlow::MethodCallNode m, DataFlow::Variable frn |
27+
m.getTarget().hasQualifiedName("github.com/valyala/fasthttp.URI", ["SetHost", "SetHostBytes"]) and
28+
pred = m.getArgument(0) and
29+
frn.getARead() = m.getReceiver() and
30+
succ = frn.getARead()
31+
)
32+
or
33+
exists(DataFlow::MethodCallNode m, DataFlow::Variable frn |
34+
m.getTarget()
35+
.hasQualifiedName("github.com/valyala/fasthttp.Request",
36+
["SetRequestURI", "SetRequestURIBytes", "SetURI"]) and
37+
pred = m.getArgument(0) and
38+
frn.getARead() = m.getReceiver() and
39+
succ = frn.getARead()
40+
)
41+
}
42+
}
43+
44+
module Flow = TaintTracking::Global<Config>;
45+
46+
import Flow::PathGraph
47+
48+
from Flow::PathNode source, Flow::PathNode sink, DataFlow::Node request
49+
where
50+
Flow::flowPath(source, sink) and
51+
request = sink.getNode()
52+
select request, source, sink, "The URL of this request depends on a user-provided value."

0 commit comments

Comments
 (0)