mirror of
https://github.com/github/codeql.git
synced 2026-04-20 06:24:03 +02:00
Java: initial update of barrier and test cases to remove FN
This commit is contained in:
@@ -64,7 +64,8 @@ private predicate exactPathMatchGuard(Guard g, Expr e, boolean branch) {
|
||||
)
|
||||
}
|
||||
|
||||
private class ExactPathMatchSanitizer extends PathInjectionSanitizer {
|
||||
// TODO: switch back to private if possible
|
||||
class ExactPathMatchSanitizer extends PathInjectionSanitizer {
|
||||
ExactPathMatchSanitizer() {
|
||||
this = DataFlow::BarrierGuard<exactPathMatchGuard/3>::getABarrierNode()
|
||||
or
|
||||
@@ -151,7 +152,8 @@ private class DotDotCheckSanitizer extends PathInjectionSanitizer {
|
||||
}
|
||||
}
|
||||
|
||||
private class BlockListGuard extends PathGuard instanceof MethodCall {
|
||||
// TODO: switch back to private if possible
|
||||
class BlockListGuard extends PathGuard instanceof MethodCall {
|
||||
BlockListGuard() {
|
||||
(isStringPartialMatch(this) or isPathPrefixMatch(this)) and
|
||||
isDisallowedWord(super.getAnArgument())
|
||||
@@ -228,6 +230,7 @@ private predicate isStringPartialMatch(MethodCall ma) {
|
||||
exists(RefType t | t = ma.getMethod().getDeclaringType() |
|
||||
t instanceof TypeString or t instanceof StringsKt
|
||||
) and
|
||||
// TODO ! Why not use `StringPartialMatchMethod` for the below?
|
||||
getSourceMethod(ma.getMethod())
|
||||
.hasName(["contains", "matches", "regionMatches", "indexOf", "lastIndexOf"])
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.FlowSources
|
||||
private import semmle.code.java.dataflow.StringPrefixes
|
||||
private import semmle.code.java.security.PathSanitizer
|
||||
private import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.controlflow.Guards
|
||||
|
||||
/** A URL forward sink. */
|
||||
abstract class UrlForwardSink extends DataFlow::Node { }
|
||||
@@ -44,16 +47,121 @@ private class PrimitiveBarrier extends UrlForwardBarrier {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: should this also take URL encoding/decoding into account?
|
||||
// TODO: and PathSanitization in general?
|
||||
private class FollowsBarrierPrefix extends UrlForwardBarrier {
|
||||
FollowsBarrierPrefix() { this.asExpr() = any(BarrierPrefix fp).getAnAppendedExpression() }
|
||||
}
|
||||
|
||||
private class BarrierPrefix extends InterestingPrefix {
|
||||
BarrierPrefix() {
|
||||
// TODO: why not META-INF here as well? (and are `/` correct?)
|
||||
not this.getStringValue().matches("/WEB-INF/%") and
|
||||
not this.getStringValue() = "forward:"
|
||||
}
|
||||
|
||||
override int getOffset() { result = 0 }
|
||||
}
|
||||
|
||||
private class UrlPathBarrier extends UrlForwardBarrier {
|
||||
UrlPathBarrier() {
|
||||
this instanceof PathInjectionSanitizer and
|
||||
(
|
||||
this instanceof ExactPathMatchSanitizer //TODO: still need a better solution for this edge case...
|
||||
or
|
||||
// TODO: these don't enforce order of checks and PathSanitization... make bypass test cases.
|
||||
this instanceof NoEncodingBarrier
|
||||
or
|
||||
this instanceof FullyDecodesBarrier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class UrlDecodeCall extends MethodCall { }
|
||||
|
||||
private class DefaultUrlDecodeCall extends UrlDecodeCall {
|
||||
DefaultUrlDecodeCall() {
|
||||
this.getMethod().hasQualifiedName("java.net", "URLDecoder", "decode") or // TODO: reuse existing class? Or make this a class?
|
||||
this.getMethod().hasQualifiedName("org.eclipse.jetty.util.URIUtil", "URIUtil", "decodePath")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this can probably be named/designed better...
|
||||
abstract class RepeatedStmt extends Stmt { }
|
||||
|
||||
private class DefaultRepeatedStmt extends RepeatedStmt {
|
||||
DefaultRepeatedStmt() { this instanceof LoopStmt }
|
||||
}
|
||||
|
||||
abstract class CheckEncodingCall extends MethodCall { }
|
||||
|
||||
private class DefaultCheckEncodingCall extends CheckEncodingCall {
|
||||
DefaultCheckEncodingCall() {
|
||||
// TODO: indexOf?, etc.
|
||||
this.getMethod().hasQualifiedName("java.lang", "String", "contains") and // TODO: reuse existing class? Or make this a class?
|
||||
this.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "%"
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: better naming?
|
||||
// TODO: check if any URL decoding implementations _fully_ decode... or if all need to be called in a loop?
|
||||
// TODO: make this extendable instead of `RepeatedStmt`?
|
||||
private class RepeatedUrlDecodeCall extends MethodCall {
|
||||
RepeatedUrlDecodeCall() {
|
||||
this instanceof UrlDecodeCall and
|
||||
this.getAnEnclosingStmt() instanceof RepeatedStmt
|
||||
}
|
||||
}
|
||||
|
||||
private class CheckEncodingGuard extends Guard instanceof MethodCall {
|
||||
CheckEncodingGuard() { this instanceof CheckEncodingCall }
|
||||
|
||||
Expr getCheckedExpr() { result = this.(MethodCall).getQualifier() }
|
||||
}
|
||||
|
||||
private predicate noEncodingGuard(Guard g, Expr e, boolean branch) {
|
||||
g instanceof CheckEncodingGuard and
|
||||
e = g.(CheckEncodingGuard).getCheckedExpr() and
|
||||
branch = false
|
||||
or
|
||||
// branch = false and
|
||||
// g instanceof AssignExpr and // AssignExpr
|
||||
// exists(CheckEncodingCall call | g.(AssignExpr).getSource() = call | e = call.getQualifier())
|
||||
branch = false and
|
||||
g.(Expr).getType() instanceof BooleanType and // AssignExpr
|
||||
(
|
||||
exists(CheckEncodingCall call, AssignExpr ae |
|
||||
ae.getSource() = call and
|
||||
e = call.getQualifier() and
|
||||
g = ae.getDest()
|
||||
)
|
||||
or
|
||||
exists(CheckEncodingCall call, LocalVariableDeclExpr vde |
|
||||
vde.getInitOrPatternSource() = call and
|
||||
e = call.getQualifier() and
|
||||
g = vde.getAnAccess() //and
|
||||
//vde.getVariable() = g
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: check edge case of !contains(%), make sure that behaves as expected at least.
|
||||
private class NoEncodingBarrier extends DataFlow::Node {
|
||||
NoEncodingBarrier() { this = DataFlow::BarrierGuard<noEncodingGuard/3>::getABarrierNode() }
|
||||
}
|
||||
|
||||
private predicate fullyDecodesGuard(Expr e) {
|
||||
exists(CheckEncodingGuard g, RepeatedUrlDecodeCall decodeCall |
|
||||
e = g.getCheckedExpr() and
|
||||
g.controls(decodeCall.getBasicBlock(), true)
|
||||
)
|
||||
}
|
||||
|
||||
private class FullyDecodesBarrier extends DataFlow::Node {
|
||||
FullyDecodesBarrier() {
|
||||
exists(Variable v, Expr e | this.asExpr() = v.getAnAccess() |
|
||||
fullyDecodesGuard(e) and
|
||||
e = v.getAnAccess() and
|
||||
e.getBasicBlock().bbDominates(this.asExpr().getBasicBlock())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,7 @@ module UrlForwardFlowConfig implements DataFlow::ConfigSig {
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof UrlForwardSink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof UrlForwardBarrier or
|
||||
node instanceof PathInjectionSanitizer
|
||||
}
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof UrlForwardBarrier }
|
||||
|
||||
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user