0x00 创建数据库
项目地址:https://github.com/l4yn3/micro_service_seclab
导入IDEA中进行构建,创建数据库,更改数据库密码测试链接,最后导入数据库文件。
创建ql数据库
codeql database create D:\CodeQL\databases\seclab --language="java" --command="mvn clean install --file pom.xml" --source-root=D:\Demo\Java\micro_service_seclab
0x01 SQL注入的规则
首先是SQL注入的部分,在该漏洞靶场中存在着不止一种SQL注入漏洞,同时也有一些迷惑性的路由接口。
/*** @id java/example/vuldemo* @name m0re* @description Sql-Injection* @kind path-problem* @problem.severity warning*/import javaimport semmle.code.java.dataflow.FlowSourcesimport semmle.code.java.security.QueryInjectionimport DataFlow::PathGraphclass SqlConfig extends TaintTracking::Configuration{SqlConfig(){this = "SqlInjectionConfig"}override predicate isSource(DataFlow::Node source) {source instanceof RemoteFlowSource}override predicate isSink(DataFlow::Node sink) {exists(Method method,MethodAccess call | method.hasName("query") andcall.getMethod()=method andsink.asExpr()=call.getArgument(0))}}from SqlConfig config, DataFlow::PathNode source, DataFlow::PathNode sinkwhere config.hasFlowPath(source, sink)select source.getNode(),source,sink,"source"

可以数据跟踪的结果已经输出,验证一下

使用order by先看下,四列,存在注入的。

然后注意下结果只有五个,虽然其中一个结果中包含两条,但对于该靶场来收,还是有漏报的,当然也有误报。接下来需要去解决这两个问题。
0x02 误报解决
在结果中对漏洞进行验证发现,有个点定位到source时,发现它的参数类型是List
参数类型为<font style="color:#DF2A3F;background-color:rgb(247, 247, 248);">List<Long></font>,意味着传递给函数或方法的一个列表,其中每个元素都是 Long 类型。在这种情况下,不太可能存在注入漏洞,因为<font style="color:#DF2A3F;background-color:rgb(247, 247, 248);">List<Long></font>本身不是一个可注入的参数类型。
CodeQL用来消除误报的方法是使用净化/清洗函数。如下图,意味将其切断,阻断数据流。无法流通自然不会被记录到结果中。

关于净化/清洗函数:<font style="color:#DF2A3F;">isSanitizer</font> codeql自带的清洗函数
node节点的类型匹配基础类型,数字类型和泛型数字类型(比如List),需要做的是在node节点匹配到这些类型时,阻断数据流,也就是跟踪不到query时,自然也就不会产生此类误报。
override predicate isSanitizer(DataFlow::Node node) {node.getType() instanceof PrimitiveType ornode.getType() instanceof BoxedType ornode.getType() instanceof NumberType}
override predicate isSanitizer(DataFlow::Node node) {node.getType() instanceof PrimitiveType ornode.getType() instanceof BoxedType ornode.getType() instanceof NumberType orexists(ParameterizedType pt| node.getType()=pt andpt.getTypeArgument(0) instanceof NumberType)}
结果就是误报被消除了

0x03 漏报解决
误报和漏报对漏洞扫描的结果来说都是很正常的,但是对于漏洞来说,抱着Sound的思想,就是能误报,不能漏报。所以势必要解决这个漏报的问题。

