Skip to content

Commit 3226184

Browse files
committed
add tests
1 parent cddd27c commit 3226184

4 files changed

Lines changed: 253 additions & 0 deletions

File tree

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,28 @@ module Fasthttp {
1818
string fasthttpPackage() { result = "github.com/valyala/fasthttp" }
1919

2020
module Functions {
21+
class FileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
22+
FileSystemAccess() {
23+
exists(DataFlow::Function f |
24+
f.hasQualifiedName("github.com/valyala/fasthttp",
25+
[
26+
"ServeFile", "ServeFileUncompressed", "ServeFileBytes", "ServeFileBytesUncompressed",
27+
"SaveMultipartFile"
28+
]) and
29+
this = f.getACall()
30+
)
31+
}
32+
33+
override DataFlow::Node getAPathArgument() {
34+
this.getTarget().getName() =
35+
[
36+
"ServeFile", "ServeFileUncompressed", "ServeFileBytes", "ServeFileBytesUncompressed",
37+
"SaveMultipartFile"
38+
] and
39+
result = this.getArgument(1)
40+
}
41+
}
42+
2143
private class Redirect extends Http::Redirect::Range, DataFlow::CallNode {
2244
Redirect() {
2345
exists(DataFlow::Function f |
@@ -194,6 +216,20 @@ module Fasthttp {
194216
}
195217

196218
module Response {
219+
class FileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
220+
FileSystemAccess() {
221+
exists(DataFlow::Method mcn |
222+
mcn.hasQualifiedName("github.com/valyala/fasthttp.Response", "SendFile") and
223+
this = mcn.getACall()
224+
)
225+
}
226+
227+
override DataFlow::Node getAPathArgument() {
228+
this.getTarget().getName() = "SendFile" and
229+
result = this.getArgument(0)
230+
}
231+
}
232+
197233
class HttpResponseBodySink extends SharedXss::Sink {
198234
HttpResponseBodySink() {
199235
exists(DataFlow::Method m |
@@ -209,6 +245,21 @@ module Fasthttp {
209245
}
210246

211247
module RequestCtx {
248+
class FileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
249+
FileSystemAccess() {
250+
exists(DataFlow::Method mcn |
251+
mcn.hasQualifiedName("github.com/valyala/fasthttp.RequestCtx",
252+
["SendFileBytes", "SendFile"]) and
253+
this = mcn.getACall()
254+
)
255+
}
256+
257+
override DataFlow::Node getAPathArgument() {
258+
this.getTarget().getName() = ["SendFile", "SendFileBytes"] and
259+
result = this.getArgument(0)
260+
}
261+
}
262+
212263
private class Redirect extends Http::Redirect::Range, DataFlow::CallNode {
213264
Redirect() {
214265
exists(DataFlow::Function f |
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"github.com/valyala/fasthttp"
6+
"log"
7+
"net"
8+
"time"
9+
)
10+
11+
func fasthttpClient() {
12+
// #SSRF
13+
response, err := fasthttp.DialDualStack("127.0.0.1:8909")
14+
response, err = fasthttp.Dial("google.com:80")
15+
response, err = fasthttp.DialTimeout("google.com:80", 5)
16+
response, err = fasthttp.DialDualStackTimeout("google.com:80", 5)
17+
log.Println(err)
18+
resByte := make([]byte, 1000)
19+
_, err = response.Read(resByte)
20+
log.Println(resByte)
21+
22+
// #SSRF
23+
fasthttp.Get(resByte, "http://127.0.0.1:8909")
24+
fasthttp.GetDeadline(resByte, "http://127.0.0.1:8909", time.Time{})
25+
fasthttp.GetTimeout(resByte, "http://127.0.0.1:8909", 5)
26+
fasthttp.Post(resByte, "http://127.0.0.1:8909", nil)
27+
log.Println(string(resByte))
28+
29+
// #SSRF
30+
client := fasthttp.Client{}
31+
client.Get(resByte, "http://127.0.0.1:8909")
32+
client.GetDeadline(resByte, "http://127.0.0.1:8909", time.Time{})
33+
client.GetTimeout(resByte, "http://127.0.0.1:8909", 5)
34+
client.Post(resByte, "http://127.0.0.1:8909", nil)
35+
36+
res := &fasthttp.Response{}
37+
req := &fasthttp.Request{}
38+
uri := fasthttp.URI{}
39+
// additional steps
40+
uri.SetHost("UserControlled.com:80")
41+
uri.SetHostBytes([]byte("UserControlled.com:80"))
42+
req.SetHost("UserControlled.com:80")
43+
req.SetHostBytes([]byte("UserControlled.com:80"))
44+
req.SetRequestURI("https://UserControlled.com")
45+
req.SetRequestURIBytes([]byte("https://UserControlled.com"))
46+
req.SetURI(&uri)
47+
fasthttp.Do(req, res)
48+
fasthttp.DoDeadline(req, res, time.Time{})
49+
fasthttp.DoTimeout(req, res, 5)
50+
client.Do(req, res)
51+
client.DoDeadline(req, res, time.Time{})
52+
client.DoTimeout(req, res, 5)
53+
54+
// #SSRF
55+
tcpDialer := fasthttp.TCPDialer{}
56+
tcpDialer.Dial("127.0.0.1:8909")
57+
tcpDialer.DialTimeout("127.0.0.1:8909", 5)
58+
tcpDialer.DialDualStack("127.0.0.1:8909")
59+
tcpDialer.DialDualStackTimeout("127.0.0.1:8909", 5)
60+
}
61+
62+
func fasthttpServer() {
63+
ln, err := net.Listen("tcp4", "127.0.0.1:8080")
64+
if err != nil {
65+
log.Fatalf("error in net.Listen: %v", err)
66+
}
67+
requestHandler := func(ctx *fasthttp.RequestCtx) {
68+
filePath := ctx.QueryArgs().Peek("filePath")
69+
// File System Access
70+
_ = ctx.Response.SendFile(string(filePath))
71+
ctx.SendFile(string(filePath))
72+
ctx.SendFileBytes(filePath)
73+
fileHeader, _ := ctx.FormFile("file")
74+
_ = fasthttp.SaveMultipartFile(fileHeader, string(filePath))
75+
fasthttp.ServeFile(ctx, string(filePath))
76+
fasthttp.ServeFileUncompressed(ctx, string(filePath))
77+
fasthttp.ServeFileBytes(ctx, filePath)
78+
fasthttp.ServeFileBytesUncompressed(ctx, filePath)
79+
80+
dstWriter := &bufio.Writer{}
81+
dstReader := &bufio.Reader{}
82+
// user controlled methods as source
83+
requestHeader := &fasthttp.RequestHeader{}
84+
ctx.Request.Header.CopyTo(requestHeader)
85+
requestHeader.Write(dstWriter)
86+
requestHeader.Header()
87+
requestHeader.TrailerHeader()
88+
requestHeader.String()
89+
requestHeader.RequestURI()
90+
requestHeader.Host()
91+
requestHeader.UserAgent()
92+
requestHeader.ContentEncoding()
93+
requestHeader.ContentType()
94+
requestHeader.Cookie("ACookie")
95+
requestHeader.CookieBytes([]byte("ACookie"))
96+
requestHeader.MultipartFormBoundary()
97+
requestHeader.Peek("AHeaderName")
98+
requestHeader.PeekAll("AHeaderName")
99+
requestHeader.PeekBytes([]byte("AHeaderName"))
100+
requestHeader.PeekKeys()
101+
requestHeader.PeekTrailerKeys()
102+
requestHeader.Referer()
103+
requestHeader.RawHeaders()
104+
// multipart.Form is already implemented
105+
//ctx.MultipartForm()
106+
ctx.URI().Path()
107+
ctx.URI().PathOriginal()
108+
newURI := &fasthttp.URI{}
109+
ctx.URI().CopyTo(newURI)
110+
ctx.URI().FullURI()
111+
ctx.URI().LastPathSegment()
112+
ctx.URI().QueryString()
113+
ctx.URI().String()
114+
ctx.URI().WriteTo(dstWriter)
115+
116+
newArgs := &fasthttp.Args{}
117+
//or ctx.PostArgs()
118+
ctx.URI().QueryArgs().CopyTo(newArgs)
119+
ctx.URI().QueryArgs().Peek("arg1")
120+
ctx.URI().QueryArgs().PeekBytes([]byte("arg1"))
121+
ctx.URI().QueryArgs().PeekMulti("arg1")
122+
ctx.URI().QueryArgs().PeekMultiBytes([]byte("arg1"))
123+
ctx.URI().QueryArgs().QueryString()
124+
ctx.URI().QueryArgs().String()
125+
ctx.URI().QueryArgs().WriteTo(dstWriter)
126+
// not sure what is the best way to write query for following
127+
//ctx.URI().QueryArgs().VisitAll(type func(,))
128+
129+
ctx.Path()
130+
// multipart.Form is already implemented
131+
// ctx.FormFile("FileName")
132+
// ctx.FormValue("ValueName")
133+
ctx.Referer()
134+
ctx.PostBody()
135+
ctx.RequestBodyStream()
136+
ctx.RequestURI()
137+
ctx.UserAgent()
138+
ctx.Host()
139+
140+
ctx.Request.Host()
141+
ctx.Request.Body()
142+
ctx.Request.RequestURI()
143+
ctx.Request.BodyGunzip()
144+
ctx.Request.BodyInflate()
145+
ctx.Request.BodyUnbrotli()
146+
ctx.Request.BodyStream()
147+
ctx.Request.BodyWriteTo(dstWriter)
148+
ctx.Request.WriteTo(dstWriter)
149+
ctx.Request.BodyUncompressed()
150+
ctx.Request.ReadBody(dstReader, 100, 1000)
151+
ctx.Request.ReadLimitBody(dstReader, 100)
152+
ctx.Request.ContinueReadBodyStream(dstReader, 100, true)
153+
ctx.Request.ContinueReadBody(dstReader, 100)
154+
// not sure what is the best way to write query for following
155+
//ctx.Request.Header.VisitAllCookie()
156+
157+
// Xss Sinks
158+
ctx.Response.AppendBody([]byte("user Controlled"))
159+
ctx.Response.AppendBodyString("user Controlled")
160+
rspWriter := ctx.Response.BodyWriter()
161+
rspWriter.Write([]byte("XSS"))
162+
ctx.Response.SetBody([]byte("user Controlled"))
163+
ctx.Response.SetBodyString("user Controlled")
164+
ctx.Response.SetBodyRaw([]byte("user Controlled"))
165+
ctx.Response.SetBodyStream(dstReader, 100)
166+
dstByte := []byte("init")
167+
// sanitizers
168+
fasthttp.AppendQuotedArg(dstByte, []byte("xsss"))
169+
fasthttp.AppendHTMLEscape(dstByte, "xss")
170+
fasthttp.AppendHTMLEscapeBytes(dstByte, []byte("xss"))
171+
172+
// TODO: unstrusted Remote IP from Header?
173+
// TODO: open redirect Sinks
174+
req := &fasthttp.Request{}
175+
res := &fasthttp.Response{}
176+
// there are additional steps from other scope
177+
req.SetRequestURI("https://userControlled.com")
178+
fasthttp.DoRedirects(req, res, 2)
179+
ctx.Redirect("https://userControlled.com", 301)
180+
ctx.RedirectBytes([]byte("https://userControlled.com"), 301)
181+
}
182+
if err := fasthttp.Serve(ln, requestHandler); err != nil {
183+
log.Fatalf("error in Serve: %v", err)
184+
}
185+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import go
2+
import semmle.go.frameworks.Fasthttp
3+
4+
from
5+
Fasthttp::Request::UntrustedFlowSource u1, Fasthttp::RequestCtx::UntrustedFlowSource u2,
6+
Fasthttp::URI::UntrustedFlowSource u3, Fasthttp::RequestHeader::UntrustedFlowSource u4
7+
select u1, u2, u3, u4
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module fastHttpModel
2+
3+
go 1.20
4+
5+
require github.com/valyala/fasthttp v1.49.0
6+
7+
require (
8+
github.com/andybalholm/brotli v1.0.5 // indirect
9+
github.com/klauspost/compress v1.16.3 // indirect
10+
github.com/valyala/bytebufferpool v1.0.0 // indirect)

0 commit comments

Comments
 (0)