前言

学习一下SummerSec师傅的项目:https://github.com/SummerSec/learning-codeql

结合项目中的database,前置:需要了解一下shiro反序列化漏洞的利用链。

本文是学习的第一篇——CodeQL分析Shiro利用链

寻找重点:source & sink

SummerSec师傅的文章中提到,在寻找反序列化漏洞的时候需要先找入口,而入口肯定是可以实现反序列化功能的,那么就说明这个入口类,一定实现了Serializable接口,而且会存在serialVersionUID字段,所以现在需要找的就是一个接口需要实现反序列化接口就行。

  1. /**
  2. * @id java/example/vuldemo
  3. * @name shiro-ctf
  4. * @description Shiro-vul
  5. * @kind path-problem
  6. * @problem.severity warning
  7. */
  8. import java
  9. from Class cls
  10. /*条件选择,需要判断是否实现了反序列化接口,还要筛选是否是来自项目源码中的,去除JDK中自带的反序列化接口类的干扰*/
  11. where cls.getASupertype() instanceof TypeSerializable and
  12. cls.fromSource()
  13. select cls
结果成功查询出来,找到一个实现了反序列化接口的Class

😶[Java]CodeQL with Shiro-CTF - 图1

这只是一个接口类,并不是一个实现类,所以需要找到哪里将User实例化了。

  1. /**
  2. * @id java/example/vuldemo
  3. * @name shiro-ctf
  4. * @description Shiro-vul
  5. * @kind path-problem
  6. * @problem.severity warning
  7. */
  8. import java
  9. class FindUser extends RefType{
  10. FindUser(){this.hasQualifiedName("com.summersec.shiroctf.bean", "User")}
  11. }
  12. from ClassInstanceExpr cie
  13. where cie.getType() instanceof FindUser
  14. select cie

查询结果如下:

😶[Java]CodeQL with Shiro-CTF - 图2

IndexController.java中的59行,对User类进行了实例化。

  1. @GetMapping({"/index/{name}"})
  2. public String index(HttpServletRequest request, HttpServletResponse response, @PathVariable String name) throws Exception {
  3. Cookie[] cookies = request.getCookies();
  4. boolean exist = false;
  5. Cookie cookie = null;
  6. User user = null;
  7. if (cookies != null) {
  8. Cookie[] var8 = cookies;
  9. int var9 = cookies.length;
  10. for(int var10 = 0; var10 < var9; ++var10) {
  11. Cookie c = var8[var10];
  12. if (c.getName().equals("hacker")) {
  13. exist = true;
  14. cookie = c;
  15. break;
  16. }
  17. }
  18. }
  19. if (exist) {
  20. byte[] bytes = Tools.base64Decode(cookie.getValue());
  21. user = (User)Tools.deserialize(bytes);
  22. } else {
  23. user = new User();
  24. user.setID(1);
  25. user.setUserName(name);
  26. cookie = new Cookie("hacker", Tools.base64Encode(Tools.serialize(user)));
  27. response.addCookie(cookie);
  28. }
  29. request.setAttribute("hacker", user);
  30. request.setAttribute("logs", new LogHandler());
  31. return "index";
  32. }

关键代码中,如果cookiesnull,那么不执行下面的逻辑判断的操作。如果不为null,而且满足c.getName().equals("hacker")就可以执行到cookie.getValue(),进而执行反序列化的操作。如果没有满足c.getName().equals("hacker")条件,那么就会执行序列化的操作。

Tools类中的base64Encode()serialize()以及deserialize都是Tools中比较关键的方法。接下来就是需要去看Tools的源码是怎么写的这几个方法。

  1. // serialize()
  2. public static byte[] serialize(final Object obj) throws Exception {
  3. ByteArrayOutputStream btout = new ByteArrayOutputStream();
  4. ObjectOutputStream objOut = new ObjectOutputStream(btout);
  5. objOut.writeObject(obj);
  6. return btout.toByteArray();
  7. }
  8. // Base64Encode(byte[] xxxx)
  9. public static String base64Encode(byte[] bytes) {
  10. Base64.Encoder encoder = Base64.getEncoder();
  11. return encoder.encodeToString(bytes);
  12. }
  13. // exeCmd() 执行系统命令
  14. // deserialize()
  15. public static Object deserialize(final byte[] serialized) throws Exception {
  16. ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
  17. ObjectInputStream objIn = new ObjectInputStream(btin);
  18. return objIn.readObject();
  19. }

