目录
0x01 Mybatis注入审计 - 若依(Ruoyi)后台管理系统 4.6.0
0x02 文件上传漏洞审计 - Inxedu && Tmall
0x03 文件下载漏洞审计 - 若依(Ruoyi)后台管理系统 4.5.0
希望和各位大佬一起学习,如果文章内容有错请多多指正,谢谢!?
个人博客链接:CH4SER的个人BLOG – Welcome To Ch4ser's Blog
若依(Ruoyi)是一个开源的后台管理系统,可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA。
版本:RuoYi-v4.6.0
项目部署:
?
2.1、明确项目框架及使用组件
首先要做的是明确项目的框架以及有没有用到 Mybatis 组件,当看到 controller、service、mapper 推测项目为 Spring 框架搭建
?
并且,在 External Libraries 看到项目有用到 Mybatis 和 Spring 相关组件,更加确定了我们的想法。
?
2.2、Mapper->Service->Controller->得到Mapping路由信息
我之前的文章提到 Mybatis 下的 SQL 注入有 3 种: order by 注入、搜索框 like 注入、in 之后多个参数的注入
总的来说,要找 Mybatis 注入点只需要全局搜索含有 ${ 的 Mapper 文件即可,全局搜索快捷键:Ctrl+Shift+F
选择跟进 SysDeptMapper.xml,很明显这里存在 in 的注入
?
来到 SysDeptMapper.xml,由 SQL 语句得知是与更新相关的操作,并且得知可控变量为 ${ancestors}。
?
Ctrl+左键点击 updateDeptStatus 跟进到 SysDeptMapper.java 可以看到该 Mapper 方法的声明,看到其传入了一个 SysDept 类。
?
Ctrl+左键点击 SysDept,跟进到该类得知其含有名为 ancestors 的成员变量,于是便知晓了 SysDeptMapper.xml 里可控变量 ${ancestors} 就是 SysDept 类的成员变量。
?
选中 updateDeptStatus - 右键 - Find Usages,在 Unclassified 下面定位到引用 updateDeptStatus 的文件,得知该文件为 SysDeptServiceImpl.java。
?
而 SysDeptServiceImpl.java 是一个 Service 层的文件,如果想要知道漏洞功能点的路由信息要到 Controller 层去找,所以还要继续跟进 updateParentDeptStatus(同样 Find Usages)
?
于是跟进到了 SysDeptServiceImpl.java,很明显这还是 Service 层的文件,同样的方法继续跟进。
最终跟进到了 SysDeptController.java,这才是我们需要的 Controller 层的文件,可以得知以下信息:
?
2.3、漏洞验证 - Ruoyi Mybatis 注入
因为该功能点提交方式为 POST,所以如果直接访问 /system/dept/edit 是不行的,这里根据之前得知的信息,我们可以定位到部门管理的编辑操作。
?
打开 Burp 抓包,填写好编辑的信息并提交,找到 URL 为 /system/dept/edit 的数据包,发送到 Repeater 模块。
?
发现 POST 请求体里没有 ancestors 字段,于是尝试将 POST 请求体替换为以下 POC。
POC:
DeptName=1&DeptId=100&ParentId=12&Status=0&OrderNum=1&ancestors=0)or(extractvalue(1,concat((select user()))));#
在 Response 里观察到 @localhost,说明确实存在注入,漏洞验证成功。
?
Statement
createStatement
PrepareStatement
like '%${
in(${
in (${
select
update
insert
delete
${
order by
setObject(
setInt(
setString(
setSQLXML(
createQuery(
createSQLQuery(
createNativeQuery
.......
项目介绍:因酷时代 Inxedu 在线教育系统 V2.0.6 是一个开源的网校项目,Tmall 是一个模拟天猫购物的项目,在我之前的文章里有提到过(SpringBoot安全&Mybatis注入&Actuator泄露&Swagger自动化 – CH4SER的个人BLOG)
Inxedu V2.0.6 部署:
Tmall 部署:
2.1、审计思路:
寻找并测试文件上传功能点,抓包得到对应路由等信息,从而定位到功能实现的具体代码(代码溯源),然后审计其是否存在漏洞。
2.2、审计流程
登录学员账号 - 个人资料设置 - 个人头像,尝试上传头像同时 BurpSuite 抓包,得到上传头像路由信息为 "/image/gok4","fileType=jpg,gif,png,jpeg"
?
在项目代码中全局搜索 "/image/gok4" 或 "fileType=jpg,gif,png,jpeg",定位到的都是 jsp 或 js 文件,没有太大价值。
实际上这个项目是将处理文件上传的代码封装到了一个 Jar 包然后引用的,一般情况下如果碰到搜不到的情况,就去 pom.xml 里看看有没有声明依赖的 Jar 包,如下:
?
IDEA 自带反编译功能,能够将 Class 文件转换为 Java 文件,所以点进 Jar 包能看到 Java 代码
处理文件上传的代码一般放在 controller 里面,由此定位到 ImageUploadController.class
?
搜索路由 "/gok4",定位到处理文件上传的方法,具体代码如下:
该方法的逻辑如下:
1、检查上传文件的大小,如果超过4M,则返回错误信息。
2、根据fileType参数判断文件类型是否符合要求,如果文件类型不符合或者后缀为jsp,则返回错误信息。
3、根据上传文件的后缀和param参数,确定上传文件的保存路径。
4、创建保存文件的目录(如果目录不存在)。
5、将上传文件保存到指定的路径。
6、返回上传成功的响应信息,包括文件路径和状态码。
7、如果发生异常,记录错误日志,并返回上传失败的错误信息。
?
上图代码中,关键点为 if (fileType.contains(ext) && !"jsp".equals(ext)),即如果文件类型符合且后缀不为 JSP 才进行下一步的上传操作。
跟进 "ext",其来自于 FileUploadUtils.getSuffix(uploadfile.getOriginalFilename()),于是跟进 "getSuffix" 方法,其代码中没有过滤逻辑,只是简单获取后缀名。
?
跟进 "fileType" 对象,发现其来自于 Request 提交的变量 "fileType",也就是之前 BurpSuite 抓包发现的 "fileType=jpg,gif,png,jpeg"
?
将已知的信息串联起来,总结这段代码的判断逻辑为:文件后缀包含 jpg,gif,png,jpeg,且不为 jsp 即进行下一步上传操作。
2.3、漏洞利用
对于 "fileType" 显然是可以抓包修改的,所以得出漏洞利用的思路为:抓包修改 "fileType=jpg,gif,png,jpeg,jspx" ,上传 jspx 的 webshell 即可(我用的冰蝎自带的)
?
点击发包,在后续包中又观察到了上传的位置 http://127.0.0.1:82/images/upload/temp/20231219/1702990600563.jspx
?
冰蝎连接成功,如下:
?
3.1、审计思路
通过搜索 Java 文件操作常用函数,定位到功能点对应代码段,审计其是否存在漏洞(常规审计思路)
3.2、审计流程
搜索 "new file(" 关键字 - 定位到 controller 层文件 AccountController.java,从注释和路由信息("admin/uploadAdminHeadImage")推测该功能点大概率是在后台。
为了方便阅读,我重新加上了注释,如下:
?
跟进文件保存的路径:"filePath" <- "fileName" <- "extension" <- "originalFileName.substring" <- "file.getOriginalFileName" <- "@RequestParam MultipartFile file"
看来好像没有做过滤,怀疑是否重写了 new File() 将过滤加在里面,跟进之后发现是 Java 原生的,所以总的来说这段代码实质上就是没有任何过滤的。
问题来了,这个漏洞是后台的,如果只有普通用户权限,进不去后台,那么该如何利用呢?
这里我的思路是继续审计过滤器 filter 有无鉴权漏洞,所以定位到 AdminPermissionFilter.java,审计其核心方法 doFilter()。
为了方便阅读,我重新加上了注释,如下:
?
这里鉴权逻辑存在问题,只要 URI 含有 "/admin/login" 或 "/admin/account" 就放行了,显然是不合理的。
于是想到构造 URI 类似:/admin/login/../../tmall/admin/uploadAdminHeadImage
3.3、漏洞利用
首先用管理员账号登录后台测试,发现确实存在文件上传漏洞,成功上传 jsp 文件。
?
接下来注销管理员账号,再次发包,发现提示我们需要登录,无法上传。
?
根据代码的鉴权逻辑,构造 URI 类似:/admin/login/../../tmall/admin/uploadAdminHeadImage,重新发包,成功上传 webshell
这里有 JS 验证限制只能选择图片上传,随便绕一下即可。
?
冰蝎连接成功,如下:
?
若依(Ruoyi)是一个开源的后台管理系统,可以用于所有的Web应用程序,如网站管理后台,网站会员中心,CMS,CRM,OA。
版本:RuoYi-v4.5.0(不用 4.6.0 版本的原因是修复了下述的文件下载漏洞)
项目部署:
2.1、审计思路
通过搜索 Java 文件操作常用函数,定位到功能点对应代码段,审计其是否存在漏洞(常规审计思路)
2.2、审计流程
全局搜索 "new FileInputStream(" ,定位到 FileUtils.java 的 writeBytes 方法,根据其代码得知是与文件输出相关的操作。
?
选中 writeBytes 方法 Find Usages,定位到 CommonController.java 的 resourceDownload 方法,其路由为 "/common/download/resource"
该方法调用 writeBytes 传入了 downloadPath, response.getOutputStream() 两个参数,即下载文件的路径、输出流对象,重点应该关注 "downloadPath" 是怎么来的。
?
跟踪得知,"downloadPath" 由 localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX) 拼接而成,
localPath 来自 Global.getProfile(),跟进 "getProfile" 得知其返回的是一个全局变量,将其调试打印输出得知为 "E:/xxxxxx/RuoYi-v4.5.0/files"(application.yml 里可以修改)
跟进 "substringAfter",其代码如下所示:(若字符串 str 和分隔符 separator 不为空,则截取分隔符之后的字符串返回)
?
现在问题来到了 StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX) 传入的两个参数:resource, Constants.RESOURCE_PREFIX,
其中 "resource" 是 GET 请求传入的参数,而对于 "RESOURCE_PREFIX" ,跟进后发现其等于固定值 "/profile",如下所示:
?
由上图知 StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX) 实际上是截取分隔符 "/profile" 之后的 "resource" 字符串
所以 downloadPath 实际等于 "E:/xxxxxx/RuoYi-v4.5.0/files" + 经过处理后的 GET 请求传入的参数 resource 二者拼接而成,最后下载的文件也就是这个路径所对应的文件。
反观整个代码段,关键就在于有没有对 GET 请求传入的参数 resource 做过滤,显然这里并没有,如此一来便可以在 GET 请求传入的参数 resource 做手脚。
2.3、漏洞利用
尝试下载数据库配置文件 application-druid.yml,已知其路径为:E:/xxxxxx/RuoYi-v4.5.0/ruoyi-admin/src/main/resources/application-druid.yml
则需构造 resource = /profile/../ruoyi-admin/src/main/resources/application-druid.yml,经过 "/profile" 截取处理后,
此时 downloadPath = E:/xxxxxx/RuoYi-v4.5.0/files/../ruoyi-admin/src/main/resources/application-druid.yml(localPath + resource),才能下载到 application-druid.yml
构造 payload 如下:
/common/download/resource?resource=/profile/../ruoyi-admin/src/main/resources/application-druid.yml
成功拿下数据库配置文件 application-druid.yml,如下:
?
Oasys 是一个 OA 办公自动化系统,基于 Springboot 框架开发,使用 Maven 进行项目管理,前端采用freemarker模板引擎,集成 JPA、Mybatis 等框架。
项目部署:
2.1、审计思路
通过搜索 Java 文件操作常用函数,定位到功能点对应代码段,审计其是否存在漏洞(常规审计思路)
2.2、审计流程
全局搜索 "new FileInputStream(" ,定位到 UserpanelController.java 的 image 方法,结合前后代码得到路由为 "/image/**"
着重关注 IOUtils.readFully(input, data),该代码为读取文件内容,其中 "input" 参数为所要读取的文件(FileInputStream 流),而 "data" 参数是为之分配的内存空间。
?
继续跟进 "input",由于 FileInputStream input = new FileInputStream(f.getPath()),所以跟进到 File f = new File(rootpath, path)
跟进参数 "rootpath",发现其在 application.properties 里声明为固定值:"E:/xxxxxx/oa_system-master/src/main/resources/static/images"
跟进参数 "path",发现其等于 "startpath" 将字符串 "/image" 替换为空之后的字符串,而 "startpath" 则来自 Request 请求的 URI
所以 File f = new File(rootpath, path) 返回的其实是 "E:/xxxxxx/oa_system-master/src/main/resources/static/images" + 经处理后的 URI 二者拼接成的文件路径对应的文件,这样一来 f.getPath() 返回的自然也是这个路径。
反观整个代码段,关键就在于有没有对 URI 做过滤,显然这里并没有,如此一来便可以在 Request 请求的 URI 做手脚。
2.3、漏洞利用
考虑读取数据库配置文件 application.properties,已知其绝对路径为:E:\xxxxxx\oa_system-master\src\main\resources\application.properties
最终构造 Payload 如下:(但不知道为啥没读取到,有无大佬告诉一下?)
/image//image..//image../application.properties
new File(
String path
String fileName
new FileInputStream(
new FileOutputStream(
new FileReader
response.setContentType("application/octet-stream;
file.delete();
FileUtils.
new ZipEntity(
file.getName(
.unzip(
.mkdirs(
stream.write(
save2File(
fos、fis.close()
MultipartFile(
file.getOriginalFilename(
IOUtil
FileUtil
download
fileName
filePath
write
getFile
getPath
getWriter
上传 // 搜注释
下载 // 搜注释
........