mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
Move code to qll file
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @name Unsafe url forward or dispatch from remote source
|
||||
* @name Unsafe URL forward or dispatch from remote source
|
||||
* @description URL forward or dispatch based on unvalidated user-input
|
||||
* may cause file information disclosure.
|
||||
* @kind path-problem
|
||||
@@ -13,127 +13,8 @@
|
||||
import java
|
||||
import UnsafeUrlForward
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.frameworks.Servlets
|
||||
import semmle.code.java.controlflow.Guards
|
||||
import semmle.code.java.dataflow.NullGuards
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* Holds if `ma` is a call to a method that checks exact match of string.
|
||||
*/
|
||||
predicate isExactStringPathMatch(MethodAccess ma) {
|
||||
ma.getMethod().getDeclaringType() instanceof TypeString and
|
||||
ma.getMethod().getName() = ["equals", "equalsIgnoreCase"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ma` is a call to a method that checks a path string.
|
||||
*/
|
||||
predicate isStringPathMatch(MethodAccess ma) {
|
||||
ma.getMethod().getDeclaringType() instanceof TypeString and
|
||||
ma.getMethod().getName() =
|
||||
["contains", "startsWith", "matches", "regionMatches", "indexOf", "lastIndexOf"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ma` is a call to a method of `java.nio.file.Path` that checks a path.
|
||||
*/
|
||||
predicate isFilePathMatch(MethodAccess ma) {
|
||||
ma.getMethod().getDeclaringType() instanceof TypePath and
|
||||
ma.getMethod().getName() = "startsWith"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ma` protects against path traversal, by either:
|
||||
* * looking for the literal `..`
|
||||
* * performing path normalization
|
||||
*/
|
||||
predicate isPathTraversalCheck(MethodAccess ma, Expr checked) {
|
||||
ma.getMethod().getDeclaringType() instanceof TypeString and
|
||||
ma.getMethod().hasName(["contains", "indexOf"]) and
|
||||
ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() = ".." and
|
||||
ma.(Guard).controls(checked.getBasicBlock(), false)
|
||||
or
|
||||
ma.getMethod() instanceof PathNormalizeMethod and
|
||||
checked = ma
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ma` protects against double URL encoding, by either:
|
||||
* * looking for the literal `%`
|
||||
* * performing URL decoding
|
||||
*/
|
||||
predicate isURLEncodingCheck(MethodAccess ma, Expr checked) {
|
||||
// Search the special character `%` used in url encoding
|
||||
ma.getMethod().getDeclaringType() instanceof TypeString and
|
||||
ma.getMethod().hasName(["contains", "indexOf"]) and
|
||||
ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() = "%" and
|
||||
ma.(Guard).controls(checked.getBasicBlock(), false)
|
||||
or
|
||||
// Call to `URLDecoder` assuming the implementation handles double encoding correctly
|
||||
ma.getMethod().getDeclaringType().hasQualifiedName("java.net", "URLDecoder") and
|
||||
ma.getMethod().hasName("decode") and
|
||||
checked = ma
|
||||
}
|
||||
|
||||
/** The Java method `normalize` of `java.nio.file.Path`. */
|
||||
class PathNormalizeMethod extends Method {
|
||||
PathNormalizeMethod() {
|
||||
this.getDeclaringType().hasQualifiedName("java.nio.file", "Path") and
|
||||
this.hasName("normalize")
|
||||
}
|
||||
}
|
||||
|
||||
private predicate isDisallowedWord(CompileTimeConstantExpr word) {
|
||||
word.getStringValue().matches(["%WEB-INF%", "%META-INF%", "%..%"])
|
||||
}
|
||||
|
||||
private predicate isAllowListCheck(MethodAccess ma) {
|
||||
(isStringPathMatch(ma) or isFilePathMatch(ma)) and
|
||||
not isDisallowedWord(ma.getAnArgument())
|
||||
}
|
||||
|
||||
private predicate isDisallowListCheck(MethodAccess ma) {
|
||||
(isStringPathMatch(ma) or isFilePathMatch(ma)) and
|
||||
isDisallowedWord(ma.getAnArgument())
|
||||
}
|
||||
|
||||
/**
|
||||
* A guard that checks a path with the following methods:
|
||||
* 1. Exact string match
|
||||
* 2. Path matches allowed values (needs to protect against path traversal)
|
||||
* 3. Path matches disallowed values (needs to protect against URL encoding)
|
||||
*/
|
||||
private class PathMatchGuard extends DataFlow::BarrierGuard {
|
||||
PathMatchGuard() {
|
||||
isExactStringPathMatch(this) or isStringPathMatch(this) or isFilePathMatch(this)
|
||||
}
|
||||
|
||||
override predicate checks(Expr e, boolean branch) {
|
||||
e = this.(MethodAccess).getQualifier() and
|
||||
(
|
||||
isExactStringPathMatch(this) and
|
||||
branch = true
|
||||
or
|
||||
isAllowListCheck(this) and
|
||||
exists(MethodAccess ma, Expr checked | isPathTraversalCheck(ma, checked) |
|
||||
DataFlow::localExprFlow(checked, e)
|
||||
or
|
||||
ma.getParent*().(BinaryExpr) = this.(MethodAccess).getParent*()
|
||||
) and
|
||||
branch = true
|
||||
or
|
||||
isDisallowListCheck(this) and
|
||||
exists(MethodAccess ma, Expr checked | isURLEncodingCheck(ma, checked) |
|
||||
DataFlow::localExprFlow(checked, e)
|
||||
or
|
||||
ma.getParent*().(BinaryExpr) = this.(MethodAccess).getParent*()
|
||||
) and
|
||||
branch = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class UnsafeUrlForwardFlowConfig extends TaintTracking::Configuration {
|
||||
UnsafeUrlForwardFlowConfig() { this = "UnsafeUrlForwardFlowConfig" }
|
||||
|
||||
@@ -154,7 +35,7 @@ class UnsafeUrlForwardFlowConfig extends TaintTracking::Configuration {
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof UnsafeUrlForwardSanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof PathMatchGuard
|
||||
guard instanceof UnsafeUrlForwardBarrierGuard
|
||||
}
|
||||
|
||||
override DataFlow::FlowFeature getAFeature() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import java
|
||||
import DataFlow
|
||||
import semmle.code.java.controlflow.Guards
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.frameworks.Servlets
|
||||
import semmle.code.java.frameworks.spring.SpringWeb
|
||||
@@ -30,6 +31,125 @@ private class FollowsSanitizingPrefix extends UnsafeUrlForwardSanitizer {
|
||||
FollowsSanitizingPrefix() { this.asExpr() = any(SanitizingPrefix fp).getAnAppendedExpression() }
|
||||
}
|
||||
|
||||
/** A barrier guard that protects against URL forward vulnerabilities. */
|
||||
abstract class UnsafeUrlForwardBarrierGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* Holds if `ma` is a call to a method that checks exact match of string.
|
||||
*/
|
||||
private predicate isExactStringPathMatch(MethodAccess ma) {
|
||||
ma.getMethod().getDeclaringType() instanceof TypeString and
|
||||
ma.getMethod().getName() = ["equals", "equalsIgnoreCase"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ma` is a call to a method that checks a path string.
|
||||
*/
|
||||
private predicate isStringPathMatch(MethodAccess ma) {
|
||||
ma.getMethod().getDeclaringType() instanceof TypeString and
|
||||
ma.getMethod().getName() =
|
||||
["contains", "startsWith", "matches", "regionMatches", "indexOf", "lastIndexOf"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ma` is a call to a method of `java.nio.file.Path` that checks a path.
|
||||
*/
|
||||
private predicate isFilePathMatch(MethodAccess ma) {
|
||||
ma.getMethod().getDeclaringType() instanceof TypePath and
|
||||
ma.getMethod().getName() = "startsWith"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ma` protects against path traversal, by either:
|
||||
* * looking for the literal `..`
|
||||
* * performing path normalization
|
||||
*/
|
||||
private predicate isPathTraversalCheck(MethodAccess ma, Expr checked) {
|
||||
ma.getMethod().getDeclaringType() instanceof TypeString and
|
||||
ma.getMethod().hasName(["contains", "indexOf"]) and
|
||||
ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() = ".." and
|
||||
ma.(Guard).controls(checked.getBasicBlock(), false)
|
||||
or
|
||||
ma.getMethod() instanceof PathNormalizeMethod and
|
||||
checked = ma
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ma` protects against double URL encoding, by either:
|
||||
* * looking for the literal `%`
|
||||
* * performing URL decoding
|
||||
*/
|
||||
private predicate isURLEncodingCheck(MethodAccess ma, Expr checked) {
|
||||
// Search the special character `%` used in url encoding
|
||||
ma.getMethod().getDeclaringType() instanceof TypeString and
|
||||
ma.getMethod().hasName(["contains", "indexOf"]) and
|
||||
ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() = "%" and
|
||||
ma.(Guard).controls(checked.getBasicBlock(), false)
|
||||
or
|
||||
// Call to `URLDecoder` assuming the implementation handles double encoding correctly
|
||||
ma.getMethod().getDeclaringType().hasQualifiedName("java.net", "URLDecoder") and
|
||||
ma.getMethod().hasName("decode") and
|
||||
checked = ma
|
||||
}
|
||||
|
||||
/** The Java method `normalize` of `java.nio.file.Path`. */
|
||||
private class PathNormalizeMethod extends Method {
|
||||
PathNormalizeMethod() {
|
||||
this.getDeclaringType().hasQualifiedName("java.nio.file", "Path") and
|
||||
this.hasName("normalize")
|
||||
}
|
||||
}
|
||||
|
||||
private predicate isDisallowedWord(CompileTimeConstantExpr word) {
|
||||
word.getStringValue().matches(["%WEB-INF%", "%META-INF%", "%..%"])
|
||||
}
|
||||
|
||||
private predicate isAllowListCheck(MethodAccess ma) {
|
||||
(isStringPathMatch(ma) or isFilePathMatch(ma)) and
|
||||
not isDisallowedWord(ma.getAnArgument())
|
||||
}
|
||||
|
||||
private predicate isDisallowListCheck(MethodAccess ma) {
|
||||
(isStringPathMatch(ma) or isFilePathMatch(ma)) and
|
||||
isDisallowedWord(ma.getAnArgument())
|
||||
}
|
||||
|
||||
/**
|
||||
* A guard that checks a path with the following methods:
|
||||
* 1. Exact string match
|
||||
* 2. Path matches allowed values (needs to protect against path traversal)
|
||||
* 3. Path matches disallowed values (needs to protect against URL encoding)
|
||||
*/
|
||||
private class PathMatchGuard extends UnsafeUrlForwardBarrierGuard {
|
||||
PathMatchGuard() {
|
||||
isExactStringPathMatch(this) or isStringPathMatch(this) or isFilePathMatch(this)
|
||||
}
|
||||
|
||||
override predicate checks(Expr e, boolean branch) {
|
||||
e = this.(MethodAccess).getQualifier() and
|
||||
(
|
||||
isExactStringPathMatch(this) and
|
||||
branch = true
|
||||
or
|
||||
isAllowListCheck(this) and
|
||||
exists(MethodAccess ma, Expr checked | isPathTraversalCheck(ma, checked) |
|
||||
DataFlow::localExprFlow(checked, e)
|
||||
or
|
||||
ma.getParent*().(BinaryExpr) = this.(MethodAccess).getParent*()
|
||||
) and
|
||||
branch = true
|
||||
or
|
||||
isDisallowListCheck(this) and
|
||||
exists(MethodAccess ma, Expr checked | isURLEncodingCheck(ma, checked) |
|
||||
DataFlow::localExprFlow(checked, e)
|
||||
or
|
||||
ma.getParent*().(BinaryExpr) = this.(MethodAccess).getParent*()
|
||||
) and
|
||||
branch = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class UnsafeUrlForwardSink extends DataFlow::Node { }
|
||||
|
||||
/** An argument to `getRequestDispatcher`. */
|
||||
|
||||
Reference in New Issue
Block a user