Skip to content

Commit 958fd9b

Browse files
authored
Merge pull request #7867 from ahmed532009/timing-attacks
Java: Timing attacks while comparing the headers value
2 parents d38cd4a + f981fee commit 958fd9b

File tree

6 files changed

+146
-0
lines changed

6 files changed

+146
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import javax.servlet.http.HttpServletRequest;
2+
import java.nio.charset.StandardCharsets;
3+
import java.security.MessageDigest;
4+
import java.lang.String;
5+
6+
7+
public class Test {
8+
private boolean UnsafeComparison(HttpServletRequest request) {
9+
String Key = "secret";
10+
return Key.equals(request.getHeader("X-Auth-Token"));
11+
}
12+
13+
private boolean safeComparison(HttpServletRequest request) {
14+
String token = request.getHeader("X-Auth-Token");
15+
String Key = "secret";
16+
return MessageDigest.isEqual(Key.getBytes(StandardCharsets.UTF_8), token.getBytes(StandardCharsets.UTF_8));
17+
}
18+
19+
}
20+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<p>
6+
A constant-time algorithm should be used for checking the value of sensitive headers.
7+
In other words, the comparison time should not depend on the content of the input.
8+
Otherwise timing information could be used to infer the header's expected, secret value.
9+
</p>
10+
</overview>
11+
12+
13+
<recommendation>
14+
<p>
15+
Use <code>MessageDigest.isEqual()</code> method to check the value of headers.
16+
If this method is used, then the calculation time depends only on the length of input byte arrays,
17+
and does not depend on the contents of the arrays.
18+
</p>
19+
</recommendation>
20+
<example>
21+
<p>
22+
The following example uses <code>String.equals()</code> method for validating a csrf token.
23+
This method implements a non-constant-time algorithm. The example also demonstrates validation using a safe constant-time algorithm.
24+
</p>
25+
<sample src="TimingAttackAgainstHeader.java" />
26+
</example>
27+
</qhelp>
28+
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* @name Timing attack against header value
3+
* @description Use of a non-constant-time verification routine to check the value of an HTTP header,
4+
* possibly allowing a timing attack to infer the header's expected value.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id java/timing-attack-against-headers-value
9+
* @tags security
10+
* external/cwe/cwe-208
11+
*/
12+
13+
import java
14+
import semmle.code.java.dataflow.FlowSources
15+
import semmle.code.java.dataflow.TaintTracking
16+
import DataFlow::PathGraph
17+
18+
/** A static method that uses a non-constant-time algorithm for comparing inputs. */
19+
private class NonConstantTimeComparisonCall extends StaticMethodAccess {
20+
NonConstantTimeComparisonCall() {
21+
this.getMethod()
22+
.hasQualifiedName("org.apache.commons.lang3", "StringUtils",
23+
["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"])
24+
}
25+
}
26+
27+
/** Methods that use a non-constant-time algorithm for comparing inputs. */
28+
private class NonConstantTimeEqualsCall extends MethodAccess {
29+
NonConstantTimeEqualsCall() {
30+
this.getMethod()
31+
.hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"])
32+
}
33+
}
34+
35+
private predicate isNonConstantEqualsCallArgument(Expr e) {
36+
exists(NonConstantTimeEqualsCall call | e = [call.getQualifier(), call.getArgument(0)])
37+
}
38+
39+
private predicate isNonConstantComparisonCallArgument(Expr p) {
40+
exists(NonConstantTimeComparisonCall call | p = [call.getArgument(0), call.getArgument(1)])
41+
}
42+
43+
class ClientSuppliedIpTokenCheck extends DataFlow::Node {
44+
ClientSuppliedIpTokenCheck() {
45+
exists(MethodAccess ma |
46+
ma.getMethod().hasName("getHeader") and
47+
ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().toLowerCase() in [
48+
"x-auth-token", "x-csrf-token", "http_x_csrf_token", "x-csrf-param", "x-csrf-header",
49+
"http_x_csrf_token", "x-api-key", "authorization", "proxy-authorization"
50+
] and
51+
ma = this.asExpr()
52+
)
53+
}
54+
}
55+
56+
class NonConstantTimeComparisonConfig extends TaintTracking::Configuration {
57+
NonConstantTimeComparisonConfig() { this = "NonConstantTimeComparisonConfig" }
58+
59+
override predicate isSource(DataFlow::Node source) {
60+
source instanceof ClientSuppliedIpTokenCheck
61+
}
62+
63+
override predicate isSink(DataFlow::Node sink) {
64+
isNonConstantEqualsCallArgument(sink.asExpr()) or
65+
isNonConstantComparisonCallArgument(sink.asExpr())
66+
}
67+
}
68+
69+
from DataFlow::PathNode source, DataFlow::PathNode sink, NonConstantTimeComparisonConfig conf
70+
where conf.hasFlowPath(source, sink)
71+
select sink.getNode(), source, sink, "Possible timing attack against $@ validation.",
72+
source.getNode(), "client-supplied token"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import javax.servlet.http.HttpServletRequest;
2+
import java.nio.charset.StandardCharsets;
3+
import java.security.MessageDigest;
4+
import java.lang.String;
5+
6+
7+
public class Test {
8+
private boolean UnsafeComparison(HttpServletRequest request) {
9+
String Key = "secret";
10+
return Key.equals(request.getHeader("X-Auth-Token"));
11+
}
12+
13+
private boolean safeComparison(HttpServletRequest request) {
14+
String token = request.getHeader("X-Auth-Token");
15+
String Key = "secret";
16+
return MessageDigest.isEqual(Key.getBytes(StandardCharsets.UTF_8), token.getBytes(StandardCharsets.UTF_8));
17+
}
18+
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
edges
2+
nodes
3+
| Test.java:10:27:10:59 | getHeader(...) | semmle.label | getHeader(...) |
4+
subpaths
5+
#select
6+
| Test.java:10:27:10:59 | getHeader(...) | Test.java:10:27:10:59 | getHeader(...) | Test.java:10:27:10:59 | getHeader(...) | Possible timing attack against $@ validation. | Test.java:10:27:10:59 | getHeader(...) | client-supplied token |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-208/TimingAttackAgainstHeader.ql

0 commit comments

Comments
 (0)