Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* The `java/log-injection` query now recognizes additional sanitizers and sanitizer guards from the JDK, Apache Commons Lang and Text, Guava, OWASP Java Encoder, and Spring Framework.
* Queries that use path-injection sanitizers now recognize additional substring guards and string replacement and removal operations from Apache Commons Lang and Guava.
5 changes: 5 additions & 0 deletions java/ql/lib/ext/com.google.common.base.model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ extensions:
- ["com.google.common.base", "Ascii", False, "truncate", "(CharSequence,int,String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["com.google.common.base", "Ascii", False, "truncate", "(CharSequence,int,String)", "", "Argument[2]", "ReturnValue", "taint", "manual"]
- ["com.google.common.base", "CaseFormat", True, "to", "(CaseFormat,String)", "", "Argument[1]", "ReturnValue", "taint", "manual"]
- ["com.google.common.base", "CharMatcher", True, "removeFrom", "(CharSequence)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["com.google.common.base", "CharMatcher", True, "replaceFrom", "(CharSequence,char)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["com.google.common.base", "CharMatcher", True, "replaceFrom", "(CharSequence,char)", "", "Argument[1]", "ReturnValue", "taint", "manual"]
- ["com.google.common.base", "CharMatcher", True, "replaceFrom", "(CharSequence,CharSequence)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["com.google.common.base", "CharMatcher", True, "replaceFrom", "(CharSequence,CharSequence)", "", "Argument[1]", "ReturnValue", "taint", "manual"]
- ["com.google.common.base", "Converter", True, "apply", "(Object)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["com.google.common.base", "Converter", True, "convert", "(Object)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["com.google.common.base", "Converter", True, "convertAll", "(Iterable)", "", "Argument[0].Element", "ReturnValue.Element", "taint", "manual"]
Expand Down
8 changes: 8 additions & 0 deletions java/ql/lib/ext/org.apache.commons.lang3.model.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
extensions:
- addsTo:
pack: codeql/java-all
extensible: barrierModel
data:
- ["org.apache.commons.lang3", "StringEscapeUtils", False, "escapeJava", "(String)", "", "ReturnValue", "log-injection", "manual"]
- ["org.apache.commons.lang3", "StringUtils", False, "deleteWhitespace", "", "", "ReturnValue", "log-injection", "manual"]
- ["org.apache.commons.lang3", "StringUtils", False, "normalizeSpace", "", "", "ReturnValue", "log-injection", "manual"]
- addsTo:
pack: codeql/java-all
extensible: sinkModel
Expand Down Expand Up @@ -75,6 +82,7 @@ extensions:
- ["org.apache.commons.lang3", "RegExUtils", False, "replaceFirst", "", "", "Argument[2]", "ReturnValue", "taint", "manual"]
- ["org.apache.commons.lang3", "RegExUtils", False, "replacePattern", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["org.apache.commons.lang3", "RegExUtils", False, "replacePattern", "", "", "Argument[2]", "ReturnValue", "taint", "manual"]
- ["org.apache.commons.lang3", "StringEscapeUtils", False, "escapeJava", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["org.apache.commons.lang3", "StringEscapeUtils", False, "escapeJson", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["org.apache.commons.lang3", "StringUtils", False, "abbreviate", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["org.apache.commons.lang3", "StringUtils", False, "abbreviate", "(java.lang.String,java.lang.String,int)", "", "Argument[1]", "ReturnValue", "taint", "manual"]
Expand Down
6 changes: 6 additions & 0 deletions java/ql/lib/ext/org.apache.commons.text.model.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
extensions:
- addsTo:
pack: codeql/java-all
extensible: barrierModel
data:
- ["org.apache.commons.text", "StringEscapeUtils", False, "escapeJava", "(String)", "", "ReturnValue", "log-injection", "manual"]
- addsTo:
pack: codeql/java-all
extensible: summaryModel
data:
- ["org.apache.commons.text", "StringEscapeUtils", False, "escapeJava", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["org.apache.commons.text", "StrBuilder", False, "StrBuilder", "(java.lang.String)", "", "Argument[0]", "Argument[this]", "taint", "manual"]
- ["org.apache.commons.text", "StrBuilder", False, "append", "", "", "Argument[this]", "ReturnValue", "taint", "manual"]
- ["org.apache.commons.text", "StrBuilder", False, "append", "", "", "Argument[this]", "ReturnValue", "value", "manual"]
Expand Down
11 changes: 11 additions & 0 deletions java/ql/lib/ext/org.owasp.encoder.model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
extensions:
- addsTo:
pack: codeql/java-all
extensible: barrierModel
data:
- ["org.owasp.encoder", "Encode", False, "forJava", "(String)", "", "ReturnValue", "log-injection", "manual"]
- addsTo:
pack: codeql/java-all
extensible: summaryModel
data:
- ["org.owasp.encoder", "Encode", False, "forJava", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
5 changes: 5 additions & 0 deletions java/ql/lib/ext/org.springframework.util.model.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
extensions:
- addsTo:
pack: codeql/java-all
extensible: barrierModel
data:
- ["org.springframework.util", "StringUtils", False, "trimAllWhitespace", "", "", "ReturnValue", "log-injection", "manual"]
- addsTo:
pack: codeql/java-all
extensible: sinkModel
Expand Down
208 changes: 207 additions & 1 deletion java/ql/lib/semmle/code/java/Concepts.qll
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ module;
import java

/**
* A module importing the frameworks that implement `RegexMatch`es,
* A module importing the frameworks that implement string matches,
* ensuring that they are visible to the concepts library.
*/
private module Frameworks {
private import semmle.code.java.JDK
private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.frameworks.guava.Guava
private import semmle.code.java.frameworks.JavaxAnnotations
}

Expand Down Expand Up @@ -79,3 +81,207 @@ module RegexMatch {
abstract string getName();
}
}

/**
* An expression that tests whether an input string contains a substring.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `StringSubstringMatch::Range` instead.
*/
class StringSubstringMatch extends Expr instanceof StringSubstringMatch::Range {
/** Gets the input string being tested. */
Expr getInputString() { result = super.getInputString() }

/** Gets the expression that specifies the substring. */
Expr getMatchExpression() { result = super.getMatchExpression() }

/** Gets an expression to be sanitized. */
Expr getASanitizedExpr() { result = [this.getInputString(), super.getAdditionalSanitizedExpr()] }

/** Holds if taking `branch` means that the input contains the substring. */
predicate hasSubstringOnBranch(boolean branch) { super.hasSubstringOnBranch(branch) }

/** Gets a descriptive name for this substring match. */
string getName() { result = super.getName() }
}

/** Provides classes for modeling substring-matching APIs. */
module StringSubstringMatch {
/** A range class for APIs that test whether an input contains a substring. */
abstract class Range extends Expr {
/** Gets the input string being tested. */
abstract Expr getInputString();

/** Gets the expression that specifies the substring. */
abstract Expr getMatchExpression();

/** Gets an additional expression to be sanitized, if any. */
Expr getAdditionalSanitizedExpr() { none() }

/** Holds if taking `branch` means that the input contains the substring. */
abstract predicate hasSubstringOnBranch(boolean branch);

/** Gets a descriptive name for this substring match. */
abstract string getName();
}
}

/**
* An expression that tests whether an input string contains characters from a
* character set.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `StringCharacterSetMatch::Range` instead.
*/
class StringCharacterSetMatch extends Expr instanceof StringCharacterSetMatch::Range {
/** Gets the input string being tested. */
Expr getInputString() { result = super.getInputString() }

/** Gets the expression that specifies the character set. */
Expr getMatchExpression() { result = super.getMatchExpression() }

/** Gets an expression to be sanitized. */
Expr getASanitizedExpr() { result = [this.getInputString(), super.getAdditionalSanitizedExpr()] }

/**
* Holds if taking `branch` means that the input contains at least one
* character from the match expression.
*/
predicate hasMatchingCharacterOnBranch(boolean branch) {
super.hasMatchingCharacterOnBranch(branch)
}

/** Gets a descriptive name for this character-set match. */
string getName() { result = super.getName() }
}

/** Provides classes for modeling character-set matching APIs. */
module StringCharacterSetMatch {
/**
* A range class for APIs that test whether an input contains characters from
* a character set.
*/
abstract class Range extends Expr {
/** Gets the input string being tested. */
abstract Expr getInputString();

/** Gets the expression that specifies the character set. */
abstract Expr getMatchExpression();

/** Gets an additional expression to be sanitized, if any. */
Expr getAdditionalSanitizedExpr() { none() }

/**
* Holds if taking `branch` means that the input contains at least one
* character from the match expression.
*/
abstract predicate hasMatchingCharacterOnBranch(boolean branch);

/** Gets a descriptive name for this character-set match. */
abstract string getName();
}
}

/**
* An expression that replaces every occurrence of a target in a string.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `StringReplacement::Range` instead.
*/
class StringReplacement extends Expr instanceof StringReplacement::Range {
/** Gets the input string whose contents are replaced. */
Expr getInputString() { result = super.getInputString() }

/** Gets the expression that identifies the content to replace. */
Expr getMatchExpression() { result = super.getMatchExpression() }

/** Gets the replacement expression. */
Expr getReplacement() { result = super.getReplacement() }

/** Holds if the match expression is interpreted as a regular expression. */
predicate matchExpressionIsRegex() { super.matchExpressionIsRegex() }

/** Holds if each character in the match expression is matched independently. */
predicate matchExpressionIsCharacterSet() { super.matchExpressionIsCharacterSet() }

/** Gets a descriptive name for this replacement operation. */
string getName() { result = super.getName() }
}

/** Provides classes for modeling string-replacement APIs. */
module StringReplacement {
/**
* An expression that replaces every occurrence of a target in a string.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `StringReplacement` instead.
*/
abstract class Range extends Expr {
/** Gets the input string whose contents are replaced. */
abstract Expr getInputString();

/** Gets the expression that identifies the content to replace. */
abstract Expr getMatchExpression();

/** Gets the replacement expression. */
abstract Expr getReplacement();

/** Holds if the match expression is interpreted as a regular expression. */
predicate matchExpressionIsRegex() { none() }

/** Holds if each character in the match expression is matched independently. */
predicate matchExpressionIsCharacterSet() { none() }

/** Gets a descriptive name for this replacement operation. */
abstract string getName();
}
}

/**
* An expression that removes every occurrence of a target from a string.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `StringRemoval::Range` instead.
*/
class StringRemoval extends Expr instanceof StringRemoval::Range {
/** Gets the input string whose contents are removed. */
Expr getInputString() { result = super.getInputString() }

/** Gets the expression that identifies the content to remove. */
Expr getMatchExpression() { result = super.getMatchExpression() }

/** Holds if the match expression is interpreted as a regular expression. */
predicate matchExpressionIsRegex() { super.matchExpressionIsRegex() }

/** Holds if each character in the match expression is matched independently. */
predicate matchExpressionIsCharacterSet() { super.matchExpressionIsCharacterSet() }

/** Gets a descriptive name for this removal operation. */
string getName() { result = super.getName() }
}

/** Provides classes for modeling string-removal APIs. */
module StringRemoval {
/**
* An expression that removes every occurrence of a target from a string.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `StringRemoval` instead.
*/
abstract class Range extends Expr {
/** Gets the input string whose contents are removed. */
abstract Expr getInputString();

/** Gets the expression that identifies the content to remove. */
abstract Expr getMatchExpression();

/** Holds if the match expression is interpreted as a regular expression. */
predicate matchExpressionIsRegex() { none() }

/** Holds if each character in the match expression is matched independently. */
predicate matchExpressionIsCharacterSet() { none() }

/** Gets a descriptive name for this removal operation. */
abstract string getName();
}
}
11 changes: 11 additions & 0 deletions java/ql/lib/semmle/code/java/Expr.qll
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,17 @@ class CompileTimeConstantExpr extends Expr {
result = this.(LiveLiteral).getValue().getStringValue()
}

/**
* Gets the string value of this expression if it is a `String` constant, or
* the single-character string value if it is a `char` constant.
*/
string getStringOrCharValue() {
result = this.getStringValue()
or
this.getType() instanceof CharacterType and
result = this.getIntValue().toUnicode()
}

/**
* Gets the boolean value of this expression, where possible.
*/
Expand Down
35 changes: 33 additions & 2 deletions java/ql/lib/semmle/code/java/JDK.qll
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ class StringContainsMethod extends Method {
}
}

/** A call to the `java.lang.String.contains` method. */
class StringContainsCall extends MethodCall, StringSubstringMatch::Range {
StringContainsCall() { this.getMethod() instanceof StringContainsMethod }

override Expr getInputString() { result = this.getQualifier() }

override Expr getMatchExpression() { result = this.getArgument(0) }

override predicate hasSubstringOnBranch(boolean branch) { branch = true }

override string getName() { result = "String.contains" }
}

/** A call to the `java.lang.String.matches` method. */
class StringMatchesCall extends MethodCall, RegexMatch::Range {
StringMatchesCall() {
Expand All @@ -64,23 +77,41 @@ class StringMatchesCall extends MethodCall, RegexMatch::Range {
}

/** A call to the `java.lang.String.replaceAll` method. */
class StringReplaceAllCall extends MethodCall {
class StringReplaceAllCall extends MethodCall, StringReplacement::Range {
StringReplaceAllCall() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypeString and
m.hasName("replaceAll")
)
}

override Expr getInputString() { result = this.getQualifier() }

override Expr getMatchExpression() { result = this.getArgument(0) }

override Expr getReplacement() { result = this.getArgument(1) }

override predicate matchExpressionIsRegex() { any() }

override string getName() { result = "String.replaceAll" }
}

/** A call to the `java.lang.String.replace` method. */
class StringReplaceCall extends MethodCall {
class StringReplaceCall extends MethodCall, StringReplacement::Range {
StringReplaceCall() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypeString and
m.hasName("replace")
)
}

override Expr getInputString() { result = this.getQualifier() }

override Expr getMatchExpression() { result = this.getArgument(0) }

override Expr getReplacement() { result = this.getArgument(1) }

override string getName() { result = "String.replace" }
}

/**
Expand Down
Loading
Loading