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: CWE-117 Log injection #6182

Merged
merged 5 commits into from
Oct 12, 2021
Merged

Python: CWE-117 Log injection #6182

merged 5 commits into from
Oct 12, 2021

Conversation

haby0
Copy link
Contributor

@haby0 haby0 commented Jun 29, 2021

If unsanitized user input is written to a log entry, a malicious user may be able to forge new log entries.

@RasmusWL
Copy link
Member

Hi @haby0, thanks for this contribution 👍 We'll take a look at this as soon, but we do have a few other things we're also working on at the moment 😊

@haby0
Copy link
Contributor Author

haby0 commented Jun 30, 2021

Hi @haby0, thanks for this contribution 👍 We'll take a look at this as soon, but we do have a few other things we're also working on at the moment 😊

Ok, thanks for your reply.

@haby0
Copy link
Contributor Author

haby0 commented Sep 15, 2021

@yoff The conflict has been resolved, please review, thank you.

Copy link
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

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

You are not handling HTML logging and encoding, did you plan to? It might require a second configuration, seeing as the sinks dictate which sanitizers are required...

* See https://docs.python.org/3/library/logging.html#logger-objects
*/
private class LogOutputMethods extends string {
LogOutputMethods() { this in ["info", "error", "warn", "warning", "debug", "critical"] }
Copy link
Contributor

Choose a reason for hiding this comment

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

We seem to be missing "debug", "log" , and "exception" from this list.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pr updated.

this = API::moduleImport("logging").getMember(any(LogOutputMethods m)).getACall()
}

override DataFlow::Node getAnInput() { result = this.getArg(_) }
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that getArg(_), perhaps confusingly, will not return all arguments. In particular, starred arguments are filtered out, as are keyword only arguments. This means that for info(msg, *args, **kwargs) you will see neither args, nor kwargs.

Note also, that if you add log, its first argument is level which should probably not be considered an input.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pr updated.

