Merge branch 'main' into MybatisSqli

This commit is contained in:
retanoj
2022-12-07 21:19:08 +08:00
committed by GitHub
323 changed files with 4062 additions and 1570 deletions

View File

@@ -0,0 +1,6 @@
@AllDefaultsAnnotation
public class User {
public static void test() { new AllDefaultsConstructor(); new AllDefaultsExplicitNoargConstructor(); }
}

View File

@@ -3,3 +3,13 @@ public class Test {
@JvmOverloads fun f(x: Int = 0, y: Int) { }
}
public class AllDefaultsConstructor(val x: Int = 1, val y: Int = 2) { }
public annotation class AllDefaultsAnnotation(val x: Int = 1, val y: Int = 2) { }
public class AllDefaultsExplicitNoargConstructor(val x: Int = 1, val y: Int = 2) {
constructor() : this(3, 4) { }
}

View File

@@ -1,4 +1,4 @@
from create_database_utils import *
os.mkdir('bin')
run_codeql_database_create(["kotlinc test.kt -d bin", "kotlinc user.kt -cp bin"], lang="java")
run_codeql_database_create(["kotlinc test.kt -d bin", "kotlinc user.kt -cp bin", "javac User.java -cp bin"], lang="java")

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The query `java/insecure-cookie` now uses global dataflow to track secure cookies being set to the HTTP response object.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The library `PathSanitizer.qll` has been improved to detect more path validation patterns in Kotlin.

View File

