安全矩阵

 找回密码
 立即注册
搜索
查看: 2566|回复: 0

wJa(java闭源项目的自动化白盒+黑盒测试工具)

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-12-31 14:18:04 | 显示全部楼层 |阅读模式
原文链接:wJa(java闭源项目的自动化白盒+黑盒测试工具)

wJa,支持反编译java生成的jar包文件,整理成语法树,根据调用链进行污点分析,通过cheetah脚本语言编写测试脚本,确定可能存在的漏洞调用链,生成测试链接,进行fuzzer测试。
下面对网络上的一个Spring靶场进行SQL注入的分析测试。
环境准备
  •         测试靶场
  •         java运行环境
  •         wJa

wJa分析流程

Step1 运行靶场搭建好靶场环境,这里使用的靶场是在网上寻找到的,里面有创建数据库的脚本文件,环境一切准备就绪之后。
运行靶场,使用命令跑起jar包:
java -jars shootingRange.jar

Step2 使用wJa打开jar文件使用命令:java -jar wJa.jar运行起程序,程序运行之后会要求选择待分析的jar程序,这里选择shootingRange.jar。
起来之后可以看到程序的主界面了。

左侧部分:
Decompile:对jar包反编译的java资源管理器
cheetahLanguage:脚本管理器,包含支持库介绍,以及编写好的cheetah脚本
中间部分:
Decompile:对jar包反编译的java代码显示部分
cheetahLanguage:编写cheetah脚本代码,运行测试

界面相对比较简单。
我们可以先看整个靶场的一个框架结构,从control层进行分析(Spring默认路径为BOOT-INF/classes)。

可以看到control层都是提供对外开放的接口,所以我们可以确定这是入口类,所以我们可以将其确定为入口点。
从其中的一个入口点(one)根据一步步的调用追踪,我们可以得到如下调用链:
  1. indexLogic.getStudent(username);
  2. indexDb.getStudent(username);
  3. sql = "select * from students where username like '%" + username + "%'";
  4. jdbcTemplate.query(sql, ROW_MAPPER);
复制代码


最终进入的危险函数:

当然,这是我们手动跟踪的,但是如何使用工具自动帮助我们进行追踪呢?
Step3 编写白盒污点跟踪代码污点分析
污点分析可以抽象成一个三元组〈sources, sinks, sanitizers〉的形式, 其中, source即污点源, 代表直接引入不受信任的数据或者机密数据到系统中; sink即污点汇聚点, 代表直接产生安全敏感操作 (违反数据完整性) 或者泄露隐私数据到外界 (违反数据保密性); sanitizer即无害处理, 代表通过数据加密或者移除危害操作等手段使数据传播不再对软件系统的信息安全产生危害.污点分析就是分析程序中由污点源引入的数据是否能够不经无害处理, 而直接传播到污点汇聚点.如果不能, 说明系统是信息流安全的; 否则, 说明系统产生了隐私数据泄露或危险数据操作等安全问题.
对于SQL注入这种漏洞,可以将污点分析的三元组实例化为下面三组内容:
source:Spring的接口入口点的参数
sink:jdbc的query方法
sanitizer:类似于Integer.value此类方法

代码编写在检测类似于SQL注入类漏洞,我们需要的是跟踪调用链,所以需要使用的是TrackVarIntoFun函数
TrackVarIntoFun
  1. 参数1:起始类
  2. 参数2:起始方法
  3. 参数3:起始方法参数下标
  4. 参数4:目标方法的类
  5. 参数5:目标方法
  6. 参数6:目标方法的参数下标
  7. 返回值:执行流node数组
复制代码


  •         起始类是我们需要分析的类,这里是com/l4yn3/microserviceseclab/controller/IndexController
  •         起始方法是入口方法,也是这个类下面的所有接口方法
  •         起始方法参数下标是要检测的入口参数下标
  •         目标方法类是jdbc,这里是org/springframework/jdbc/core/JdbcTemplate
  •         目标方法是query,jdbc查询数据的方法
  •         目标方法的参数下标是第一个参数,sql语句