比如反序列化的方法可以这么来找

  1. class Deserialize extends RefType{
  2. Deserialize(){
  3. this.hasQualifiedName("com.summersec.shiroctf.Tools", "Tools")
  4. }
  5. }
  6. class DeserializeMethod extends Method{
  7. DeserializeMethod(){
  8. this.getDeclaringType() instanceof Deserialize
  9. and this.hasName("deserialize")
  10. }
  11. }
  12. from DeserializeMethod dm
  13. select dm

😶[Java]CodeQL with Shiro-CTF - 图3

然后找到exeCmd,这个可以执行命令的点,可以放一下,待会重新计划规则来找链子

  1. from MethodAccess method
  2. where method.getMethod().hasName("exeCmd")
  3. select method

可以发现在LogHandler.java两处方法调用了exeCmd()

😶[Java]CodeQL with Shiro-CTF - 图4

寻找利用链

完整规则

  1. /**
  2. * @id java/example/vuldemo
  3. * @name shiro-ctf
  4. * @description Shiro-vul
  5. * @kind path-problem
  6. * @problem.severity warning
  7. */
  8. import java
  9. import semmle.code.java.dataflow.FlowSources
  10. import semmle.code.java.security.QueryInjection
  11. import DataFlow::PathGraph
  12. class ShiroCtf extends TaintTracking::Configuration{
  13. ShiroCtf(){
  14. this="Shiro-CTF"
  15. }
  16. override predicate isSource(DataFlow::Node source) {
  17. source instanceof RemoteFlowSource
  18. }
  19. override predicate isSink(DataFlow::Node sink) {
  20. exists(Expr arg|
  21. isDes(arg) and
  22. sink.asExpr() = arg
  23. )
  24. }
  25. }
  26. predicate isDes(Expr arg){
  27. exists(MethodAccess des |
  28. des.getMethod().hasName("deserialize")
  29. and
  30. arg = des.getArgument(0)
  31. )
  32. }
  33. from ShiroCtf config, DataFlow::PathNode source, DataFlow::PathNode sink
  34. where config.hasFlowPath(source, sink)
  35. select sink.getNode(), source, sink, "Unsafe shiro deserialization" ,source.getNode(), "this user input"

执行结果

😶[Java]CodeQL with Shiro-CTF - 图5

source是这里,这里是SummerSec师傅在GitHub Securitylab discussions提出的问题之一,想要让source的点从request开始显示

😶[Java]CodeQL with Shiro-CTF - 图6

实际上师傅的意思也就是在这里

😶[Java]CodeQL with Shiro-CTF - 图7

规则中isSource使用了RemoteFlowSource,其实这个已经包含了大多数情况中的用户输入。

直接在isSource点击"Quick Evaluation",就可以看到所有的用户输入内容。

😶[Java]CodeQL with Shiro-CTF - 图8

这里本想截图的,可是窗口太小了,无法放下,就直接把这部分代码贴过来吧。

  1. @GetMapping({"/index/{name}"})
  2. public String index(HttpServletRequest request, HttpServletResponse response, @PathVariable String name)

这里的name才是真正的用户输入,而不是request,所以想要直接获取到request--cookies这个阶段的数据流应该是不行了。因为使用了RemoteFlowSource不会把request当作一个用户输入的。

SummerSec师傅最后也得到了答案,也就是需要增加一个谓词isAdditionTaintStep来连接这个中断的数据流。

不过确实,没什么必要,只不过是为了更好看而已。codeql可以直接判断getValue()为用户输入。

最后说一下exeCmd的这个利用方式,这个好像不太行的,因为执行命令中间涉及到了第三方包,在一开始创建数据库的时候是没有加载进来的。所以codeql无法跟踪到其中的一些调用关系。

  1. Gadget
  2. Tools.base64Decode()
  3. Tools.deserialize()
  4. ObjectInputStream.readObject()
  5. BadAttributeValueExpException.readObject()
  6. LogHandler.toSting()
  7. Tools.exeCmd()

我没去debug,这个链子是直接拉取SummerSec师傅的。从中可以看出,污点在经过这两个节点的时候已经超出了项目源码的范畴。ObjectInputStreamBadAttributeValueExpException

所以是无法这样来分析这条链子的。这样的链子要去挖掘的话,是需要将第三方包进行反编译并生成ql数据库的,再去寻找。有些繁琐,本人技术有限,同时也有点懒,不想去找。