Skip to content

Commit 122d188

Browse files
authored
Merge pull request github#10832 from erik-krogh/passRb
RB: add model for the `Digest` and `OpenSSL::Digest` modules
2 parents 85fbf4b + 191efdf commit 122d188

File tree

7 files changed

+124
-1
lines changed

7 files changed

+124
-1
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The hashing algorithms from `Digest` and `OpenSSL::Digest` are now recognized and can be flagged by the `rb/weak-cryptographic-algorithm` query.

ruby/ql/lib/codeql/ruby/ApiGraphs.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ module API {
203203
/**
204204
* Gets a node representing a call to `method` on the receiver represented by this node.
205205
*/
206-
Node getMethod(string method) {
206+
MethodAccessNode getMethod(string method) {
207207
result = this.getASubclass().getASuccessor(Label::method(method))
208208
}
209209

ruby/ql/lib/codeql/ruby/frameworks/Core.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import core.Hash
1414
import core.String
1515
import core.Regexp
1616
import core.IO
17+
import core.Digest
1718

1819
/**
1920
* A system command executed via subshell literal syntax.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Provides modeling for the `Digest` module.
3+
*/
4+
5+
private import codeql.ruby.ApiGraphs
6+
private import codeql.ruby.Concepts
7+
private import codeql.ruby.DataFlow
8+
9+
/** Gets an API node for a Digest class that hashes using `algo`. */
10+
private API::Node digest(Cryptography::HashingAlgorithm algo) {
11+
exists(string name | result = API::getTopLevelMember("Digest").getMember(name) |
12+
name = ["MD5", "SHA1", "SHA2", "RMD160"] and
13+
algo.matchesName(name)
14+
)
15+
}
16+
17+
/** A call that hashes some input using a hashing algorithm from the `Digest` module. */
18+
private class DigestCall extends Cryptography::CryptographicOperation::Range instanceof DataFlow::CallNode {
19+
Cryptography::HashingAlgorithm algo;
20+
21+
DigestCall() {
22+
this = digest(algo).getAMethodCall(["hexdigest", "base64digest", "bubblebabble"])
23+
or
24+
this = digest(algo).getAMethodCall("file") // it's directly hashing the contents of a file, but that's close enough for us.
25+
or
26+
this = digest(algo).getInstance().getAMethodCall(["digest", "update", "<<"])
27+
}
28+
29+
override Cryptography::HashingAlgorithm getAlgorithm() { result = algo }
30+
31+
override DataFlow::Node getAnInput() { result = super.getArgument(0) }
32+
33+
override Cryptography::BlockMode getBlockMode() { none() }
34+
}

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,3 +581,49 @@ private class CipherOperation extends Cryptography::CryptographicOperation::Rang
581581
result = cipherNode.getCipherMode().getBlockMode()
582582
}
583583
}
584+
585+
/** Predicates and classes modeling the `OpenSSL::Digest` module */
586+
private module Digest {
587+
private import codeql.ruby.ApiGraphs
588+
589+
/** A call that hashes some input using a hashing algorithm from the `OpenSSL::Digest` module. */
590+
private class DigestCall extends Cryptography::CryptographicOperation::Range instanceof DataFlow::CallNode {
591+
Cryptography::HashingAlgorithm algo;
592+
593+
DigestCall() {
594+
exists(API::MethodAccessNode call |
595+
call = API::getTopLevelMember("OpenSSL").getMember("Digest").getMethod("new")
596+
|
597+
this = call.getReturn().getAMethodCall(["digest", "update", "<<"]) and
598+
algo.matchesName(call.getCallNode()
599+
.getArgument(0)
600+
.asExpr()
601+
.getExpr()
602+
.getConstantValue()
603+
.getString())
604+
)
605+
}
606+
607+
override Cryptography::HashingAlgorithm getAlgorithm() { result = algo }
608+
609+
override DataFlow::Node getAnInput() { result = super.getArgument(0) }
610+
611+
override Cryptography::BlockMode getBlockMode() { none() }
612+
}
613+
614+
/** A call to `OpenSSL::Digest.digest` that hashes input directly without constructing a digest instance. */
615+
private class DigestCallDirect extends Cryptography::CryptographicOperation::Range instanceof DataFlow::CallNode {
616+
Cryptography::HashingAlgorithm algo;
617+
618+
DigestCallDirect() {
619+
this = API::getTopLevelMember("OpenSSL").getMember("Digest").getMethod("digest").getCallNode() and
620+
algo.matchesName(this.getArgument(0).asExpr().getExpr().getConstantValue().getString())
621+
}
622+
623+
override Cryptography::HashingAlgorithm getAlgorithm() { result = algo }
624+
625+
override DataFlow::Node getAnInput() { result = super.getArgument(1) }
626+
627+
override Cryptography::BlockMode getBlockMode() { none() }
628+
}
629+
}