返回值是一个node执行流数组,node包含次node所在的class和node的AST。
我们设置开头的包名,那如何获取所有的方法名呢?
GetAllMethodName可以获取所有的方法名称,但是这里有一个注意的地方是,如果方法名是<init>和<cinit>的需要跳过,因为这两个方法是构造方法和静态代码块。
node中的AST可以通过GetJavaSentence方法得到对应生成的java代码。
还有一点需要注意的是,TrackVarIntoFun方法只是跟踪流,只是到目标方法就停止,如果没有到目标方法就停止了那么也是会返回所有的执行流,所以这里我们需要自己进行过滤。
所以现在的思路已经完成,通过GetAllMethodName获取所有的方法,然后对方法中的第一个参数进行追踪,查看其最终流向的是否是jdbc,并且判断流动过程中是否有类似于Integer.value()方法的存在,如果不存在,那噩梦非常可能就是一条可以被污染的链条。
最终我们可以编写出如下代码:
  1. #define filter1=String.valueOf(.*?)
  2. #define filter2=Integer.valueOf(.*?)
  3. function filter(sentence){
  4.     a = StrRe(sentence,filter1);
  5.     if(GetArrayNum(a) != 0){return 0;}
  6.     a = StrRe(sentence,filter2);
  7.     if(GetArrayNum(a) != 0){return 0;}
  8.     return 1;
  9. }
  10. function track(className,methodName){
  11.     array allNode;
  12.     allNode = TrackVarIntoFun(className,methodName,0,"org/springframework/jdbc/core/JdbcTemplate","query",0);
  13.     size = GetArrayNum(allNode);
  14.     if(StrFindStr(GetJavaSentence(allNode[ToInt(size-1)]),".query(",0) != "-1"){
  15.         i = 0;
  16.         print(methodName."参数流动:");
  17.         cc = 7;
  18.         cs = 1;
  19.         while(i < size){
  20.             sentence = GetJavaSentence(allNode[i]);
  21.             if(filter(sentence) == 0){cc = 5;cs = 5;printcolor("想办法绕过此类:",4);}
  22.             if(i == ToInt((size-1))){
  23.                 if(cc != 5){cs = 2;cc = 3;}
  24.             }else{}
  25.             if(cc == 5){printcolor("[-]",6);}else{printcolor("[+]",1);}
  26.             printcolor(GetClassName(GetNodeClassName(allNode[i]))."   ",cc);
  27.             printcolor(sentence.StrRN(),cs);
  28.             i = ToInt(i+1);
  29.         }
  30.     }
  31.     return 0;
  32. }
  33. function main(args){
  34.     className = "com/l4yn3/microserviceseclab/controller/IndexController";
  35.     methods = GetAllMethodName(className);
  36.     size = GetArrayNum(methods);
  37.     i = 0;
  38.     while(i < size){
  39.         if(methods[i] != "<init>"){track(className,methods[i]);
  40. }
  41.         i = ToInt(i+1);
  42.     }
  43. }
复制代码


如果对cheetah语法不熟悉,那么可以到https://github.com/Wker666/Demo中了解cheetah的详细语法,里面含有500+的渗透测试脚本可供学习。
最终我们白盒审计可以打印出如下内容:

可以看到我们对没有过滤的调用链都进行了高亮显示,对有过滤的用红色进行显示。
Step4 编写黑盒Fuzzer测试代码​​
SQL注入检测函数我们可以使用简单 or 1=1与or 1=2进行判断,为什么不能用and呢?因为我们没有默认值,所以需要通过or进行判断。
  1. function judgeSQLI(api){
  2.     res = HttpGet(api,"");
  3.     res1 = HttpGet(api."%27%20or%201=1--+","");
  4.     if(GetStrLength(res1[0]) != GetStrLength(res[0])){
  5.         res2 = HttpGet(api."%27%20or%202=1--+","");
  6.         if(GetStrLength(res2[0]) == GetStrLength(res[0])){
  7.             return 1;
  8.         }
  9.     }
  10.     return 0;
  11. }