Comment on lines +18 to +23
override predicate isSanitizer(DataFlow::Node node) {
exists(CallNode call |
node.asCfgNode() = call.getFunction().(AttrNode).getObject("replace") and
call.getArg(0).getNode().(StrConst).getText() in ["\r\n", "\n"]
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is slightly unsafe as it deems either replacement ("\r\n" or "\n") a sanitizer when really both are required.
I am not sure how to improve that easily, though. Perhaps we will turn it into a concept with a single implementation and improve it over time, but I am not sure you need to do that now.

Further, this seems overly specific, so I would expect some false positives from this. Consider adding at least StringConstCompare as a sanitizer option. (Either directly here in this predicate, or by a construction similar to here.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you have a specific case here? Because the safety laboratory did not give me advice on the elimination of FP.

Copy link
Contributor

Choose a reason for hiding this comment

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

If the user does replace("\r\n", ""), then that will match your predicate and be considered a sanitizer. However, that would not do any replacements on a message having only "\n"'s in it.

Copy link
Contributor

Choose a reason for hiding this comment

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

The other comment is that if the message is compared against a known string, that should be considered a sanitizer (since that is much stronger than doing a replacement).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it would be better if you give me the actual case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@yoff Excuse me again. Do you have examples in actual open source projects for me?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, I had to do a bit of digging. My comment came from seeing code like this, although in that particular case we do want to alert (because the logged value is outside the known set). I think I have seen also that only values in a known set is logged, which would be safe, and that is the sanitizer I proposed. However, it does not seem to be very common, so we could also add it once we see the first FP...

@haby0
Copy link
Contributor Author

haby0 commented Sep 15, 2021

You are not handling HTML logging and encoding, did you plan to? It might require a second configuration, seeing as the sinks dictate which sanitizers are required...

Thanks for the review, about this suggestion, can you describe it again? I am not too understanding.

@yoff
Copy link
Contributor

yoff commented Sep 16, 2021

You are not handling HTML logging and encoding, did you plan to? It might require a second configuration, seeing as the sinks dictate which sanitizers are required...

Thanks for the review, about this suggestion, can you describe it again? I am not too understanding.

I meant that to find html-based log injection, you would need sinks to be logs displayed as HTML and sanitizers to be HTML-relevant escaping. On the whole that seems to be an entirely separate taint tracking configuration. I wonder if that is out of scope for now, but it is mentioned in the help file.

@haby0
Copy link
Contributor Author

haby0 commented Sep 17, 2021

You are not handling HTML logging and encoding, did you plan to? It might require a second configuration, seeing as the sinks dictate which sanitizers are required...

Thanks for the review, about this suggestion, can you describe it again? I am not too understanding.

I meant that to find html-based log injection, you would need sinks to be logs displayed as HTML and sanitizers to be HTML-relevant escaping. On the whole that seems to be an entirely separate taint tracking configuration. I wonder if that is out of scope for now, but it is mentioned in the help file.

Thank you for your reply. I am not going to add HTML injection due to log injection in this PR. I will create a new PR to complete this query.

Copy link
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

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

I have tried to clarify my points.

Comment on lines 38 to 44
override DataFlow::Node getAnInput() {
this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
result = this.getArg(0)
or
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
result = this.getArg(1)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
override DataFlow::Node getAnInput() {
this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
result = this.getArg(0)
or
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
result = this.getArg(1)
}
override DataFlow::Node getAnInput() {
this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
result in [this.getArg(_), this.getArgByName(_) ] // this includes the arg named "msg"
or
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
result in [this.getArg(any(int i | i > 0)), this.getArgByName(any(string s | s != "level")]
}

I think my previous comment was a bit unclear, it should be something like the above.

If you have a signature like

warning(msg, *args, **kwargs)

you can call it with

warning("msg", "arg1", "arg2", kwarg1="kwarg1", kwargs="kwarg2")

and all of these arguments seem to end up in the log.

Comment on lines +18 to +23
override predicate isSanitizer(DataFlow::Node node) {
exists(CallNode call |
node.asCfgNode() = call.getFunction().(AttrNode).getObject("replace") and
call.getArg(0).getNode().(StrConst).getText() in ["\r\n", "\n"]
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

If the user does replace("\r\n", ""), then that will match your predicate and be considered a sanitizer. However, that would not do any replacements on a message having only "\n"'s in it.

Comment on lines +18 to +23
override predicate isSanitizer(DataFlow::Node node) {
exists(CallNode call |
node.asCfgNode() = call.getFunction().(AttrNode).getObject("replace") and
call.getArg(0).getNode().(StrConst).getText() in ["\r\n", "\n"]
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The other comment is that if the message is compared against a known string, that should be considered a sanitizer (since that is much stronger than doing a replacement).

@haby0
Copy link
Contributor Author

haby0 commented Sep 22, 2021

@yoff Compared with the known string, is the known string "\r\n" or "\n"?

@haby0 haby0 requested a review from yoff September 23, 2021 09:05
@haby0
Copy link
Contributor Author

haby0 commented Sep 30, 2021

Friendly ping.

yoff
yoff previously approved these changes Oct 4, 2021
Copy link
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

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

I am still vary of the FP rate, but I do not have any evidence for it, so happy to approve for now.

@haby0
Copy link
Contributor Author

haby0 commented Oct 5, 2021

I am still vary of the FP rate, but I do not have any evidence for it, so happy to approve for now.

Thank you for your reply. If you have any suggestions, please let me know and I will improve the configuration query.

@yoff
Copy link
Contributor

yoff commented Oct 11, 2021

The language tests fail because we now have a mechanism for computing subpaths. You can merge from main and rerun tests. I will also make a suggestion, that I think will make them pass..

…jection.expected

Co-authored-by: yoff <lerchedahl@gmail.com>
@yoff
Copy link
Contributor

yoff commented Oct 11, 2021

The final error is due to formatting, particularly ql/python/ql/src/experimental/semmle/python/frameworks/Log.qll and ql/python/ql/src/experimental/semmle/python/Frameworks.qll.

@haby0
Copy link
Contributor Author

haby0 commented Oct 12, 2021

The final error is due to formatting, particularly ql/python/ql/src/experimental/semmle/python/frameworks/Log.qll and ql/python/ql/src/experimental/semmle/python/Frameworks.qll.

@yoff Automatic formatting is done, please review whether it is passed, thank you.

Copy link
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

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

LGTM

@yoff yoff merged commit 43f7eed into github:main Oct 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants