查看原文
其他

记对某Spring项目代码审计

听风安全 2023-11-28

The following article is from 顶级玩家安全团队 Author 顶级玩家团队

免责声明
由于传播、利用本公众号听风安全所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号听风安全及作者不为承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!

公众号现在只对常读和星标的公众号才展示大图推送,

建议大家把听风安全设为星标,否则可能就看不到啦!

----------------------------------------------------------------------

一. 前言

之前实战遇到的一个系统,尽管开发者使用了一些安全校验过滤的措施,然而系统本身仍然存在不少漏洞,遂找到源码审计了一番,特此记录下。文章中所有漏洞均已提交CNVD。

二. 审计前准备

首先确认技术栈:Spring+servlet+mybatis+redis

目录结构:

检查有哪些过滤器,后面验证漏洞的时候需要注意是否能bypass:

1)权限过滤器

在CommandFilter类中实现,看下逻辑,依次实现三个功能:

1.获取实例化bean,读取配置


定义了一个接口白名单,凡是白名单中的接口请求,都会直接放行,不再进行后续验证

2.判断校验csrf token是否存在且不为空,通过代码可以看出这个token是base64编码的,而且过滤器只对其进行了简单的解码后格式验证就直接放行请求,再没有做更多的验证。

3.最后根据Action参数使用if嵌套判断用户是否拥有执行对应操作的权限,如果该操作不需要鉴权或者用户拥有权限就放行请求,反之则返回203错误。

2)防sql注入过滤器

进入实现类SqlInjectionFilter,定义了两个字符串数组injectionWords和punctuations

使用isSqlInjection()方法判断请求是否为sql注入,我们跟进这个方法:

程序首先获取所有请求参数中的值,存入到名为params的collection集合中,使用迭代器循环读取它们,然后转储到var5这个字符串数组中,在for循环中使用hasInjectionWords()方法逐个逐个判断请求参数的值是否包含sql注入关键字,我们继续跟进:

首先将待检查的字符转成小写,用tmpParam变量接收,然后将其传入replacePunctuation()方法判断是否包含上述提到的特殊符号,如等号=、大于小于符号><等,如果存在,则将特殊符号替换为空格后返回,返回的值用tmp变量接收

随后,使用split()方法,以空格作为切割符,分割出一个字符串数组,最后判断这个数组中的字符串有没有非法关键字,若有则返回true,确认是sql注入。

简单总结一下这个过滤器的工作机制:读取所有请求参数的值,挨个判断是否包含特殊符号,包含的话先用空格过滤掉,再判断过滤后的值是否包含非法关键字。

3)文件过滤器

在FileFilter类中实现,看关键代码,获取实例化的bean,拼接好正则表达式,每次访问/SystemFile/路径下的文件都会尝试匹配,如果匹配到是恶意后缀直接返回403错误。

二. 审计

组件漏洞

进入pom.xml,简单看一下是否使用了存在已知漏洞的组件:

并不满足log4j2的利用版本,忽略。

FasterXML Jackson-databind的反序列化,在commonlib类下多次使用了这个包【等待后续分析】

手工审计

进入Controller层,我们还是着重挖掘文件上传、SQL注入这类危害等级高一点的漏洞。

1)逻辑漏洞挖掘

·第一次尝试

首先从登录接口入手,定位到RegisterLogin.do的代码,看一下系统是如何处理登录逻辑的获取用户的输入

对比用户输入的验证码和session中的验证码字符串是否符合

随后判断用户名密码,代码第112行,发现系统存在硬编码的用户名和密码,如果用户输入了对应账号密码,系统会将userId变量赋值为1,否则才会去数据库中检查

若账号密码正确则返回相应用户的userId,而如果userId大于0的话允许其登录

我们尝试利用这个账号登录

可以登录,且由于admin账户的userId为1,所以直接获取到admin账号权限

成功获得后门账号一枚

·第二次尝试

继续翻看代码,在Home.do接口,发现一个获取用户信息的方法

代码第64行读取cookie,跟进分析getCookieInfo()方法

读取某个cookie字段,将其值进行base64解码

随后使用for循环加if判断读取需要的参数,此例中是UID参数

我们将登陆后的cookie进行手动base64解码,可以得到这样的字符串

回到接口代码,UID参数对应账号的userId字段,程序直接根据cookie中的UID参数就向数据库请求用户数据而没有再做过多的验证


Sql语句直接使用了select * 且没有对返回结果中的password字段进行屏蔽或过滤,导致密码hash泄露

猜测此接口可能存在未授权访问,到权限过滤器搜索,果不其然,并没有定义访问该接口所需的权限,导致未授权攻击者可获取任意账号的密码hash

手动构造payload就可以获得系统内任意用户的信息

编写exp即可批量获取账号信息

密码采用md5进行hash,彩虹表可破解

2)任意文件下载/读取漏洞挖掘

通过全局搜索download、fileutil等字样定位到文件操作有关的方法

代码第108行,直接将用户输入的路径与“/SystemFile”进行拼接,未作任何校验和过滤,导致我们可以使用“../”穿越到上层目录,实现任意文件的读取。Poc:

直接读取web.xml:

直接读取系统配置:

同时我们发现该接口在白名单中,未授权用户也可以直接读取服务器上的任意文件,危害更大。

这里我们删掉Token和Cookie后发包测试,发现依然能成功读取到,验证成功:

同样的还有DeviceFileUpload.do接口的Download操作

该接口在请求白名单中,未授权用户也可以直接利用。

以及Service.do接口的Download操作,都存在相同的漏洞。

3)Sql注入漏洞挖掘