复制代码


如果你想要了解更多的cheetah编写sql注入的代码,可以看cheetah的GitHub,里面是有一个非常完整的SQL注入脚本代码的。
组成测试链接因为Spring中使用大量注解进行设置,对于注解的解析,wJa提供了获取注解的方法。
  •         GetClassAnnotation获取类注解
  •         GetClassMethodAnnotation获取方法上的注解
  •         GetClassMethodArgAnnotation获取参数上的注解
  •         GetAnnotationArgListValue获取注解中list数据
  •         GetAnnotationArgSingValue获取注解中的数据

通过上述的注解方法我们可以构造完整的测试链接,当然我们可以编写一个参数进行解析注解参数数据。
  1. function getSpringAnnotationValue(an){
  2.     anSize = GetArrayNum(an);
  3.     i = 0;
  4.     while(i < anSize){
  5.         if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/RequestMapping"){
  6.             allValue = GetAnnotationArgListValue(an[i],"value");
  7.             return allValue[0];
  8.         }
  9.         if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/PostMapping"){
  10.             allValue = GetAnnotationArgListValue(an[i],"value");
  11.             return allValue[0];
  12.         }
  13.         if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/RequestParam"){
  14.             allValue = GetAnnotationArgSingValue(an[i],"value");
  15.             return allValue;
  16.         }

  17.         i = ToInt(i + 1);
  18.     }
  19.     return "";
  20. }
复制代码