ruby/ql/test/query-tests/security/cwe-327/BrokenCryptoAlgorithm.expected

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,13 @@
1717
| broken_crypto.rb:75:1:75:24 | call to new | The cryptographic algorithm RC4 is broken or weak, and should not be used. |
1818
| broken_crypto.rb:77:1:77:29 | call to new | The cryptographic algorithm RC4 is broken or weak, and should not be used. |
1919
| broken_crypto.rb:79:1:79:35 | call to new | The cryptographic algorithm RC4 is broken or weak, and should not be used. |
20+
| broken_crypto.rb:81:1:81:28 | call to hexdigest | The cryptographic algorithm MD5 is broken or weak, and should not be used. |
21+
| broken_crypto.rb:84:1:84:31 | call to base64digest | The cryptographic algorithm MD5 is broken or weak, and should not be used. |
22+
| broken_crypto.rb:87:1:87:20 | call to digest | The cryptographic algorithm MD5 is broken or weak, and should not be used. |
23+
| broken_crypto.rb:89:1:89:21 | call to update | The cryptographic algorithm MD5 is broken or weak, and should not be used. |
24+
| broken_crypto.rb:90:1:90:17 | ... << ... | The cryptographic algorithm MD5 is broken or weak, and should not be used. |
25+
| broken_crypto.rb:95:1:95:34 | call to bubblebabble | The cryptographic algorithm MD5 is broken or weak, and should not be used. |
26+
| broken_crypto.rb:97:11:97:37 | call to file | The cryptographic algorithm MD5 is broken or weak, and should not be used. |
27+
| broken_crypto.rb:103:1:103:21 | call to digest | The cryptographic algorithm SHA1 is broken or weak, and should not be used. |
28+
| broken_crypto.rb:104:1:104:17 | ... << ... | The cryptographic algorithm SHA1 is broken or weak, and should not be used. |
29+
| broken_crypto.rb:106:1:106:37 | call to digest | The cryptographic algorithm SHA1 is broken or weak, and should not be used. |

ruby/ql/test/query-tests/security/cwe-327/broken_crypto.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,31 @@
7777
OpenSSL::Cipher::RC4.new '40'
7878
# BAD: weak encryption algorithm
7979
OpenSSL::Cipher::RC4.new 'hmac-md5'
80+
81+
Digest::MD5.hexdigest('foo') # BAD: weak hash algorithm
82+
Digest::SHA256.hexdigest('foo') # GOOD: strong hash algorithm
83+
84+
Digest::MD5.base64digest('foo') # BAD: weak hash algorithm
85+
86+
md5 = Digest::MD5.new
87+
md5.digest 'message' # BAD: weak hash algorithm
88+
89+
md5.update 'message1' # BAD: weak hash algorithm
90+
md5 << 'message2' # << is an alias for update
91+
92+
sha256 = Digest::SHA256.new
93+
sha256.digest 'message' # GOOD: strong hash algorithm
94+
95+
Digest::MD5.bubblebabble 'message' # BAD: weak hash algorithm
96+
97+
filemd5 = Digest::MD5.file 'testfile'
98+
filemd5.hexdigest
99+
100+
Digest("MD5").hexdigest('foo') # BAD: weak hash algorithm
101+
102+
sha1 = OpenSSL::Digest.new('SHA1')
103+
sha1.digest 'message' # BAD: weak hash algorithm
104+
sha1 << 'message' # << is an alias for update
105+
106+
OpenSSL::Digest.digest('SHA1', "abc") # BAD: weak hash algorithm
107+
OpenSSL::Digest.digest('SHA3-512', "abc") # GOOD: strong hash algorithm

0 commit comments

Comments
 (0)