Skip to content

Go: Update query help for go/path-injection to include example fixes. #16020

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

Merged
merged 3 commits into from
Mar 25, 2024

Conversation

max-schaefer
Copy link
Contributor

Closely mimics the changes for Java in #15409.

Copy link
Contributor

github-actions bot commented Mar 22, 2024

QHelp previews:

go/ql/src/Security/CWE-022/TaintedPath.qhelp

Uncontrolled data used in path expression

Accessing files using paths constructed from user-controlled data can allow an attacker to access unexpected resources. This can result in sensitive information being revealed or deleted, or an attacker being able to influence behavior by modifying unexpected files.

Paths that are naively constructed from data controlled by a user may be absolute paths, or may contain unexpected special characters such as "..". Such a path could point anywhere on the file system.

Recommendation

Validate user input before using it to construct a file path.

Common validation methods include checking that the normalized path is relative and does not contain any ".." components, or checking that the path is contained within a safe folder. The method you should use depends on how the path is used in the application, and whether the path should be a single path component.

If the path should be a single path component (such as a file name), you can check for the existence of any path separators ("/" or "\"), or ".." sequences in the input, and reject the input if any are found.

Note that removing "../" sequences is not sufficient, since the input could still contain a path separator followed by "..". For example, the input ".../...//" would still result in the string "../" if only "../" sequences are removed.

Finally, the simplest (but most restrictive) option is to use an allow list of safe patterns and make sure that the user input matches one of these patterns.

Example

In the first example, a file name is read from an HTTP request and then used to access a file. However, a malicious user could enter a file name which is an absolute path, such as "/etc/passwd".

In the second example, it appears that the user is restricted to opening a file within the "user" home directory. However, a malicious user could enter a file name containing special characters. For example, the string "../../etc/passwd" will result in the code reading the file located at "/home/user/../../etc/passwd", which is the system's password file. This file would then be sent back to the user, giving them access to password information.

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)
}

If the input should only be a file name, you can check that it doesn't contain any path separators or ".." sequences.

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)
}

Note that this approach is only suitable if the input is expected to be a single file name.

If the input can be a path with multiple components, you can make it safe by verifying that the path is within a specific directory that is considered safe. You can do this by resolving the input with respect to that directory, and then checking that the resulting path is still within it.

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)
}

Note that /home/user is just an example, you should replace it with the actual safe directory in your application. Also, while in this example the path of the safe directory is absolute, this may not always be the case, and you may need to resolve it first before checking the input.

References

Copy link
Member

@mbg mbg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable. I have one or two small suggestions for the first, new example.

mbg
mbg previously approved these changes Mar 22, 2024
@max-schaefer max-schaefer added the ready-for-doc-review This PR requires and is ready for review from the GitHub docs team. label Mar 22, 2024
subatoi
subatoi previously approved these changes Mar 22, 2024
Copy link
Contributor

@subatoi subatoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thank you!

Very minor but I noticed a couple of very minor mistakes in the formatting on unchanged lines—I don't think it's worth changing unless you have time, as eventually all this will be migrated and it won't affect reader comprehension.

In the second example, it appears that the user is restricted to opening a file within the "user" home directory. However, a malicious user could enter a file name containing special characters. For example, the string "../../etc/passwd" will result in the code reading the file located at "/home/user/../../etc/passwd", which is the system's password file. This file would then be sent back to the user, giving them access to password information.

Would recommend changing a couple of things enclosed in "" to be enclosed in backticks (monospaced) per below, with bold formatting used to indicate places:

In the second example, it appears that the user is restricted to opening a file within the user home directory. However, a malicious user could enter a file name containing special characters. For example, the string "../../etc/passwd" will result in the code reading the file located at /home/user/../../etc/passwd, which is the system's password file. This file would then be sent back to the user, giving them access to password information.

@max-schaefer max-schaefer dismissed stale reviews from subatoi and mbg via 034ed17 March 22, 2024 15:24
@max-schaefer
Copy link
Contributor Author

@subatoi / @mbg, can I get an approval after the latest round of fixups?

I haven't addressed this yet, since it's generally inconsistent:

Would recommend changing a couple of things enclosed in "" to be enclosed in backticks (monospaced) per below, with bold formatting used to indicate places:

@max-schaefer max-schaefer merged commit ffbe3e6 into main Mar 25, 2024
@max-schaefer max-schaefer deleted the max-schaefer/go-path-injection-qhelp branch March 25, 2024 10:25
@owen-mc
Copy link
Contributor

owen-mc commented Apr 29, 2024

@max-schaefer Note that more sanitizers for this query were added in #11703, soon after this PR was merged, in case you want to update the query help to use one of those.

@max-schaefer
Copy link
Contributor Author

Thanks! From a quick look I think none of these new sanitisers are general enough to suggest in the QHelp.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Go ready-for-doc-review This PR requires and is ready for review from the GitHub docs team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants