wJa实战白盒审计华夏ERP

wJa实战操作

经过一段时间的迭代,大家提出的问题我也进行了修改,wJa已经可以步入实战了,我将会用几篇文章来讲解对一个比较成熟的java web项目的自动化审计。

由于黑盒测试的普适性,所以这里不将会引入之前文章白盒+黑盒的方式,而是只进行白盒审计。

目标项目:GitHub - jishenghua/jshERP: 华夏ERP基于SpringBoot框架和SaaS模式,立志为中小企业提供开源好用的ERP软件,目前专注进销存+财务功能。主要模块有零售管理、采购管理、销售管理、仓库管理、财务管理、报表查询、系统管理等。支持预付款、收入支出、仓库调拨、组装拆卸、订单等特色功能。拥有库存状况、出入库统计等报表。同时对角色和权限进行了细致全面控制,精确到每个按钮和菜单。

华夏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的。