Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go: New File System Access Sinks #14064

Merged
merged 22 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go/ql/lib/go.qll
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ import semmle.go.frameworks.XPath
import semmle.go.frameworks.Yaml
import semmle.go.frameworks.Zap
import semmle.go.security.FlowSources
import semmle.go.security.FileSystemAccess
225 changes: 225 additions & 0 deletions go/ql/lib/semmle/go/security/FileSystemAccess.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import go

Check warning on line 1 in go/ql/lib/semmle/go/security/FileSystemAccess.qll

View workflow job for this annotation

GitHub Actions / qldoc

Missing QLdoc for file FileSystemAccess

/**
* The File system access sinks of `net/http` package
*/
class HttpServeFile extends FileSystemAccess::Range, DataFlow::CallNode {
int pathArg;

HttpServeFile() {
exists(Function f |
f.hasQualifiedName("net/http", "ServeFile") and
this = f.getACall() and
pathArg = 2
)
}

override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
owen-mc marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* The File system access sinks of [beego](https://github.com/beego/beego) web framework
*/
class BeegoFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
int pathArg;

BeegoFileSystemAccess() {
exists(Method m |
(
m.hasQualifiedName(package("github.com/beego/beego", "server/web/context"), "BeegoOutput",
am0o0 marked this conversation as resolved.
Show resolved Hide resolved
"Download") and
pathArg = 0
or
m.hasQualifiedName(package("github.com/beego/beego", "server/web"), "Controller",
"SaveToFileWithBuffer") and
pathArg = 1
) and
this = m.getACall()
)
}

override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
}

/**
* Provide File system access sinks of [beego](https://github.com/beego/beego) web framework
*/
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
class EchoFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
int pathArg;

EchoFileSystemAccess() {
exists(Method m |
m.hasQualifiedName(package("github.com/labstack/echo", ""), "Context", ["Attachment", "File"]) and
this = m.getACall() and
pathArg = 0
)
}

override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
}

/**
* The File system access sinks of [gin](https://github.com/gin-gonic/gin) web framework
*/
class GinFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
int pathArg;

GinFileSystemAccess() {
exists(Method m |
(
m.hasQualifiedName(package("github.com/gin-gonic/gin", ""), "Context",
["File", "FileAttachment"]) and
pathArg = 0
or
m.hasQualifiedName(package("github.com/gin-gonic/gin", ""), "Context", "SaveUploadedFile") and
pathArg = 1
) and
this = m.getACall()
)
}

override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
}

/**
* The File system access sinks of [iris](https://github.com/kataras/iris) web framework
*/
class IrisFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
int pathArg;

IrisFileSystemAccess() {
exists(Method m |
(
m.hasQualifiedName(package("github.com/kataras/iris", "context"), "Context",
["SendFile", "ServeFile", "SendFileWithRate", "ServeFileWithRate", "UploadFormFiles"]) and
pathArg = 0
or
m.hasQualifiedName(package("github.com/kataras/iris", "context"), "Context", "SaveFormFile") and
pathArg = 1
) and
this = m.getACall()
)
}

override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
}

/**
* The File system access sinks of [fiber](https://github.com/gofiber/fiber) web framework
*/
class FiberSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
int pathArg;

FiberSystemAccess() {
exists(Method m |
(
m.hasQualifiedName(package("github.com/gofiber/fiber", ""), "Ctx", "SendFile") and
pathArg = 0
or
m.hasQualifiedName(package("github.com/gofiber/fiber", ""), "Ctx", "SaveFile") and
pathArg = 1
) and
this = m.getACall()
)
}

override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
}

