-
Notifications
You must be signed in to change notification settings - Fork 2k
Python: promote log injection #7735
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
Changes from 12 commits
20d5454
8b5114d
bf1145e
9d41666
c03f89d
7511b33
26befeb
ecea392
119a7e4
c587084
bec8c0d
448e078
f21ac04
bd14ade
62598c0
3a995ec
62d4bb5
b59ab7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -237,6 +237,54 @@ module Stdlib { | |
| } | ||
| } | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // logging | ||
| // --------------------------------------------------------------------------- | ||
| /** | ||
| * Provides models for the `logging.Logger` class | ||
|
yoff marked this conversation as resolved.
Outdated
|
||
| * | ||
| * See https://docs.python.org/3.9/library/logging.html#logging.Logger. | ||
| */ | ||
| module Logger { | ||
| /** Gets a reference to the `logging.Logger` class or any subclass. */ | ||
| private API::Node subclassRef() { | ||
| result = API::moduleImport("logging").getMember("Logger").getASubclass*() | ||
| } | ||
|
|
||
| /** | ||
| * A source of instances of `logging.Logger`, extend this class to model new instances. | ||
| * | ||
| * This can include instantiations of the class, return values from function | ||
| * calls, or a special parameter that will be set when functions are called by an external | ||
| * library. | ||
| * | ||
| * Use the predicate `Logger::instance()` to get references to instances of `logging.Logger`. | ||
| */ | ||
| abstract class InstanceSource extends DataFlow::LocalSourceNode { } | ||
|
|
||
| /** A direct instantiation of `logging.Logger`. */ | ||
| private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode { | ||
| ClassInstantiation() { | ||
| this = subclassRef().getACall() | ||
| or | ||
| this = API::moduleImport("logging").getMember("root").getAnImmediateUse() | ||
| or | ||
| this = API::moduleImport("logging").getMember("getLogger").getACall() | ||
| } | ||
| } | ||
|
|
||
| /** Gets a reference to an instance of `logging.Logger`. */ | ||
| private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) { | ||
| t.start() and | ||
| result instanceof InstanceSource | ||
| or | ||
| exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t)) | ||
| } | ||
|
|
||
| /** Gets a reference to an instance of `logging.Logger`. */ | ||
| DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } | ||
| } | ||
|
Comment on lines
+249
to
+287
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see that I created this non-standard setup with just using Looking over the code again, I don't quite know why I did this in the first place.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Surely it is nicer, if we can get away with using an
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved to standard setup as discussed offline. |
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -2642,27 +2690,6 @@ private module StdlibPrivate { | |
| // --------------------------------------------------------------------------- | ||
| // logging | ||
| // --------------------------------------------------------------------------- | ||
| /** | ||
| * Provides models for the `logging.Logger` class and subclasses. | ||
| * | ||
| * See https://docs.python.org/3.9/library/logging.html#logging.Logger. | ||
| */ | ||
| module Logger { | ||
| /** Gets a reference to the `logging.Logger` class or any subclass. */ | ||
| API::Node subclassRef() { | ||
| result = API::moduleImport("logging").getMember("Logger").getASubclass*() | ||
| } | ||
|
|
||
| /** Gets a reference to an instance of `logging.Logger` or any subclass. */ | ||
| API::Node instance() { | ||
| result = subclassRef().getReturn() | ||
| or | ||
| result = API::moduleImport("logging").getMember("root") | ||
| or | ||
| result = API::moduleImport("logging").getMember("getLogger").getReturn() | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A call to one of the logging methods from `logging` or on a `logging.Logger` | ||
| * subclass. | ||
|
|
@@ -2683,14 +2710,14 @@ private module StdlibPrivate { | |
| method = "log" and | ||
| msgIndex = 1 | ||
| | | ||
| this = Logger::instance().getMember(method).getACall() | ||
| this.(DataFlow::MethodCallNode).calls(Stdlib::Logger::instance(), method) | ||
| or | ||
| this = API::moduleImport("logging").getMember(method).getACall() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't agree about this removal. I see that
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there value in modelling instances specifically? Or could we just rename
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reverted to previous modelling approach as discussed offline. |
||
| ) | ||
| } | ||
|
|
||
| override DataFlow::Node getAnInput() { | ||
| result = this.getArgByName("msg") | ||
| result = this.getArgByName(["msg", "extra"]) | ||
| or | ||
| result = this.getArg(any(int i | i >= msgIndex)) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| /** | ||
| * Provides a taint-tracking configuration for tracking untrusted user input used in log entries. | ||
| * | ||
| * Note, for performance reasons: only import this file if | ||
| * `LogInjection::Configuration` is needed, otherwise | ||
| * `LogInjectionCustomizations` should be imported instead. | ||
| */ | ||
|
|
||
| import python | ||
| import semmle.python.dataflow.new.DataFlow | ||
| import semmle.python.dataflow.new.TaintTracking | ||
|
|
||
| /** | ||
| * Provides a taint-tracking configuration for tracking untrusted user input used in log entries. | ||
| */ | ||
| module LogInjection { | ||
| import LogInjectionCustomizations::LogInjection | ||
|
|
||
| /** | ||
| * A taint-tracking configuration for tracking untrusted user input used in log entries. | ||
| */ | ||
| class Configuration extends TaintTracking::Configuration { | ||
| Configuration() { this = "LogInjection" } | ||
|
|
||
| override predicate isSource(DataFlow::Node source) { source instanceof Source } | ||
|
|
||
| override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } | ||
|
|
||
| override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } | ||
|
|
||
| override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { | ||
| guard instanceof SanitizerGuard | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| /** | ||
| * Provides default sources, sinks and sanitizers for detecting | ||
| * "log injection" | ||
| * vulnerabilities, as well as extension points for adding your own. | ||
| */ | ||
|
|
||
| private import python | ||
| private import semmle.python.dataflow.new.DataFlow | ||
| private import semmle.python.Concepts | ||
| private import semmle.python.dataflow.new.RemoteFlowSources | ||
| private import semmle.python.dataflow.new.BarrierGuards | ||
|
|
||
| /** | ||
| * Provides default sources, sinks and sanitizers for detecting | ||
| * "log injection" | ||
| * vulnerabilities, as well as extension points for adding your own. | ||
| */ | ||
| module LogInjection { | ||
| /** | ||
| * A data flow source for "log injection" vulnerabilities. | ||
| */ | ||
| abstract class Source extends DataFlow::Node { } | ||
|
|
||
| /** | ||
| * A data flow sink for "log injection" vulnerabilities. | ||
| */ | ||
| abstract class Sink extends DataFlow::Node { } | ||
|
|
||
| /** | ||
| * A sanitizer for "log injection" vulnerabilities. | ||
| */ | ||
| abstract class Sanitizer extends DataFlow::Node { } | ||
|
|
||
| /** | ||
| * A sanitizer guard for "log injection" vulnerabilities. | ||
| */ | ||
| abstract class SanitizerGuard extends DataFlow::BarrierGuard { } | ||
|
|
||
| /** | ||
| * A source of remote user input, considered as a flow source. | ||
| */ | ||
| class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { } | ||
|
|
||
| /** | ||
| * A logging operation, considered as a flow sink. | ||
| */ | ||
| class LoggingAsSink extends Sink { | ||
| LoggingAsSink() { this = any(Logging write).getAnInput() } | ||
| } | ||
|
|
||
| /** | ||
| * A comparison with a constant string, considered as a sanitizer-guard. | ||
| */ | ||
| class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { } | ||
|
|
||
| /** | ||
| * A call to replace line breaks functions as a sanitizer. | ||
|
yoff marked this conversation as resolved.
Outdated
|
||
| */ | ||
| class ReplaceLineBreaksSanitizer extends Sanitizer, DataFlow::CallCfgNode { | ||
| ReplaceLineBreaksSanitizer() { | ||
| this.getFunction().(DataFlow::AttrRead).getAttributeName() = "replace" and | ||
| this.getArg(0).asExpr().(StrConst).getText() in ["\r\n", "\n"] | ||
| } | ||
| } | ||
|
Comment on lines
+59
to
+72
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sanitizer check seems a bit weak. What if it only did
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have had the same concerns from the beginning.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be great to have a small comment in the code to explain this dilemma, and explain why the solution implemented was deemed ok. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| --- | ||
| category: newQuery | ||
| --- | ||
| * The query "Log Injection" (`py/log-injection`) has been promoted from experimental to the main query pack. Its results will now appear when `security-extended` is used. This query was originally [submitted as an experimental query by @haby0](https://github.com/github/codeql/pull/6182). |
Uh oh!
There was an error while loading. Please reload this page.