wJa丨Java闭源项目的自动化测试「建议收藏」

wJa丨Java闭源项目的自动化测试「建议收藏」本文是i春秋论坛作家「Wker」表哥分享的技术文章,文章旨在为大家提供更多的学习方法与技能技巧,文章仅供学习参考。wJa支持反编译Java生成的jar包文件,整理成语法树,根据调用链进行污点分析

大家好,又见面了,我是你们的朋友全栈君。

 
wJa丨Java闭源项目的自动化测试「建议收藏」

 

本文是 i 春秋论坛作家「Wker」表哥分享的技术文章,文章旨在为大家提供更多的学习方法与技能技巧,文章仅供学习参考。

wJa支持反编译Java生成的jar包文件,整理成语法树,根据调用链进行污点分析,通过cheetah脚本语言编写测试脚本,确定可能存在的漏洞调用链,生成测试链接,进行fuzzer测试。

下面对网络上的一个Spring靶场进行SQL注入的分析测试。

环境准备

1、测试靶场

2、Java运行环境

3、wJa

wJa分析流程

解析jar包

解析class文件结构

反编译得到AST

优化AST

生成Java代码

编写调用链追踪

过滤函数剪枝

黑盒测试Fuzzer

运行靶场

搭建好靶场环境,这里使用的靶场是在网上寻找到的,里面有创建数据库的脚本文件,环境一切准备就绪后。

运行靶场,使用命令跑起jar包:java -jars shootingRange.jar



wJa丨Java闭源项目的自动化测试

 

使用wJa打开jar文件

使用命令:java -jar wJa.jar运行起程序,程序运行之后会要求选择待分析的jar程序,这里选择shootingRange.jar。

起来之后可以看到程序的主界面了。



wJa丨Java闭源项目的自动化测试

 

左侧部分:

Decompile:对jar包反编译的java资源管理器。

cheetahLanguage:脚本管理器,包含支持库介绍,以及编写好的cheetah脚本。

中间部分:

Decompile:对jar包反编译的java代码显示部分。

cheetahLanguage:编写cheetah脚本代码,运行测试。

界面相对比较简单。

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



wJa丨Java闭源项目的自动化测试

可以看到control层都是提供对外开放的接口,所以我们可以确定这是入口类,所以我们可以将其确定为入口点。

从其中的一个入口点(one)根据一步步的调用追踪,我们可以得到如下调用链:

indexLogic.getStudent(username);
indexDb.getStudent(username);
sql = "select * from students where username like '%" + username + "%'";
jdbcTemplate.query(sql, ROW_MAPPER);

  

最终进入的危险函数:

wJa丨Java闭源项目的自动化测试

 

当然,这是我们手动跟踪的,但是如何使用工具自动帮助我们进行追踪呢?

编写白盒污点跟踪代码

污点分析

污点分析可以抽象成一个三元组〈sources, sinks, sanitizers〉的形式, 其中, source即污点源, 代表直接引入不受信任的数据或者机密数据到系统中;

sink即污点汇聚点, 代表直接产生安全敏感操作 (违反数据完整性) 或者泄露隐私数据到外界 (违反数据保密性);

sanitizer即无害处理, 代表通过数据加密或者移除危害操作等手段使数据传播不再对软件系统的信息安全产生危害。

污点分析就是分析程序中由污点源引入的数据是否能够不经无害处理, 而直接传播到污点汇聚点。如果不能, 说明系统是信息流安全的; 否则, 说明系统产生了隐私数据泄露或危险数据操作等安全问题。

对于SQL注入这种漏洞,可以将污点分析的三元组实例化为下面三组内容:

source:Spring的接口入口点的参数;

sink:jdbc的query方法;

sanitizer:类似于Integer.value此类方法。

代码编写

在检测类似于SQL注入类漏洞,我们需要的是跟踪调用链,所以需要使用的是TrackVarIntoFun函数。

TrackVarIntoFun

参数1:起始类
参数2:起始方法
参数3:起始方法参数下标
参数4:目标方法的类
参数5:目标方法
参数6:目标方法的参数下标
返回值:执行流node数组

