mirror of
https://github.com/github/codeql.git
synced 2026-04-28 02:05:14 +02:00
Merge branch 'master' into java-spring-boot-actuators
This commit is contained in:
12
java/ql/src/Customizations.qll
Normal file
12
java/ql/src/Customizations.qll
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Contains customizations to the standard library.
|
||||
*
|
||||
* This module is imported by `java.qll`, so any customizations defined here automatically
|
||||
* apply to all queries.
|
||||
*
|
||||
* Typical examples of customizations include adding new subclasses of abstract classes such as
|
||||
* the `RemoteFlowSource` and `AdditionalTaintStep` classes associated with the security queries
|
||||
* to model frameworks that are not covered by the standard library.
|
||||
*/
|
||||
|
||||
import java
|
||||
@@ -4,7 +4,7 @@ class Test {
|
||||
{
|
||||
String latlonCoords = args[1];
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
Process exec = rt.exec("cmd.exe /C latlon2utm.exe -" + latlonCoords);
|
||||
Process exec = rt.exec("cmd.exe /C latlon2utm.exe " + latlonCoords);
|
||||
}
|
||||
|
||||
// GOOD: use an array of arguments instead of executing a string
|
||||
|
||||
1
java/ql/src/experimental/README.md
Normal file
1
java/ql/src/experimental/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries.
|
||||
@@ -0,0 +1,8 @@
|
||||
public class TestServlet extends HttpServlet {
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
// BAD: a URL from a remote source is opened with URL#openStream()
|
||||
URL url = new URL(request.getParameter("url"));
|
||||
InputStream inputStream = new URL(url).openStream();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Calling <code>openStream</code> on URLs created from remote source can lead to local file disclosure.</p>
|
||||
<p>If <code>openStream</code> is called on a <code>java.net.URL</code>, that was created from a remote source,
|
||||
an attacker can try to pass absolute URLs starting with <code>file://</code> or <code>jar://</code> to access
|
||||
local resources in addition to remote ones.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>When you construct a URL using <code>java.net.URL</code> from a remote source,
|
||||
don't call <code>openStream</code> on it. Instead, use an HTTP Client to fetch the URL and access its content.
|
||||
You should also validate the URL to check that it uses the correct protocol and host combination.</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>The following example shows an URL that is constructed from a request parameter. Afterwards <code>openStream</code>
|
||||
is called on the URL, potentially leading to a local file access.</p>
|
||||
<sample src="OpenStream.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>Java Platform, Standard Edition 11, API Specification:
|
||||
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/URL.html">
|
||||
Class URL</a>.
|
||||
</li>
|
||||
<!-- LocalWords: CWE -->
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
55
java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql
Normal file
55
java/ql/src/experimental/Security/CWE/CWE-036/OpenStream.ql
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @name openStream called on URLs created from remote source
|
||||
* @description Calling openStream on URLs created from remote source
|
||||
* can lead to local file disclosure.
|
||||
* @kind path-problem
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class URLConstructor extends ClassInstanceExpr {
|
||||
URLConstructor() { this.getConstructor().getDeclaringType() instanceof TypeUrl }
|
||||
|
||||
Expr stringArg() {
|
||||
// Query only in URL's that were constructed by calling the single parameter string constructor.
|
||||
this.getConstructor().getNumberOfParameters() = 1 and
|
||||
this.getConstructor().getParameter(0).getType() instanceof TypeString and
|
||||
result = this.getArgument(0)
|
||||
}
|
||||
}
|
||||
|
||||
class URLOpenStreamMethod extends Method {
|
||||
URLOpenStreamMethod() {
|
||||
this.getDeclaringType() instanceof TypeUrl and
|
||||
this.getName() = "openStream"
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteURLToOpenStreamFlowConfig extends TaintTracking::Configuration {
|
||||
RemoteURLToOpenStreamFlowConfig() { this = "OpenStream::RemoteURLToOpenStreamFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(MethodAccess m |
|
||||
sink.asExpr() = m.getQualifier() and m.getMethod() instanceof URLOpenStreamMethod
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(URLConstructor u |
|
||||
node1.asExpr() = u.stringArg() and
|
||||
node2.asExpr() = u
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, MethodAccess call
|
||||
where
|
||||
sink.getNode().asExpr() = call.getQualifier() and
|
||||
any(RemoteURLToOpenStreamFlowConfig c).hasFlowPath(source, sink)
|
||||
select call, source, sink,
|
||||
"URL on which openStream is called may have been constructed from remote source"
|
||||
@@ -0,0 +1,4 @@
|
||||
// Bad: ScriptEngine allows arbitrary code injection
|
||||
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
|
||||
ScriptEngine scriptEngine = scriptEngineManager.getEngineByExtension("js");
|
||||
Object result = scriptEngine.eval(code);
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>The ScriptEngine API has been available since the release of Java 6.
|
||||
It allows applications to interact with scripts written in languages such as JavaScript.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>Use "Cloudbees Rhino Sandbox" or sandboxing with SecurityManager or use <a href="https://www.graalvm.org/">graalvm</a> instead.</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>The following code could execute random JavaScript code</p>
|
||||
<sample src="ScriptEngine.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
CERT coding standard: <a href="https://wiki.sei.cmu.edu/confluence/display/java/IDS52-J.+Prevent+code+injection">ScriptEngine code injection</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @name ScriptEngine evaluation
|
||||
* @description Malicious Javascript code could cause arbitrary command execution at the OS level
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/unsafe-eval
|
||||
* @tags security
|
||||
* external/cwe/cwe-094
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class ScriptEngineMethod extends Method {
|
||||
ScriptEngineMethod() {
|
||||
this.getDeclaringType().hasQualifiedName("javax.script", "ScriptEngine") and
|
||||
this.hasName("eval")
|
||||
}
|
||||
}
|
||||
|
||||
predicate scriptEngine(MethodAccess ma, Expr sink) {
|
||||
exists(Method m | m = ma.getMethod() |
|
||||
m instanceof ScriptEngineMethod and
|
||||
sink = ma.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
class ScriptEngineSink extends DataFlow::ExprNode {
|
||||
ScriptEngineSink() { scriptEngine(_, this.getExpr()) }
|
||||
|
||||
MethodAccess getMethodAccess() { scriptEngine(result, this.getExpr()) }
|
||||
}
|
||||
|
||||
class ScriptEngineConfiguration extends TaintTracking::Configuration {
|
||||
ScriptEngineConfiguration() { this = "ScriptEngineConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source instanceof RemoteFlowSource
|
||||
or
|
||||
source instanceof LocalUserInput
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof ScriptEngineSink }
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, ScriptEngineConfiguration conf
|
||||
where conf.hasFlowPath(source, sink)
|
||||
select sink.getNode().(ScriptEngineSink).getMethodAccess(), source, sink, "ScriptEngine eval $@.",
|
||||
source.getNode(), "user input"
|
||||
@@ -0,0 +1,73 @@
|
||||
final String xmlStr = "<users>" +
|
||||
" <user name=\"aaa\" pass=\"pass1\"></user>" +
|
||||
" <user name=\"bbb\" pass=\"pass2\"></user>" +
|
||||
"</users>";
|
||||
try {
|
||||
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
|
||||
domFactory.setNamespaceAware(true);
|
||||
DocumentBuilder builder = domFactory.newDocumentBuilder();
|
||||
//Document doc = builder.parse("user.xml");
|
||||
Document doc = builder.parse(new InputSource(new StringReader(xmlStr)));
|
||||
|
||||
XPathFactory factory = XPathFactory.newInstance();
|
||||
XPath xpath = factory.newXPath();
|
||||
|
||||
// Injectable data
|
||||
String user = request.getParameter("user");
|
||||
String pass = request.getParameter("pass");
|
||||
if (user != null && pass != null) {
|
||||
boolean isExist = false;
|
||||
|
||||
// Bad expression
|
||||
String expression1 = "/users/user[@name='" + user + "' and @pass='" + pass + "']";
|
||||
isExist = (boolean)xpath.evaluate(expression1, doc, XPathConstants.BOOLEAN);
|
||||
System.out.println(isExist);
|
||||
|
||||
// Bad expression
|
||||
XPathExpression expression2 = xpath.compile("/users/user[@name='" + user + "' and @pass='" + pass + "']");
|
||||
isExist = (boolean)expression2.evaluate(doc, XPathConstants.BOOLEAN);
|
||||
System.out.println(isExist);
|
||||
|
||||
// Bad expression
|
||||
StringBuffer sb = new StringBuffer("/users/user[@name=");
|
||||
sb.append(user);
|
||||
sb.append("' and @pass='");
|
||||
sb.append(pass);
|
||||
sb.append("']");
|
||||
String query = sb.toString();
|
||||
XPathExpression expression3 = xpath.compile(query);
|
||||
isExist = (boolean)expression3.evaluate(doc, XPathConstants.BOOLEAN);
|
||||
System.out.println(isExist);
|
||||
|
||||
// Good expression
|
||||
String expression4 = "/users/user[@name=$user and @pass=$pass]";
|
||||
xpath.setXPathVariableResolver(v -> {
|
||||
switch (v.getLocalPart()) {
|
||||
case "user":
|
||||
return user;
|
||||
case "pass":
|
||||
return pass;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
});
|
||||
isExist = (boolean)xpath.evaluate(expression4, doc, XPathConstants.BOOLEAN);
|
||||
System.out.println(isExist);
|
||||
|
||||
|
||||
// Bad Dom4j
|
||||
org.dom4j.io.SAXReader reader = new org.dom4j.io.SAXReader();
|
||||
org.dom4j.Document document = reader.read(new InputSource(new StringReader(xmlStr)));
|
||||
isExist = document.selectSingleNode("/users/user[@name='" + user + "' and @pass='" + pass + "']").hasContent();
|
||||
// or document.selectNodes
|
||||
System.out.println(isExist);
|
||||
}
|
||||
} catch (ParserConfigurationException e) {
|
||||
|
||||
} catch (SAXException e) {
|
||||
|
||||
} catch (XPathExpressionException e) {
|
||||
|
||||
} catch (org.dom4j.DocumentException e) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
If an XPath expression is built using string concatenation, and the components of the concatenation
|
||||
include user input, a user is likely to be able to create a malicious XPath expression.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
If user input must be included in an XPath expression, pre-compile the query and use variable
|
||||
references to include the user input.
|
||||
</p>
|
||||
<p>
|
||||
XPath injection can also be prevented by using XQuery.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the first, second, and third example, the code accepts a name and password specified by the user, and uses this
|
||||
unvalidated and unsanitized value in an XPath expression. This is vulnerable to the user providing
|
||||
special characters or string sequences that change the meaning of the XPath expression to search
|
||||
for different values.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the fourth example, the code utilizes setXPathVariableResolver which prevents XPath Injection.
|
||||
</p>
|
||||
<p>
|
||||
The fifth example is a dom4j XPath injection example.
|
||||
</p>
|
||||
<sample src="XPathInjection.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>OWASP: <a href="https://www.owasp.org/index.php?title=Testing_for_XPath_Injection_(OTG-INPVAL-010)">Testing for XPath Injection</a>.</li>
|
||||
<li>OWASP: <a href="https://www.owasp.org/index.php/XPATH_Injection">XPath Injection</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @name XPath injection
|
||||
* @description Building an XPath expression from user-controlled sources is vulnerable to insertion of
|
||||
* malicious code by the user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id java/xml/xpath-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-643
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.security.XmlParsers
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class XPathInjectionConfiguration extends TaintTracking::Configuration {
|
||||
XPathInjectionConfiguration() { this = "XPathInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof XPathInjectionSink }
|
||||
}
|
||||
|
||||
class XPathInjectionSink extends DataFlow::ExprNode {
|
||||
XPathInjectionSink() {
|
||||
exists(Method m, MethodAccess ma | ma.getMethod() = m |
|
||||
m.getDeclaringType().hasQualifiedName("javax.xml.xpath", "XPath") and
|
||||
(m.hasName("evaluate") or m.hasName("compile")) and
|
||||
ma.getArgument(0) = this.getExpr()
|
||||
or
|
||||
m.getDeclaringType().hasQualifiedName("org.dom4j", "Node") and
|
||||
(m.hasName("selectNodes") or m.hasName("selectSingleNode")) and
|
||||
ma.getArgument(0) = this.getExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, XPathInjectionConfiguration c
|
||||
where c.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ flows to here and is used in an XPath expression.",
|
||||
source.getNode(), "User-provided value"
|
||||
@@ -1,5 +1,6 @@
|
||||
/** Provides all default Java QL imports. */
|
||||
|
||||
import Customizations
|
||||
import semmle.code.FileSystem
|
||||
import semmle.code.Location
|
||||
import semmle.code.java.Annotation
|
||||
|
||||
@@ -1016,7 +1016,7 @@ class LambdaExpr extends FunctionalExpr, @lambdaexpr {
|
||||
}
|
||||
|
||||
/** Gets the body of this lambda expression, if it is a statement. */
|
||||
Stmt getStmtBody() { hasStmtBody() and result = asMethod().getBody() }
|
||||
Block getStmtBody() { hasStmtBody() and result = asMethod().getBody() }
|
||||
|
||||
/** Gets a printable representation of this expression. */
|
||||
override string toString() { result = "...->..." }
|
||||
|
||||
@@ -481,13 +481,13 @@ class GetterMethod extends Method {
|
||||
|
||||
/**
|
||||
* A finalizer method, with name `finalize`,
|
||||
* return type `void` and modifier `protected`.
|
||||
* return type `void` and no parameters.
|
||||
*/
|
||||
class FinalizeMethod extends Method {
|
||||
FinalizeMethod() {
|
||||
this.hasName("finalize") and
|
||||
this.getReturnType().hasName("void") and
|
||||
this.isProtected()
|
||||
this.hasNoParameters()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -364,13 +364,45 @@ private predicate typeFlow(TypeFlowNode n, RefType t) {
|
||||
typeFlowJoin(lastRank(n), n, t)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate erasedTypeBound(RefType t) {
|
||||
exists(RefType t0 | typeFlow(_, t0) and t = t0.getErasure())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate typeBound(RefType t) { typeFlow(_, t) }
|
||||
|
||||
/**
|
||||
* Holds if we have a bound for `n` that is better than `t`, taking only erased
|
||||
* types into account.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate irrelevantErasedBound(TypeFlowNode n, RefType t) {
|
||||
exists(RefType bound |
|
||||
typeFlow(n, bound)
|
||||
or
|
||||
n.getType() = bound and typeFlow(n, _)
|
||||
|
|
||||
t = bound.getErasure().(RefType).getASourceSupertype+() and
|
||||
erasedTypeBound(t)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if we have a bound for `n` that is better than `t`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate irrelevantBound(TypeFlowNode n, RefType t) {
|
||||
exists(RefType bound | typeFlow(n, bound) or n.getType() = bound |
|
||||
t = bound.getErasure().(RefType).getASourceSupertype+()
|
||||
exists(RefType bound |
|
||||
typeFlow(n, bound) and
|
||||
t = bound.getASupertype+() and
|
||||
typeBound(t) and
|
||||
typeFlow(n, t) and
|
||||
not t.getASupertype*() = bound
|
||||
or
|
||||
n.getType() = bound and
|
||||
typeFlow(n, t) and
|
||||
t = bound.getASupertype*()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -380,7 +412,8 @@ private predicate irrelevantBound(TypeFlowNode n, RefType t) {
|
||||
*/
|
||||
private predicate bestTypeFlow(TypeFlowNode n, RefType t) {
|
||||
typeFlow(n, t) and
|
||||
not irrelevantBound(n, t.getErasure())
|
||||
not irrelevantErasedBound(n, t.getErasure()) and
|
||||
not irrelevantBound(n, t)
|
||||
}
|
||||
|
||||
cached
|
||||
|
||||
@@ -115,11 +115,19 @@ private predicate taintPreservingQualifierToMethod(Method m) {
|
||||
or
|
||||
m.(CollectionMethod).hasName("remove") and m.getParameterType(0).(PrimitiveType).hasName("int")
|
||||
or
|
||||
m.(CollectionMethod).hasName("remove") and m.getNumberOfParameters() = 0
|
||||
or
|
||||
m.(CollectionMethod).hasName("subList")
|
||||
or
|
||||
m.(CollectionMethod).hasName("firstElement")
|
||||
or
|
||||
m.(CollectionMethod).hasName("lastElement")
|
||||
or
|
||||
m.(CollectionMethod).hasName("poll")
|
||||
or
|
||||
m.(CollectionMethod).hasName("peek")
|
||||
or
|
||||
m.(CollectionMethod).hasName("element")
|
||||
}
|
||||
|
||||
private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
|
||||
@@ -147,6 +155,8 @@ private predicate taintPreservingArgumentToQualifier(Method method, int arg) {
|
||||
method.(CollectionMethod).hasName("addElement") and arg = 0
|
||||
or
|
||||
method.(CollectionMethod).hasName("set") and arg = 1
|
||||
or
|
||||
method.(CollectionMethod).hasName("offer") and arg = 0
|
||||
}
|
||||
|
||||
private predicate argToQualifierStep(Expr tracked, Expr sink) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Provides consistency queries for checking invariants in the language-specific
|
||||
* data-flow classes and predicates.
|
||||
*/
|
||||
|
||||
private import DataFlowImplSpecific::Private
|
||||
private import DataFlowImplSpecific::Public
|
||||
private import tainttracking1.TaintTrackingParameter::Private
|
||||
private import tainttracking1.TaintTrackingParameter::Public
|
||||
|
||||
module Consistency {
|
||||
private class RelevantNode extends Node {
|
||||
RelevantNode() {
|
||||
this instanceof ArgumentNode or
|
||||
this instanceof ParameterNode or
|
||||
this instanceof ReturnNode or
|
||||
this = getAnOutNode(_, _) or
|
||||
simpleLocalFlowStep(this, _) or
|
||||
simpleLocalFlowStep(_, this) or
|
||||
jumpStep(this, _) or
|
||||
jumpStep(_, this) or
|
||||
storeStep(this, _, _) or
|
||||
storeStep(_, _, this) or
|
||||
readStep(this, _, _) or
|
||||
readStep(_, _, this) or
|
||||
defaultAdditionalTaintStep(this, _) or
|
||||
defaultAdditionalTaintStep(_, this)
|
||||
}
|
||||
}
|
||||
|
||||
query predicate uniqueEnclosingCallable(Node n, string msg) {
|
||||
exists(int c |
|
||||
n instanceof RelevantNode and
|
||||
c = count(n.getEnclosingCallable()) and
|
||||
c != 1 and
|
||||
msg = "Node should have one enclosing callable but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate uniqueTypeBound(Node n, string msg) {
|
||||
exists(int c |
|
||||
n instanceof RelevantNode and
|
||||
c = count(n.getTypeBound()) and
|
||||
c != 1 and
|
||||
msg = "Node should have one type bound but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate uniqueTypeRepr(Node n, string msg) {
|
||||
exists(int c |
|
||||
n instanceof RelevantNode and
|
||||
c = count(getErasedRepr(n.getTypeBound())) and
|
||||
c != 1 and
|
||||
msg = "Node should have one type representation but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate uniqueNodeLocation(Node n, string msg) {
|
||||
exists(int c |
|
||||
c =
|
||||
count(string filepath, int startline, int startcolumn, int endline, int endcolumn |
|
||||
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
) and
|
||||
c != 1 and
|
||||
msg = "Node should have one location but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate missingLocation(string msg) {
|
||||
exists(int c |
|
||||
c =
|
||||
strictcount(Node n |
|
||||
not exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
|
||||
n.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
)
|
||||
) and
|
||||
msg = "Nodes without location: " + c
|
||||
)
|
||||
}
|
||||
|
||||
query predicate uniqueNodeToString(Node n, string msg) {
|
||||
exists(int c |
|
||||
c = count(n.toString()) and
|
||||
c != 1 and
|
||||
msg = "Node should have one toString but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate missingToString(string msg) {
|
||||
exists(int c |
|
||||
c = strictcount(Node n | not exists(n.toString())) and
|
||||
msg = "Nodes without toString: " + c
|
||||
)
|
||||
}
|
||||
|
||||
query predicate parameterCallable(ParameterNode p, string msg) {
|
||||
exists(DataFlowCallable c | p.isParameterOf(c, _) and c != p.getEnclosingCallable()) and
|
||||
msg = "Callable mismatch for parameter."
|
||||
}
|
||||
|
||||
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
|
||||
simpleLocalFlowStep(n1, n2) and
|
||||
n1.getEnclosingCallable() != n2.getEnclosingCallable() and
|
||||
msg = "Local flow step does not preserve enclosing callable."
|
||||
}
|
||||
|
||||
private DataFlowType typeRepr() { result = getErasedRepr(any(Node n).getTypeBound()) }
|
||||
|
||||
query predicate compatibleTypesReflexive(DataFlowType t, string msg) {
|
||||
t = typeRepr() and
|
||||
not compatibleTypes(t, t) and
|
||||
msg = "Type compatibility predicate is not reflexive."
|
||||
}
|
||||
|
||||
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
|
||||
isUnreachableInCall(n, call) and
|
||||
exists(DataFlowCallable c |
|
||||
c = n.getEnclosingCallable() and
|
||||
not viableCallable(call) = c
|
||||
) and
|
||||
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
|
||||
}
|
||||
|
||||
query predicate localCallNodes(DataFlowCall call, Node n, string msg) {
|
||||
(
|
||||
n = getAnOutNode(call, _) and
|
||||
msg = "OutNode and call does not share enclosing callable."
|
||||
or
|
||||
n.(ArgumentNode).argumentOf(call, _) and
|
||||
msg = "ArgumentNode and call does not share enclosing callable."
|
||||
) and
|
||||
n.getEnclosingCallable() != call.getEnclosingCallable()
|
||||
}
|
||||
|
||||
query predicate postIsNotPre(PostUpdateNode n, string msg) {
|
||||
n.getPreUpdateNode() = n and msg = "PostUpdateNode should not equal its pre-update node."
|
||||
}
|
||||
|
||||
query predicate postHasUniquePre(PostUpdateNode n, string msg) {
|
||||
exists(int c |
|
||||
c = count(n.getPreUpdateNode()) and
|
||||
c != 1 and
|
||||
msg = "PostUpdateNode should have one pre-update node but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate uniquePostUpdate(Node n, string msg) {
|
||||
1 < strictcount(PostUpdateNode post | post.getPreUpdateNode() = n) and
|
||||
msg = "Node has multiple PostUpdateNodes."
|
||||
}
|
||||
|
||||
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
|
||||
n.getEnclosingCallable() != n.getPreUpdateNode().getEnclosingCallable() and
|
||||
msg = "PostUpdateNode does not share callable with its pre-update node."
|
||||
}
|
||||
|
||||
private predicate hasPost(Node n) { exists(PostUpdateNode post | post.getPreUpdateNode() = n) }
|
||||
|
||||
query predicate reverseRead(Node n, string msg) {
|
||||
exists(Node n2 | readStep(n, _, n2) and hasPost(n2) and not hasPost(n)) and
|
||||
msg = "Origin of readStep is missing a PostUpdateNode."
|
||||
}
|
||||
|
||||
query predicate storeIsPostUpdate(Node n, string msg) {
|
||||
storeStep(_, _, n) and
|
||||
not n instanceof PostUpdateNode and
|
||||
msg = "Store targets should be PostUpdateNodes."
|
||||
}
|
||||
|
||||
query predicate argHasPostUpdate(ArgumentNode n, string msg) {
|
||||
not hasPost(n) and
|
||||
not isImmutableOrUnobservable(n) and
|
||||
msg = "ArgumentNode is missing PostUpdateNode."
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
private import java
|
||||
private import DataFlowUtil
|
||||
private import DataFlowImplCommon::Public
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowDispatch
|
||||
private import semmle.code.java.controlflow.Guards
|
||||
private import semmle.code.java.dataflow.SSA
|
||||
@@ -113,14 +113,15 @@ private predicate variableCaptureStep(Node node1, ExprNode node2) {
|
||||
*/
|
||||
predicate jumpStep(Node node1, Node node2) {
|
||||
staticFieldStep(node1, node2) or
|
||||
variableCaptureStep(node1, node2)
|
||||
variableCaptureStep(node1, node2) or
|
||||
variableCaptureStep(node1.(PostUpdateNode).getPreUpdateNode(), node2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `fa` is an access to an instance field that occurs as the
|
||||
* destination of an assignment of the value `src`.
|
||||
*/
|
||||
predicate instanceFieldAssign(Expr src, FieldAccess fa) {
|
||||
private predicate instanceFieldAssign(Expr src, FieldAccess fa) {
|
||||
exists(AssignExpr a |
|
||||
a.getSource() = src and
|
||||
a.getDest() = fa and
|
||||
@@ -324,3 +325,16 @@ predicate isUnreachableInCall(Node n, DataFlowCall call) {
|
||||
guard.controls(n.asExpr().getBasicBlock(), arg.getBooleanValue().booleanNot())
|
||||
)
|
||||
}
|
||||
|
||||
int accessPathLimit() { result = 5 }
|
||||
|
||||
/**
|
||||
* Holds if `n` does not require a `PostUpdateNode` as it either cannot be
|
||||
* modified or its modification cannot be observed, for example if it is a
|
||||
* freshly created object that is not saved in a variable.
|
||||
*
|
||||
* This predicate is only used for consistency checks.
|
||||
*/
|
||||
predicate isImmutableOrUnobservable(Node n) {
|
||||
n.getType() instanceof ImmutableType or n instanceof ImplicitVarargsArray
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ import semmle.code.java.dataflow.InstanceAccess
|
||||
|
||||
cached
|
||||
private newtype TNode =
|
||||
TExprNode(Expr e) or
|
||||
TExprNode(Expr e) {
|
||||
not e.getType() instanceof VoidType and
|
||||
not e.getParent*() instanceof Annotation
|
||||
} or
|
||||
TExplicitParameterNode(Parameter p) { exists(p.getCallable().getBody()) } or
|
||||
TImplicitVarargsArray(Call c) {
|
||||
c.getCallee().isVarargs() and
|
||||
|
||||
@@ -10,6 +10,7 @@ private import semmle.code.java.frameworks.Guice
|
||||
private import semmle.code.java.frameworks.Protobuf
|
||||
private import semmle.code.java.Maps
|
||||
private import semmle.code.java.dataflow.internal.ContainerFlow
|
||||
private import semmle.code.java.frameworks.jackson.JacksonSerializability
|
||||
|
||||
/**
|
||||
* Holds if taint can flow from `src` to `sink` in zero or more
|
||||
@@ -380,10 +381,25 @@ private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
|
||||
taintPreservingArgumentToMethod(m, i) and
|
||||
tracked = sink.(MethodAccess).getArgument(i)
|
||||
)
|
||||
or
|
||||
exists(MethodAccess ma |
|
||||
taintPreservingArgumentToMethod(ma.getMethod()) and
|
||||
tracked = ma.getAnArgument() and
|
||||
sink = ma
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `method` is a library method that return tainted data if its
|
||||
* Holds if `method` is a library method that returns tainted data if any
|
||||
* of its arguments are tainted.
|
||||
*/
|
||||
private predicate taintPreservingArgumentToMethod(Method method) {
|
||||
method.getDeclaringType() instanceof TypeString and
|
||||
(method.hasName("format") or method.hasName("join"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `method` is a library method that returns tainted data if its
|
||||
* `arg`th argument is tainted.
|
||||
*/
|
||||
private predicate taintPreservingArgumentToMethod(Method method, int arg) {
|
||||
@@ -430,6 +446,13 @@ private predicate taintPreservingArgumentToMethod(Method method, int arg) {
|
||||
method.getName() = "wrap" and arg = 0
|
||||
)
|
||||
or
|
||||
method.getDeclaringType().hasQualifiedName("org.apache.commons.codec.binary", "Base64") and
|
||||
(
|
||||
method.getName() = "decodeBase64" and arg = 0
|
||||
or
|
||||
method.getName().matches("encodeBase64%") and arg = 0
|
||||
)
|
||||
or
|
||||
method.getDeclaringType().hasQualifiedName("org.apache.commons.io", "IOUtils") and
|
||||
(
|
||||
method.getName() = "buffer" and arg = 0
|
||||
@@ -451,6 +474,10 @@ private predicate taintPreservingArgumentToMethod(Method method, int arg) {
|
||||
method.getName() = "toString" and arg = 0
|
||||
)
|
||||
or
|
||||
method.getDeclaringType().hasQualifiedName("java.net", "URLDecoder") and
|
||||
method.hasName("decode") and
|
||||
arg = 0
|
||||
or
|
||||
// A URI created from a tainted string is still tainted.
|
||||
method.getDeclaringType().hasQualifiedName("java.net", "URI") and
|
||||
method.hasName("create") and
|
||||
@@ -465,6 +492,11 @@ private predicate taintPreservingArgumentToMethod(Method method, int arg) {
|
||||
or
|
||||
exists(ProtobufMessageLite m | method = m.getAParseFromMethod()) and
|
||||
arg = 0
|
||||
or
|
||||
// Jackson serialization methods that return the serialized data
|
||||
method instanceof JacksonWriteValueMethod and
|
||||
method.getNumberOfParameters() = 1 and
|
||||
arg = 0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -511,6 +543,12 @@ private predicate taintPreservingArgToArg(Method method, int input, int output)
|
||||
method.hasName("arraycopy") and
|
||||
input = 0 and
|
||||
output = 2
|
||||
or
|
||||
// Jackson serialization methods that write data to the first argument
|
||||
method instanceof JacksonWriteValueMethod and
|
||||
method.getNumberOfParameters() > 1 and
|
||||
input = method.getNumberOfParameters() - 1 and
|
||||
output = 0
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
/**
|
||||
* Provides an implementation of global (interprocedural) taint tracking.
|
||||
* This file re-exports the local (intraprocedural) taint-tracking analysis
|
||||
* from `TaintTrackingParameter::Public` and adds a global analysis, mainly
|
||||
* exposed through the `Configuration` class. For some languages, this file
|
||||
* exists in several identical copies, allowing queries to use multiple
|
||||
* `Configuration` classes that depend on each other without introducing
|
||||
* mutual recursion among those configurations.
|
||||
*/
|
||||
|
||||
import TaintTrackingParameter::Public
|
||||
private import TaintTrackingParameter::Private
|
||||
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
/**
|
||||
* Provides an implementation of global (interprocedural) taint tracking.
|
||||
* This file re-exports the local (intraprocedural) taint-tracking analysis
|
||||
* from `TaintTrackingParameter::Public` and adds a global analysis, mainly
|
||||
* exposed through the `Configuration` class. For some languages, this file
|
||||
* exists in several identical copies, allowing queries to use multiple
|
||||
* `Configuration` classes that depend on each other without introducing
|
||||
* mutual recursion among those configurations.
|
||||
*/
|
||||
|
||||
import TaintTrackingParameter::Public
|
||||
private import TaintTrackingParameter::Private
|
||||
|
||||
|
||||
@@ -305,6 +305,7 @@ private module Unification {
|
||||
arg2 = t2.getTypeArgument(pos)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate failsUnification(Type t1, Type t2) {
|
||||
unificationTargets(t1, t2) and
|
||||
(
|
||||
|
||||
@@ -12,6 +12,10 @@ class TypeSocket extends RefType {
|
||||
TypeSocket() { hasQualifiedName("java.net", "Socket") }
|
||||
}
|
||||
|
||||
class TypeUrl extends RefType {
|
||||
TypeUrl() { hasQualifiedName("java.net", "URL") }
|
||||
}
|
||||
|
||||
class URLConnectionGetInputStreamMethod extends Method {
|
||||
URLConnectionGetInputStreamMethod() {
|
||||
getDeclaringType() instanceof TypeUrlConnection and
|
||||
|
||||
Reference in New Issue
Block a user