系统使用了mybatis框架,随着mybatis这类ORM框架的使用,sql注入的可能性也在慢慢降低,然而在mybatis使用不当的情况下,仍然可能出现漏洞。

使用小工具快速扫一下

工具由于是基于正则匹配的,容易出现误报,还是需要我们手工验证一下

这里工具扫到第80行有漏洞,但是查看后发现该语句被注释掉,这个漏洞点也被修复了

功夫不负有心人,找到一处在in后使用${}查询的语句

根据id找到对应


4)文件上传挖掘

·第一次尝试

通过全局搜索upload、MultipartFile等字样,定位文件上传点,

找到一个,但是对后缀名进行了限制,仅允许上传Excel表格文件

·第二次尝试

换一个,代码第341行,只要后缀名不为jsp就可以成功上传,一般看到这里可能首先想到的就是Windows不区分大小写的特性,使用大小写混写绕过。

然而与php不同的是,如果jsp后缀这三个字母不均为小写的话,tomcat不会对其解析。

如图,将shell.jsp文件的字母p改为大写后无法被tomcat解析:

除了大小写混写,我们还可以使用空格绕过或者点号绕过,代码要求后缀名不为jsp即可,那我们构造文件名shell.jsp.进行上传

响应包显示Code是200,上传结果却失败了,回到代码

如果后缀名错误或者上传过程捕获到错误,响应Code都应该是202才对

再看一下上传目录,我们上传的文件全被删除了

代码中只有第344行出现了文件删除的操作,判断条件是result结果为false或者code等于202,结合响应包中Code为200我们可以知道是第336行的代码执行返回了false,我们跟进这个setMaintainPath()方法

分析代码发现系统会起一个web service client服务,当上传文件后,系统会作为client端调用远程web service服务端,将上传文件保存路径发送到远程服务器,根据配置文件中的server_address字段,wsdl文档部署在本地的8210端口

尝试访问这个文档地址时却发现404报错了,说明创建失败了

远程web service也无法正常连接

因此无论上传什么文件最终都会都会失败,这个接口也无法利用。

·第三次尝试

前两次出师不利,这次我们换个思路,白盒加黑盒审计,根据请求的接口去审计对应接口的代码:

在后台找到一个文件管理的模块,进行一次正常的文件上传试试

通过burp抓包发现一次完整的上传发送了多个请求包

前三个GET请求都是在判断是否存在同名文件以及文件路径是否过长,返回都是正常的,我们可以直接忽略掉,只关注Upload和UploadFile这两个方法。

选择上传文件后请求了Upload方法

代码第578行,生成了一段随机字符串作为暂时文件名tempName,随后使用checkFileType函数对真实文件名realName进行检查,跟进这个方法

定义了一个非法后缀名List,能禁的后缀名差不多禁完了。

检查通过后,在代码第598行生成暂时保存的完整路径,以我们上传的111.txt为例

这个文件会保存到/Plugin/FileManage/Temp/目录下的165607238767794txt文件中

点击“确认”按钮后调用UploadFile方法

可以看到,程序仍然使用了checkFileType()方法判断请求中FileName参数,然后使用addFile()方法,根据上一步文件上传过程中生成的tempName参数找到对应文件,将这个文件移出暂存路径,重命名为FileName参数值,随后移动到SystemPath路径下,最后将文件属性写入数据库方便查询。

看似滴水不漏,然而由于UploadFile操作中对FileName的过滤不严导致我们仍然可以成功上传webshell

绕过思路如下:

选择上传文件时,上传一个文件内容为webshell,文件名为合法后缀名的文件,目的是绕过checkFileType()方法的后缀检查

通过响应包看到webshell上传成功了,随后点击“确定”,系统调用UploadFile接口

此时抓包修改FileName参数,在其后面添加一个点,checkFileType()方法会判断目标文件的后缀为空,继续执行后面的操作,将我们之前上传的文件移动并重命名为shell.jsp.,又由于Windows特性,会自动抹去不符合规则符号,于是最终这个文件名为shell.jsp,是一个正常的webshell了(加::$DATA等绕过技巧同样适用,但是由于程序使用了trim()方法去除后置空格,所以加空格绕过会失败)。

从管理面板上看到入库的数据仍然是shell.jsp.

而实际物理文件却是shell.jsp


尝试访问,结果忘了还有文件过滤器这玩意儿了,寄!

不甘心,尝试把这个webshell移动到某个不受文件过滤器检查的目录去,想到Java可以使用File类的renameTo()方法,以及commons.io.FileUtils#moveFile函数移动文件,于是全局检索对应关键字来寻找可用接口。

通过全局搜索moveFile关键字,找到一处移动文件的接口,

是updateFile()方法的实现类,再搜索这个关键字,定位到controller层某个更新文件的接口

初略看一下代码,根据已上传文件的id找到对应物理文件,将其移动到SYSTEMPATH目录下去

然而这个SYSTEMPATH仍然在/SystemFile/路径下,也就是说移动后访问仍然会被过滤器拦截,这条路至此也走不通了。

不可错过的往期推荐哦


记一次看似简单到处是坑的入口打点

无人机mavlink中间人攻击

APT是如何杜绝软件包被篡改的

瑞数WAF保护站点的渗透测试

利用sqlserver agent job实现权限维持

商城优惠券处的漏洞挖掘技巧

NPS反制之绕过登陆验证

区分Spring与Struts2框架的几种新方法

SRC挖掘葵花宝典

SRC漏洞挖掘之看不见的羊毛

点击下方名片,关注我们

觉得内容不错,就点下“”和“在看

如果不想错过新的内容推送可以设为星标
继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存