CodeQL系列之域敏感和兜底规则冲突的解决过程
效果: 检测SQL注入漏洞准确率达到90%以上,几乎无误报漏报。
0x01 问题
起初isAdditionalTaintStep的污染转播链规则是将所有情况都标记为污染。目的是为了避免漏报的情况发生。
例如:此种情况下CodeQL默认的TaintStep不会扫出链路,加了兜底规则后则会扫出该链路。
但是因为兜底规则将所有的情况都标记为了污染,那么也会出现误报的情况。
例如:模拟XML的SQL注入漏洞情况,在XML文件里,直接是获取对象的属性然后执行sql语句,而在触发SQL语句的入口点却传入的是对象。如下例子vul11testExec,souce传递进来后赋值给dsUser的cmd属性,然后dsUser的user属性赋值为常量,最后将整个对象dsUser传递给执行SQL语句的函数,在xml里的sql语句可能是select cmd from table或者是select user from table。那么当sql语句里是cmd属性时,那么是存在漏洞的,如果是user属性时,则不存在漏洞。但是因为兜底规则的影响,将dsUser整个对象标记为了污染,因此无论xml里的sql语句是cmd属性还是user属性,都会认为该链路存在SQL注入漏洞。这样就造成了误报。
0x02 解决过程
0x02-1 方案1(解决了部分误报)
链路中出现getXXX、setXXX时,不走兜底规则,走CodeQL默认的链路,这样就不会因为当出现setXXX赋值的语句时,将整个对象标记为污染,从而解决了部分误报漏报的情况。
第一次优化
isAdditionalTaintStep只有兜底规则
则会出现如下的误报,因为在dstest.setCmd(cmd);
中,node1是cmd,node2是dstest,将对象dstest标记为污染了。所以最终能够扫出下面的误报链路。
解决方案:CodeQL默认是会处理域敏感问题,那么当链路中出现了setXXX、getXXX方法时,不让走我们定义的兜底规则,走CodeQL默认的域敏感处理规则,那么就会将dstest的cmd属性标记为污染,不会将整个dstest对象标记污染,因此解决该误报链路问题。
在isAdditionalTaintStep
方法里,通过if判断条件,如果链路中存在setXXX、getXXX方法,那么执行none语句,即不走兜底规则,走CodeQL默认的域敏感处理规则。可以看到结果里没有了getUsername这条链路了。的确解决了误报问题。
第二次优化
但是发现该if判断条件较简单粗暴,是直接粗暴的将所有setXXX、getXXX方法都走默认的CodeQL规则,但是没有将其和node1、node2连接起来。
忘记当时的场景是什么状况了,反正要优化下,将node1和node2连接起来。
第三次优化
后面又发现一处bug,通过下面的if判断条件,居然扫出了一条误报链路
误报情况如下,dstest对象仍然被标记为污染
解决方案:isAdditionalTaintStep里Method m起初放在最外层的exists中,会出现dstest.setUsername(“zhangsan”);的误报链路,将Method m移到if判断语句里,解决了该误报问题。
优化后如下
解决了误报问题。
第四次优化
下图红框走了兜底规则,将dsUser对象标记为污染。
解决方案:实例化对象的时候,不走兜底规则,走codeql默认的TaintStep,则添加一条规则,判断call是不是实例化。即下图红框里的代码。
优化后如下,成功解决了上面的误报问题。
最终规则
优化后代码如下:
// 链路里的所有可能污染的地方
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
exists(Call call, Callable callable |
// storeStep(src, c, sink) and
call.getCallee() = callable and
if exists(Method m |
// 如果是object.setXXXXXX(YYYYYY)时,则走codeql默认的TaintStep,这样只会污染object的XXXXXX属性,不会将整个对象污染
(
node1.asExpr() = call.getAnArgument() and
node2.asExpr() = call.getQualifier() and
m = call.getCallee() and
(
m instanceof SetterMethod or
m instanceof GetterMethod
)
)
// 如果是DSUser dsUser = new DSUser(command)时,则走codeql默认的TaintStep
or
(
node1.asExpr() = call.getAnArgument() and
node2.asExpr() = call and
call instanceof ConstructorCall
)
)
then
none()
else
// none()
// 兜底函数,避免漏报
(
(
node1.asExpr() = call.getAnArgument() or
node1.asExpr() = call.getQualifier()
) and
(
node2.asExpr() = call or
node2.asExpr() = call.getQualifier()
)
)
)
}
0x02-2 方案2(最终解决思路可行,方式错误)
在XML格式的SQL注入里,例如selectByExample函数的orderByClause注入,sink点是example对象,而xml文件里直接是调用了orderByClause属性,并没有通过getXXX方法获取orderByClause值,因此前面的通过过滤getXXX方法走CodeQL默认的规则并不适用。分析CodeQL展现结果的链路,发现当对象的属性被污染时,展现的是dsUser [cmd]:String类似的格式,即dsUser对象的cmd属性被污染。因此想到的解决方案是获取最后sink点的污染属性名字,再和xml文件里的注入点名字做对比。从而解决误报问题。
跟踪CodeQL默认的污染传播链路
为了读取Sink点的污染属性,起初想到的是寻找方法能够读取最后sink点被污染的属性(最终证实该方法并不可行,因为当sink是对象的链路打印出来后,其实整个对象就已经被标记为污染了,那么就不可能打印出被污染的属性,但是当时没有想到这一点)。于是一步步的分析跟踪CodeQL默认的污染传播链路。
TaintTracking::Configuration#isAdditionalFlowStep
方法入口开始分析
发现isAdditionalFlowStep
方法里调用了isAdditionalTaintStep
(我们自写的污染转播规则)和defaultAdditionalTaintStep
(CodeQL默认的污点传播规则)
跟入到defaultAdditionalTaintStep
(CodeQL默认的污点传播规则)里
后面一顿分析
分析PathNode节点
PathNode入口开始分析
发现了PathNodeImpl
类,该类继承于PathNode
看了下所有的方法,发现了toString
方法,猜测可能是通过该方法打印每条链路里的每一节点信息。于是继续深入跟踪测试。
更改了原先的toString
方法里的内容,添加了一下---
字符串做测试,发现的确影响到了链路里的每一个节点的内容。
跟入this.ppAp()
方法里,发现如果节点PathNode是PathNodeSink时,则返回空,如果节点PathNode是PathNodeMid时,返回getAp()方法的结果。
那么PathNodeSink和PathNodeMid的区别是什么?在toString方法里,添加打印this.getAQlClass()方法
从结果中可以分析出来,PathNodeSink是链路中最后的一个节点,PathNodeMid是链路中的每个过程的中间节点
接下来分析PathNodeMid的getAp()方法返回了什么内容,通过PathNodeMid的定义知道,getAp()返回了AccessPath对象。
继续跟入分析AccessPath类,通过介绍猜测是链路中的每条路径里的信息
继续分析发现不少类继承了AccessPath类,通过一个个测试发现AccessPathCons类的toString()方法会影响到我们结果的内容
继续跟入到AccessPathCons#toStringImpl()
方法里,红框里的内容就是展示结果里打印的内容。
有意思的地方是head.toSting()
,该方法打印了对象被污染了的属性。于是分析下head的类TypedContent
类TypedContent
执行TypedContent#toString()
方法,发现打印的结果均是对象的属性。
那么就能理解了为什么AccessPathCons#toStringImpl()
里的head.toSting()
能够将对象里被污染的属性打印出来了。
将打印污染属性的中括号里加上*测试效果
效果如下:的确将source传递给dstest对象的cmd属性污染了,在链路中打印出了[*cmd*]
此时想的是以为已经能够打印对象被污染的属性,那么在PathNodeSink节点时也通过getAp()方法将对象被污染的属性打印出来。如下例子,将dsUser对象的cmd属性打印出来。
但是发现PathNodeSink没有getAp()方法,那么可能就得需要改动大量源码,给PathNodeSink增加类似PathNodeMid的getAp()方法。但是因为感觉改动太大,就先暂时放一边了。后面重新回顾了下,发现该方案不可行。因为当出现该链路时,dsUser整个对象就已经被标记为污染了,不再存在dsUser对象没被污染,dsUser的cmd属性被污染的情况。
证明如下例子:
之所以vul11testExec扫不出来,而vul12testExec能够扫出来,问题出在sink点一个是对象,一个是对象的属性,当sink是对象时,因为污染的是对象的属性,所以对象dsUser并不认为被污染,因此没扫出来
当重构isAdditionalTaintStep时,因为默认的TaintStep没扫出来,然后经过我们的规则,在DSUser dsUser = new DSUser(cmd);时将dsUser设置为污染,因此到test.testExecDSUser(dsUser)时,sink点dsUser也是污染的。所以能够扫出来,但也会因此造成误报。
0x02-3 方案三(获取存在漏洞链路)
解决方案:将对象的属性设置为Sink点
allowImplicitRead-匹配对象被污染的属性
没有重构allowImplicitRead
方法,打印出来的链路是走了我们的兜底规则,直接将example对象标记为污染。
重构allowImplicitRead
方法,将Content类对象c的值设置为sink对象污染属性的值,下面例子就是匹配example对象的orderByClause属性。可以看到结果里多出了一条链路,并且打印出污染属性orderByClause。
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::Content c) {
(this.isSink(node) or this.isAdditionalTaintStep(node, _)) and
(
defaultImplicitTaintRead(node, c) or
c.toString() = "orderByClause"
)
}
SQL注入实践效果,成功的打印出对象属性被污染的链路。
但是最后测试发现,误报链路还是存在,说明allowImplicitRead只能增加真实存在漏洞的链路,但是并不能过滤掉误报链路。
0x03 方案四(sink点分类讨论,彻底解决了误报)
发现新的问题:大量的误报还来源于将sql函数的所有参数标记为sink点的原因,然后结合了兜底规则造成了大量的误报。
解决方案:针对sink点分类讨论,共三种情况(Object.getAttrName(),String,Obejct)
Object.getAttrName() 对象调用get获取属性值
漏洞代码如下:
test.testExampleTwoString(request.getSortType(), request.getCmd());
如果sink点是object.getAttrName()会造成大量的误报,那么思路就是将getAttrName中的AttrName取出来,然后和xml里的注入点参数名一一做对比,只有相同时才说明是个漏洞,这样可以减少大量的误报。代码arg.toString().substring(3, arg.toString().length()-5)
取出Object.getAttrName()的AttrName值,然后全部转换成小写,和xml里取出来的注入点参数名作比较。
String 字符串
漏洞代码如下:
test.testExampleTwoString(sortType, cmd);
如果sink点是字符串类型,那么就直接和xml里的注入点参数名一一做对比即可。
Object 对象
漏洞代码如下:
test.testExampleTwoString(dsUser);
如果sink点是对象,检测sink点打印是否有 [被污染的属性] ,如果有则提取出被污染的属性,然后和xml的注入点参数名做比较,如果一样则是真实链路,如果不一样则是误报。如果没有,就是对象本身,那么获取对象的所有属性名,然后比较对象的属性里面有没有注入点参数名。
sink点没有 [被污染的属性] ,提取出对象的所有属性名,然后比较对象的属性里面有没有注入点参数名。下图认为是真实存在漏洞的链路
sink点有 [被污染的属性] ,提取出被污染的属性,然后和xml的注入点参数名做比较。下图认为是真实存在漏洞的链路
sink点有 [被污染的属性] ,提取出被污染的属性,然后和xml的注入点参数名做比较。因为不一致,所以下图认为是误报链路。
最终判断代码如下(添加到isMyBatisXMLSQL方法里):经过测试,的确大幅度的提升了漏洞的准确率。
// 如果sink点是object.getAttrName()会造成大量的误报,如下例子。那么通过正则将attrName取出来,然后和xml里的参数名一一做对比,这样可以减少大量的误报
// test.testExampleTwoString(request.getSortType(), request.getCmd());
if arg.toString().regexpMatch("get[A-Z].*")
then
funcArgName = arg.toString().substring(3, arg.toString().length()-5).toLowerCase() and
funcArgName.toLowerCase() = argumentName.toLowerCase()
// funcArgName.toLowerCase() = "platformName".toLowerCase()
else
funcArgName = "" and
// 如果sink点是字符串
if arg.getType() instanceof TypeString
then
arg.toString() = argumentName
// 最后一种情况,sink点是对象
else
// 检测sink点是否有 [被污染的属性]
if sink.toString().regexpMatch(".*\\[.*\\]")
then
// 如果sink点有 [被污染的属性] ,则提取出被污染的属性,然后和xml的注入点参数名做比较,如果一样则是真实链路,如果不一样则是误报
objectAttrName = sink.toString().regexpFind("[^\\[\\]]*", _, _) and
objectAttrName = argumentName
else
// 如果sink点没有 [被污染的属性] ,就是对象本身,那么获取对象的所有属性名,然后比较对象的属性里面有没有注入点参数名。
objectAttrName = arg.getType().(RefType).getAField().toString() and
objectAttrName = argumentName
0x03 总结
问题的根源
是在sink点的定义上,之前是将sink点标记为每一个参数,然后又通过兜底规则将整个对象也标记为污染,所以造成了大量的误报。
解决方案
是将兜底规则直接删除,然后增加allowImplicitRead方法(可以打印出真实存在漏洞的链路),再对sink点分类讨论过滤误报。最终几乎0误报检测出SQL注入漏洞。
技术难点突破
其实很多的误报都是来源于sink点为对象时导致的,其实通过PartialPathNode
可以打印出sink点为对象时被污染的属性。那么为什么PartialPathNode
可以打印出对象被污染的属性,而PathNode
却不能打印出来(之前在这里研究了好久),通过定义可以看出PartialPathNode
的toString()
打印出了ppAp()
,而ppAP在之前分析过,就是AccessPath,也可以理解为路径吧。所以在最后的链路中,通过PartialPathNode
的toString()
就能够取出被污染的属性,而如果使用PathNode
就不行了。
0x04 靶场测试代码
package org.joychou.controller;
import org.apache.commons.lang.StringUtils;
import org.joychou.dao.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.StringWriter;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* Java code execute
*
* @author JoyChou @ 2018-05-24
*/
@RestController
@RequestMapping("/domainSensitive")
public class DomainSensitive {
private static Map<String, String> sortFieldMap = new HashMap<>();
private static String SORT_STR = "ISNULL(%s),%s %s";
public String testExecDSTestCmdGet(DSTest dsTest) throws IOException{
Runtime run = Runtime.getRuntime();
Process p = run.exec(dsTest.getCmd()); // vul1, vul2
return "1";
}
public String testExecDSTestCmdDot(DSTest dsTest) throws IOException{
Runtime run = Runtime.getRuntime();
Process p = run.exec(dsTest.cmd); // vul1, vul2
return "1";
}
public String testExecDSTestUserNameGet(DSTest dsTest) throws IOException{
Runtime run = Runtime.getRuntime();
Process p = run.exec(dsTest.getUsername()); // 安全
return "1";
}
public String testExecDSTestUserNameDot(DSTest dsTest) throws IOException{
Runtime run = Runtime.getRuntime();
Process p = run.exec(dsTest.username); // 安全
return "1";
}
public String testExecDSUserCmdGet(DSUser dsUser) throws IOException{
Runtime run = Runtime.getRuntime();
Process p = run.exec(dsUser.getCmd());
return "1";
}
public String testExecDSUserCmdDot(DSUser dsUser) throws IOException{
Runtime run = Runtime.getRuntime();
Process p = run.exec(dsUser.cmd);
return "1";
}
public String testExecDSUserUserGet(DSUser dsUser) throws IOException{
Runtime run = Runtime.getRuntime();
Process p = run.exec(dsUser.getUser());
return "1";
}
public String testExecDSUserUserDot(DSUser dsUser) throws IOException{
Runtime run = Runtime.getRuntime();
Process p = run.exec(dsUser.user);
return "1";
}
public String testExecDSUserPassGet(DSUser dsUser) throws IOException{
Runtime run = Runtime.getRuntime();
Process p = run.exec(dsUser.getPass());
return "1";
}
public String testExecDSUserPassDot(DSUser dsUser) throws IOException{
Runtime run = Runtime.getRuntime();
Process p = run.exec(dsUser.pass);
return "1";
}
public String testExecDSUser(DSUser dsUser) throws IOException{
Runtime run = Runtime.getRuntime();
Process p = run.exec(String.valueOf(dsUser)); // vul5, vul6, vul7
return "1";
}
// [+] CodeQL默认的TaintStep可以扫出来
@GetMapping("/vul1testExecDSTestCmdGet")
public String vul1testExecDSTestCmdGet(String cmd) throws IOException {
DSTest dstest = new DSTest();
dstest.setCmd(cmd);
dstest.setUsername("zhangsan");
return testExecDSTestCmdGet(dstest);
}
// [+] CodeQL默认的TaintStep可以扫出来
@GetMapping("/vul2testExecDSTestCmdDot")
public String vul2testExecDSTestCmdDot(String cmd) throws IOException {
DSTest dstest = new DSTest();
dstest.cmd = cmd;
dstest.username = "zhangsan";
return testExecDSTestCmdDot(dstest);
}
@GetMapping("/sec3testExecDSTestUserNameGet")
public String sec3testExecDSTestUserNameGet(String cmd) throws IOException {
DSTest dstest = new DSTest();
dstest.setCmd(cmd);
dstest.setUsername("zhangsan");
return testExecDSTestUserNameGet(dstest);
}
@GetMapping("/sec4testExecDSTestUserNameGet")
public String sec4testExecDSTestUserNameGet(String cmd) throws IOException {
DSTest dstest = new DSTest();
dstest.cmd = cmd;
dstest.username = "zhangsan";
return testExecDSTestUserNameDot(dstest);
}
// [+] CodeQL默认的TaintStep可以扫出来
@GetMapping("/vul5testExecDSUserCmdGet")
public String vul5testExecDSUserCmdGet(String cmd) throws IOException {
DSTest dstest = new DSTest();
dstest.cmd = cmd;
dstest.username = "zhangsan";
String command = dstest.getCmd() + dstest.getUsername();
DSUser dsUser = new DSUser(command); // 新对象被污染
return testExecDSUserCmdGet(dsUser);
}
@GetMapping("/sec6testExecDSUserUserGet")
public String sec6testExecDSUserUserGet(String cmd) throws IOException {
DSTest dstest = new DSTest();
dstest.cmd = cmd;
dstest.username = "zhangsan";
String command = dstest.getCmd() + dstest.getUsername();
DSUser dsUser = new DSUser(command); // 新对象被污染
return testExecDSUserUserGet(dsUser);
}
// [+] CodeQL默认的TaintStep可以扫出来
@GetMapping("/vul7")
public String vul7(String cmd) throws IOException {
DSTest dstest = new DSTest();
dstest.cmd = cmd;
dstest.username = "zhangsan";
String command = dstest.getCmd() + dstest.getUsername();
DSUser dsUser = new DSUser("111");
dsUser.pass = command; // 新对象的属性被污染
return testExecDSUserPassGet(dsUser);
}
@GetMapping("/sec8testExecDSUserUserGet")
public String sec8testExecDSUserUserGet(String cmd) throws IOException {
DSTest dstest = new DSTest();
dstest.cmd = cmd;
dstest.username = "zhangsan";
String command = dstest.getCmd() + dstest.getUsername();
DSUser dsUser = new DSUser("111");
dsUser.pass = command; // 新对象的属性被污染
return testExecDSUserUserGet(dsUser);
}
// 下面的例子分析构造函数是否会处理域敏感问题,结果显示是会处理的
// codeql默认的TaintStep不会扫出下面的链路,
// codeql默认的TaintStep在DSUser dsUser = new DSUser(cmd)表达式中会将dsUser的cmd属性污染,但是因为后面重构的isAdditionalTaintStep,导致将整个对象标记为污染了。所以产生了如下的误报。
// 因此通过m instanceof GetterMethod的方法将所有调用了getXXXX的链路都走codeql默认的TaintStep,从而解决了如下的误报
@GetMapping("/sec9testExecDSUserUserGet")
public String sec9testExecDSUserUserGet(String cmd) throws IOException {
DSUser dsUser = new DSUser(cmd); // 新对象被污染
dsUser.setUser("aaa");
return testExecDSUserUserGet(dsUser);
}
// [+] CodeQL默认的TaintStep可以扫出来
@GetMapping("/vul10testExecDSUserCmdGet")
public String vul10testExecDSUserCmdGet(String cmd) throws IOException {
DSUser dsUser = new DSUser(cmd); // 新对象被污染
dsUser.setUser("aaa");
return testExecDSUserCmdGet(dsUser);
}
// 场景
// 模拟xml的sql注入-orderByCause的注入点,vul11testExec和vul12testExec作比较
// 问题:
// 之所以vul11testExec扫不出来,而vul12testExec能够扫出来,问题出在sink点一个是对象,一个是对象的属性,当sink是对象时,因为污染的是对象的属性,所以对象dsUser并不认为被污染,因此没扫出来
// 当重构isAdditionalTaintStep时,因为默认的TaintStep没扫出来,然后经过我们的规则,在DSUser dsUser = new DSUser(cmd);时将dsUser设置为污染,
// 因此到test.testExecDSUser(dsUser)时,sink点dsUser也是污染的。所以能够扫出来,但也会因此造成误报。
// 解决方案
// 获取sink的被污染的属性
// [-] CodeQL默认的TaintStep扫不出来
@GetMapping("/vul11testExec")
public String vul11testExec(String cmd) throws IOException {
DSUser dsUser = new DSUser(cmd); // 新对象被污染
dsUser.setUser("aaa");
TestExec test = new TestExec();
return test.testExecDSUser(dsUser); // select cmd form
}
// [+] CodeQL默认的TaintStep扫出来
@GetMapping("/vul12testExec")
public String vul12testExec(String cmd) throws IOException {
DSUser dsUser = new DSUser(cmd); // 新对象被污染
dsUser.setUser("aaa");
TestExec test = new TestExec();
return test.testExecDSUserCmd(dsUser.getCmd());
}
// [-] CodeQL默认的TaintStep扫不出来
// 模拟DA186DE56A57D9697A1791B37D6E0E20漏洞的误报
// 经过了一层函数exampleAssemble
@GetMapping("/vul13testExec")
public String vul13testExec(String cmd) throws IOException {
DSTest dsTest = exampleAssemble(cmd);
TestExec test = new TestExec();
return test.testExecDSTest(dsTest);
}
public DSTest exampleAssemble(String cmd){
DSTest dsTest = new DSTest();
dsTest.cmd = cmd;
return dsTest;
}
// CodeQL默认扫不出来
// 不走过滤构造函数-即走兜底的规则 应该要扫出来
// 走过滤构造函数 应该要扫不出来 主要解决该情况
@GetMapping("/vul14testExec")
public void vul14testExec(String templateContent) throws IOException, TemplateException {
Configuration cfg = new Configuration();
StringTemplateLoader stringLoader = new StringTemplateLoader();
DSUser dsUser = new DSUser(templateContent);
stringLoader.putTemplate("tpl", String.valueOf(dsUser));
cfg.setTemplateLoader(stringLoader);
Template template = cfg.getTemplate("tpl");
StringWriter stringWriter = new StringWriter();
//在模版上执行插值操作,并输出到制定的输出流中
template.process(null, stringWriter);
String resultStr = stringWriter.toString();
System.out.println(resultStr);
}
// CodeQL默认扫不出来
// 走了兜底函数,在 stringLoader.putTemplate("tpl", dsUser.getCmd()); 这里经过我们的兜底函数,将stringLoader污染了,所以能够扫出来
@GetMapping("/vul15testExec")
public void vul15testExec(String templateContent) throws IOException, TemplateException {
Configuration cfg = new Configuration();
StringTemplateLoader stringLoader = new StringTemplateLoader();
DSUser dsUser = new DSUser(templateContent);
stringLoader.putTemplate("tpl", dsUser.getCmd());
cfg.setTemplateLoader(stringLoader);
Template template = cfg.getTemplate("tpl");
StringWriter stringWriter = new StringWriter();
//在模版上执行插值操作,并输出到制定的输出流中
template.process(null, stringWriter);
String resultStr = stringWriter.toString();
System.out.println(resultStr);
}
// CodeQL默认扫出来
@GetMapping("/vul16testExec")
public String vul16testExec(QueryRequest request) throws IOException {
QueryExample example = buildExample(request);
TestExec test = new TestExec();
return test.testExampleSQL(example);
}
private QueryExample buildExample(QueryRequest request) {
QueryExample example = new QueryExample();
example.setUser(request.getUser());
example.setOrderByClause(request.getSortType());
return example;
}
// CodeQL默认扫不出来
// 检测是否是 HashMap导致没扫出来
// 因为HashMap的get方法返回值不会被标记为污染,但因为走了兜底规则,将sortFieldMap.get(request.getUser())标记为了污点,然后成功扫出真实存在漏洞的链路
@GetMapping("/vul17testExec")
public String vul17testExec(QueryRequest request) throws IOException {
QueryExample example = buildExample2(request);
TestExec test = new TestExec();
return test.testExampleSQL(example);
}
private QueryExample buildExample2(QueryRequest request) {
QueryExample example = new QueryExample();
example.setOrderByClause("created_at desc");
example.setUser(request.getUser());
String field = sortFieldMap.get(request.getUser());
example.setOrderByClause(field);
return example;
}
// CodeQL默认扫出来
// 检测是否是 String.format()导致没扫出来
// 说明String.format(taint) 会将结果标记为污染
@GetMapping("/vul18testExec")
public String vul18testExec(QueryRequest request) throws IOException {
QueryExample example = buildExample3(request);
TestExec test = new TestExec();
return test.testExampleSQL(example);
}
private QueryExample buildExample3(QueryRequest request) {
QueryExample example = new QueryExample();
example.setOrderByClause("created_at desc");
example.setUser(request.getUser());
String field = sortFieldMap.get(request.getUser());
example.setOrderByClause(String.format(SORT_STR, field, field, request.getSortType()));
return example;
}
@GetMapping("/vul19testExec")
public String vul19testExec(QueryRequest request) throws IOException {
QueryExample example = buildExample4(request);
TestExec test = new TestExec();
return test.testExampleSQL(example);
}
private QueryExample buildExample4(QueryRequest request) {
QueryExample example = new QueryExample();
example.setOrderByClause("created_at desc");
example.setUser(request.getUser());
String field = sortFieldMap.get(request.getUser());
if (StringUtils.isNotBlank(field) && StringUtils.isNotBlank(request.getSortType())) {
example.setOrderByClause(String.format(SORT_STR, field, field, request.getSortType()));
}
return example;
}
// [-] CodeQL默认的TaintStep扫不出来
// 漏报问题
@GetMapping("/vul100")
public void vul100(String templateContent) throws IOException, TemplateException {
Configuration cfg = new Configuration();
StringTemplateLoader stringLoader = new StringTemplateLoader();
stringLoader.putTemplate("tpl", templateContent);
cfg.setTemplateLoader(stringLoader);
Template template = cfg.getTemplate("tpl");
StringWriter stringWriter = new StringWriter();
//在模版上执行插值操作,并输出到制定的输出流中
template.process(null, stringWriter);
String resultStr = stringWriter.toString();
System.out.println(resultStr);
}
}
package org.joychou.dao;
import java.io.Serializable;
public class DSTest implements Serializable {
public String username;
public String cmd;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
}
package org.joychou.dao;
import java.io.Serializable;
public class DSUser implements Serializable {
public String user;
public String pass;
public String cmd;
public DSUser(String cmd) {
this.cmd = cmd;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
public String getCmd() {
return cmd;
}
}
package org.joychou.dao;
public class QueryExample {
public String user;
public String orderByClause;
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getOrderByClause() {
return orderByClause;
}
public void setOrderByClause(String orderByClause) {
this.orderByClause = orderByClause;
}
public static class Criteria {
protected Criteria() {
super();
}
}
public Criteria createCriteria() {
return createCriteriaInternal();
}
public Criteria createCriteriaInternal(){
return new Criteria();
}
}
package org.joychou.dao;
public class QueryRequest {
public String user;
public String pass;
public String cmd;
public String sortType;
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
public String getCmd() {
return cmd;
}
public void setCmd(String cmd) {
this.cmd = cmd;
}
public String getSortType() {
return sortType;
}
public void setSortType(String sortType) {
this.sortType = sortType;
}
}
package org.joychou.dao;
import java.io.IOException;
public class TestExec {
public String testExecDSUser(DSUser dsUser) throws IOException {
return dsUser.getCmd();
}
public String testExecDSUserCmd(String cmd) throws IOException {
return cmd;
}
public String testExecDSTest(DSTest dsTest) throws IOException {
return dsTest.getCmd();
}
public String testExampleSQL(QueryExample example) throws IOException {
return "1";
}
}