路径表达式中使用的不受控制的数据¶
ID: go/path-injection
Kind: path-problem
Security severity: 7.5
Severity: error
Precision: high
Tags:
- security
- external/cwe/cwe-022
- external/cwe/cwe-023
- external/cwe/cwe-036
- external/cwe/cwe-073
- external/cwe/cwe-099
Query suites:
- go-code-scanning.qls
- go-security-extended.qls
- go-security-and-quality.qls
使用从用户控制的数据构造的路径访问文件可能允许攻击者访问意外的资源。这可能导致敏感信息被泄露或删除,或者攻击者能够通过修改意外文件来影响行为。
从用户控制的数据简单构造的路径可能是绝对路径,或者可能包含意外的特殊字符,例如“..”。这样的路径可能指向文件系统上的任何位置。
建议¶
在使用用户输入构造文件路径之前对其进行验证。
常见的验证方法包括检查规范化路径是否是相对路径并且不包含任何“..”组件,或者检查路径是否包含在安全文件夹中。您应该使用的方法取决于路径在应用程序中的使用方式,以及路径是否应该是单个路径组件。
如果路径应该是单个路径组件(例如文件名),则可以检查输入中是否存在任何路径分隔符(“/”或“\”)或“..”序列,如果找到任何一个,则拒绝输入。
请注意,删除“../”序列_不够_,因为输入仍然可以包含路径分隔符后跟“..”。例如,如果仅删除“../”序列,则输入“…/…//”仍将导致字符串“../”。
最后,最简单(但限制最多)的选择是使用安全模式的允许列表,并确保用户输入与其中一种模式匹配。
示例¶
在第一个示例中,从 HTTP 请求中读取文件名,然后使用该文件名访问文件。但是,恶意用户可以输入一个文件名,该文件名是一个绝对路径,例如“/etc/passwd”。
在第二个示例中,用户似乎被限制在 "user"
主目录中打开文件。但是,恶意用户可以输入包含特殊字符的文件名。例如,字符串 "../../etc/passwd"
将导致代码读取位于“/home/user/../../etc/passwd”的文件,这是系统的密码文件。然后,该文件将被发送回用户,使他们能够访问密码信息。
package main
import (
"io/ioutil"
"net/http"
"path/filepath"
)
func handler(w http.ResponseWriter, r *http.Request) {
path := r.URL.Query()["path"][0]
// BAD: This could read any file on the file system
data, _ := ioutil.ReadFile(path)
w.Write(data)
// BAD: This could still read any file on the file system
data, _ = ioutil.ReadFile(filepath.Join("/home/user/", path))
w.Write(data)
}
如果输入应该只是一个文件名,则可以检查它是否包含任何路径分隔符或“..”序列。
package main
import (
"io/ioutil"
"net/http"
"path/filepath"
"strings"
)
func handler(w http.ResponseWriter, r *http.Request) {
path := r.URL.Query()["path"][0]
// GOOD: ensure that the filename has no path separators or parent directory references
// (Note that this is only suitable if `path` is expected to have a single component!)
if strings.Contains(path, "/") || strings.Contains(path, "\\") || strings.Contains(path, "..") {
http.Error(w, "Invalid file name", http.StatusBadRequest)
return
}
data, _ := ioutil.ReadFile(filepath.Join("/home/user/", path))
w.Write(data)
}
请注意,此方法仅适用于预期输入为单个文件名的场景。
如果输入可以是包含多个组件的路径,则可以通过验证路径是否在被视为安全的特定目录中来确保其安全。您可以通过解析该目录相对于该目录的输入,然后检查结果路径是否仍在该目录中来完成此操作。
package main
import (
"io/ioutil"
"net/http"
"path/filepath"
"strings"
)
const safeDir = "/home/user/"
func handler(w http.ResponseWriter, r *http.Request) {
path := r.URL.Query()["path"][0]
// GOOD: ensure that the resolved path is within the safe directory
absPath, err := filepath.Abs(filepath.Join(safeDir, path))
if err != nil || !strings.HasPrefix(absPath, safeDir) {
http.Error(w, "Invalid file name", http.StatusBadRequest)
return
}
data, _ := ioutil.ReadFile(absPath)
w.Write(data)
}
请注意,/home/user
只是一个示例,您应该将其替换为应用程序中的实际安全目录。此外,虽然在此示例中安全目录的路径是绝对路径,但这并非总是如此,您可能需要先解析它,然后再检查输入。