注意:在文章——https://www.freebuf.com/articles/web/283795.html漏报解决中,原作者说的是现有规则无法处理Optional这种类型,说的是Optional的使用没有在CodeQL的语法库里。所以username没有流转到<font style="color:rgb(51, 51, 51);">username.get()</font>导致无法检测出SQL注入,但是这次尝试发现已经可以流转,并且可以检测出这个注入漏洞。于是在思考原因是什么。所以返回思考如果没有检测出来这个SQL注入,那么实际情况应该是怎么去看是否有漏报。以下是我的反推思路。
还有几种SQL注入没被检测到,所以还需要再次更新规则,来匹配这些没被匹配到的路由。
在所有的结果中,发现一个问题就是,在IndexDB.java中存放的是SQL语句的执行调用代码,
而在最后的结果中,没有一条链子在最后调用到这段代码
public List<Student> getStudentWithOptional(Optional<String> username) {String sqlWithOptional = "select * from students where username like '%" + username.get() + "%'";//String sql = "select * from students where username like ?";return jdbcTemplate.query(sqlWithOptional, ROW_MAPPER);}
怎么判断是在哪里断掉的,看到代码这边的注释,下面这条SQL语句几乎和上面这条一样,只是变量名和传参不一样。将代码修改成这样的
public List<Student> getStudentWithOptional(Optional<String> username) {//String sqlWithOptional = "select * from students where username like '%" + username.get() + "%'";String sqlWithOptional = "select * from students where username like ?";return jdbcTemplate.query(sqlWithOptional, ROW_MAPPER);}
重新生成database数据库,再次使用上述规则去检测SQL注入

结果中可以发现,已经可以匹配到。正常按照原文中作者的流程应该是这样。可是这里已经可以匹配到,那么就说明在CodeQL的不断更新中,已经将Optional的使用,加入到了CodeQL的规则库中了。所以这里无需强链接username-->username.get()了。
不过强行链接的思路和方法还是值得学习。

isAdditionalTaintStep()方法的两个参数,让node1强行流转至node2
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {}
在此之前先找出username和username.get()
predicate isTaintedString(Expr expSrc, Expr expDest) {exists(Method method, MethodAccess call, MethodAccess call1 |expSrc=call1.getArgument(0) andexpDest=call and call.getMethod()=method and method.hasName("get") and method.getDeclaringType().toString()="Optional<String>" and call1.getArgument(0).getType().toString()="Optinal<String>")}
随后在class中调用它,重写isAdditionalTaintStep()谓词
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {isTaintedString(node1.asExpr(), node2.asExpr())}
0x04 Lombok影响
Lombox是一个Java类库,起因注解编程,方便开发。可是这种注解开发的模式,CodeQL无法识别,导致Lombox机制出现的SQL注入漏洞,因为无法被检测出来。
解决办法:issues
只是这个办法的实际处理是
lombok文件是不完整的Java文件。lombok注解处理器与Javac编译器挂钩,在运行中重写Javac的语法树,使其完整。 去掉代码里的lombok注解,并还原setter和getter方法的java代码,从而使CodeQL的Flow流能够顺利走下去mark一下,不知道后续官方会不会将此进行完善

目前还没有…
0x05 FastJson的检测规则
Fastjson反序列化时,利用JSON字符串中的aututype字段来表明指定反序列化的目标恶意类。同时在反序列化时,会自动调用恶意对象的构造方法,getter/setter,最后在调用JNDI注入的lookup函数上汇聚。
此时需要理清楚两个点,也就是source和sink,定义好污点输入,污点汇聚。
如何定义这两个点
getter的规则:setter的规则:
- 以get开头
- 没有函数参数
- 是我们的code database中的函数
- 为public方法
- 函数名长度要大于3
- 以set开头
- 函数参数为一个
- 是我们code database中的函数
- 为public方法
- 函数名长度大于3
- 返回值为void
这里以一个setter的规则为例
class FastJsonSetMethod extends Method{FastJsonSetMethod(){this.getName().indexOf("set")=0 and //set前截取字符为0,也就是说set开头this.getName().length()>3 and //长度>3this.isPublic() and //是public方法this.fromSource() and //是code database中的exists(VoidType vt |vt=this.getReturnType()) and //返回值为void类型this.getNumberOfParameters()=1 //该函数的参数只有一个,如果是get则不规定}}
然后是污点汇聚的位置,也就是lookup函数。单纯找这个还是比较常用的一个方式
class FastJsonJndiMethod extends Method{FastJsonJndiMethod(){this.getDeclaringType().getASupertype*().hasQualifiedName("javax.naming", "Context") andthis.hasName("lookup")}}
mark,卡住,暂时先跳过
问题解决,继续整理
因为这个愚蠢的问题,去问了GitHub的技术人员,经过几天的沟通(语言不通确实有些难搞)
https://github.com/github/securitylab/discussions/769
最后是因为codeql不能分析闭源依赖包的源码,我是知道这一点的,只是遇到问题的时候没有考虑到,同时也是一个机会吧,我去学习了这个工具的用法。不然要在收藏夹吃灰了。
https://www.yuque.com/m0re/demosec/kp17nflcz1kaasry
如何用codeql来挖利用链,将fastjson的源码下载下来生成database,去寻找可以利用的方法,每一个都去找,找调用关系,连接起来就可以成为一条链子。只不过这个过程有点繁琐,这大概就是劝退我的一点。