1、起始类是我们需要分析的类,这里是
com/l4yn3/microserviceseclab/controller/IndexController

2、起始方法是入口方法,也是这个类下面的所有接口方法

3、起始方法参数下标是要检测的入口参数下标

4、目标方法类是jdbc,这里是
org/springframework/jdbc/core/JdbcTemplate

5、目标方法是query,jdbc查询数据的方法

6、目标方法的参数下标是第一个参数,sql语句

返回值是一个node执行流数组,node包含次node所在的class和node的AST。

我们设置开头的包名,那如何获取所有的方法名呢?

GetAllMethodName可以获取所有的方法名称,但是这里有一个注意的地方是,如果方法名是<init>和<cinit>的需要跳过,因为这两个方法是构造方法和静态代码块。

node中的AST可以通过GetJavaSentence方法得到对应生成的java代码。

还有一点需要注意的是,TrackVarIntoFun方法只是跟踪流,只是到目标方法就停止,如果没有到目标方法就停止了那么也是会返回所有的执行流,所以这里我们需要自己进行过滤。

所以现在的思路已经完成,通过GetAllMethodName获取所有的方法,然后对方法中的第一个参数进行追踪,查看其最终流向的是否是jdbc,并且判断流动过程中是否有类似于Integer.value()方法的存在,如果不存在,那噩梦非常可能就是一条可以被污染的链条。

最终我们可以编写出如下代码:

#define filter1=String.valueOf(.*?)
#define filter2=Integer.valueOf(.*?)
function filter(sentence){
    a = StrRe(sentence,filter1);
    if(GetArrayNum(a) != 0){return 0;}
    a = StrRe(sentence,filter2);
    if(GetArrayNum(a) != 0){return 0;}
    return 1;
}
function track(className,methodName){
    array allNode;
    allNode = TrackVarIntoFun(className,methodName,0,"org/springframework/jdbc/core/JdbcTemplate","query",0);
    size = GetArrayNum(allNode);
    if(StrFindStr(GetJavaSentence(allNode[ToInt(size-1)]),".query(",0) != "-1"){
        i = 0;
        print(methodName."参数流动:");
        cc = 7;
        cs = 1;
        while(i < size){
            sentence = GetJavaSentence(allNode[i]);
            if(filter(sentence) == 0){cc = 5;cs = 5;printcolor("想办法绕过此类:",4);}
            if(i == ToInt((size-1))){
                if(cc != 5){cs = 2;cc = 3;}
            }else{}
            if(cc == 5){printcolor("[-]",6);}else{printcolor("[+]",1);}
            printcolor(GetClassName(GetNodeClassName(allNode[i]))."   ",cc);
            printcolor(sentence.StrRN(),cs);
            i = ToInt(i+1);
        }
    }
    return 0;
}
function main(args){
    className = "com/l4yn3/microserviceseclab/controller/IndexController";
    methods = GetAllMethodName(className);
    size = GetArrayNum(methods);
    i = 0;
    while(i < size){
        if(methods[i] != "<init>"){track(className,methods[i]);
}
        i = ToInt(i+1);
    }
}

  

如果对cheetah语法不熟悉,那么可以到https://github.com/Wker666/Demo中了解cheetah的详细语法,里面含有500+的渗透测试脚本可供学习。

最终我们白盒审计可以打印出如下内容:

wJa丨Java闭源项目的自动化测试

 

可以看到我们对没有过滤的调用链都进行了高亮显示,对有过滤的用红色进行显示。

编写黑盒Fuzzer测试代码

SQL注入检测函数

我们可以使用简单or 1=1与or 1=2进行判断,为什么不能用and呢?因为我们没有默认值,所以需要通过or进行判断。

function judgeSQLI(api){
    res = HttpGet(api,"");
    res1 = HttpGet(api."%27%20or%201=1--+","");
    if(GetStrLength(res1[0]) != GetStrLength(res[0])){
        res2 = HttpGet(api."%27%20or%202=1--+","");
        if(GetStrLength(res2[0]) == GetStrLength(res[0])){
            return 1;
        }
    }
    return 0;
}

 