/**
* Provide File system access sinks of [afero](https://github.com/spf13/afero) framework
* The Types that are not vulnerable: `afero.BasePathFs` and `afero.IOFS`
*/
module Afero {
string aferoPackage() { result = "github.com/spf13/afero" }

Check warning on line 134 in go/ql/lib/semmle/go/security/FileSystemAccess.qll

View workflow job for this annotation

GitHub Actions / qldoc

Missing QLdoc for classless-predicate FileSystemAccess::Afero::aferoPackage/0
owen-mc marked this conversation as resolved.
Show resolved Hide resolved

/**
* Provide File system access sinks of [afero](https://github.com/spf13/afero) framework methods
*/
Fixed Show fixed Hide fixed
class AferoSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
int pathArg;

AferoSystemAccess() {
exists(Method f |
f.hasQualifiedName(package(aferoPackage(), ""), "HttpFs",
["Create", "Open", "OpenFile", "Remove", "RemoveAll"]) and
this = f.getACall() and
pathArg = 0
or
f.hasQualifiedName(package(aferoPackage(), ""), "RegexpFs",
["Create", "Open", "OpenFile", "Remove", "RemoveAll", "Mkdir", "MkdirAll"]) and
this = f.getACall() and
pathArg = 0
or
f.hasQualifiedName(package(aferoPackage(), ""), "ReadOnlyFs",
["Create", "Open", "OpenFile", "ReadDir", "ReadlinkIfPossible", "Mkdir", "MkdirAll"]) and
this = f.getACall() and
pathArg = 0
or
f.hasQualifiedName(package(aferoPackage(), ""), "OsFs",
[
"Create", "Open", "OpenFile", "ReadlinkIfPossible", "Remove", "RemoveAll", "Mkdir",
"MkdirAll"
]) and
this = f.getACall() and
pathArg = 0
or
f.hasQualifiedName(package(aferoPackage(), ""), "MemMapFs",
["Create", "Open", "OpenFile", "Remove", "RemoveAll", "Mkdir", "MkdirAll"]) and
this = f.getACall() and
pathArg = 0
)
}

override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
}

/**
* Provide File system access sinks of [afero](https://github.com/spf13/afero) framework utility functions
am0o0 marked this conversation as resolved.
Show resolved Hide resolved
* The Types that are not vulnerable: `afero.BasePathFs` and `afero.IOFS`
*/
class AferoUtilityFunctionSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
int pathArg;

AferoUtilityFunctionSystemAccess() {
// utility functions
exists(Function f |
f.hasQualifiedName(package(aferoPackage(), ""),
["WriteReader", "SafeWriteReader", "WriteFile", "ReadFile", "ReadDir"]) and
this = f.getACall() and
pathArg = 1 and
not aferoSanitizer(this.getArgument(0))
)
}

override DataFlow::Node getAPathArgument() { result = this.getArgument(pathArg) }
}

/**
* A sanitizer for when the Afero utility functions has a first argument of a safe type like NewBasePathFs
owen-mc marked this conversation as resolved.
Show resolved Hide resolved
*
* e.g.
* ```
* basePathFs := afero.NewBasePathFs(osFS, "tmp")
* afero.ReadFile(basePathFs, filepath)
* ```
*/
Fixed Show fixed Hide fixed
predicate aferoSanitizer(DataFlow::Node n) {
exists(Function f |
f.hasQualifiedName(package(aferoPackage(), ""), "NewBasePathFs") and
TaintTracking::localTaint(f.getACall(), n)
am0o0 marked this conversation as resolved.
Show resolved Hide resolved
)
}

