Skip to content

Commit ff8bb2b

Browse files
authored
Merge pull request github#16760 from owen-mc/java/reverse-dns-separate-threat-model-kind
Java: make a separate threat model kind for reverse DNS sources
2 parents d257331 + 2a5144d commit ff8bb2b

File tree

10 files changed

+104
-29
lines changed

10 files changed

+104
-29
lines changed

docs/codeql/reusables/threat-model-description.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ A threat model is a named class of dataflow sources that can be enabled or disab
55
The ``kind`` property of the ``sourceModel`` determines which threat model a source is associated with. There are two main categories:
66

77
- ``remote`` which represents requests and responses from the network.
8-
- ``local`` which represents data from local files (``file``), command-line arguments (``commandargs``), database reads (``database``), and environment variables(``environment``).
8+
- ``local`` which represents data from local files (``file``), command-line arguments (``commandargs``), database reads (``database``), environment variables(``environment``) and Windows registry values ("windows-registry"). Currently, Windows registry values are used by C# only.
9+
10+
Note that subcategories can be turned included or excluded separately, so you can specify ``local`` without ``database``, or just ``commandargs`` and ``environment`` without the rest of ``local``.
11+
12+
The less commonly used categories are:
13+
14+
- ``android`` which represents reads from external files in Android (``android-external-storage-dir``) and parameter of an entry-point method declared in a ``ContentProvider`` class (``contentprovider``). Currently only used by Java/Kotlin.
15+
- ``database-access-result`` which represents a database access. Currently only used by JavaScript.
16+
- ``file-write`` which represents opening a file in write mode. Currently only used in C#.
17+
- ``reverse-dns`` which represents reverse DNS lookups. Currently only used in Java.
918

1019
When running a CodeQL analysis, the ``remote`` threat model is included by default. You can optionally include other threat models as appropriate when using the CodeQL CLI and in GitHub code scanning. For more information, see `Analyzing your code with CodeQL queries <https://docs.github.com/code-security/codeql-cli/getting-started-with-the-codeql-cli/analyzing-your-code-with-codeql-queries#including-model-packs-to-add-potential-sources-of-tainted-data>`__ and `Customizing your advanced setup for code scanning <https://docs.github.com/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#extending-codeql-coverage-with-threat-models>`__.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: majorAnalysis
3+
---
4+
* We previously considered reverse DNS resolutions (IP address -> domain name) as sources of untrusted data, since compromised/malicious DNS servers could potentially return malicious responses to arbitrary requests. We have now removed this source from the default set of untrusted sources and made a new threat model kind for them, called "reverse-dns". You can optionally include other threat models as appropriate when using the CodeQL CLI and in GitHub code scanning. For more information, see [Analyzing your code with CodeQL queries](https://docs.github.com/code-security/codeql-cli/getting-started-with-the-codeql-cli/analyzing-your-code-with-codeql-queries#including-model-packs-to-add-potential-sources-of-tainted-data>) and [Customizing your advanced setup for code scanning](https://docs.github.com/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning#extending-codeql-coverage-with-threat-models).

java/ql/lib/semmle/code/java/dataflow/FlowSources.qll

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -119,21 +119,6 @@ private predicate variableStep(Expr tracked, VarAccess sink) {
119119
)
120120
}
121121

