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

Python : Add sanitizers for Path Injection Query #7009

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

Labels
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

3 participants
@porcupineyhairs
Copy link
Contributor

@porcupineyhairs porcupineyhairs commented Oct 29, 2021

This PR adds a few sanitizers to the exisitng Path injection query.

This PR adds a few sanitizers to the exisitng Path injection query.
*/
private class OsPathAbsoluteCallAsSanitizer extends Sanitizer {
OsPathAbsoluteCallAsSanitizer() {
this = API::moduleImport("os").getMember("path").getMember("abspath").getACall().getArg(_)
Copy link
Contributor

@intrigus-lgtm intrigus-lgtm Oct 31, 2021

I might be wrong, but isn't this saying that the argument to abspath has been sanitized?
Don't we instead want to say that the result of such a call has been sanitized?
(EDIT: I misunderstood how abspath works in this case. Only the combination of abspath and checking whether it starts with a specific prefix should be considered a sanitizer-guard)

So in this example:

sanitizedPath = os.path.abspath(taintedPath)
dangerousUse(taintedPath)

taintedPath could not flow to dangerousUse because it is used once as an argument to abspath and therefore considered sanitized (which is not correct).

If I'm right you could add new tests that show the improvements of this PR.

Copy link
Contributor Author

@porcupineyhairs porcupineyhairs Nov 1, 2021

I considered it when writing this PR. Yes, what you are saying is true. But consider the case here. https://github.com/kizniche/Mycodo/blob/6c0d710c8138aa06e6dcd909097854afe0d5cebe/mycodo/mycodo_flask/routes_general.py#L120-L121

In this case, since file_path is checked earlier, the code is not exploitable. So I thought it would be best to avoid multiple FPs and accept a reduced TP count instead.

@porcupineyhairs
Copy link
Contributor Author

@porcupineyhairs porcupineyhairs commented Nov 1, 2021

@RasmusWL The query currently flags some sinks which shouldn't otherwise be there. For ex, a simple run of the query with the proposed changes included leads to 3 results. Of which 2 of them are calls to os.path.getctime and os.path.exists. I think these can be easily avoided if we were to use code from the StdLibPrivate module. This would reduce code duplication and let us reduce FP's. Should I mark it public?

Copy link
Member

@RasmusWL RasmusWL left a comment

This PR goes against how we deal with path injection. You should NOT change the query.

abspath is already modeled, see

/**
* A call to `os.path.abspath`.
* See https://docs.python.org/3/library/os.path.html#os.path.abspath
*/
private class OsPathAbspathCall extends Path::PathNormalization::Range, DataFlow::CallCfgNode {
OsPathAbspathCall() { this = os::path().getMember("abspath").getACall() }
DataFlow::Node getPathArg() { result in [this.getArg(0), this.getArgByName("path")] }
}

and .startswith is already modeled:

/**
* A call to the `startswith` method on a string.
* See https://docs.python.org/3.9/library/stdtypes.html#str.startswith
*/
private class StartswithCall extends Path::SafeAccessCheck::Range {
StartswithCall() { this.(CallNode).getFunction().(AttrNode).getName() = "startswith" }
override predicate checks(ControlFlowNode node, boolean branch) {
node = this.(CallNode).getFunction().(AttrNode).getObject() and
branch = true
}
}

So all in all, I'm surprised this isn't handled properly already.

As a first step, can you please update the query test file to include the new case (FP) that you want to handle better (remember to update the .expected files).

Then we will need to figure out why the current modeling doesn't work.

@RasmusWL
Copy link
Member

@RasmusWL RasmusWL commented Nov 1, 2021

(I didn't notice this reply at first)

@RasmusWL The query currently flags some sinks which shouldn't otherwise be there. For ex, a simple run of the query with the proposed changes included leads to 3 results. Of which 2 of them are calls to os.path.getctime and os.path.exists. I think these can be easily avoided if we were to use code from the StdLibPrivate module. This would reduce code duplication and let us reduce FP's. Should I mark it public?

@porcupineyhairs can you please add tests with these cases? I'm not able to imagine what these FPs looks like based on just that text 🤷

@porcupineyhairs
Copy link
Contributor Author

@porcupineyhairs porcupineyhairs commented Nov 2, 2021

@RasmusWL I have added the tests now. These are based on kizniche/Mycodo

Also, there are sinks which we are not detected by the query but which otherwise should have been included there eg. os.mkdirs. I can see when you modeled the os module, these were specifically left out. Was there a specific reason for the same? Do you want me to add them now?

def camera_img_acquire(filename):
filename = request.args.get('filename', '')
image_path = os.path.join(STATIC_DIR, filename)
if not os.path.exists(image_path): # OK as exposure of ctime of a particular file does not pose a security thread
Copy link
Contributor

@intrigus-lgtm intrigus-lgtm Nov 2, 2021

Duplicate comment? Compare with line 136.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment