[UNIFOX] 웹해킹 2차시 XSS와 CSRF

류지율·2일 전

unifox

목록 보기
3/3

문제 1. XSS-1

서버 코드 핵심


@app.route("/vuln")
def vuln():
    param = request.args.get("param", "")
    return param

    
@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "GET":
        return render_template("flag.html")
    elif request.method == "POST":
        param = request.form.get("param")
        if not check_xss(param, {"name": "flag", "value": FLAG.strip()}):
            return '<script>alert("wrong??");history.go(-1);</script>'

        return '<script>alert("good");history.go(-1);</script>'


memo_text = ""


@app.route("/memo")
def memo():
    global memo_text
    text = request.args.get("memo", "")
    memo_text += text + "\n"
    return render_template("memo.html", memo=memo_text)


취약점 분석

  • /flag에서 스크립트가 막혀있지 않아, HTML의 script 태그로 flag를 memo에 출력되도록 만들 수 있다.

공격 시나리오

  1. flag창에 접속한다
  2. <​script>location='/memo?memo='+document.cookie</​script> HTML코드로 document.cookie를 memo로 출력되도록 한다

핵심 포인트

/flag에서 스크립트 태그가 사용가능함을 알 수 있어, HTML로 document.cookie의 위치를 다른곳으로 출력 할 수 있도록 한다.


문제 2. CSRF-1

서버 코드 핵심

@app.route("/")
def index():
    return render_template("index.html")


@app.route("/vuln")
def vuln():
    param = request.args.get("param", "").lower()
    xss_filter = ["frame", "script", "on"]
    for _ in xss_filter:
        param = param.replace(_, "*")
    return param


@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "GET":
        return render_template("flag.html")
    elif request.method == "POST":
        param = request.form.get("param", "")
        if not check_csrf(param):
            return '<script>alert("wrong??");history.go(-1);</script>'

        return '<script>alert("good");history.go(-1);</script>'


memo_text = ""


@app.route("/memo")
def memo():
    global memo_text
    text = request.args.get("memo", None)
    if text:
        memo_text += text
    return render_template("memo.html", memo=memo_text)


@app.route("/admin/notice_flag")
def admin_notice_flag():    
    global memo_text
    if request.remote_addr != "127.0.0.1":
        return "Access Denied"
    if request.args.get("userid", "") != "admin":
        return "Access Denied 2"
    memo_text += f"[Notice] flag is {FLAG}\n"
    return "Ok"

취약점 분석

  • frame, script, on 이 필터링되어있다.
  • userid가 admin이라면 memo에 flag를 출력한다.
  • 다른 태그로 userid를 바꿔 줄 수 있다.

공격 시나리오

  1. /flag에 들어간다
  2. img 태그로 userid를 admin으로 변경한다.

핵심 포인트

script 태그가 막혀있어 img 태그를 대신 사용하여
<​img src="/admin/notice_flag?userid=admin">를 넣어
/admin/notice_flag의 userid를 admin으로 바꿔준다.


문제 3. XSS-2 — Stored XSS로 쿠키 탈취

서버 코드 핵심


@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "POST":
        param = request.form.get("param")
        if not check_xss(param, {"name": "flag", "value": FLAG.strip()}):
            return '<script>alert("wrong??");history.go(-1);</script>'
        return '<script>alert("good");history.go(-1);</script>'

memo_text = ""

@app.route("/memo")
def memo():
    global memo_text
    text = request.args.get("memo", "")
    memo_text += text + "\n"
    return render_template("memo.html", memo=memo_text)

취약점 분석

  • /flag에 POST 요청을 보내면, 서버는 헤드리스 브라우저 봇을 띄워서 flag쿠키를 심어준 뒤 {내가 보낸 값}을 방문시킨다.
  • /vuln 페이지는 param을 이스케이프 없이 그대로 HTML에 반영하기 때문에 XSS가 그대로 실행된다.
  • /memo는 GET 파라미터로 받은 텍스트를 전역 변수에 계속 누적 저장하는 엔드포인트이다.

공격 시나리오

  1. /flag의 param 입력창에 아래 payload 제출
<img src=x onerror="fetch('/memo?memo='+document.cookie)">
  1. fetch()가 document.cookie(flag 포함)를 /memo로 전송 하여memo_text에 저장됨
  2. 브라우저에서 직접 /memo접속하여 저장된 flag 확인

핵심 포인트

필터링이 전혀 없는 순수 XSS였고, flag가 포함된 document.cookie를 memo에 저장하면 flag를 얻을 수 있다.


문제 4. CSRF-2 — 필터 우회 + 강제 비밀번호 변경

서버 코드 핵심

users = {'guest': 'guest', 'admin': FLAG}
session_storage = {}

@app.route("/vuln")
def vuln():
    param = request.args.get("param", "").lower()
    xss_filter = ["frame", "script", "on"]
    for _ in xss_filter:
        param = param.replace(_, "*")
    return param

@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "POST":
        param = request.form.get("param", "")
        session_id = os.urandom(16).hex()
        session_storage[session_id] = 'admin'
        if not check_csrf(param, {"name":"sessionid", "value": session_id}):
            return '<script>alert("wrong??");history.go(-1);</script>'
        return '<script>alert("good");history.go(-1);</script>'

@app.route("/change_password")
def change_password():
    pw = request.args.get("pw", "")
    session_id = request.cookies.get('sessionid', None)
    try:
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')
    users[username] = pw
    return 'Done'

취약점 분석

  • /change_password는 CSRF 방어가 전혀 없고, 로그인된 세션이면 누구든 자신의 비밀번호를 임의의 값으로 변경 가능했다.
  • /vuln은 frame, script, on 문자열을 *로 치환하는 필터가 있지만, img태그로 우회가 가능하다

공격 시나리오

  1. /flag의 param 입력창에 아래 payload 제출
<img src="/change_password?pw=iamthebestunifox">
  1. 봇이 해당 쿠키를 들고 /vuln?param=... 방문
  2. 브라우저가 HTML로 렌더링하며 <​img> 태그가 /change_password?pw=iamthebestunifox으로 요청함
  3. /login에서 admin / iamthebestunifox으로 로그인
  4. Hello admin, flag is {FLAG} 확인

핵심 포인트

script나 다른 태그들은 막혀있어도 img 태그로 우회하여 html을 실행시킬 수 있어, 봇이 html로 랜더링하여 실행시킬 수 있다.


두 문제의 공통 패턴 정리

구분XSS-2CSRF-2
취약점 유형XSSCSRF (필터 우회)
필터링없음frame, script, on 치환
우회 방법불필요<img src="...">로 우회
탈취 대상쿠키(flag 값)admin 비밀번호 변경 권한
유출 채널same-origin /memo 저장소없음 (직접 계정 탈취)
최종 목표/memo에서 쿠키 확인admin 로그인 후 flag 직접 확인

0개의 댓글