记一次攻防中的easethink代码审计
也是一年没更新博客了,记录一下近期的一场攻防,遇到了easethink二开的系统,该系统比较古老,使用了tp2的框架,但历史rce漏洞并不存在,为了getshell,也是进行了一波代码审计,最终也是成功getshell,过程比较有意思,简单记录一下审计思路。
1.前台sql注入获取管理员账号密码
这里掏出远古的seay对源码进行了一波扫描,简单看了下结果和代码,存在较多的前台sql注入。这里不分析网上公开的漏洞,实战环境都已修复,找一些未公开的。
1.1 user.php verify注入
user.php下存在大量注入,这里不看登录和注册功能点的,实战场景下都已修复,在此文件的verify功能点下,存在两处漏洞,也是非常简单,一处是任意用户登录,这里爆破一下用户id即可获取其session;二是sql注入,这里将get_client_ip()函数直接拼接至update语句中,并无过滤。
看一下get_client_ip()函数是如何赋值的,显然是用户可控的,请求包传递一个xff头即可
简单验证一下,首先爆破出一个存在的用户id,随后xff头传入恶意sql语句即可,也是成功获得管理员账号密码,需要注意的是,这里报错回显有强度限制,截断一下读取完整hash即可。
1.2 subscribe.php 多个注入
该文件下也是存在多个sql注入,直接看图,没什么好说的,此处传入的数据需要进行base64编码
简单验证一下,没问题
2.管理员登录存在md5弱比较
仅存在于ctf中的知识点,让我终于遇到一次了,可惜的是真实环境中管理员密码的hash并非0开头,可恶啊。
由下代码可见,使用php弱比较!=
验证密码是否相等,众所周知,php弱比较时会将每一个以”0E”开头的哈希值都解释为0,那么如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同。
if($adm_data['adm_password']!=md5($adm_password))
为了不留遗憾,这里也是果断向数据库里添加了md5为0e开头的管理员密码,随便找了一个:QNKCDZO
然后在随便找个md5为0e开头的字符:
240610708
尝试登录管理后台,test/240610708。爽😂
3.thinkphp cache getshell
经过一番审计发现后台并无可rce的点,但是天无绝人之路,发现其使用了thinkphp的缓存机制,将缓存内容序列化后存入了php缓存文件中,且可直接访问。芜湖,这不就是专门让我来rce的?😜
之前并没学过此trick,那就重头调一下喽,看下缓存文件存放的位置
直接就是全局搜索目录,代码逻辑位于system/cache/CacheFileService.php下
简单看了下其内的函数,有两个比较关键,一个set函数,用来设置缓存,一个get函数,用来获取缓存
set函数接受两个参数,分别是name和cache的值,这里会对name进行简单处理作为缓存文件名,然后将cache进行序列化处理后将其写入处理后的文件,简单易懂。
简单看下filename函数,对name做了哪些处理。so easy,进行了md5处理然后拼接了php后缀,最终拼接前缀$prefix(~@)和$dir路径(APP_ROOT_PATH."app/Runtime/data_caches),
以本机为例,即F:\phpstudy_pro\WWW\default\app\Runtime\data_caches\~@hash($name).php
接下来找下哪里调用了set函数,这里直接将缓存文件删空,打断点调试即可,简单粗暴。
调用栈也是清晰明了,简单来说,程序会将几条固定的sql语句查询结果放入缓存中,等用户访问相应功能点时取出,节省服务器资源。
简单看下流程,首先在获取城市功能中,默认调用getRowCached将以下sql语句的查询结果保存为缓存
select * from ".DB_PREFIX."deal_city where is_effect = 1 and is_delete = 0 and id = .intval($deal_city_id)
然后判断系统是否存在该sql语句的缓存文件,如果不存在则先调用getSqlCacheData,在调用setSqlCacheData方法
getSqlCacheData函数用来获取sql语句的查询结果以及构造上文提到的name
可以看到,这里对传入的sql语句进行了相应的加密处理,将其赋值为filename,具体的处理如下,这里变量只有$sql与$this->dbhash,显然$sql的值我们是可控的。dbhash先不去说,继续向下看。
$this->root_path . $this->cache_data_dir . 'sqlcache_' . abs(crc32($this->dbhash . $sql)) . '_' . md5($this->dbhash . $sql) . '.php'
调用setSqlCacheData方法,其调用了之前提到的set方法,将getSqlCacheData函数获取的cache和name进行处理后保存至缓存文件。
ok,分析的十分清爽。那如果我们要写入shell至缓存文件,有两个问题:1.dbhash的值如何获取;2.如何将shell写入sql语句的结果中使其存到缓存中;
先看第一个问题,看一下dbhash是如何构造的。需要数据库账号密码等信息,好的,构造不了一点,结束审计。
当然不可能喽,天无绝人之路,简单的调试了一下,发现有一条sql语句的dbhash为空。该语句为获取城市信息的功能。
select * from think_deal_city where uname='fujian' and is_effect = 1 and is_delete = 0
那么如何解决第二个问题,首先想到的是使用是sql语句更新数据库内的值,但是sql注入无法进行堆叠,没办法使用之前挖掘的漏洞。后台是存在sql操作的功能点,经测试可以成功更新数据库内的值。但是实战环境下无法使用该更新功能,这里使用了管理员后台的配置功能,直接更改城市信息,简单粗暴。。。
添加恶意代码至城市信息,这里需要注意,要清除一下缓存,后台存在此功能,或者直接更改城市名称,使用之前未使用过的也可。
随后访问url触发缓存操作即可
http://192.168.14.1/index.php?city=test123
然后根据之前分析的流程,构造缓存文件名,进行访问即可getshell。
$sql=select * from think_deal_city where uname='test123' and is_effect = 1 and is_delete = 0
首先getSqlCacheData中对其进行处理
$this->root_path . $this->cache_data_dir . 'sqlcache_' . abs(crc32($this->dbhash . $sql)) . '_' . md5($this->dbhash . $sql) . '.php'
结果为
F:/phpstudy_pro/WWW/default/app/Runtime/db_caches/sqlcache_965070236_534bc2aa5bd50e6e1a1459a2069dbb06.php
随后在set函数中对其进行md5处理,最终结果为app/Runtime/data_caches/~@9b39f9028136ab3d8063c68b4399b494.php
直接访问,也是轻松拿下。