Skip to content

Java: make a separate threat model kind for reverse DNS sources #16760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions docs/codeql/reusables/threat-model-description.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@ A threat model is a named class of dataflow sources that can be enabled or disab

The ``kind`` property of the ``sourceModel`` determines which threat model a source is associated with. There are two main categories:

- ``remote`` which represents requests and responses from the network.
- ``local`` which represents data from local files (``file``), command-line arguments (``commandargs``), database reads (``database``), and environment variables(``environment``).
- ``remote`` which represents requests (``request``) and responses (``response``) from the network.
- ``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.

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``.

The less commonly used categories are:

- ``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.
- ``database-access-result`` which represents a database access. Currently only used by JavaScript.
- ``file-write`` which represents opening a file in write mode. Currently only used in C#.
- ``reverse-dns`` which represents reverse DNS lookups. Currently only used in Java.

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>`__.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: majorAnalysis
---
* 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).
33 changes: 18 additions & 15 deletions java/ql/lib/semmle/code/java/dataflow/FlowSources.qll
Original file line number Diff line number Diff line change
Expand Up @@ -119,21 +119,6 @@ private predicate variableStep(Expr tracked, VarAccess sink) {
)
}

private class ReverseDnsSource extends RemoteFlowSource {
ReverseDnsSource() {
// Try not to trigger on `localhost`.
exists(MethodCall m | m = this.asExpr() |
m.getMethod() instanceof ReverseDnsMethod and
not exists(MethodCall l |
(variableStep(l, m.getQualifier()) or l = m.getQualifier()) and
(l.getMethod().getName() = "getLocalHost" or l.getMethod().getName() = "getLoopbackAddress")
)
)
}

override string getSourceType() { result = "reverse DNS lookup" }
}

private class MessageBodyReaderParameterSource extends RemoteFlowSource {
MessageBodyReaderParameterSource() {
exists(MessageBodyReaderRead m |
Expand Down Expand Up @@ -388,6 +373,24 @@ class AndroidJavascriptInterfaceMethodParameter extends RemoteFlowSource {
}
}

/** A node with input that comes from a reverse DNS lookup. */
abstract class ReverseDnsUserInput extends UserInput {
override string getThreatModel() { result = "reverse-dns" }
}

private class ReverseDnsSource extends ReverseDnsUserInput {
ReverseDnsSource() {
// Try not to trigger on `localhost`.
exists(MethodCall m | m = this.asExpr() |
m.getMethod() instanceof ReverseDnsMethod and
not exists(MethodCall l |
(variableStep(l, m.getQualifier()) or l = m.getQualifier()) and
(l.getMethod().getName() = "getLocalHost" or l.getMethod().getName() = "getLoopbackAddress")
)
)
}
}

/**
* A data flow source node for an API, which should be considered
* supported for a modeling perspective.
Expand Down
2 changes: 1 addition & 1 deletion java/ql/test/library-tests/dataflow/taintsources/A.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void test(ResultSet rs) throws SQLException {
};
sink(new URL("test").openConnection().getInputStream()); // $hasRemoteValueFlow
sink(new Socket("test", 1234).getInputStream()); // $hasRemoteValueFlow
sink(InetAddress.getByName("test").getHostName()); // $hasRemoteValueFlow
sink(InetAddress.getByName("test").getHostName()); // $hasReverseDnsValueFlow

sink(System.in); // $hasLocalValueFlow
sink(new FileInputStream("test")); // $hasLocalValueFlow
Expand Down
8 changes: 2 additions & 6 deletions java/ql/test/library-tests/dataflow/taintsources/local.ql
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,20 @@ import java
import semmle.code.java.dataflow.FlowSources
import TestUtilities.InlineExpectationsTest

class LocalSource extends DataFlow::Node instanceof UserInput {
LocalSource() { not this instanceof RemoteFlowSource }
}

predicate isTestSink(DataFlow::Node n) {
exists(MethodCall ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
}

module LocalValueConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node n) { n instanceof LocalSource }
predicate isSource(DataFlow::Node n) { n instanceof LocalUserInput }

predicate isSink(DataFlow::Node n) { isTestSink(n) }
}

module LocalValueFlow = DataFlow::Global<LocalValueConfig>;

module LocalTaintConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node n) { n instanceof LocalSource }
predicate isSource(DataFlow::Node n) { n instanceof LocalUserInput }

predicate isSink(DataFlow::Node n) { isTestSink(n) }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
failures
testFailures
47 changes: 47 additions & 0 deletions java/ql/test/library-tests/dataflow/taintsources/reversedns.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import java
import semmle.code.java.dataflow.FlowSources
import TestUtilities.InlineExpectationsTest

predicate isTestSink(DataFlow::Node n) {
exists(MethodCall ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
}

module ReverseDnsValueConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node n) { n instanceof ReverseDnsUserInput }

predicate isSink(DataFlow::Node n) { isTestSink(n) }
}

module ReverseDnsValueFlow = DataFlow::Global<ReverseDnsValueConfig>;

module ReverseDnsTaintConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node n) { n instanceof ReverseDnsUserInput }

predicate isSink(DataFlow::Node n) { isTestSink(n) }
}

module ReverseDnsTaintFlow = TaintTracking::Global<ReverseDnsTaintConfig>;

module ReverseDnsFlowTest implements TestSig {
string getARelevantTag() { result = ["hasReverseDnsValueFlow", "hasReverseDnsTaintFlow"] }

predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasReverseDnsValueFlow" and
exists(DataFlow::Node sink | ReverseDnsValueFlow::flowTo(sink) |
sink.getLocation() = location and
element = sink.toString() and
value = ""
)
or
tag = "hasReverseDnsTaintFlow" and
exists(DataFlow::Node src, DataFlow::Node sink |
ReverseDnsTaintFlow::flow(src, sink) and not ReverseDnsValueFlow::flow(src, sink)
|
sink.getLocation() = location and
element = sink.toString() and
value = ""
)
}
}

import MakeTest<ReverseDnsFlowTest>
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;

import javax.servlet.http.HttpServletRequest;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.io.FileUtils;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.DirectoryScanner;
Expand All @@ -24,10 +26,10 @@

public class Test {

private InetAddress address;
private HttpServletRequest request;

public Object source() {
return address.getHostName();
return request.getParameter("source");
}

void test() throws IOException {
Expand Down Expand Up @@ -166,8 +168,8 @@ void test(AntClassLoader acl) {
new LargeText((File) source(), null, false, false); // $ hasTaintFlow
}

void doGet6(String root, InetAddress address) throws IOException {
String temp = address.getHostName();
void doGet6(String root, HttpServletRequest request) throws IOException {
String temp = request.getParameter("source");
// GOOD: Use `contains` and `startsWith` to check if the path is safe
if (!temp.contains("..") && temp.startsWith(root + "/")) {
File file = new File(temp);
Expand Down
2 changes: 1 addition & 1 deletion shared/mad/codeql/mad/ModelValidation.qll
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ module KindValidation<KindValidationConfigSig Config> {
this =
[
// shared
"local", "remote", "file", "commandargs", "database", "environment",
"local", "remote", "file", "commandargs", "database", "environment", "reverse-dns",
// Java
"android-external-storage-dir", "contentprovider",
// C#
Expand Down
7 changes: 7 additions & 0 deletions shared/threat-models/ext/threat-model-grouping.model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,10 @@ extensions:
# Android threat models
- ["android-external-storage-dir", "android"]
- ["contentprovider", "android"]

# Threat models that are not grouped with any other threat models.
# (Note that all threat models are a child of "all" implicitly, and we
# make it explicit here just to make sure all threat models are listed.)
- ["database-access-result", "all"]
- ["file-write", "all"]
- ["reverse-dns", "all"]
Loading