如果你想要了解更多的cheetah编写sql注入的代码,可以看cheetah的GitHub,里面是有一个非常完整的SQL注入脚本代码的。

组成测试链接

因为Spring中使用大量注解进行设置,对于注解的解析,wJa提供了获取注解的方法。

1、GetClassAnnotation获取类注解

2、GetClassMethodAnnotation获取方法上的注解

3、GetClassMethodArgAnnotation获取参数上的注解

4、GetAnnotationArgListValue获取注解中list数据

5、GetAnnotationArgSingValue获取注解中的数据

通过上述的注解方法我们可以构造完整的测试链接,当然我们可以编写一个参数进行解析注解参数数据。

function getSpringAnnotationValue(an){
    anSize = GetArrayNum(an);
    i = 0;
    while(i < anSize){
        if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/RequestMapping"){
            allValue = GetAnnotationArgListValue(an[i],"value");
            return allValue[0];
        }
        if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/PostMapping"){
            allValue = GetAnnotationArgListValue(an[i],"value");
            return allValue[0];
        }
        if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/RequestParam"){
            allValue = GetAnnotationArgSingValue(an[i],"value");
            return allValue;
        }

        i = ToInt(i + 1);
    }
    return "";
}

根据Spring的的注解,我们得到路径中的某一个值。

因为我们已经开启了8080端口的javaWeb服务,所以可以直接进行拼接组合成测试链接。

白盒+黑盒 自动化测试

有了白盒测试和黑盒测试的代码部分,我们可以进行组装拼接,当白盒测试代码发现没有过滤函数,并且最终进入了危险函数,那么我们就启动黑盒测试进行真正意义上的Fuzzer。

这里附带完整的白盒+黑盒 自动化测试脚本。

#define filter1=String.valueOf(.*?)
#define filter2=Integer.valueOf(.*?)
function filter(sentence){
    a = StrRe(sentence,filter1);
    if(GetArrayNum(a) != 0){return 0;}
    a = StrRe(sentence,filter2);
    if(GetArrayNum(a) != 0){return 0;}
    return 1;
}
function judgeSQLI(api){
    res = HttpGet(api,"");
    res1 = HttpGet(api."%27%20or%201=1--+","");
    if(GetStrLength(res1[0]) != GetStrLength(res[0])){
        res2 = HttpGet(api."%27%20or%202=1--+","");
        if(GetStrLength(res2[0]) == GetStrLength(res[0])){
            return 1;
        }
    }
    return 0;
}
function track(className,methodName,url){
    array allNode;
    allNode = TrackVarIntoFun(className,methodName,0,"org/springframework/jdbc/core/JdbcTemplate","query",0);
    size = GetArrayNum(allNode);
    if(StrFindStr(GetJavaSentence(allNode[ToInt(size-1)]),".query(",0) != "-1"){
        i = 0;
        print(methodName."白盒测试调用链跟踪:");
        cc = 7;
        cs = 1;
        while(i < size){
            sentence = GetJavaSentence(allNode[i]);
            noSan = filter(sentence);
            if(noSan == 0){cc = 5;cs = 5;}
            if(i == ToInt((size-1))){
                if(cc != 5){cs = 2;cc = 3;}
            }else{}
            if(noSan == 0){
                printcolor("[-]",6);printcolor("想办法绕过此类:",4);
            }else{
                printcolor("[+]",1);
            }
            printcolor(GetClassName(GetNodeClassName(allNode[i]))."   ",cc);
            printcolor(sentence.StrRN(),cs);
            i = ToInt(i+1);
        }
        if(cc != 5){
            printcolor("白盒测试发现此调用链可能存在漏洞,生成测试链接进行黑盒测试".StrRN(),7);
            an = GetClassMethodAnnotation(className,methodName);
            arg_an = GetClassMethodArgAnnotation(className,methodName,0);
            argName = getSpringAnnotationValue(arg_an);
            if(argName != ""){
                api = url.getSpringAnnotationValue(an)."?".argName."=Wker";
                if(judgeSQLI(api) == 1){
                    printcolor("[+]生成测试链接:".api."   测试存在SQL注入漏洞!".StrRN(),3);
                }else{
                    printcolor("[-]生成测试链接:".api."   测试不存在SQL注入漏洞!请自行测试。".StrRN(),5);
                }
            }else{
                printcolor("测试链接生成失败,error:未找到参数入口!".StrRN(),5);
            }
        }
    }
    return 0;
}
function getSpringAnnotationValue(an){
    anSize = GetArrayNum(an);
    i = 0;
    while(i < anSize){
        if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/RequestMapping"){
            allValue = GetAnnotationArgListValue(an[i],"value");
            return allValue[0];
        }
        if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/PostMapping"){
            allValue = GetAnnotationArgListValue(an[i],"value");
            return allValue[0];
        }
        if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/RequestParam"){
            allValue = GetAnnotationArgSingValue(an[i],"value");
            return allValue;
        }

        i = ToInt(i + 1);
    }
    return "";
}
function main(args){
    className = "com/l4yn3/microserviceseclab/controller/IndexController";
    an = GetClassAnnotation(className);
    classPath = "http://127.0.0.1:8080".getSpringAnnotationValue(an);
    methods = GetAllMethodName(className);
    size = GetArrayNum(methods);
    i = 0;
    while(i < size){
        if(methods[i] != "<init>"){track(className,methods[i],classPath);
}
        i = ToInt(i+1);
    }
}

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

