읽기 안내
Dreamhack의 개인 문제 풀이 기록입니다. 원문에 남아 있는 실습 주소·플래그와 실패·미완료 결과를 그대로 보존했습니다.
XSS-2
웹 서비스 분석
엔드포인트: /vuln
@app.route("/vuln")
def vuln():
return render_template("vuln.html")
XSS-1과 XSS-2의 문제가 차이가 하나 있다.** get**으로 유저가 입력한 파라미터 값을 바로 return 하는 형태가 아니라, render_template 함수를 사용하고 있다는 점이다.
render_template 함수는 Flask 웹 프레임워크에서 제공하는 함수다. 주어진 템플릿 파일의 이름과 함께 전달된 변수나 값들을 템플릿에 적용하여 완성된 HTML을 생성한다.
render_template 함수를 사용하면, 전달된 템플릿 변수가 기록될 때 HTML 엔티티코드로 변환해 저장되기 때문에 XSS가 발생하지 않게 된다. 즉, 사용자가 입력한 값을 페이지에 그대로 출력하지 않는 것이다.
그래서 XSS-2의 vuln 페이지에서는 파라미터 값으로 <script>alert(1)</script>를 입력하더라도 XSS가 발생하지 않는다.
엔드포인트: /memo
@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)
memo 페이지는 XSS-1와 동일하다. 유저가 전달한 memo 파라미터 값을 render_template 함수를 통해 기록하고 출력한다. 유저가 URL의 memo 파라미터에 어떤 값을 입력하는지에 따라 화면에 출력하는 값도 달라진다.
엔드포인트: /flag
def read_url(url, cookie={"name": "name", "value": "value"}):
cookie.update({"domain": "127.0.0.1"})
try:
service = Service(executable_path="/chromedriver")
options = webdriver.ChromeOptions()
for _ in [
"headless",
"window-size=1920x1080",
"disable-gpu",
"no-sandbox",
"disable-dev-shm-usage",
]:
options.add_argument(_)
driver = webdriver.Chrome(service=service, options=options)
driver.implicitly_wait(3)
driver.set_page_load_timeout(3)
driver.get("<http://127.0.0.1:8000/>")
driver.add_cookie(cookie)
driver.get(url)
except Exception as e:
driver.quit()
# return str(e)
return False
driver.quit()
return True
def check_xss(param, cookie={"name": "name", "value": "value"}):
url = f"<http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}>"
return read_url(url, cookie)
@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>'
check_xss 함수에서는 유저가 flag에 POST로 전송한 파라미터 값이 XSS 공격에 사용될 수 있는 값인지 아닌지를 확인한다. 이때 check_xss에서는, 유저가 flag 페이지에 입력한 파라미터 값을 포함하여 vuln 페이지에 접근하는 URL을 생성하게 된다.
결과적으로 read_url 함수를 통해 vuln 페이지에 접근하는 URL과 사용자의 쿠키가 전달되는 것이다.
취약점 분석
XSS-1과는 다르게, vuln 페이지에 존재하는 innerHTML을 통해 <script> 태그로 XSS를 발생시킬 수 없는 상황이다. 따라서 다른 XSS 공격 벡터를 시도해봐야 한다.
이때 innerHTML은 웹 개발에서 사용하는 DOM(Document Object Model)의 속성 중 하나다. HTML 문서의 구조나 내용을 프로그래밍적으로 조작하고자 할 때 Javascript를 통해 DOM을 사용한다.
innerHTML 관련 문서는 여기로...
innerHTML이란?
자바스크립트에서innerHTML은 HTML 요소의 내용을 가져오거나 변경할 때 사용하는 속성이다.
| 코드 | 설명 |
|---|---|
var content = document.getElementById('example').innerHTML; |
읽기: 요소의 내부 HTML을 가져옵니다. |
document.getElementById('example').innerHTML = 'New Content'; |
쓰기: 요소의 내부 HTML을 변경합니다. |
innerHTML을 사용하면 요소의 내부 HTML을 문자열로 읽거나 설정할 수 있다. 이렇게 유저 입력을 바탕으로 innerHTML을 설정했을 때 XSS 공격에 취약할 수 있으므로 주의가 필요하다.
vuln.html
<script>var x=new URLSearchParams(location.search); document.getElementById('vuln').innerHTML = x.get('param');</script>
현재 페이지의 URL 쿼리 문자열에서 파라미터 값을 추출하고, 해당 값을 페이지 내의 vuln이라는 ID를 가진 요소의 내부 HTML로 설정하는 코드다. 즉, 파라미터의 값을 통해 '쓰기'를 수행하는 것이다.
vuln.html에 존재하는 위 코드는, innerHTML을 통해 사용자가 URL의 param 쿼리 파라미터 값을 조작하여 웹 페이지의 내용을 변경할 수 있게 한다는 점에서 취약할 수 있다.
URL 쿼리 파라미터?
웹 페이지 URL의 쿼리 문자열(예:?key=value)을 통해 서버나 클라이언트에 데이터를 전달할 수 있다.
예를 들어,example.com?page=1에서page=1이 쿼리 파라미터다.
익스플로잇
이제 유저의 쿠키 값을 탈취해 문제를 풀어보자.
<img src> 태그를 사용하여 다음과 같은 익스플로잇 코드를 작성할 수 있다.
<img src="XSS-2" onerror="location.href='/memo?memo='+document.cookie">
이 코드를 /flag 페이지의 param 부분에 입력하면, memo에서 유저가 쿠키 정보를 확인할 수 있게 된다.
이때 /vuln 페이지의 param에 해당 익스플로잇 코드를 입력하면 이미지 로딩에 실패한 화면이 보여진다.
왜 <script> 태그가 아닌 <img src> 태그를 사용할까??
XSS 공격에서는 악의적인 스크립트를 주입하여 피해자의 브라우저에서 해당 스크립트가 실행되도록 한다. 그러나, 일부 웹 앱에서는 보안 조치를 통해 <script> 태그를 필터링하거나 제거하는 경우가 많다.
이때 <img src> 태그는 대체로 필터링되지 않으며, 이미지 경로에 악성 스크립트를 삽입하는 방식으로 스크립트 오류를 발생시키는 것이다. 이미지의 경로가 잘못되었을 때 브라우저가 이를 처리하면서 오류를 발생시키고, 그 과정에서 onerror 이벤트 핸들러를 사용해 Javascript를 실행할 수 있는 것이 그 이유다.
후기
이 문제에서는 XSS-2 문제를 통해 쿼리 파라미터를 조작해 웹 페이지의 내용을 변경할 수 있게 하는 취약점이 발생시킬 수 있는 문제를 알아봤다.
이 문제처럼 유저 입력을 바탕으로 innerHTML을 설정했을 때 XSS 공격에 취약할 수 있으므로, 사용자 입력을 받기 전에 서버 측에서 입력값을 검증하거나 escape 처리를 하는 방식 등을 사용하는 것이 좋다.
작성 기록과 문제 출처
원문과 출처
풀이와 설명: MadEyE / 이선용의 개인 Notion 기록.
문제에서 제공한 코드·서비스 화면과 외부 참고자료의 권리는 해당 작성자와 플랫폼에 있습니다.
원문 본문·코드·실습 증적을 보존했으며, 원문에 포함된 외부 글은 참고 링크로 안내합니다.
다음 글