Skip to content

Commit 95bf18f

Browse files
committed
Ruby: make hex-escaped strings ("\xCD\xEF" etc.) sources of hardcoded data
1 parent acf5b11 commit 95bf18f

File tree

3 files changed

+32
-4
lines changed

3 files changed

+32
-4
lines changed

ruby/ql/lib/codeql/ruby/security/HardcodedDataInterpretedAsCodeCustomizations.qll

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
private import codeql.ruby.DataFlow
88
private import codeql.ruby.security.CodeInjectionCustomizations
99
private import codeql.ruby.AST as AST
10+
private import codeql.ruby.controlflow.CfgNodes
1011

1112
/**
1213
* Provides default sources, sinks and sanitizers for reasoning about hard-coded
@@ -57,15 +58,32 @@ module HardcodedDataInterpretedAsCode {
5758
* least one digit), viewed as a source of hard-coded data that should not be
5859
* interpreted as code.
5960
*/
60-
private class DefaultSource extends Source, DataFlow::LocalSourceNode {
61-
DefaultSource() {
62-
exists(string val | this.asExpr().getConstantValue().isString(val) |
61+
private class HexStringSource extends Source {
62+
HexStringSource() {
63+
exists(string val |
64+
this.asExpr().(ExprNodes::StringLiteralCfgNode).getConstantValue().isString(val)
65+
|
6366
val.regexpMatch("[0-9a-fA-F]{8,}") and
6467
val.regexpMatch(".*[0-9].*")
6568
)
6669
}
6770
}
6871

72+
/**
73+
* A string literal whose raw text is made up entirely of `\x` escape
74+
* sequences, viewed as a source of hard-coded data that should not be
75+
* interpreted as code.
76+
*/
77+
private class HexEscapedStringSource extends Source {
78+
HexEscapedStringSource() {
79+
forex(StringComponentCfgNode c |
80+
c = this.asExpr().(ExprNodes::StringlikeLiteralCfgNode).getAComponent()
81+
|
82+
c.getNode().(AST::StringEscapeSequenceComponent).getRawText().prefix(2) = "\\x"
83+
)
84+
}
85+
}
86+
6987
/**
7088
* A code injection sink; hard-coded data should not flow here.
7189
*/

ruby/ql/test/query-tests/security/cwe-506/HardcodedDataInterpretedAsCode.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ edges
66
| tst.rb:7:8:7:30 | totally_harmless_string : | tst.rb:7:6:7:31 | call to e |
77
| tst.rb:10:11:10:24 | "666f6f626172" : | tst.rb:1:7:1:7 | r : |
88
| tst.rb:10:11:10:24 | "666f6f626172" : | tst.rb:10:9:10:25 | call to e |
9+
| tst.rb:16:31:16:84 | "\\x70\\x75\\x74\\x73\\x28\\x27\\x68\\..." : | tst.rb:17:6:17:32 | another_questionable_string : |
10+
| tst.rb:17:6:17:32 | another_questionable_string : | tst.rb:17:6:17:38 | call to strip |
911
nodes
1012
| tst.rb:1:7:1:7 | r : | semmle.label | r : |
1113
| tst.rb:2:3:2:15 | call to pack : | semmle.label | call to pack : |
@@ -15,9 +17,13 @@ nodes
1517
| tst.rb:7:8:7:30 | totally_harmless_string : | semmle.label | totally_harmless_string : |
1618
| tst.rb:10:9:10:25 | call to e | semmle.label | call to e |
1719
| tst.rb:10:11:10:24 | "666f6f626172" : | semmle.label | "666f6f626172" : |
20+
| tst.rb:16:31:16:84 | "\\x70\\x75\\x74\\x73\\x28\\x27\\x68\\..." : | semmle.label | "\\x70\\x75\\x74\\x73\\x28\\x27\\x68\\..." : |
21+
| tst.rb:17:6:17:32 | another_questionable_string : | semmle.label | another_questionable_string : |
22+
| tst.rb:17:6:17:38 | call to strip | semmle.label | call to strip |
1823
subpaths
1924
| tst.rb:7:8:7:30 | totally_harmless_string : | tst.rb:1:7:1:7 | r : | tst.rb:2:3:2:15 | call to pack : | tst.rb:7:6:7:31 | call to e |
2025
| tst.rb:10:11:10:24 | "666f6f626172" : | tst.rb:1:7:1:7 | r : | tst.rb:2:3:2:15 | call to pack : | tst.rb:10:9:10:25 | call to e |
2126
#select
2227
| tst.rb:7:6:7:31 | call to e | tst.rb:5:27:5:72 | "707574732822636f646520696e6a6..." : | tst.rb:7:6:7:31 | call to e | Hard-coded data from $@ is interpreted as code. | tst.rb:5:27:5:72 | "707574732822636f646520696e6a6..." | here |
2328
| tst.rb:10:9:10:25 | call to e | tst.rb:10:11:10:24 | "666f6f626172" : | tst.rb:10:9:10:25 | call to e | Hard-coded data from $@ is interpreted as an import path. | tst.rb:10:11:10:24 | "666f6f626172" | here |
29+
| tst.rb:17:6:17:38 | call to strip | tst.rb:16:31:16:84 | "\\x70\\x75\\x74\\x73\\x28\\x27\\x68\\..." : | tst.rb:17:6:17:38 | call to strip | Hard-coded data from $@ is interpreted as code. | tst.rb:16:31:16:84 | "\\x70\\x75\\x74\\x73\\x28\\x27\\x68\\..." | here |

ruby/ql/test/query-tests/security/cwe-506/tst.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@ def e(r)
1111
require '666f6f626172' # OK: no taint step between source and sink
1212

1313
x = 'deadbeef'
14-
require e(x) # OK: doesn't meet our criteria for being a source
14+
require e(x) # OK: doesn't meet our criteria for being a source
15+
16+
another_questionable_string = "\x70\x75\x74\x73\x28\x27\x68\x65\x6C\x6C\x6F\x27\x29"
17+
eval(another_questionable_string.strip) # NOT OK: eval("puts('hello'")
18+
eval(another_questionable_string) # OK: no taint step between source and sink

0 commit comments

Comments
 (0)