wJa丨Java闭源项目的自动化测试

 

可以看到我们最终找到了两处白盒与黑盒完全符合要求的调用链,这样子的调用链是有极大可能存在漏洞的。

以上为今天分享的内容,小伙伴们看懂了吗?

 

 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/155696.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • oracle 数据库隔离级别

    oracle 数据库隔离级别[b]事务不同引发的状况:[/b]脏读(Dirtyreads)一个事务读取另一个事务尚未提交的修改时,产生脏读很多数据库允许脏读以避免排它锁的竞争。不可重复读(Nonrepeatablereads)同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发…

  • mybatis缓存问题「建议收藏」

    mybatis缓存问题「建议收藏」目前系统正常使用,突然来个用户注册,可是账号太长,导致数据库没法保存,所以觉得把数据库表的字段改大点,问题解决。但是问题又来了,修改字段长度后系统没有重启,导致查出来的数据为字段没有修改长度之前的那个长度,比如说:字段长度修改前,字段长度为varchar(16),用户账号是;8832226780@qq.com,注册失败,字段修改后,字段长度改为varchar(32),用户账号是;88322

  • Java 构造函数的详解

    Java 构造函数的详解我们人出生的时候,有些人一出生之后再起名字的,但是有些人一旦出生就已经起好名字的。那么我们在java里面怎么在对象一旦创建就赋值呢?1.构造方法的作用:构造方法作用:对对象进行初始化.如图:2.构造函数与普通函数的区别:(1). 一般函数是用于定义对象应该具备的功能。而构造函数定义的是,对象在调用功能之前,在建立时,应该具备的一些内容。也就是对象的初

  • Pytest(11)allure报告「建议收藏」

    Pytest(11)allure报告「建议收藏」前言allure是一个report框架,支持java的Junit/testng等框架,当然也可以支持python的pytest框架,也可以集成到Jenkins上展示高大上的报告界面。mac环境:

  • 使用AjaxFileUpload.js实现文件异步上�

    使用AjaxFileUpload.js实现文件异步上�

  • python分析人口出生率代码_国家统计局居然也能用的上Python?人口数据Python脚本了解一下?…[通俗易懂]

    python分析人口出生率代码_国家统计局居然也能用的上Python?人口数据Python脚本了解一下?…[通俗易懂]原标题:国家统计局居然也能用的上Python?人口数据Python脚本了解一下?通过采集国家统计局“国家数据”网站中提供的中国历年人口数据,并对数据进行可视化的探索,发现了一些有意思和令人深思的现象和趋势。有一些小伙伴后台留言希望公布一下采集“国家数据”网站人口数据的代码,在这里,就将总人口、人口出生率、人口年龄结构和人口平均寿命4项数据的代码公布出来,方便大家学习和使用。文章目录涉及到的模块在这…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号