@@ -80,53 +80,6 @@ private import internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific
private import internal.AccessPathSyntax
private import FlowSummary
/**
* A module importing the frameworks that provide external flow data,
* ensuring that they are visible to the taint tracking / data flow library.
*/
private module Frameworks {
private import internal.ContainerFlow
private import semmle.code.java.frameworks.android.Android
private import semmle.code.java.frameworks.android.ContentProviders
private import semmle.code.java.frameworks.android.ExternalStorage
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.android.SharedPreferences
private import semmle.code.java.frameworks.android.Slice
private import semmle.code.java.frameworks.android.SQLite
private import semmle.code.java.frameworks.android.Widget
private import semmle.code.java.frameworks.ApacheHttp
private import semmle.code.java.frameworks.apache.Collections
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.frameworks.Flexjson
private import semmle.code.java.frameworks.guava.Guava
private import semmle.code.java.frameworks.jackson.JacksonSerializability
private import semmle.code.java.frameworks.javaee.jsf.JSFRenderer
private import semmle.code.java.frameworks.JaxWS
private import semmle.code.java.frameworks.JoddJson
private import semmle.code.java.frameworks.Stream
private import semmle.code.java.frameworks.ratpack.RatpackExec
private import semmle.code.java.frameworks.spring.SpringHttp
private import semmle.code.java.frameworks.spring.SpringWebClient
private import semmle.code.java.security.AndroidIntentRedirection
private import semmle.code.java.security.ResponseSplitting
private import semmle.code.java.security.InformationLeak
private import semmle.code.java.security.FragmentInjection
private import semmle.code.java.security.GroovyInjection
private import semmle.code.java.security.ImplicitPendingIntents
private import semmle.code.java.security.JndiInjection
private import semmle.code.java.security.LdapInjection
private import semmle.code.java.security.MvelInjection
private import semmle.code.java.security.OgnlInjection
private import semmle.code.java.security.TemplateInjection
private import semmle.code.java.security.XPath
private import semmle.code.java.security.XsltInjection
private import semmle.code.java.frameworks.Jdbc
private import semmle.code.java.frameworks.SpringJdbc
private import semmle.code.java.frameworks.MyBatis
private import semmle.code.java.frameworks.Hibernate
private import semmle.code.java.frameworks.jOOQ
}
/**
* DEPRECATED: Define source models as data extensions instead.
*

View File

@@ -36,6 +36,13 @@ abstract class RemoteFlowSource extends DataFlow::Node {
abstract string getSourceType();
}
/**
* A module for importing frameworks that define remote flow sources.
*/
private module RemoteFlowSources {
private import semmle.code.java.frameworks.android.Widget
}
private class ExternalRemoteFlowSource extends RemoteFlowSource {
ExternalRemoteFlowSource() { sourceNode(this, "remote") }

View File

@@ -10,17 +10,19 @@ private import semmle.code.java.dataflow.DataFlow
* ensuring that they are visible to the taint tracking library.
*/
private module Frameworks {
private import semmle.code.java.JDK
private import semmle.code.java.frameworks.jackson.JacksonSerializability
private import semmle.code.java.frameworks.android.AsyncTask
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.android.Slice
private import semmle.code.java.frameworks.android.SQLite
private import semmle.code.java.frameworks.Guice
private import semmle.code.java.frameworks.Properties
private import semmle.code.java.frameworks.Protobuf
private import semmle.code.java.frameworks.guava.Guava
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.frameworks.ApacheHttp
private import semmle.code.java.frameworks.guava.Guava
private import semmle.code.java.frameworks.Guice
private import semmle.code.java.frameworks.jackson.JacksonSerializability
private import semmle.code.java.frameworks.Properties
private import semmle.code.java.frameworks.Protobuf
private import semmle.code.java.frameworks.ratpack.RatpackExec
private import semmle.code.java.JDK
}
/**

View File

@@ -6,11 +6,6 @@ import java
private import internal.FlowSummaryImpl as Impl
private import internal.DataFlowUtil
// import all instances of SummarizedCallable below
private module Summaries {
private import semmle.code.java.dataflow.ExternalFlow
}
class SummaryComponent = Impl::Public::SummaryComponent;
/** Provides predicates for constructing summary components. */
@@ -102,6 +97,14 @@ abstract class SyntheticCallable extends string {
Type getReturnType() { none() }
}
/**
* A module for importing frameworks that define synthetic callables.
*/
private module SyntheticCallables {
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.Stream
}
private newtype TSummarizedCallableBase =
TSimpleCallable(Callable c) { c.isSourceDeclaration() } or
TSyntheticCallable(SyntheticCallable c)

View File

@@ -53,16 +53,6 @@ private class TypeFlowNode extends TTypeFlowNode {
}
}
private int getNodeKind(TypeFlowNode n) {
result = 1 and n instanceof TField
or
result = 2 and n instanceof TSsa
or
result = 3 and n instanceof TExpr
or
result = 4 and n instanceof TMethod
}
/** Gets `t` if it is a `RefType` or the boxed type if `t` is a primitive type. */
private RefType boxIfNeeded(Type t) {
t.(PrimitiveType).getBoxedType() = result or
@@ -158,107 +148,45 @@ private predicate joinStep(TypeFlowNode n1, TypeFlowNode n2) {
private predicate anyStep(TypeFlowNode n1, TypeFlowNode n2) { joinStep(n1, n2) or step(n1, n2) }
private import SccReduction
private predicate sccEdge(TypeFlowNode n1, TypeFlowNode n2) { anyStep(n1, n2) and anyStep+(n2, n1) }
/**
* SCC reduction.
*
* This ought to be as easy as `equivalenceRelation(sccEdge/2)(n, scc)`, but
* this HOP is not currently supported for newtypes.
*
* A straightforward implementation would be:
* ```ql
* predicate sccRepr(TypeFlowNode n, TypeFlowNode scc) {
* scc =
* max(TypeFlowNode n2 |
* sccEdge+(n, n2)
* |
* n2
* order by
* n2.getLocation().getStartLine(), n2.getLocation().getStartColumn(), getNodeKind(n2)
* )
* }
*
* ```
* but this is quadratic in the size of the SCCs.
*
* Instead we find local maxima by following SCC edges and determine the SCC
* representatives from those.
* (This is still worst-case quadratic in the size of the SCCs, but generally
* performs better.)
*/
private module SccReduction {
private predicate sccEdge(TypeFlowNode n1, TypeFlowNode n2) {
anyStep(n1, n2) and anyStep+(n2, n1)
}
private module Scc = QlBuiltins::EquivalenceRelation<TypeFlowNode, sccEdge/2>;
private predicate sccEdgeWithMax(TypeFlowNode n1, TypeFlowNode n2, TypeFlowNode m) {
sccEdge(n1, n2) and
m =
max(TypeFlowNode n |
n = [n1, n2]
|
n order by n.getLocation().getStartLine(), n.getLocation().getStartColumn(), getNodeKind(n)
)
}
private class TypeFlowScc = Scc::EquivalenceClass;
private predicate hasLargerNeighbor(TypeFlowNode n) {
exists(TypeFlowNode n2 |
sccEdgeWithMax(n, n2, n2) and
not sccEdgeWithMax(n, n2, n)
or
sccEdgeWithMax(n2, n, n2) and
not sccEdgeWithMax(n2, n, n)
)
}
/** Holds if `n` is part of an SCC of size 2 or more represented by `scc`. */
private predicate sccRepr(TypeFlowNode n, TypeFlowScc scc) { scc = Scc::getEquivalenceClass(n) }
private predicate localMax(TypeFlowNode m) {
sccEdgeWithMax(_, _, m) and
not hasLargerNeighbor(m)
}
private predicate sccReprFromLocalMax(TypeFlowNode scc) {
exists(TypeFlowNode m |
localMax(m) and
scc =
max(TypeFlowNode n2 |
sccEdge+(m, n2) and localMax(n2)
|
n2
order by
n2.getLocation().getStartLine(), n2.getLocation().getStartColumn(), getNodeKind(n2)
)
)
}
/** Holds if `n` is part of an SCC of size 2 or more represented by `scc`. */
predicate sccRepr(TypeFlowNode n, TypeFlowNode scc) {
sccEdge+(n, scc) and sccReprFromLocalMax(scc)
}
predicate sccJoinStep(TypeFlowNode n, TypeFlowNode scc) {
exists(TypeFlowNode mid |
joinStep(n, mid) and
sccRepr(mid, scc) and
not sccRepr(n, scc)
)
}
private predicate sccJoinStep(TypeFlowNode n, TypeFlowScc scc) {
exists(TypeFlowNode mid |
joinStep(n, mid) and
sccRepr(mid, scc) and
not sccRepr(n, scc)
)
}
private signature predicate edgeSig(TypeFlowNode n1, TypeFlowNode n2);
private signature class NodeSig;
private signature module RankedEdge {
predicate edgeRank(int r, TypeFlowNode n1, TypeFlowNode n2);
private signature module Edge {
class Node;
int lastRank(TypeFlowNode n);
predicate edge(TypeFlowNode n1, Node n2);
}
private module RankEdge<edgeSig/2 edge> implements RankedEdge {
private signature module RankedEdge<NodeSig Node> {
predicate edgeRank(int r, TypeFlowNode n1, Node n2);
int lastRank(Node n);
}
private module RankEdge<Edge E> implements RankedEdge<E::Node> {
private import E
/**
* Holds if `r` is a ranking of the incoming edges `(n1,n2)` to `n2`. The used
* ordering is not necessarily total, so the ranking may have gaps.
*/
private predicate edgeRank1(int r, TypeFlowNode n1, TypeFlowNode n2) {
private predicate edgeRank1(int r, TypeFlowNode n1, Node n2) {
n1 =
rank[r](TypeFlowNode n |
edge(n, n2)
@@ -271,19 +199,19 @@ private module RankEdge<edgeSig/2 edge> implements RankedEdge {
* Holds if `r2` is a ranking of the ranks from `edgeRank1`. This removes the
* gaps from the ranking.
*/
private predicate edgeRank2(int r2, int r1, TypeFlowNode n) {
private predicate edgeRank2(int r2, int r1, Node n) {
r1 = rank[r2](int r | edgeRank1(r, _, n) | r)
}
/** Holds if `r` is a ranking of the incoming edges `(n1,n2)` to `n2`. */
predicate edgeRank(int r, TypeFlowNode n1, TypeFlowNode n2) {
predicate edgeRank(int r, TypeFlowNode n1, Node n2) {
exists(int r1 |
edgeRank1(r1, n1, n2) and
edgeRank2(r, r1, n2)
)
}
int lastRank(TypeFlowNode n) { result = max(int r | edgeRank(r, _, n)) }
int lastRank(Node n) { result = max(int r | edgeRank(r, _, n)) }
}
private signature module TypePropagation {
@@ -296,16 +224,16 @@ private signature module TypePropagation {
}
/** Implements recursion through `forall` by way of edge ranking. */
private module ForAll<RankedEdge Edge, TypePropagation T> {
private module ForAll<NodeSig Node, RankedEdge<Node> E, TypePropagation T> {
/**
* Holds if `t` is a bound that holds on one of the incoming edges to `n` and
* thus is a candidate bound for `n`.
*/
pragma[nomagic]
private predicate candJoinType(TypeFlowNode n, T::Typ t) {
private predicate candJoinType(Node n, T::Typ t) {
exists(TypeFlowNode mid |
T::candType(mid, t) and
Edge::edgeRank(_, mid, n)
E::edgeRank(_, mid, n)
)
}
@@ -314,13 +242,13 @@ private module ForAll<RankedEdge Edge, TypePropagation T> {
* through the edges into `n` ranked from `1` to `r`.
*/
pragma[assume_small_delta]
private predicate flowJoin(int r, TypeFlowNode n, T::Typ t) {
private predicate flowJoin(int r, Node n, T::Typ t) {
(
r = 1 and candJoinType(n, t)
or
flowJoin(r - 1, n, t) and Edge::edgeRank(r, _, n)
flowJoin(r - 1, n, t) and E::edgeRank(r, _, n)
) and
forall(TypeFlowNode mid | Edge::edgeRank(r, mid, n) | T::supportsType(mid, t))
forall(TypeFlowNode mid | E::edgeRank(r, mid, n) | T::supportsType(mid, t))
}
/**
@@ -328,12 +256,24 @@ private module ForAll<RankedEdge Edge, TypePropagation T> {
* coming through all the incoming edges, and therefore is a valid bound for
* `n`.
*/
predicate flowJoin(TypeFlowNode n, T::Typ t) { flowJoin(Edge::lastRank(n), n, t) }
predicate flowJoin(Node n, T::Typ t) { flowJoin(E::lastRank(n), n, t) }
}
module RankedJoinStep = RankEdge<joinStep/2>;
private module JoinStep implements Edge {
class Node = TypeFlowNode;
module RankedSccJoinStep = RankEdge<sccJoinStep/2>;
predicate edge = joinStep/2;
}
private module SccJoinStep implements Edge {
class Node = TypeFlowScc;
predicate edge = sccJoinStep/2;
}
private module RankedJoinStep = RankEdge<JoinStep>;
private module RankedSccJoinStep = RankEdge<SccJoinStep>;
private predicate exactTypeBase(TypeFlowNode n, RefType t) {
exists(ClassInstanceExpr e |
@@ -363,13 +303,13 @@ private predicate exactType(TypeFlowNode n, RefType t) {
or
// The following is an optimized version of
// `forex(TypeFlowNode mid | joinStep(mid, n) | exactType(mid, t))`
ForAll<RankedJoinStep, ExactTypePropagation>::flowJoin(n, t)
ForAll<TypeFlowNode, RankedJoinStep, ExactTypePropagation>::flowJoin(n, t)
or
exists(TypeFlowNode scc |
exists(TypeFlowScc scc |
sccRepr(n, scc) and
// Optimized version of
// `forex(TypeFlowNode mid | sccJoinStep(mid, scc) | exactType(mid, t))`
ForAll<RankedSccJoinStep, ExactTypePropagation>::flowJoin(scc, t)
ForAll<TypeFlowScc, RankedSccJoinStep, ExactTypePropagation>::flowJoin(scc, t)
)
}
@@ -563,11 +503,11 @@ private predicate typeFlow(TypeFlowNode n, RefType t) {
or
exists(TypeFlowNode mid | typeFlow(mid, t) and step(mid, n))
or
ForAll<RankedJoinStep, TypeFlowPropagation>::flowJoin(n, t)
ForAll<TypeFlowNode, RankedJoinStep, TypeFlowPropagation>::flowJoin(n, t)
or
exists(TypeFlowNode scc |
exists(TypeFlowScc scc |
sccRepr(n, scc) and
ForAll<RankedSccJoinStep, TypeFlowPropagation>::flowJoin(scc, t)
ForAll<TypeFlowScc, RankedSccJoinStep, TypeFlowPropagation>::flowJoin(scc, t)
)
}
@@ -703,13 +643,13 @@ private predicate hasUnionTypeFlow(TypeFlowNode n) {
(
// Optimized version of
// `forex(TypeFlowNode mid | joinStep(mid, n) | unionTypeFlowBaseCand(mid, _, _) or hasUnionTypeFlow(mid))`
ForAll<RankedJoinStep, HasUnionTypePropagation>::flowJoin(n, _)
ForAll<TypeFlowNode, RankedJoinStep, HasUnionTypePropagation>::flowJoin(n, _)
or
exists(TypeFlowNode scc |
exists(TypeFlowScc scc |
sccRepr(n, scc) and
// Optimized version of
// `forex(TypeFlowNode mid | sccJoinStep(mid, scc) | unionTypeFlowBaseCand(mid, _, _) or hasUnionTypeFlow(mid))`
ForAll<RankedSccJoinStep, HasUnionTypePropagation>::flowJoin(scc, _)
ForAll<TypeFlowScc, RankedSccJoinStep, HasUnionTypePropagation>::flowJoin(scc, _)
)
or
exists(TypeFlowNode mid | step(mid, n) and hasUnionTypeFlow(mid))

View File

@@ -3,7 +3,6 @@ import semmle.code.java.Collections
import semmle.code.java.Maps
private import semmle.code.java.dataflow.SSA
private import DataFlowUtil
private import semmle.code.java.dataflow.ExternalFlow
private class EntryType extends RefType {
EntryType() {

View File

@@ -14,6 +14,13 @@ private import semmle.code.java.dataflow.internal.AccessPathSyntax as AccessPath
class SummarizedCallableBase = FlowSummary::SummarizedCallableBase;
/**
* A module for importing frameworks that define synthetic globals.
*/
private module SyntheticGlobals {
private import semmle.code.java.frameworks.android.Intent
}
DataFlowCallable inject(SummarizedCallable c) { result.asSummarizedCallable() = c }
/** Gets the parameter position of the instance parameter. */

View File

@@ -10,7 +10,6 @@ private import semmle.code.java.dataflow.internal.ContainerFlow
private import semmle.code.java.frameworks.spring.SpringController
private import semmle.code.java.frameworks.spring.SpringHttp
private import semmle.code.java.frameworks.Networking
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSources
private import semmle.code.java.dataflow.internal.DataFlowPrivate
import semmle.code.java.dataflow.FlowSteps

View File

@@ -4,7 +4,6 @@
import java
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
class ApacheHttpGetParams extends Method {
ApacheHttpGetParams() {

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/** The class `flexjson.JSONDeserializer`. */
class FlexjsonDeserializer extends RefType {

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/** The interface `org.hibernate.query.QueryProducer`. */
class HibernateQueryProducer extends RefType {

View File

@@ -4,7 +4,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.security.XSS
/**

View File

@@ -2,7 +2,7 @@
* Provides classes and predicates for working with the Java JDBC API.
*/
private import semmle.code.java.dataflow.ExternalFlow
import java
/*--- Types ---*/
/** The interface `java.sql.Connection`. */

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/** The class `jodd.json.Parser`. */
class JoddJsonParser extends RefType {

View File

@@ -5,7 +5,6 @@
import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.TaintTracking
private import semmle.code.java.dataflow.ExternalFlow
/** The class `org.apache.ibatis.jdbc.SqlRunner`. */
class MyBatisSqlRunner extends RefType {

View File

@@ -1,6 +1,6 @@
/** Definitions related to `java.util.regex`. */
private import semmle.code.java.dataflow.ExternalFlow
import java
/** The class `java.util.regex.Pattern`. */
class TypeRegexPattern extends Class {

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/** The class `org.springframework.jdbc.core.JdbcTemplate`. */
class JdbcTemplate extends RefType {

View File

@@ -1,6 +1,5 @@
/** Definitions related to `java.util.stream`. */
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSummary
private class CollectCall extends MethodAccess {

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.xml.AndroidManifest
/**

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/** The class `android.content.ContentValues`. */
class ContentValues extends Class {

View File

@@ -1,7 +1,6 @@
/** Provides classes and predicates for working with SQLite databases. */
import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.frameworks.android.Android

View File

@@ -1,7 +1,6 @@
/** Provides classes related to `android.content.SharedPreferences`. */
import java
private import semmle.code.java.dataflow.ExternalFlow
/** The interface `android.content.SharedPreferences`. */
class SharedPreferences extends Interface {

View File

@@ -3,7 +3,6 @@
import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
/** The class `androidx.slice.SliceProvider`. */
class SliceProvider extends Class {

View File

@@ -2,7 +2,6 @@
import java
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
/**
* The method `isEmpty` in either `org.apache.commons.collections.CollectionUtils`

View File

@@ -2,7 +2,6 @@
import java
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
/**
* The class `org.apache.commons.lang.RandomStringUtils` or `org.apache.commons.lang3.RandomStringUtils`.

View File

@@ -3,7 +3,6 @@
import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.Collections
private string guavaCollectPackage() { result = "com.google.common.collect" }

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/**
* Methods annotated with this allow for generation of "plain SQL"

View File

@@ -9,7 +9,6 @@ import semmle.code.java.Reflection
import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.internal.DataFlowForSerializability
import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
/**
* A `@com.fasterxml.jackson.annotation.JsonIgnore` annoation.

View File

@@ -1,7 +1,6 @@
/** Provides classes and predicates for working with JavaServer Faces renderer. */
import java
private import semmle.code.java.dataflow.ExternalFlow
/**
* The JSF class `FacesContext` for processing HTTP requests.

View File

@@ -0,0 +1,8 @@
/** Provides classes and predicates related to `kotlin.io`. */
import java
/** The type `kotlin.io.FilesKt`, where `File` extension methods are declared. */
class FilesKt extends RefType {
FilesKt() { this.hasQualifiedName("kotlin.io", "FilesKt") }
}

View File

@@ -0,0 +1,21 @@
/** Provides classes and predicates related to `kotlin.text`. */
import java
/** The type `kotlin.text.StringsKt`, where `String` extension methods are declared. */
class StringsKt extends RefType {
StringsKt() { this.hasQualifiedName("kotlin.text", "StringsKt") }
}
/** A call to the extension method `String.toRegex` from `kotlin.text`. */
class KtToRegex extends MethodAccess {
KtToRegex() {
this.getMethod().getDeclaringType() instanceof StringsKt and
this.getMethod().hasName("toRegex")
}
/** Gets the constant string value being converted to a regex by this call. */
string getExpressionString() {
result = this.getArgument(0).(CompileTimeConstantExpr).getStringValue()
}
}

View File

@@ -5,7 +5,6 @@
import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
/** A reference type that extends a parameterization the Promise type. */
private class RatpackPromise extends RefType {

View File

@@ -4,7 +4,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.frameworks.spring.SpringController
private import semmle.code.java.security.XSS as XSS

View File

@@ -4,7 +4,6 @@
import java
import SpringHttp
private import semmle.code.java.dataflow.ExternalFlow
/** The class `org.springframework.web.client.RestTemplate`. */
class SpringRestTemplate extends Class {

View File

@@ -2,6 +2,7 @@
private import semmle.code.java.security.Encryption
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.security.internal.EncryptionKeySizes
/** A source for an insufficient key size. */
abstract class InsufficientKeySizeSource extends DataFlow::Node {
@@ -21,39 +22,67 @@ private module Asymmetric {
private module NonEllipticCurve {
/** A source for an insufficient key size used in RSA, DSA, and DH algorithms. */
private class Source extends InsufficientKeySizeSource {
Source() { this.asExpr().(IntegerLiteral).getIntValue() < getMinKeySize() }
string algoName;
override predicate hasState(DataFlow::FlowState state) { state = getMinKeySize().toString() }
Source() { this.asExpr().(IntegerLiteral).getIntValue() < getMinKeySize(algoName) }
override predicate hasState(DataFlow::FlowState state) {
state = getMinKeySize(algoName).toString()
}
}
/** A sink for an insufficient key size used in RSA, DSA, and DH algorithms. */
private class Sink extends InsufficientKeySizeSink {
string algoName;
Sink() {
exists(KeyPairGenInit kpgInit, KeyPairGen kpg |
kpg.getAlgoName().matches(["RSA", "DSA", "DH"]) and
algoName in ["RSA", "DSA", "DH"] and
kpg.getAlgoName() = algoName and
DataFlow::localExprFlow(kpg, kpgInit.getQualifier()) and
this.asExpr() = kpgInit.getKeySizeArg()
)
or
exists(Spec spec | this.asExpr() = spec.getKeySizeArg())
exists(Spec spec | this.asExpr() = spec.getKeySizeArg() and algoName = spec.getAlgoName())
}
override predicate hasState(DataFlow::FlowState state) { state = getMinKeySize().toString() }
override predicate hasState(DataFlow::FlowState state) {
state = getMinKeySize(algoName).toString()
}
}
/** Returns the minimum recommended key size for RSA, DSA, and DH algorithms. */
private int getMinKeySize() { result = 2048 }
private int getMinKeySize(string algoName) {
algoName = "RSA" and
result = minSecureKeySizeRsa()
or
algoName = "DSA" and
result = minSecureKeySizeDsa()
or
algoName = "DH" and
result = minSecureKeySizeDh()
}
/** An instance of an RSA, DSA, or DH algorithm specification. */
private class Spec extends ClassInstanceExpr {
string algoName;
Spec() {
this.getConstructedType() instanceof RsaKeyGenParameterSpec or
this.getConstructedType() instanceof DsaGenParameterSpec or
this.getConstructedType() instanceof DhGenParameterSpec
this.getConstructedType() instanceof RsaKeyGenParameterSpec and
algoName = "RSA"
or
this.getConstructedType() instanceof DsaGenParameterSpec and
algoName = "DSA"
or
this.getConstructedType() instanceof DhGenParameterSpec and
algoName = "DH"
}
/** Gets the `keysize` argument of this instance. */
Argument getKeySizeArg() { result = this.getArgument(0) }
/** Gets the algorithm name of this spec. */
string getAlgoName() { result = algoName }
}
}
@@ -87,7 +116,7 @@ private module Asymmetric {
}
/** Returns the minimum recommended key size for elliptic curve (EC) algorithms. */
private int getMinKeySize() { result = 256 }
private int getMinKeySize() { result = minSecureKeySizeEcc() }
/** Returns the key size from an EC algorithm's curve name string */
bindingset[algorithm]
@@ -168,7 +197,7 @@ private module Symmetric {
}
/** Returns the minimum recommended key size for AES algorithms. */
private int getMinKeySize() { result = 128 }
private int getMinKeySize() { result = minSecureKeySizeAes() }
/** A call to the `init` method declared in `javax.crypto.KeyGenerator`. */
private class KeyGenInit extends MethodAccess {

View File

@@ -3,7 +3,6 @@
import java
import semmle.code.java.security.PartialPathTraversal
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources

View File

@@ -2,9 +2,10 @@
import java
private import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSources
private import semmle.code.java.dataflow.SSA
private import semmle.code.java.frameworks.kotlin.IO
private import semmle.code.java.frameworks.kotlin.Text
/** A sanitizer that protects against path injection vulnerabilities. */
abstract class PathInjectionSanitizer extends DataFlow::Node { }
@@ -51,12 +52,14 @@ private predicate exactPathMatchGuard(Guard g, Expr e, boolean branch) {
t instanceof TypeUri or
t instanceof TypePath or
t instanceof TypeFile or
t.hasQualifiedName("android.net", "Uri")
t.hasQualifiedName("android.net", "Uri") or
t instanceof StringsKt or
t instanceof FilesKt
|
e = getVisualQualifier(ma).getUnderlyingExpr() and
ma.getMethod().getDeclaringType() = t and
ma = g and
ma.getMethod().getName() = ["equals", "equalsIgnoreCase"] and
e = ma.getQualifier() and
getSourceMethod(ma.getMethod()).hasName(["equals", "equalsIgnoreCase"]) and
branch = true
)
}
@@ -87,7 +90,7 @@ private class AllowedPrefixGuard extends PathGuard instanceof MethodAccess {
not isDisallowedWord(super.getAnArgument())
}
override Expr getCheckedExpr() { result = super.getQualifier() }
override Expr getCheckedExpr() { result = getVisualQualifier(this).getUnderlyingExpr() }
}
/**
@@ -154,7 +157,7 @@ private class BlockListGuard extends PathGuard instanceof MethodAccess {
isDisallowedWord(super.getAnArgument())
}
override Expr getCheckedExpr() { result = super.getQualifier() }
override Expr getCheckedExpr() { result = getVisualQualifier(this).getUnderlyingExpr() }
}
/**
@@ -188,15 +191,31 @@ private class BlockListSanitizer extends PathInjectionSanitizer {
}
}
private class ConstantOrRegex extends Expr {
ConstantOrRegex() {
this instanceof CompileTimeConstantExpr or
this instanceof KtToRegex
}
string getStringValue() {
result = this.(CompileTimeConstantExpr).getStringValue() or
result = this.(KtToRegex).getExpressionString()
}
}
private predicate isStringPrefixMatch(MethodAccess ma) {
exists(Method m | m = ma.getMethod() and m.getDeclaringType() instanceof TypeString |
m.hasName("startsWith")
exists(Method m, RefType t |
m.getDeclaringType() = t and
(t instanceof TypeString or t instanceof StringsKt) and
m = ma.getMethod()
|
getSourceMethod(m).hasName("startsWith")
or
m.hasName("regionMatches") and
ma.getArgument(0).(CompileTimeConstantExpr).getIntValue() = 0
getSourceMethod(m).hasName("regionMatches") and
getVisualArgument(ma, 0).(CompileTimeConstantExpr).getIntValue() = 0
or
m.hasName("matches") and
not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().matches(".*%")
not getVisualArgument(ma, 0).(ConstantOrRegex).getStringValue().matches(".*%")
)
}
@@ -206,52 +225,52 @@ private predicate isStringPrefixMatch(MethodAccess ma) {
private predicate isStringPartialMatch(MethodAccess ma) {
isStringPrefixMatch(ma)
or
ma.getMethod().getDeclaringType() instanceof TypeString and
ma.getMethod().hasName(["contains", "matches", "regionMatches", "indexOf", "lastIndexOf"])
exists(RefType t | t = ma.getMethod().getDeclaringType() |
t instanceof TypeString or t instanceof StringsKt
) and
getSourceMethod(ma.getMethod())
.hasName(["contains", "matches", "regionMatches", "indexOf", "lastIndexOf"])
}
/**
* Holds if `ma` is a call to a method that checks whether a path starts with a prefix.
*/
private predicate isPathPrefixMatch(MethodAccess ma) {
exists(RefType t |
t instanceof TypePath
or
t.hasQualifiedName("kotlin.io", "FilesKt")
|
t = ma.getMethod().getDeclaringType() and
ma.getMethod().hasName("startsWith")
)
exists(RefType t | t = ma.getMethod().getDeclaringType() |
t instanceof TypePath or t instanceof FilesKt
) and
getSourceMethod(ma.getMethod()).hasName("startsWith")
}
private predicate isDisallowedWord(CompileTimeConstantExpr word) {
private predicate isDisallowedWord(ConstantOrRegex word) {
word.getStringValue().matches(["/", "\\", "%WEB-INF%", "%/data%"])
}
/** A complementary guard that protects against path traversal, by looking for the literal `..`. */
private class PathTraversalGuard extends PathGuard {
Expr checkedExpr;
PathTraversalGuard() {
exists(MethodAccess ma |
ma.getMethod().getDeclaringType() instanceof TypeString and
exists(MethodAccess ma, Method m, RefType t |
m = ma.getMethod() and
t = m.getDeclaringType() and
(t instanceof TypeString or t instanceof StringsKt) and
checkedExpr = getVisualQualifier(ma).getUnderlyingExpr() and
ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() = ".."
|
this = ma and
ma.getMethod().hasName("contains")
getSourceMethod(m).hasName("contains")
or
exists(EqualityTest eq |
this = eq and
ma.getMethod().hasName(["indexOf", "lastIndexOf"]) and
getSourceMethod(m).hasName(["indexOf", "lastIndexOf"]) and
eq.getAnOperand() = ma and
eq.getAnOperand().(CompileTimeConstantExpr).getIntValue() = -1
)
)
}
override Expr getCheckedExpr() {
exists(MethodAccess ma | ma = this.(EqualityTest).getAnOperand() or ma = this |
result = ma.getQualifier()
)
}
override Expr getCheckedExpr() { result = checkedExpr }
boolean getBranch() {
this instanceof MethodAccess and result = false
@@ -263,15 +282,46 @@ private class PathTraversalGuard extends PathGuard {
/** A complementary sanitizer that protects against path traversal using path normalization. */
private class PathNormalizeSanitizer extends MethodAccess {
PathNormalizeSanitizer() {
exists(RefType t |
t instanceof TypePath or
t.hasQualifiedName("kotlin.io", "FilesKt")
|
this.getMethod().getDeclaringType() = t and
exists(RefType t | this.getMethod().getDeclaringType() = t |
(t instanceof TypePath or t instanceof FilesKt) and
this.getMethod().hasName("normalize")
or
t instanceof TypeFile and
this.getMethod().hasName(["getCanonicalPath", "getCanonicalFile"])
)
or
this.getMethod().getDeclaringType() instanceof TypeFile and
this.getMethod().hasName(["getCanonicalPath", "getCanonicalFile"])
}
}
/**
* Gets the qualifier of `ma` as seen in the source code.
* This is a helper predicate to solve discrepancies between
* what `getQualifier` actually gets in Java and Kotlin.
*/
private Expr getVisualQualifier(MethodAccess ma) {
if getSourceMethod(ma.getMethod()) instanceof ExtensionMethod
then result = ma.getArgument(0)
else result = ma.getQualifier()
}
/**
* Gets the argument of `ma` at position `argPos` as seen in the source code.
* This is a helper predicate to solve discrepancies between
* what `getArgument` actually gets in Java and Kotlin.
*/
bindingset[argPos]
private Argument getVisualArgument(MethodAccess ma, int argPos) {
if getSourceMethod(ma.getMethod()) instanceof ExtensionMethod
then result = ma.getArgument(argPos + 1)
else result = ma.getArgument(argPos)
}
/**
* Gets the proxied method if `m` is a Kotlin proxy that supplies default parameter values.
* Otherwise, just gets `m`.
*/
private Method getSourceMethod(Method m) {
m = result.getKotlinParameterDefaultsProxy()
or
not exists(Method src | m = src.getKotlinParameterDefaultsProxy()) and
result = m
}

View File

@@ -2,7 +2,6 @@
import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.frameworks.spring.SpringExpression
/** A data flow sink for unvalidated user input that is used to construct SpEL expressions. */

View File

@@ -1,7 +1,6 @@
/** Provides taint tracking configurations to be used in unsafe content URI resolution queries. */
import java
import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.UnsafeContentUriResolution

View File

@@ -0,0 +1,21 @@
/**
* INTERNAL: Do not use.
*
* Provides predicates for recommended encryption key sizes.
* Such that we can share this logic across our CodeQL analysis of different languages.
*/
/** Returns the minimum recommended key size for RSA. */
int minSecureKeySizeRsa() { result = 2048 }
/** Returns the minimum recommended key size for DSA. */
int minSecureKeySizeDsa() { result = 2048 }
/** Returns the minimum recommended key size for DH. */
int minSecureKeySizeDh() { result = 2048 }
/** Returns the minimum recommended key size for elliptic curve cryptography. */
int minSecureKeySizeEcc() { result = 256 }
/** Returns the minimum recommended key size for AES. */
int minSecureKeySizeAes() { result = 128 }

View File

@@ -26,18 +26,31 @@ predicate isSafeSecureCookieSetting(Expr e) {
)
}
class SecureCookieConfiguration extends DataFlow::Configuration {
SecureCookieConfiguration() { this = "SecureCookieConfiguration" }
override predicate isSource(DataFlow::Node source) {
exists(MethodAccess ma, Method m | ma.getMethod() = m |
m.getDeclaringType() instanceof TypeCookie and
m.getName() = "setSecure" and
source.asExpr() = ma.getQualifier() and
forex(DataFlow::Node argSource |
DataFlow::localFlow(argSource, DataFlow::exprNode(ma.getArgument(0))) and
not DataFlow::localFlowStep(_, argSource)
|
isSafeSecureCookieSetting(argSource.asExpr())
)
)
}
override predicate isSink(DataFlow::Node sink) {
sink.asExpr() =
any(MethodAccess add | add.getMethod() instanceof ResponseAddCookieMethod).getArgument(0)
}
}
from MethodAccess add
where
add.getMethod() instanceof ResponseAddCookieMethod and
not exists(Variable cookie, MethodAccess m |
add.getArgument(0) = cookie.getAnAccess() and
m.getMethod().getName() = "setSecure" and
forex(DataFlow::Node argSource |
DataFlow::localFlow(argSource, DataFlow::exprNode(m.getArgument(0))) and
not DataFlow::localFlowStep(_, argSource)
|
isSafeSecureCookieSetting(argSource.asExpr())
) and
m.getQualifier() = cookie.getAnAccess()
)
not any(SecureCookieConfiguration df).hasFlowToExpr(add.getArgument(0))
select add, "Cookie is added to response without the 'secure' flag being set."

View File

@@ -1,5 +1,4 @@
import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSources
/** The class `com.jfinal.core.Controller`. */

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSources
/** A utility class for resolving resource locations to files in the file system in the Spring framework. */

View File

@@ -4,7 +4,6 @@
import java
private import semmle.code.java.dataflow.internal.DataFlowUtil
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSummary
private import semmle.code.java.dataflow.internal.FlowSummaryImpl
private import FlowTestCaseUtils

View File

@@ -1,6 +1,5 @@
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.ExternalFlow
class Conf extends TaintTracking::Configuration {
Conf() { this = "qltest:extension-method" }

View File

@@ -1,6 +1,5 @@
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.ExternalFlow
class Conf extends TaintTracking::Configuration {
Conf() { this = "qltest:foreach-array-iterator" }

View File

@@ -1,6 +1,5 @@
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.ExternalFlow
class Conf extends TaintTracking::Configuration {
Conf() { this = "qltest:lambdaFlow" }

View File

@@ -1,6 +1,5 @@
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.ExternalFlow
class Conf extends TaintTracking::Configuration {
Conf() { this = "qltest:notNullExprFlow" }

View File

@@ -1,6 +1,5 @@
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.ExternalFlow
class Conf extends TaintTracking::Configuration {
Conf() { this = "qltest:notNullExprFlow" }

View File

@@ -1,6 +1,5 @@
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.ExternalFlow
import TestUtilities.InlineExpectationsTest
class Conf extends DataFlow::Configuration {

View File

@@ -1,6 +1,5 @@
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.ExternalFlow
import TestUtilities.InlineFlowTest
class HasFlowTest extends InlineFlowTest {

View File

@@ -279,7 +279,7 @@ public class Test {
}
private void blockListGuardValidation(String path) throws Exception {
if (path.contains("..") || !path.startsWith("/data"))
if (path.contains("..") || path.startsWith("/data"))
throw new Exception();
}

View File

@@ -0,0 +1,499 @@
import java.io.File
import java.net.URI
import java.nio.file.Path
import java.nio.file.Paths
import android.net.Uri
class TestKt {
fun source(): Any? {
return null
}
fun sink(o: Any?) {}
@Throws(Exception::class)
private fun exactPathMatchGuardValidation(path: String?) {
if (!path.equals("/safe/path")) throw Exception()
}
@Throws(Exception::class)
fun exactPathMatchGuard() {
run {
val source = source() as String?
if (source!!.equals("/safe/path"))
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
run {
val source = source() as URI?
if (source!!.equals(URI("http://safe/uri")))
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
run {
val source = source() as File?
if (source!!.equals(File("/safe/file")))
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
run {
val source = source() as Uri?
if (source!!.equals(Uri.parse("http://safe/uri")))
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
run {
val source = source() as String?
exactPathMatchGuardValidation(source)
sink(source) // Safe
}
}
@Throws(Exception::class)
private fun allowListGuardValidation(path: String?) {
if (path!!.contains("..") || !path.startsWith("/safe")) throw Exception()
}
@Throws(Exception::class)
fun allowListGuard() {
// Prefix check by itself is not enough
run {
val source = source() as String?
if (source!!.startsWith("/safe")) {
sink(source) // $ hasTaintFlow
} else
sink(source) // $ hasTaintFlow
}
// PathTraversalGuard + allowListGuard
run {
val source = source() as String?
if (!source!!.contains("..") && source.startsWith("/safe"))
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
run {
val source = source() as String?
if (source!!.indexOf("..") == -1 && source.startsWith("/safe"))
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
run {
val source = source() as String?
if (source!!.lastIndexOf("..") == -1 && source.startsWith("/safe"))
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
// PathTraversalSanitizer + allowListGuard
run {
val source: File? = source() as File?
val normalized: String = source!!.canonicalPath
if (normalized.startsWith("/safe")) {
sink(source) // Safe
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ hasTaintFlow
}
}
run {
val source: File? = source() as File?
val normalized: File = source!!.canonicalFile
if (normalized.startsWith("/safe")) {
sink(source) // Safe
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ hasTaintFlow
}
}
run {
val source: File? = source() as File?
val normalized: String = source!!.canonicalFile.toString()
if (normalized.startsWith("/safe")) {
sink(source) // Safe
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ hasTaintFlow
}
}
run {
val source = source() as String?
val normalized: Path = Paths.get(source).normalize()
if (normalized.startsWith("/safe")) {
sink(source) // Safe
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ hasTaintFlow
}
}
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (normalized.startsWith("/safe")) {
sink(source) // $ SPURIOUS: hasTaintFlow
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (normalized.regionMatches(0, "/safe", 0, 5)) {
sink(source) // $ SPURIOUS: hasTaintFlow
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (normalized.matches("/safe/.*".toRegex())) {
sink(source) // $ SPURIOUS: hasTaintFlow
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
// validation method
run {
val source = source() as String?
allowListGuardValidation(source)
sink(source) // Safe
}
// PathInjectionSanitizer + partial string match is considered unsafe
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (normalized.contains("/safe")) {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (normalized.regionMatches(1, "/safe", 0, 5)) {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (normalized.matches(".*/safe/.*".toRegex())) {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
}
@Throws(Exception::class)
private fun dotDotCheckGuardValidation(path: String?) {
if (!path!!.startsWith("/safe") || path.contains("..")) throw Exception()
}
@Throws(Exception::class)
fun dotDotCheckGuard() {
// dot dot check by itself is not enough
run {
val source = source() as String?
if (!source!!.contains("..")) {
sink(source) // $ hasTaintFlow
} else
sink(source) // $ hasTaintFlow
}
// allowListGuard + dotDotCheckGuard
run {
val source = source() as String?
if (source!!.startsWith("/safe") && !source.contains(".."))
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
run {
val source = source() as String?
if (source!!.startsWith("/safe") && source.indexOf("..") == -1)
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
run {
val source = source() as String?
if (!source!!.startsWith("/safe") || source.indexOf("..") != -1)
sink(source) // $ hasTaintFlow
else
sink(source) // Safe
}
run {
val source = source() as String?
if (source!!.startsWith("/safe") && source.lastIndexOf("..") == -1)
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
// blockListGuard + dotDotCheckGuard
run {
val source = source() as String?
if (!source!!.startsWith("/data") && !source.contains(".."))
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
run {
val source = source() as String?
if (!source!!.startsWith("/data") && source.indexOf("..") == -1)
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
run {
val source = source() as String?
if (source!!.startsWith("/data") || source.indexOf("..") != -1)
sink(source) // $ hasTaintFlow
else
sink(source) // Safe
}
run {
val source = source() as String?
if (!source!!.startsWith("/data") && source.lastIndexOf("..") == -1)
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
// validation method
run {
val source = source() as String?
dotDotCheckGuardValidation(source)
sink(source) // Safe
}
}
@Throws(Exception::class)
private fun blockListGuardValidation(path: String?) {
if (path!!.contains("..") || path.startsWith("/data")) throw Exception()
}
@Throws(Exception::class)
fun blockListGuard() {
// Prefix check by itself is not enough
run {
val source = source() as String?
if (!source!!.startsWith("/data")) {
sink(source) // $ hasTaintFlow
} else
sink(source) // $ hasTaintFlow
}
// PathTraversalGuard + blockListGuard
run {
val source = source() as String?
if (!source!!.contains("..") && !source.startsWith("/data"))
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
run {
val source = source() as String?
if (source!!.indexOf("..") == -1 && !source.startsWith("/data"))
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
run {
val source = source() as String?
if (source!!.lastIndexOf("..") == -1 && !source.startsWith("/data"))
sink(source) // Safe
else
sink(source) // $ hasTaintFlow
}
// PathTraversalSanitizer + blockListGuard
run {
val source: File? = source() as File?
val normalized: String = source!!.canonicalPath
if (!normalized.startsWith("/data")) {
sink(source) // Safe
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ hasTaintFlow
}
}
run {
val source: File? = source() as File?
val normalized: File = source!!.canonicalFile
if (!normalized.startsWith("/data")) {
sink(source) // Safe
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ hasTaintFlow
}
}
run {
val source: File? = source() as File?
val normalized: String = source!!.canonicalFile.toString()
if (!normalized.startsWith("/data")) {
sink(source) // Safe
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ hasTaintFlow
}
}
run {
val source = source() as String?
val normalized: Path = Paths.get(source).normalize()
if (!normalized.startsWith("/data")) {
sink(source) // Safe
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ hasTaintFlow
}
}
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (!normalized.startsWith("/data")) {
sink(source) // $ SPURIOUS: hasTaintFlow
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (!normalized.regionMatches(0, "/data", 0, 5)) {
sink(source) // $ SPURIOUS: hasTaintFlow
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (!normalized.matches("/data/.*".toRegex())) {
sink(source) // $ SPURIOUS: hasTaintFlow
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
// validation method
run {
val source = source() as String?
blockListGuardValidation(source)
sink(source) // Safe
}
// PathInjectionSanitizer + partial string match with disallowed words
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (!normalized.contains("/")) {
sink(source) // $ SPURIOUS: hasTaintFlow
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (!normalized.regionMatches(1, "/", 0, 5)) {
sink(source) // $ SPURIOUS: hasTaintFlow
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (!normalized.matches("/".toRegex())) {
sink(source) // $ SPURIOUS: hasTaintFlow
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
// PathInjectionSanitizer + partial string match with disallowed prefixes
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (!normalized.contains("/data")) {
sink(source) // $ SPURIOUS: hasTaintFlow
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (!normalized.regionMatches(1, "/data", 0, 5)) {
sink(source) // $ SPURIOUS: hasTaintFlow
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
run {
val source = source() as String?
// normalize().toString() gets extracted as Object.toString, stopping taint here
val normalized: String = Paths.get(source).normalize().toString()
if (!normalized.matches(".*/data/.*".toRegex())) {
sink(source) // $ SPURIOUS: hasTaintFlow
sink(normalized) // Safe
} else {
sink(source) // $ hasTaintFlow
sink(normalized) // $ MISSING: hasTaintFlow
}
}
}
}

View File

@@ -1 +1,2 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../stubs/google-android-9.0.0
//codeql-extractor-kotlin-options: ${testdir}/../../stubs/google-android-9.0.0

View File

@@ -84,5 +84,15 @@ class Test {
response.addCookie(cookie);
}
{
// GOOD: set secure flag in call to `createSecureCookie`
response.addCookie(createSecureCookie());
}
}
private static Cookie createSecureCookie() {
Cookie cookie = new Cookie("secret", "fakesecret");
cookie.setSecure(constTrue);
return cookie;
}
}