122-
private class ReverseDnsSource extends RemoteFlowSource {
123-
ReverseDnsSource() {
124-
// Try not to trigger on `localhost`.
125-
exists(MethodCall m | m = this.asExpr() |
126-
m.getMethod() instanceof ReverseDnsMethod and
127-
not exists(MethodCall l |
128-
(variableStep(l, m.getQualifier()) or l = m.getQualifier()) and
129-
(l.getMethod().getName() = "getLocalHost" or l.getMethod().getName() = "getLoopbackAddress")
130-
)
131-
)
132-
}
133-
134-
override string getSourceType() { result = "reverse DNS lookup" }
135-
}
136-
137122
private class MessageBodyReaderParameterSource extends RemoteFlowSource {
138123
MessageBodyReaderParameterSource() {
139124
exists(MessageBodyReaderRead m |
@@ -388,6 +373,24 @@ class AndroidJavascriptInterfaceMethodParameter extends RemoteFlowSource {
388373
}
389374
}
390375

376+
/** A node with input that comes from a reverse DNS lookup. */
377+
abstract class ReverseDnsUserInput extends UserInput {
378+
override string getThreatModel() { result = "reverse-dns" }
379+
}
380+
381+
private class ReverseDnsSource extends ReverseDnsUserInput {
382+
ReverseDnsSource() {
383+
// Try not to trigger on `localhost`.
384+
exists(MethodCall m | m = this.asExpr() |
385+
m.getMethod() instanceof ReverseDnsMethod and
386+
not exists(MethodCall l |
387+
(variableStep(l, m.getQualifier()) or l = m.getQualifier()) and
388+
(l.getMethod().getName() = "getLocalHost" or l.getMethod().getName() = "getLoopbackAddress")
389+
)
390+
)
391+
}
392+
}
393+
391394
/**
392395
* A data flow source node for an API, which should be considered
393396
* supported for a modeling perspective.

java/ql/test/library-tests/dataflow/taintsources/A.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ public void test(ResultSet rs) throws SQLException {
4343
};
4444
sink(new URL("test").openConnection().getInputStream()); // $hasRemoteValueFlow
4545
sink(new Socket("test", 1234).getInputStream()); // $hasRemoteValueFlow
46-
sink(InetAddress.getByName("test").getHostName()); // $hasRemoteValueFlow
46+
sink(InetAddress.getByName("test").getHostName()); // $hasReverseDnsValueFlow
47+
sink(InetAddress.getLocalHost().getHostName());
48+
sink(InetAddress.getLoopbackAddress().getHostName());
49+
sink(InetAddress.getByName("test").getCanonicalHostName()); // $hasReverseDnsValueFlow
50+
sink(InetAddress.getLocalHost().getCanonicalHostName());
51+
sink(InetAddress.getLoopbackAddress().getCanonicalHostName());
4752

4853
sink(System.in); // $hasLocalValueFlow
4954
sink(new FileInputStream("test")); // $hasLocalValueFlow

java/ql/test/library-tests/dataflow/taintsources/local.ql

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,20 @@ import java
22
import semmle.code.java.dataflow.FlowSources
33
import TestUtilities.InlineExpectationsTest
44

5-
class LocalSource extends DataFlow::Node instanceof UserInput {
6-
LocalSource() { not this instanceof RemoteFlowSource }
7-
}
8-
95
predicate isTestSink(DataFlow::Node n) {
106
exists(MethodCall ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
117
}
128

139
module LocalValueConfig implements DataFlow::ConfigSig {
14-
predicate isSource(DataFlow::Node n) { n instanceof LocalSource }
10+
predicate isSource(DataFlow::Node n) { n instanceof LocalUserInput }
1511

1612
predicate isSink(DataFlow::Node n) { isTestSink(n) }
1713
}
1814

1915
module LocalValueFlow = DataFlow::Global<LocalValueConfig>;
2016

2117
module LocalTaintConfig implements DataFlow::ConfigSig {
22-
predicate isSource(DataFlow::Node n) { n instanceof LocalSource }
18+
predicate isSource(DataFlow::Node n) { n instanceof LocalUserInput }
2319

2420
predicate isSink(DataFlow::Node n) { isTestSink(n) }
2521
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
failures
2+
testFailures
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import java
2+
import semmle.code.java.dataflow.FlowSources
3+
import TestUtilities.InlineExpectationsTest
4+
5+
predicate isTestSink(DataFlow::Node n) {
6+
exists(MethodCall ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
7+
}
8+
9+
module ReverseDnsValueConfig implements DataFlow::ConfigSig {
10+
predicate isSource(DataFlow::Node n) { n instanceof ReverseDnsUserInput }
11+
12+
predicate isSink(DataFlow::Node n) { isTestSink(n) }
13+
}
14+
15+
module ReverseDnsValueFlow = DataFlow::Global<ReverseDnsValueConfig>;
16+
17+
module ReverseDnsTaintConfig implements DataFlow::ConfigSig {
18+
predicate isSource(DataFlow::Node n) { n instanceof ReverseDnsUserInput }
19+
20+
predicate isSink(DataFlow::Node n) { isTestSink(n) }
21+
}
22+
23+
module ReverseDnsTaintFlow = TaintTracking::Global<ReverseDnsTaintConfig>;
24+
25+
module ReverseDnsFlowTest implements TestSig {
26+
string getARelevantTag() { result = ["hasReverseDnsValueFlow", "hasReverseDnsTaintFlow"] }
27+
28+
predicate hasActualResult(Location location, string element, string tag, string value) {
29+
tag = "hasReverseDnsValueFlow" and
30+
exists(DataFlow::Node sink | ReverseDnsValueFlow::flowTo(sink) |
31+
sink.getLocation() = location and
32+
element = sink.toString() and
33+
value = ""
34+
)
35+
or
36+
tag = "hasReverseDnsTaintFlow" and
37+
exists(DataFlow::Node src, DataFlow::Node sink |
38+
ReverseDnsTaintFlow::flow(src, sink) and not ReverseDnsValueFlow::flow(src, sink)
39+
|
40+
sink.getLocation() = location and
41+
element = sink.toString() and
42+
value = ""
43+
)
44+
}
45+
}
46+
47+
import MakeTest<ReverseDnsFlowTest>

java/ql/test/query-tests/security/CWE-022/semmle/tests/Test.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import java.io.IOException;
66
import java.io.InputStream;
77
import java.io.OutputStream;
8-
import java.net.InetAddress;
98
import java.net.URL;
109
import java.nio.charset.Charset;
1110
import java.nio.file.Files;
1211
import java.nio.file.Path;
12+
13+
import javax.servlet.http.HttpServletRequest;
1314
import javax.xml.transform.stream.StreamResult;
15+
1416
import org.apache.commons.io.FileUtils;
1517
import org.apache.tools.ant.AntClassLoader;
1618
import org.apache.tools.ant.DirectoryScanner;
@@ -24,10 +26,10 @@
2426

2527
public class Test {
2628

27-
private InetAddress address;
29+
private HttpServletRequest request;
2830

2931
public Object source() {
30-
return address.getHostName();
32+
return request.getParameter("source");
3133
}
3234

3335
void test() throws IOException {
@@ -166,8 +168,8 @@ void test(AntClassLoader acl) {
166168
new LargeText((File) source(), null, false, false); // $ hasTaintFlow
167169
}
168170

169-
void doGet6(String root, InetAddress address) throws IOException {
170-
String temp = address.getHostName();
171+
void doGet6(String root, HttpServletRequest request) throws IOException {
172+
String temp = request.getParameter("source");
171173
// GOOD: Use `contains` and `startsWith` to check if the path is safe
172174
if (!temp.contains("..") && temp.startsWith(root + "/")) {
173175
File file = new File(temp);

shared/mad/codeql/mad/ModelValidation.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ module KindValidation<KindValidationConfigSig Config> {
118118
this =
119119
[
120120
// shared
121-
"local", "remote", "file", "commandargs", "database", "environment",
121+
"local", "remote", "file", "commandargs", "database", "environment", "reverse-dns",
122122
// Java
123123
"android-external-storage-dir", "contentprovider",
124124
// C#

shared/threat-models/ext/threat-model-grouping.model.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,10 @@ extensions:
2121
# Android threat models
2222
- ["android-external-storage-dir", "android"]
2323
- ["contentprovider", "android"]
24+
25+
# Threat models that are not grouped with any other threat models.
26+
# (Note that all threat models are a child of "all" implicitly, and we
27+
# make it explicit here just to make sure all threat models are listed.)
28+
- ["database-access-result", "all"]
29+
- ["file-write", "all"]
30+
- ["reverse-dns", "all"]

0 commit comments

Comments
 (0)