Merge branch 'master' into java-spring-boot-actuators

This commit is contained in:
Grzegorz Golawski
2020-04-02 22:06:25 +02:00
1730 changed files with 80327 additions and 53732 deletions

View 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

View File

@@ -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

View File

@@ -0,0 +1 @@
This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries.

View File

@@ -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();
}
}

View File

@@ -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>

View 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"

View File

@@ -0,0 +1,4 @@
// Bad: ScriptEngine allows arbitrary code injection
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine scriptEngine = scriptEngineManager.getEngineByExtension("js");
Object result = scriptEngine.eval(code);

View File

@@ -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>

View File

@@ -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"

View File

@@ -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) {
}

View File

@@ -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>

View File

@@ -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"

View File

@@ -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

View File

@@ -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 = "...->..." }

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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."
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}
/**

View File

@@ -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

View File

@@ -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

View File

@@ -305,6 +305,7 @@ private module Unification {
arg2 = t2.getTypeArgument(pos)
}
pragma[nomagic]
predicate failsUnification(Type t1, Type t2) {
unificationTargets(t1, t2) and
(

View File

@@ -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