1. thinkadmin历史漏洞复习
已经找到对方的app的后台地址,是thinkadmin的,因此我们需要去复习thinkadmin的历史漏洞。
CVE-2020-25540
https://github.com/zoujingli/ThinkAdmin/issues/244
利用POC如下
https://github.com/Schira4396/CVE-2020-25540
列目录
POST /?s=admin/api.Update/node
rules=["runtime/"]
文件读取
/?s=admin/api.Update/get/encode/34392q302x2r1b37382p382x2r1b1a1a1b1a1a1b363932382x312t1b
其本质大概想设计出来一个能让第三方去对比服务器上的系统web文件的功能,结果因为目录穿越造成任意目录读取。虽然有一定限制,但危害还是非常非常大,因此后续更新直接将这个功能下架。
还有一个没有CVE的反序列化漏洞
https://github.com/zoujingli/ThinkAdmin/issues/238
存在两处接口,其中一处正是上面列目录功能的rules传参。
POST /?s=admin/api.Update/node
rules=payload
另外一处则是
POST /?s=wechat/api.Push/index
receive=payload
2. 第一份源码
因为官方已经不提供旧版源码下载,所以我第一时间去其他地方找到了一份使用thinkphp5.1.38的旧版源码。检测了下,其有如下漏洞。
application/wechat/controller/api/Push.php
两处反序列化只修复了一处。
application/admin/controller/api/Update.php
列目录和任意文件读取的路由稍微变了一下,而且列目录无法用rules传参进行控制,只能列web根目录。但任意文件读取取消了各种限制,这意味着可以直接读config/database.php获取数据库配置。
获取了数据库配置之后,数据库如果能够外连就可以深入一点利用。
application/admin/controller/api/Plugs.php
这个是thinkadmin自带的文件上传接口,正如很多cms设计的一样,其白名单storage_local_exts在数据库或者系统后台中都能进行配置。正常来说,我们可以利用这个进行getshell操作,但很明显,如果直接给白名单加上php,过不去第四个if,且后台的系统配置中也有拦截。
application/admin/controller/Config.php
我们如果直接操作数据库,可以绕过后台配置限制,但绕不过upload()的限制。
很显然,只过滤php是不够的,如果对方是windows服务器,我们还有php::$DATA可选,如果对方是apache且进行了错误的配置,我们还有php3/php4/php5/php7/pht/phtml/phar这些可能的解析后缀。
3. 第二份源码
然而第一份源码除了熟悉thinkadmin架构之外没有任何卵用。因为目标是thinkphp6.0.3的,而且漏洞也和第一份不一样,不存在反序列化。不过还是存在列目录和文件读取,而且和历史漏洞一模一样。
app/admin/controller/api/Update.php
但是在列目录时,碰到了一个问题。
这是因为我列的是web根目录,如果对方项目巨大,或者某个文件夹没有权限,就会导致报错,这个时候就需要针对性列目录,以./app和./runtime为主。
读./app可以获取controller路径,在原版thinkadmin中,突破口并不多,但这种程序很多都是二开的,对比原版thinkadmin那些没有的controller,就可能直接审计出漏洞。审计漏洞需要配合任意文件读取,具体该怎么读请回顾前面的。总而言之,有了CVE-2020-25540我们等于获取了其源码。
这个程序就很容易发现一个SQL注入。
/app/admin/controller/api/Main.php
然而等我吭呲吭呲的注出密码后却发现,登录需要OTP验证,没办法,继续审计。
/app/admin/controller/Posting.php
非常愚蠢的命令拼接,同位置有三处,但都需要后台权限,而且最后发现exec()被disable_functions了,所以无法利用。
/app/admin/controller/api/Upload.php
最后一处是在朋友的提醒下发现的,这乍一看不就是thinkadmin自带的上传吗?前面分析过需要特定环境才能利用,所以我就直接跳过了。结果居然多了个xkey传参可以完全控制$this->name,很难不让人怀疑这是个后门。最后getshell是这样的。
但这个上传接口也需要后台权限,怎么办呢?这个时候就轮到thinkphp经常用到的./runtime出场了。
读runtime/admin/log/single_error.log这个文件很容易发现它记录了一系列的session报错。
而且我们可以知道,这套程序用的php原版session,而且没有放在/tmp或者/var/lib/php/sessions/中,而是runtime/session。那就简单了,我们直接利用列目录列出所有session,然后进行爆破。
这样就可以直接进入后台绕过了OTP限制,接下来再用它的xkey后门getshell就行了。
4. 另类脑洞
万一没有后门怎么办呢?这套系统是linux+nginx,无法绕过thinkadmin原版upload限制。
但在后续代码审计中,我发现了其有个图床服务器。
这台被getshell的服务器(A),可以带file_paths参数访问图床服务器(B)的一个接口,其目的是为了让服务器B反过来下载服务器A上面的图片备份下来。为什么我会知道这点呢?
因为服务器B更加千疮百孔,直接访问这个接口就会发现。
不但因为debug泄露了源码,而且这个命令拼接也太赤裸裸了,甚至可以直接当shell用。
所以我们完全可以在不拿下服务器A的情况下,通过任意文件读取和代码审计直接拿下服务器B。
拿下服务器B又有什么用呢?服务器A是会用curl请求服务器B的,这种情况下,可以篡改服务器B的代码,将接口改成302跳转,然后修改协议为gopher,就可以打到服务器A的本地端口了。
如果服务器A的本地存在fpm的9000端口,以及redis的6379端口,就可以这样曲折的进行SSRF getshell,这种案例在discuz的SSRF漏洞上经常能得到利用。
这次虽然没9000 fpm,但却有redis,redis密钥和端口也在config/cache.php存储着,而且web目录恰好是777权限,完全符合gopher打本地redis的条件。
当然,最终我并没有进行尝试,但理论上完全没有问题。