/**
* A helper for `aferoSanitizer` for when the Afero instance is initialized with one of the safe FS types like IOFS
*
* e.g.`n2 := &afero.Afero{Fs: afero.NewBasePathFs(osFS, "./")}` n1 is `afero.NewBasePathFs(osFS, "./")`
*/
Fixed Show fixed Hide fixed
predicate additionalTaintStep(DataFlow::Node n1, DataFlow::Node n2) {
exists(StructLit st | st.getType().hasQualifiedName(package(aferoPackage(), ""), "Afero") |
n1.asExpr() = st.getAChildExpr*() and
owen-mc marked this conversation as resolved.
Show resolved Hide resolved
n2.asExpr() = st
)
}
}
am0o0 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
category: minorAnalysis
---
* Support for the File System Access Sinks of [net/http ServeFile](https://pkg.go.dev/net/http#ServeFile) has been added.
* Support for the File System Access Sinks of [Beego framework](https://pkg.go.dev/github.com/astaxie/beego) has been added.
* Support for the File System Access Sinks of [Echo framework](https://pkg.go.dev/github.com/labstack/echo) has been added.
* Support for the File System Access Sinks of [Gin framework](https://pkg.go.dev/github.com/gin-gonic/gin) has been added.
* Support for the File System Access Sinks of [Iris framework](https://github.com/kataras/iris) has been added.
* Support for the File System Access Sinks of [Fiber framework](https://github.com/kataras/iris) has been added.
* Support for the File System Access Sinks of [Afero filesystem framework](https://github.com/spf13/afero) has been added.
am0o0 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
| main.go:23:2:23:48 | call to Download |
| main.go:25:6:25:85 | call to SaveToFileWithBuffer |
| main.go:33:14:33:41 | call to MkdirAll |
| main.go:34:14:34:75 | call to WriteFile |
| main.go:35:14:35:77 | call to WriteFile |
| main.go:36:14:36:75 | call to WriteFile |
| main.go:37:16:37:45 | call to ReadFile |
| main.go:39:14:39:32 | call to Open |
| main.go:41:14:41:61 | call to SafeWriteReader |
| main.go:42:14:42:57 | call to WriteReader |
| main.go:48:14:48:47 | call to ReadFile |
| main.go:53:14:53:49 | call to ReadFile |
| main.go:58:14:58:52 | call to ReadFile |
| main.go:63:17:63:37 | call to Open |
| main.go:94:10:94:41 | call to File |
| main.go:98:10:98:72 | call to Attachment |
| main.go:108:7:108:34 | call to SaveFile |
| main.go:109:10:109:29 | call to SendFile |
| main.go:116:6:116:35 | call to SendFile |
| main.go:117:6:117:49 | call to SendFileWithRate |
| main.go:118:6:118:28 | call to ServeFile |
| main.go:119:6:119:42 | call to ServeFileWithRate |
| main.go:120:12:120:45 | call to UploadFormFiles |
| main.go:122:9:122:46 | call to SaveFormFile |
| main.go:129:3:129:18 | call to File |
| main.go:130:3:130:47 | call to ServeFile |
| main.go:131:3:131:53 | call to FileAttachment |
| main.go:133:7:133:40 | call to SaveUploadedFile |
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import go
import semmle.go.security.FileSystemAccess
Fixed Show fixed Hide fixed

from FileSystemAccess f
select f
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
module aferno

go 1.20

require (
github.com/beego/beego/v2 v2.1.1
github.com/gin-gonic/gin v1.9.1
github.com/gofiber/fiber/v2 v2.48.0
github.com/kataras/iris/v12 v12.2.5
github.com/labstack/echo/v4 v4.11.1
github.com/spf13/afero v1.9.5
github.com/valyala/fasthttp v1.48.0
)

require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
github.com/CloudyKit/jet/v6 v6.2.0 // indirect
github.com/Joker/jade v1.1.3 // indirect
github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/flosch/pongo2/v4 v4.0.2 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/iris-contrib/schema v0.0.6 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kataras/blocks v0.0.7 // indirect
github.com/kataras/golog v0.1.9 // indirect
github.com/kataras/pio v0.0.12 // indirect
github.com/kataras/sitemap v0.0.6 // indirect
github.com/kataras/tunnel v0.0.4 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailgun/raymond/v2 v2.0.48 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/microcosm-cc/bluemonday v1.0.25 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.16.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/schollz/closestmatch v2.1.0+incompatible // indirect
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/tdewolff/minify/v2 v2.12.8 // indirect
github.com/tdewolff/parse/v2 v2.6.7 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/yosssi/ace v0.0.5 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading
Loading