Skip to content

Commit 494e7d9

Browse files
committed
add unsafe HMAC comparison query and qlhelp file
1 parent c69a9ea commit 494e7d9

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
private import codeql.ruby.AST
2+
import codeql.ruby.AST
3+
import codeql.ruby.DataFlow
4+
import codeql.ruby.ApiGraphs
5+
import codeql.ruby.dataflow.RemoteFlowSources
6+
import codeql.ruby.ast.Operation
7+
import codeql.ruby.TaintTracking
8+
import ruby
9+
10+
/**
11+
* @kind problem
12+
*/
13+
14+
// A call to OpenSSL::HMAC.hexdigest
15+
class OpenSSLHMACHexdigest extends DataFlow::Node {
16+
OpenSSLHMACHexdigest() {
17+
this = API::getTopLevelMember("OpenSSL").getMember("HMAC").getAMethodCall("hexdigest")
18+
}
19+
}
20+
21+
// A call to OpenSSL::HMAC.to_s (which is an alias for OpenSSL::HMAC.hexdigest)
22+
class OpenSSLHMACtos extends DataFlow::Node {
23+
OpenSSLHMACtos() {
24+
this = API::getTopLevelMember("OpenSSL").getMember("HMAC").getAMethodCall("to_s")
25+
}
26+
}
27+
28+
// A call to OpenSSL::HMAC.digest
29+
class OpenSSLHMACdigest extends DataFlow::Node {
30+
OpenSSLHMACdigest() {
31+
this = API::getTopLevelMember("OpenSSL").getMember("HMAC").getAMethodCall("digest")
32+
}
33+
}
34+
35+
// A call to OpenSSL::HMAC.new
36+
class OpenSSLnewHMAC extends DataFlow::Node {
37+
OpenSSLnewHMAC() {
38+
this = API::getTopLevelMember("OpenSSL").getMember("HMAC").getAnInstantiation()
39+
}
40+
}
41+
42+
// A call to OpenSSL::HMAC.base64digest
43+
class OpenSSLHmacbase64digest extends DataFlow::Node {
44+
OpenSSLHmacbase64digest() {
45+
this = API::getTopLevelMember("OpenSSL").getMember("HMAC").getAMethodCall("base64digest")
46+
}
47+
}
48+
49+
class Configuration extends DataFlow::Configuration {
50+
Configuration() { this = "UnsafeHMACComparison" }
51+
52+
override predicate isSource(DataFlow::Node source) {
53+
source instanceof OpenSSLHMACHexdigest or
54+
source instanceof OpenSSLnewHMAC or
55+
source instanceof OpenSSLHmacbase64digest or
56+
source instanceof OpenSSLHMACdigest or
57+
source instanceof OpenSSLHMACtos
58+
}
59+
60+
// Holds if a given sink is an Equality Operation (== or !=)
61+
override predicate isSink(DataFlow::Node sink) {
62+
exists(EqualityOperation eqOp |
63+
eqOp.getLeftOperand() = sink.asExpr().getExpr()
64+
or
65+
eqOp.getRightOperand() = sink.asExpr().getExpr()
66+
)
67+
}
68+
}
69+
70+
from DataFlow::Node source, DataFlow::Node sink, Configuration config
71+
where config.hasFlow(source, sink)
72+
select sink,
73+
"An HMAC is being compared using the equality operator. This may be vulnerable to a cryptographic timing attack because the equality operation does not occur in constant time."
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
Using the `==` or `!=` operator to compare a known valid HMAC with a user-supplied HMAC digest could lead to a timing attack, as these operations do not occur in constant time.
8+
</p>
9+
</overview>
10+
<recommendation>
11+
<p>
12+
Instead of using `==` or `!=` to compare a known valid HMAC with a user-supplied HMAC digest use Rack::Utils#secure_compare, ActiveSupport::SecurityUtils#secure_compare or OpenSSL.secure_compare
13+
</p>
14+
</recommendation>
15+
<example>
16+
<p>
17+
In this example, the HMAC is validated using the `==` operation.
18+
</p>
19+
<sample src="./examples/unsafe_hmac_comparison.rb" />
20+
</example>
21+
</qhelp>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class UnsafeHmacComparison
2+
def verify_hmac(host, hmac, salt)
3+
sha1 = OpenSSL::Digest.new('sha1')
4+
if OpenSSL::HMAC.digest(sha1, salt, host) == hmac
5+
puts "HMAC verified"
6+
else
7+
puts "HMAC not verified"
8+
end
9+
end
10+
end
11+

0 commit comments

Comments
 (0)