根据Spring的的注解,我们得到路径中的某一个值。
因为我们已经开启了8080端口的javaWeb服务,所以可以直接进行拼接组合成测试链接。
Step5 白盒+黑盒 自动化测试有了白盒测试和黑盒测试的代码部分,我们可以进行组装拼接,当白盒测试代码发现没有过滤函数,并且最终进入了危险函数,那么我们就启动黑盒测试进行真正意义上的Fuzzer。
这里附带完整的 白盒+黑盒 自动化测试脚本。
  1. #define filter1=String.valueOf(.*?)
  2. #define filter2=Integer.valueOf(.*?)
  3. function filter(sentence){
  4.     a = StrRe(sentence,filter1);
  5.     if(GetArrayNum(a) != 0){return 0;}
  6.     a = StrRe(sentence,filter2);
  7.     if(GetArrayNum(a) != 0){return 0;}
  8.     return 1;
  9. }
  10. function judgeSQLI(api){
  11.     res = HttpGet(api,"");
  12.     res1 = HttpGet(api."%27%20or%201=1--+","");
  13.     if(GetStrLength(res1[0]) != GetStrLength(res[0])){
  14.         res2 = HttpGet(api."%27%20or%202=1--+","");
  15.         if(GetStrLength(res2[0]) == GetStrLength(res[0])){
  16.             return 1;
  17.         }
  18.     }
  19.     return 0;
  20. }
  21. function track(className,methodName,url){
  22.     array allNode;
  23.     allNode = TrackVarIntoFun(className,methodName,0,"org/springframework/jdbc/core/JdbcTemplate","query",0);
  24.     size = GetArrayNum(allNode);
  25.     if(StrFindStr(GetJavaSentence(allNode[ToInt(size-1)]),".query(",0) != "-1"){
  26.         i = 0;
  27.         print(methodName."白盒测试调用链跟踪:");
  28.         cc = 7;
  29.         cs = 1;
  30.         while(i < size){
  31.             sentence = GetJavaSentence(allNode[i]);
  32.             noSan = filter(sentence);
  33.             if(noSan == 0){cc = 5;cs = 5;}
  34.             if(i == ToInt((size-1))){
  35.                 if(cc != 5){cs = 2;cc = 3;}
  36.             }else{}
  37.             if(noSan == 0){
  38.                 printcolor("[-]",6);printcolor("想办法绕过此类:",4);
  39.             }else{
  40.                 printcolor("[+]",1);
  41.             }
  42.             printcolor(GetClassName(GetNodeClassName(allNode[i]))."   ",cc);
  43.             printcolor(sentence.StrRN(),cs);
  44.             i = ToInt(i+1);
  45.         }
  46.         if(cc != 5){
  47.             printcolor("白盒测试发现此调用链可能存在漏洞,生成测试链接进行黑盒测试".StrRN(),7);
  48.             an = GetClassMethodAnnotation(className,methodName);
  49.             arg_an = GetClassMethodArgAnnotation(className,methodName,0);
  50.             argName = getSpringAnnotationValue(arg_an);
  51.             if(argName != ""){
  52.                 api = url.getSpringAnnotationValue(an)."?".argName."=Wker";
  53.                 if(judgeSQLI(api) == 1){
  54.                     printcolor("[+]生成测试链接:".api."   测试存在SQL注入漏洞!".StrRN(),3);
  55.                 }else{
  56.                     printcolor("[-]生成测试链接:".api."   测试不存在SQL注入漏洞!请自行测试。".StrRN(),5);
  57.                 }
  58.             }else{
  59.                 printcolor("测试链接生成失败,error:未找到参数入口!".StrRN(),5);
  60.             }
  61.         }
  62.     }
  63.     return 0;
  64. }
  65. function getSpringAnnotationValue(an){
  66.     anSize = GetArrayNum(an);
  67.     i = 0;
  68.     while(i < anSize){
  69.         if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/RequestMapping"){
  70.             allValue = GetAnnotationArgListValue(an[i],"value");
  71.             return allValue[0];
  72.         }
  73.         if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/PostMapping"){
  74.             allValue = GetAnnotationArgListValue(an[i],"value");
  75.             return allValue[0];
  76.         }
  77.         if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/RequestParam"){
  78.             allValue = GetAnnotationArgSingValue(an[i],"value");
  79.             return allValue;
  80.         }

  81.         i = ToInt(i + 1);
  82.     }
  83.     return "";
  84. }
  85. function main(args){
  86.     className = "com/l4yn3/microserviceseclab/controller/IndexController";
  87.     an = GetClassAnnotation(className);
  88.     classPath = "http://127.0.0.1:8080".getSpringAnnotationValue(an);
  89.     methods = GetAllMethodName(className);
  90.     size = GetArrayNum(methods);
  91.     i = 0;
  92.     while(i < size){
  93.         if(methods[i] != "<init>"){track(className,methods[i],classPath);
  94. }
  95.         i = ToInt(i+1);
  96.     }
  97. }
复制代码


让我们运行一下,看一下最终执行的结果:

可以看到我们最终找到了两处白盒与黑盒完全符合要求的调用链,这样子的调用链是有极大可能存在漏洞的。
优化在上述的代码脚本中,其实还是有可优化的的地方的,当程序项目比较大时,类就无法通过手动输入进行测试,所以可以通过GetAllClassName类进行测试,所以可以进行如此优化。
  1. function SQLTrack(className){
  2.     an = GetClassAnnotation(className);
  3.     classPath = "http://127.0.0.1:8080".getSpringAnnotationValue(an);
  4.     methods = GetAllMethodName(className);
  5.     size = GetArrayNum(methods);
  6.     i = 0;
  7.     while(i < size){
  8.         if(methods[i] != "<init>"){track(className,methods[i],classPath);}
  9.         i = ToInt(i+1);
  10.     }
  11.     return 0;
  12. }
  13. function main(args){
  14.     allClass = GetAllClassName();
  15.     size = GetArrayNum(allClass);
  16.     i = 0;
  17.     while(i < size){
  18.         an = GetClassAnnotation(allClass[i]);
  19.         p = getSpringAnnotationValue(an);
  20.         if(p != ""){
  21.             SQLTrack(allClass[i]);
  22.         }
  23.         i = ToInt(i + 1);
  24.     }

  25. }
