-
Notifications
You must be signed in to change notification settings - Fork 1.8k
RB: don't flag code-injection for dynamic loading where an attacker only controls a substring #10883
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
RB: don't flag code-injection for dynamic loading where an attacker only controls a substring #10883
Changes from all commits
cb33d5a
d77b316
2a72e89
3e51f6f
226bd1f
3dd89bb
24916f8
bb8bcd4
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 |
---|---|---|
|
@@ -11,20 +11,41 @@ private import codeql.ruby.dataflow.BarrierGuards | |
* adding your own. | ||
*/ | ||
module CodeInjection { | ||
/** Flow states used to distinguish whether an attacker controls the entire string. */ | ||
module FlowState { | ||
/** Flow state used for normal tainted data, where an attacker might only control a substring. */ | ||
DataFlow::FlowState substring() { result = "substring" } | ||
|
||
/** Flow state used for data that is entirely controlled by the attacker. */ | ||
DataFlow::FlowState full() { result = "full" } | ||
} | ||
|
||
/** | ||
* A data flow source for "Code injection" vulnerabilities. | ||
*/ | ||
abstract class Source extends DataFlow::Node { } | ||
abstract class Source extends DataFlow::Node { | ||
/** Gets a flow state for which this is a source. */ | ||
DataFlow::FlowState getAFlowState() { result = [FlowState::substring(), FlowState::full()] } | ||
} | ||
|
||
/** | ||
* A data flow sink for "Code injection" vulnerabilities. | ||
*/ | ||
abstract class Sink extends DataFlow::Node { } | ||
abstract class Sink extends DataFlow::Node { | ||
/** Holds if this sink is safe for an attacker that only controls a substring. */ | ||
DataFlow::FlowState getAFlowState() { result = [FlowState::substring(), FlowState::full()] } | ||
} | ||
|
||
/** | ||
* A sanitizer for "Code injection" vulnerabilities. | ||
*/ | ||
abstract class Sanitizer extends DataFlow::Node { } | ||
abstract class Sanitizer extends DataFlow::Node { | ||
/** | ||
* Gets a flow state for which this is a sanitizer. | ||
* Sanitizes all states if the result is empty. | ||
*/ | ||
DataFlow::FlowState getAFlowState() { none() } | ||
} | ||
|
||
/** | ||
* DEPRECATED: Use `Sanitizer` instead. | ||
|
@@ -42,6 +63,35 @@ module CodeInjection { | |
* A call that evaluates its arguments as Ruby code, considered as a flow sink. | ||
*/ | ||
class CodeExecutionAsSink extends Sink { | ||
CodeExecutionAsSink() { this = any(CodeExecution c).getCode() } | ||
CodeExecution c; | ||
|
||
CodeExecutionAsSink() { this = c.getCode() } | ||
|
||
/** Gets a flow state for which this is a sink. */ | ||
override DataFlow::FlowState getAFlowState() { | ||
if c.runsArbitraryCode() | ||
then result = [FlowState::substring(), FlowState::full()] // If it runs immediately, then it's always vulnerable. | ||
else result = FlowState::full() // If it "just" loads something, then it's only vulnerable if the attacker controls the entire string. | ||
} | ||
} | ||
|
||
private import codeql.ruby.AST as Ast | ||
|
||
/** | ||
* A string-concatenation that sanitizes the `full()` state. | ||
*/ | ||
class StringConcatenationSanitizer extends Sanitizer { | ||
StringConcatenationSanitizer() { | ||
// string concatenations sanitize the `full` state, as an attacker no longer controls the entire string | ||
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 seems overly restrictive. User input flowing into params[:foo] = "f; end; system('bad stuff'); def g"
eval("def " + params[:foo] + "; end")
# => def f; end; system('bad stuff'); def g; end 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. It only restricts flow for the But I found an unrelated issue while adding a test that confirmed that it works: #10968 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. Interesting. I tested this myself to check that it didn't work correctly, and I guess was also affected by that issue. |
||
exists(Ast::AstNode str | | ||
str instanceof Ast::StringLiteral | ||
or | ||
str instanceof Ast::AddExpr | ||
| | ||
this.asExpr().getExpr() = str | ||
) | ||
} | ||
|
||
override DataFlow::FlowState getAFlowState() { result = FlowState::full() } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,13 @@ import DataFlow::PathGraph | |
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Source sourceNode | ||
where | ||
config.hasFlowPath(source, sink) and | ||
sourceNode = source.getNode() | ||
sourceNode = source.getNode() and | ||
// removing duplications of the same path, but different flow-labels. | ||
sink = | ||
min(DataFlow::PathNode otherSink | | ||
config.hasFlowPath(any(DataFlow::PathNode s | s.getNode() = source.getNode()), otherSink) | ||
| | ||
otherSink order by otherSink.getState() | ||
) | ||
Comment on lines
-24
to
+31
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. Seems like this should be fixed in the shared libraries, the same way we did in JS. I've opened an internal issue for it. |
||
select sink.getNode(), source, sink, "This code execution depends on a $@.", source.getNode(), | ||
"user-provided value" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.