wJa实战操作
经过一段时间的迭代,大家提出的问题我也进行了修改,wJa已经可以步入实战了,我将会用几篇文章来讲解对一个比较成熟的java web项目的自动化审计。
由于黑盒测试的普适性,所以这里不将会引入之前文章白盒+黑盒的方式,而是只进行白盒审计。
华夏ERP基于SpringBoot框架和SaaS模式,立志为中小企业提供开源好用的ERP软件,目前专注进销存+财务功能。主要模块有零售管理、采购管理、销售管理、仓库管理、财务管理、报表查询、系统管理等。支持预付款、收入支出、仓库调拨、组装拆卸、订单等特色功能。拥有库存状况、出入库统计等报表。同时对角色和权限进行了细致全面控制,精确到每个按钮和菜单。
接近三百个类文件,四五十个xml文件,如果手动跟踪所有的文件将会是一个不小的工作量。
SQL注入
简单浏览一下pom.xml,发现使用的是Mybits的框架,这个框架目前还是比较热门的一个框架,主要用作数据库执行的相关内容。
使用的是xml的方式进行配置。
在Spring-Boot的配置文件中找到下面的配置:mybatis-plus.mapper-locations=classpath:./mapper_xml/*.xml
找到对应的目录发现是许多xml文件,打开一个可以观察到:
<mapper namespace="com.jsh.erp.datasource.mappers.FunctionMapperEx">
<select id="selectByConditionFunction" parameterType="com.jsh.erp.datasource.entities.FunctionExample" resultMap="com.jsh.erp.datasource.mappers.FunctionMapper.BaseResultMap">
select *
FROM jsh_function
where 1=1
<if test="name != null">
<bind name="bindName" value="'%'+name+'%'"/>
and name like #{bindName}
</if>
<if test="type != null">
and type=#{type}
</if>
and ifnull(delete_flag,'0') !='1'
order by sort asc
<if test="offset != null and rows != null">
limit #{offset},#{rows}
</if>
</select>
.......
namespace对应着类,子节点对应着类型,例如select就是查询,id属性对应着方法名,可以跟进看一下:
这里就不能通过之前的那种指定目标类和函数的方式进行追踪,因为这个类是一个动态的!
获取所有mybatis绑定类和方法
这里只获取select操作,update等暂时不输出了(select的就已经很多了)。
wJa提供了获取文件名称和内容的操作,并且提供了方便的xml操作库,所以解析xml文件是比较方便的操作。
获取所有xml文件名
function getAllMapperXmlFileNames(){
return GetFileName("BOOT-INF/classes/mapper_xml/");
}
通过xml的目录获得所有的mybatis的配置文件名称。
获取所有class名称
function getClassName(root){
attributes = GetElementAttributes(root);
attributeSize = GetArrayNum(attributes);
j = 0;
while(j < attributeSize){
if(GetAttributeName(attributes[j]) == "namespace"){
return GetAttributeText(attributes[j]);
}
j = ToInt(j + 1);
}
return "";
}
需要传入根节点对象,根节点可以通过GetXMLRoot
支持库函数获得,得到节点的属性,遍历,如果属性名称是namespace,则返回对应的值。
获取所有select方法
function getClassMethodName(root){
array res;
childs = GetElementChilds(root);
childSize = GetArrayNum(childs);
i = 0;
while(i < childSize){
if(GetElementName(childs[i]) == "select"){
attributes = GetElementAttributes(childs[i]);
attributeSize = GetArrayNum(attributes);
j = 0;
while(j < attributeSize){
if(GetAttributeName(attributes[j]) == "id"){
ArrayAddEle(res,GetAttributeText(attributes[j]));
}
j = ToInt(j + 1);
}
}
i = ToInt(i + 1);
}
return res;
}
得到根节点的子节点,遍历子节点的名称为select的属性,看一下是否是id,是则加入数组。
修改之前SQL注入代码
由于之前通过allNode = TrackVarIntoFun(className,methodName,argIndex,"java/sql/Statement","executeQuery",0,1);
存在确定的类和方法名,但是这里不能确定,所以目标类和目标函数需要外部传入。
目标函数我们已经通过xml文件获取到了,现在就是进行传入调用,修改之前的代码:
#include SpringBoot/utils/SpringUtils.cheetah
#include SpringBoot/filiter/filiter.cheetah
#include SpringBoot/parse/mybatis.cheetah
function trackMybatisSQL(className,methodName,url,argIndex,destClass,destMethod){
array allNode;
allNode = TrackVarIntoFun(className,methodName,argIndex,destClass,destMethod,0,1);
size = GetArrayNum(allNode);
if(ToInt(size-1) < 0){return 0;}
i = 0;
print(methodName.":Mybatis类型SQLI:");
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("the link maybe sink:".StrRN(),3);
printcolor(" sourceClassName:".className.StrRN(),3);
printcolor(" sourceMethodName:".methodName.StrRN(),3);
printcolor(" MybitsMapperClass:".destClass.StrRN(),3);
printcolor(" MybitsMethod:".destMethod.StrRN(),3);
}
return 0;
}
function MybatisSQLTrack(className){
an = GetClassAnnotation(className);
classPath = "http://127.0.0.1:8080".getSpringAnnotationValue(an);
methods = GetAllMethodName(className);
size = GetArrayNum(methods);
mybatisXmls = getAllMapperXmlFileNames();
xmlSize = GetArrayNum(mybatisXmls);
xmlIndex = 0;
while(xmlIndex < xmlSize){
root = GetXMLRoot(GetFileContent(mybatisXmls[xmlIndex]));
mybatisClassName = StrReplace(getClassName(root),"\.","/");
mybatisMethodNames = getClassMethodName(root);
mybatisMethodNameSize = GetArrayNum(mybatisMethodNames);
mybatisMethodIndex = 0;
while(mybatisMethodIndex < mybatisMethodNameSize){
curMybatisMethodName = mybatisMethodNames[mybatisMethodIndex];
//mybatisע注入
i = 0;
while(i < size){
argCnt = GetMethodArgCnt(className,methods[i]);
j = 0;
while(j < argCnt){
if(methods[i] != "<init>"){
trackMybatisSQL(className,methods[i],classPath,j,mybatisClassName,curMybatisMethodName);
}
j = ToInt(j+1);
}
i = ToInt(i+1);
}
mybatisMethodIndex = ToInt(mybatisMethodIndex + 1);
}
xmlIndex = ToInt(xmlIndex + 1);
}
return 0;
}
代码非常简单,只是比之前多传入了目标类和函数,其余的操作都是相同的,需要注意的是,xml中使用的是.,这里需要替换为/,并且替换函数中的.需要转义!
其余的代码都是类似的,可以看一下结果:
输出的内容特别多,这里就不拷贝出来了,因为在操作过程有许多无关操作
这里打印出一条存在的调用链的细节:
ResourceController.getList parameterMap.put("search",search)
ResourceController.getList this.configResourceManager.select(apiName,parameterMap)
this.container.getCommonQuery(apiName).select(parameterMap)
UserComponent.select this.getUserList(map)
UserComponent.getUserList this.userService.select(userName,loginName,QueryUtils.offset(map),QueryUtils.rows(map))
UserService.select this.userMapperEx.selectByConditionUser(userName,loginName,Integer.valueOf(offset),Integer.valueOf(rows))
新版本的wJa已经支持对于map映射的追踪,并且解决了递归函数无法跳出的问题。
目前存在的一个问题就是特别多无关信息的打印,下一个版本进行解决。
先写到这里,这个web应用很多bug的。