复制代码


而且也可以观察到,脚本已经非常的大了,所以有一点臃肿了,之后我会将Spring的常用函数全部编写成工具文件,直接使用#include就可以直接包含进来常用的方法。
更复杂的调用链这里我们更换一个靶场,使用一个朋友的靶场,下面是Spring的项目结构:

相同的,我们同样运行之前写好的SQL注入漏洞脚本,我们可以看到如下的结果:

仔细观察调用链你会发现一件很有意思的事情。
首先先手动分析一下这条路径的真正执行流:
return this.sqliDao.addUser(name,age);
这里注意sqliDao这个属性变量,当你看这个变量类型时,你会发现他是一个接口:
  1. private final SQLIDao sqliDao;

  2. //_       ____                ____                                  _ __
  3. //| |     / / /_____  _____   / __ \___  _________  ____ ___  ____  (_) /__
  4. //| | /| / / //_/ _ \/ ___/  / / / / _ \/ ___/ __ \/ __ `__ \/ __ \/ / / _ \
  5. //| |/ |/ / ,< /  __/ /     / /_/ /  __/ /__/ /_/ / / / / / / /_/ / / /  __/
  6. //|__/|__/_/|_|\___/_/     /_____/\___/\___/\____/_/ /_/ /_/ .___/_/_/\___/
  7. //                                                        /_/
  8. package org.sec.cidemo.dao;
  9. public interface SQLIDao{
  10.     public abstract List selectUser(){
  11.     }
  12.     public abstract String addUser(){
  13.     }

  14. }
复制代码


但最终为何我们还是可以跟踪出真正的调用链呢?
实际上wJa会帮我们进行特定于java的分析,并且可以自动分析子类对象和实现类是否进入危险函数。
看wJa的调用链第三条可以看到,当前处于:SQLIDaoImpl.selectUser
跳转到此方法:
  1. public class SQLIDaoImpl implements SQLIDao
  2. ...
  3. public List selectUser(String name){
  4.         String sql;
  5.         List users;
  6.         System.out.println(name);
  7.         sql = new StringBuilder().append("select * from t_user where name='").append(name).append("'").toString();
  8.         users = this.jdbcTemplate.query(sql,new BeanPropertyRowMapper(User.class));
  9.         return users;
  10.     }
  11. ...
复制代码


发现正是这个实现类存在注入漏洞。
并且仔细观察可以看到,wJa还可以帮助我们进行变量转换跟踪。
其实实际上jdbc最终调用的是sql这个变量,并不是name,wJa可以自动追加追踪接受追踪变量的变量。
SSRF漏洞分析我们这里主要跟踪SSRF漏洞,相比于SQL注入漏洞,我们要对目标进入函数和黑盒测试代码进行修改。
  1. url = new URL(data);
  2. con = url.openConnection();
  3. con.setRequestMethod("GET");
  4. con.setRequestProperty("User-Agent","Mozilla/5.0");
  5. in = new BufferedReader(new InputStreamReader(con.getInputStream()));
复制代码


这里可以通过进入BufferedReader的构造方法来判断是否是SSRF,但是更安全的是如果路径存在URL类,那么就可以基本判断是存在SSRF漏洞。
对于黑盒测试代码可以通过传递百度的地址进行关键字判断。
  1. function judgeSSRF(api){
  2.     res = HttpGet(api."http://www.baidu.com","");
  3.     if(StrFindStr(res[0],"//www.baidu.com/",0) != "-1"){
  4.         return 1;
  5.     }
  6.     return 0;
  7. }
复制代码


其余的代码都是进行小幅度更改,但是需要注意的是,构造函数是<init>。
最终可以得到如下:

~工作顺利


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-23 15:22 , Processed in 0.013597 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表