publicclassSchedulingConstraintSetValidatorimplementsConstraintValidator<SchedulingConstraintSetValidator.SchedulingConstraintSet, Container> { ... @Override publicbooleanisValid(Container container, ConstraintValidatorContext context){ if (container == null) { returntrue; } Set<String> common = new HashSet<>(container.getSoftConstraints().keySet()); common.retainAll(container.getHardConstraints().keySet());
if (common.isEmpty()) { returntrue; }
context.buildConstraintViolationWithTemplate( "Soft and hard constraints not unique. Shared constraints: " + common ).addConstraintViolation().disableDefaultConstraintViolation(); returnfalse; } }
通过Bean Validation 2.0 规范可知在构建违反约束的错误信息时,可以插入多种类型的值,包括Java EL 表达式。因此如果ConstraintValidatorContext.buildConstraintViolationWithTemplate()的第一个参数即错误信息模板被攻击者可控,就有可能导致任意代码执行,即CVE-2020-9297。这些错误信息模板就是注入漏洞的sink。经过验证的bean属性通常会流入自定义错误信息,这些就是source。
from MyTaintTrackingConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink, source, sink, "Custom constraint error message contains unsanitized user data"
查询结果为0。
问题分析
目前source和sink点都已经明确,结果为0则说明从source到sink的路径上缺少了一步。CodeQL提供了partial data flow来进行Debug,这个功能可以查找从给定的source到任何可能的sink的流,让sink不受限制,同时限制从source到sink的搜索步骤的数量。因此可以使用这个功能来跟踪污点从source到所有可能的sink的流向,并查看流在哪一步不再被进一步跟踪。
/** @kind path-problem */ import java import semmle.code.java.dataflow.TaintTracking import DataFlow::PartialPathGraph // this is different!
class MyTaintTrackingConfig extends TaintTracking::Configuration { MyTaintTrackingConfig() { ... } // same as before override predicate isSource(DataFlow::Node source) { ... } // same as before override predicate isSink(DataFlow::Node sink) { ... } // same as before override int explorationLimit() { result = 10} // this is different! } from MyTaintTrackingConfig cfg, DataFlow::PartialPathNode source, DataFlow::PartialPathNode sink where cfg.hasPartialFlow(source, sink, _) and source.getNode() = ... // TODO restrict to the one source we are interested in, for ease of debugging select sink, source, sink, "Partial flow from unsanitized user data"
predicate partial_flow(PartialPathNode n, Node src, int dist) { exists(MyTaintTrackingConfig conf, PartialPathNode source | conf.hasPartialFlow(source, n, dist) and src = source.getNode() and source = // TODO - restrict to THE source we are interested in ) }
from MyTaintTrackingConfig cfg, DataFlow::PartialPathNode source, DataFlow::PartialPathNode sink where cfg.hasPartialFlow(source, sink, _) and source.getNode().asParameter().getName() = "container" select sink, source, sink, "Partial flow from unsanitized user data"
predicate partial_flow(DataFlow::PartialPathNode n, DataFlow::Node src, int dist) { exists(MyTaintTrackingConfig conf, DataFlow::PartialPathNode source | conf.hasPartialFlow(source, n, dist) and src = source.getNode() and source.getNode().asParameter().getName() = "container" ) }
You must have found that CodeQL does not propagate taint through getters like container.getHardConstraints and container.getSoftConstraints. Can you guess why this default behaviour was implemented?
class MyTaintTrackingConfig extends TaintTracking::Configuration { MyTaintTrackingConfig() { this = "MyTaintTrackingConfig" }
override predicate isSource(DataFlow::Node source) { exists(Method m | m.hasQualifiedName("", "Demo", "test") and m.getAParameter() = source.asParameter() ) }
override predicate isSink(DataFlow::Node sink) { exists(MethodAccess ma | ma.getCallee().getDeclaringType().hasQualifiedName("java.io", "PrintStream") and sink.asExpr() = ma.getAnArgument() ) }
override int explorationLimit() { result = 10} } from MyTaintTrackingConfig cfg, DataFlow::PartialPathNode source, DataFlow::PartialPathNode sink where cfg.hasPartialFlow(source, sink, _) select sink, source, sink, "Partial flow from unsanitized user data"
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) { super.allowImplicitRead(node, c) or this.isAdditionalTaintStep(node, _) and ( c instanceof DataFlow::ArrayContent or c instanceof DataFlow::CollectionContent or c instanceof DataFlow::MapValueContent ) }
class MyTaintTrackingConfig extends TaintTracking::Configuration { MyTaintTrackingConfig() { this = "MyTaintTrackingConfig" }
override predicate isSource(DataFlow::Node source) { exists(Method m | m.hasQualifiedName("", "Demo", "test") and m.getAParameter() = source.asParameter() ) }
override predicate isSink(DataFlow::Node sink) { exists(MethodAccess ma | ma.getCallee().getDeclaringType().hasQualifiedName("java.io", "PrintStream") and sink.asExpr() = ma.getAnArgument() ) }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { pred.asExpr() = succ.asExpr().(AddExpr).getAnOperand() } override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) { super.allowImplicitRead(node, c) or this.isAdditionalTaintStep(node, _) and ( c instanceof DataFlow::ArrayContent or c instanceof DataFlow::CollectionContent or c instanceof DataFlow::MapValueContent ) } } from MyTaintTrackingConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink, source, sink, "Partial flow from unsanitized user data"