Computer Science/네트워크 및 웹 보안(Network & Web Security)

[네웹보/NWS] XSS 공격

gxxgsta 2024. 4. 22. 17:30
반응형
SMALL

The Cross-Site Scripting attack

XSS에서는 공격자가 target 웹 사이트를 통해 피해자의 브라우저에 악성코드를 삽입한다.

 

브라우저는 해당 악성 코드를 실행시키는데, 이 악성코드는 유저(victim)의 권한으로 실행된다. 따라서 웹 사이트는 해당 접근이 신뢰가 가능하다고 판단하여 페이지에 존재하는 콘텐츠에 접근, 변경이 가능하고, 쿠키를 읽으며, 사용자를 대신하여 요청을 전송할 수도 있다.

즉, 해당 악성 코드는 유저가 웹 페이지에서 수행할 수 있는 모든 작업을 수행할 수 있다.

 

이때, 악성코드가 사이트를 거쳐서 오기 때문에 일종의 code injection attack, 즉 Cross-Site Scripting Attack(공격 우회)라고 한다. XSS는 사용자의 권한으로 코드를 실행하기 때문에 SOP(Same Origin Policy)의 영향을 받지 않는다.

 

XSS Attack는 브라우저에서 실행된다는 점에서는 동일하나, 전달 방식에 의해 Non-persistent (Reflected) XSS AttackPersistent (Stored) XSS Attack 두 가지로 분류할 수 있다.

Non-persistent(Reflected) XSS

 

Non-persistent(Reflected) XSS 방법은 target 웹 사이트와 연결된 url 내부에 악성코드가 존재하며, 해당 url을 유저가 클릭할 경우 전달되는 request 내부에 악성코드를 심는 방법이다.

 

이때, 웹은 request로 온 내용을 response로 다시 전달하는데 request에 악성코드가 존재하기 때문에 response에도 마찬가지로 악성코드가 존재하게 된다.

 

사용자의 입력으로 JavaScript 코드가 들어간다면, 해당 입력이 웹페이지에 반영되면서 JavaScript 코드가 삽입되는 것이다.

 

Example

보안이 취약한 사이트 http://www.exame.com/search?input=word 있다고 가정해보자. 이때, "word"는 사용자가 제공하는 부분으로 사용자에 의해 입력된 부분이다.

 

attacker는 victim에게 http://www.example.com/search?input=<script>alert(“attack”);</script>이라는 url을 전송하고, 피해자가 해당 링크를 클릭하도록 유도한다.

 

피해자가 위의 링크를 클릭하면, HTTP GET 요청을 통해 해당 script 코드가 서버로 전송되고, 페이지에 입력으로 들어온 내용(word)과 함께 검색된 결과가 포함된 페이지를 반환한다.

 

이때, <script>alert(“attack”);</script>는 script 코드로 해당 코드가 실행되어 사용자의 브라우저에 alert 화면을 띄우게 된다.

 

즉, url에 어떤 내용이 있고, 유저가 해당 url을 클릭하면 url에 대한 응답이 유저에게 돌아오는데, 돌아오며 무언가가 실행된다면 Non-persistent(Reflected) XSS attack이라고 할 수 있다.

 

Persistent(Stored) XSS

Persistent(Stored) XSS 방법은 공격자가 target 웹사이트에 악성코드가 저장하고, 해당 사이트를 이용하는 사용자가 사이트에 접근 시 저장된 코드가 전달되며 사용자의 브라우저에서 코드가 실행되는 방법이다.

 

예를 들어 어떤 게시판의 글 내용으로 악성 코드가 저장하면, 다른 사용자가 해당 글을 조회할 때 저장되어 있는 악성코드가 실행되는 방법이다.

 

따라서 웹은 어떤 데이터를 저장하고 이를 유저에게 전달하는 하나의 채널처럼 동작한다.

 

웹사이트에서 입력 내용이 제대로 처리되지 않은 경우 해당 채널을 통해 다른 사용자의 브라우저로 전송되어 해당 브라우저에서 실행됩니다.

 

브라우저는 저장된 악성코드에 대해서도 웹사이트가 보내는 일반적인 코드로 간주한다. 따라서 해당 코드에는 웹사이트의 코드와 동일한 권한이 부여되기 때문에 웹사이트의 사용자 권한으로 실행된다. 따라서 웹에서 제공되는 여러 가지 요소(예를 들어 쿠키)에 접근 및 변조가 가능하다.

 

CSRF의 경우 Same Origin Policy에 의해 다른 사이트의 경우 서로 접근할 수 없다. 반면, XSS는 악성코드를 유저에게 전달하여 악성 코드가 유저의 권한으로 실행되게 하는 방법으로 CSRF의 우회 방법이라고도 할 수 있다.

 

XSS Contexts

웹페이지를 구성할 때 유저가 입력할 수 있는 부분이 존재한다. 이때, 유저의 입력은 요청으로 웹사이트를 향했다가, 응답으로 돌아온다. 이때 웹페이지의 스크립트를 실행하여 변경사항을 웹페이지에 반영하게 된다.

 

위 사진은 <input> 태그를 이용하여 입력을 받아온 사례이다.

- <input> tag

<input id="keyword" type="text" name="q" value="REFLECTED_HERE">

 

- script

<script> var sitekey = 'REFLECTED_HERE'; </script>

 

HTML과 script 각각에서 붉은 글씨 부분에 사용자의 입력이 들어갈 수 있다. 따라서 해당 부분에 악성 코드를 넣고 실행하면 해당 코드는 페이지 내의 여러 요소에 들어갈 수 있게 된다.

 

악성코드는 태그 내부의 attribute에도 들어갈 수 있고, script의 어떤 변수에도 들어갈 수 있게 된다.

 

https://github.com/daffainfo/AllAboutBugBounty/blob/master/Cross%20Site%20Scripting.md

 

AllAboutBugBounty/Cross Site Scripting.md at master · daffainfo/AllAboutBugBounty

All about bug bounty (bypasses, payloads, and etc) - daffainfo/AllAboutBugBounty

github.com

위 github에서 다양한 XSS 공격 방법을 설명해두었다.

해당 사이트의 내용을 보면 XSS의 Context에 따라 공격 payload가 달라져야 함을 알 수 있다.

Damage done by XSS attacks

앞서 설명했듯이, CSRF는 사용자의 쿠키를 훔쳐 Cross-site request를 통해 악성 코드를 실행시키므로 SOP에 위배되어 공격이 제대로 진행되지 않을 수 있다.

 

그러나 XSS는 유저에게 악성코드를 전달하고, 악성코드(이상한 요청)를 유저의 권한으로 실행시켜 웹사이트에 대한 변조가 가능하므로 CSRF에 비해 더 많은 것을 할 수 있다. 브라우저가 target 웹사이트로부터 응답을 수신할 때, 해당 응답에 악성 JS코드가 존재하는 경우 해당 JS 코드는 유저가 보는 페이지를 변조시킬 수 있다.

 

몇 가지 예를 살펴보자.

 

- Web defacing

JS 코드는 DOM API를 사용하여 웹페이지 내부의 DOM 노드에 접근할 수 있다. 따라서 삽입된 JS는 임의로 웹페이지를 변경할 수 있는데, 예를 들어 뉴스 사이트의 기사를 변조할 수 있다.

 

- Spoofing requests

삽입된 JS 코드는 사용자의 권한으로 HTTP request를 전송할 수 있다. 이 경우 뒤에서 더 알아보자.

 

- Stealing information

삽입된 JS 코드는 웹페이지 내부에 존재하는 세션 쿠키, 유저의 개인정보 등의 정보를 스크립트를 통해 훔칠 수도 있다.

 

XSS는 유저의 권한으로 악성 코드를 실행시키기 때문에 SOP의 영향을 받지 않는다는 점에서 CSRF와의 차이점이 존재한다.

Practice

실습을 진행해보자. 실습을 위한 환경설정은 아래와 같다.

우리는 XSS 공격에 대한 대책이 설정되지 않은 Apache 가상 환경으로 호스팅되는 Elgg 웹사이트를 이용할 것이다.

 

공격자는 자신이 공격하고자 하는 웹페이지를 분석하는 과정이 필요하다.

 

공격을 하기 위해 JS 코드를 삽입할 수 있는 위치를 찾아주는데, 이러한 입력 필드는 JS를 삽입할 수 있는 잠재적인 공격 인터페이스가 된다. 

 

이때 웹사이트는 입력으로 들어온 악성코드에 대한 조치를 취하지 않으면 브라우저에서 해당 코드가 실행되어 웹페이지가 손상될 수 있다.

XSS attacks to befriend with others

목표: 사용자의 동의 없이 Samy를 친구 목록에 추가하기

 

공격자인 Samy는 해당 SNS를 이용하는 모든 사용자와 친구가 되도록 악성코드를 심고 싶다. 따라서 Samy는 해당 웹사이트에 대한 분석을 하였다.

 

Samy는 CSRF에서와 동일하게 친구 추가 시 HTTP request가 어떻게 전송되는지 확인하기 위해 Charlie라는 가계정을 만들어 Samy의 계정으로 친구 요청을 보내고, Firefox의 LiveHTTPHeader 확장 프로그램을 이용하여 해당 요청을 캡처한다.

해당 요청을 캡쳐한 모습은 위와 같다.

 

-

Elgg의 친구추가 요청 시 url이다. 친구 목록에 추가될 사용자의 UserId가 사용되며, Samy의 GUID는 47임을 확인할 수 있다.

 

-

현재는 CSRF 공격에 대한 대응책으로 secret token이 활성화 되어 있음을 확인할 수 있다.

 

-

각 사용자에 대해 고유한 세션 쿠키 정보이다. 브라우저에서 자동으로 전송되며 CSRF로 공격하는 경우 Cross-site request로 전송되기 때문에 허용되지 않지만, 현재는 Elgg 웹사이트(동일 출처)에서 온 쿠키이므로 허용된다.

우리는 CSRF의 대비책으로 same-site인지, cross-site인지를 판단하기 위해 랜덤 값을 생성하고, 해당 값을 페이지에 삽입한다고 이야기했다. 이러한 랜덤 값을 시크릿 쿠키라고 하였고, 해킹 사이트에서는 웹 페이지가 생성하는 랜덤 값을 예측하지 못하기 때문에 CSRF 공격이 불가능하다고 이야기 하였다.

 

XSS 공격의 경우 해당 토큰을 알 필요가 없다. 왜냐하면 악성코드가 유저에게 있고, 각 시크릿 쿠키 값은 위 사진처럼 js 코드로 가져와 확인할 수 있기 때문이다.

 

하지만 우리는 악성코드를 구성할 때 위와 같은 시크릿 코드 값을 잘 넣어주어야 한다. 잘 넣어주지 않는다면 서버에서는 다른 웹 사이트에서 온 요청으로 판단하여 해당 코드를 실행하지 않을 수 있기 때문이다.

따라서 시크릿 쿠키의 값을 위 코드의 ①, ② 라인처럼 넣어줄 수 있고, ③, ④번 라인처럼 실행하고자 하는 명령어를 url의 형태로 넣어준 후 Ajax를 사용하여 서버에 전송하면 쉽게 Samy에게 친구 요청하도록 하는 코드를 만들어 낼 수 있다.

 

실제 Elgg 실습에서는 아래와 같은 단계로 악성 스크립트를 실행할 수 있다.

 

1. Samy는 프로필의 "About me" 부분에 위 사진과 같이 자바 스크립트 코드를 넣을 수 있다.

2. "Alice" 계정으로 로그인한 유저가 Samy의 프로필을 방문한다.

3. 저장된 자바 스크립트 코드가 실행되지만, Alice는 실행 여부를 알 수 없다.

4. 해당 코드는 친구 추가 request로 서버에 전송된다.

5. Alice가 친구 목록을 확인하면, Samy가 친구로 추가되어 있다.

XSS attacks to change other people’s profiles

이번에는 GET 요청이 아닌, PUT 요청을 하는 실습을 진행해보자.

목표: 사용자의 동의 없이 프로필을 "Samy is MY HERO"라는 문구로 수정해보자.

 

마찬가지로 공격자인 Samy는 프로필을 수정할 때에 요청이 어떻게 날아가는지 LiveHTTPHeader를 사용하여 패킷을 캡처한 후 분석하는 과정이 필요하다. 

프로필 수정 시 캡쳐된 패킷의 모습이다.

 

-

프로필 수정 시의 url이다.

 

-

각 사용자 별로 고유한 세션 쿠키이다. 브라우저에서 자동으로 발급해준다.

 

-

CSRF의 대비책에 구축되어 있음을 확인할 수 있다.

 

-

descript 필드에 "samy is my hero"라는 내용으로 채워져 있음을 확인할 수 있다.

 

-

프로필의 공개 설정으로, 2는 전체공개를 의미한다.

 

-

피해 유저의 GUID이다. 이때, 자바 스크립트에서 elgg.session.user.guid 변수를 통해 현재 사용자의 GUID에 접근할 수 있다. 아래 사진과 같이 적용할 수 있다.

 

이때, 공격자는 Samy이고 위의 코드를 프로필에 저장하여 프로필 방문 시 해당 코드가 실행되는 작동 과정이다. 따라서 공격자의 Samy가 자신의 프로필을 방문할 때마다 코드가 실행되어 프로필에 미리 작성된 코드가 overwrite되면 안 되기 때문에 Samy의 GUID인 47을 이용하여 아래 사진과 같이 본인이 아닐 때에만 해당 스크립트를 실행할 수 있도록 코드를 수정해 줄 수 있다.

따라서 Samy가 자신의 프로필을 해당 코드로 바꾼 뒤, 다른 사람이 Samy의 프로필을 방문하면, 방문한 사람은 자동으로 description 부분이 "Samy is MY HERO"로 변경되도록 만들 수 있다.

 

이때 실행되는 악성코드는 방문한 사람의 권한으로 실행이 되고, 이 경우는 persistent XSS 공격이라고 할 수 있다.

Self-propagation XSS Worm

우리는 내 프로필에 방문하는 사용자의 프로필을 수정하는 방법에 대해 알아보았다. 그렇다면, 내 프로필을 방문한 사용자의 프로필에 악성코드를 심을 수도 있다. 따라서 다른 사용자의 프로필에 악성코드를 심으면서 감염시키고, 감염된 사용자는 또 다른 사용자를 감염시킬 수 있다.

 

우리는 어떻게 다른 사람의 프로필에 악성 코드를 심을 수 있을까? 방법을 아래와 같이 두 가지로 나뉠 수 있다.

 

DOM apporach

DOM API를 통해 DOM에서 JavaScript 코드의 자체 복사본을 얻을 수 있다.

즉, 페이지 내에 악성코드를 심어놓고, DOM 구조이기 떄문에 악성 코드를 포함한 트리 형태의 변수가 존재한다. 따라서 해당 변수를 복사하여 심는 방법이다.

 

Link apporach

스크립트 태그의 src 속성을 사용하는 링크를 통해 JavaScript 코드를 웹페이지에 포함할 수 있다.

즉, 악성 코드 라이브러리를 만들어 웹에 업로드 한 후 javascript의 라이브러리와 연결하여 심는 방법이다.

 

하나씩 자세히 알아보도록 하자.

Document Object Model(DOM) apporach

DOM 구조는 웹 페이지의 객체들을 트리로 구성한다. 이때, DOM API를 사용하여 트리의 각 노드(객체)에 접근할 수 있다. 이때, 해당 페이지에 javascript 코드가 저장되어 있다면, 마찬가지로 트리의 객체로 저장된다.

 

우리는 해당 부분의 DOM 노드가 무엇인지를 안다면, DOM API를 통해 해당 노드에서 코드를 가져올 수 있다. 이때, document.getElementById() 함수를 사용하여, 해당 스크립트 코드를 가져올 수 있다.

간단한 예제를 살펴보자.

 

위 사진과 같이 <script> 내부의 내용이 악성코드고, 해당 스크립트의 아이디를 worm으로 지어주었다. 이때, document.getElementById("worm").innerHTML으로 스크립트의 내용을 가져올 수 있는데, innerHTML은 스크립트의 "<script>"태그를 포함하지 않고, 내용만 가져오기 때문에 공격 코드에서 전체코드의 복사본과 함께 description 필드에 원하는 메세지를 넣을 수 있다.

 

위 방법을 이용하여 구성한 악성코드이다.

- ①, 

worm 코드를 복사하여 <script> 태그를 포함하도록 구성한다.

 

-

이때, <script> 태그를 닫아야 하는데, </script>가 문자열 그대로 들어가면 firefox의 HTML parser가 해당 문자열까지를 스크립트의 닫는 태그로 판단하여 뒷 부분의 코드를 모두 무시하게 된다. 따라서 "</"와 "script>"를 + 기호를 통해 분리해준다.

 

-

HTTP POST 요청 시 데이터는 "application/x-www-form-urlencoded"와 같은 Content-Type를 사용하여 전송되기 때문에, encodeURIComponent() 함수를 사용하여 문자열을 인코딩한 후 전달한다.

 

-

프로필의 공개 여부를 설정한다. 2는 전체공개를 의미한다.

 

프로필을 이렇게 설정해두면, 해당 프로필을 방문하는 사용자에게도 해당 코드가 전파되고, 전파된 사용자의 프로필을 또 다른 사용자가 방문하면 또 악성 코드가 전파되는 worm 형식의 악성코드이다.

 

Link Apporach

악성코드 js 파일의 이름을 xssworm.js라고 했을 때, 우리는 src 속성을 이용하여 해당 스크립트를 가져올 수 있다.

link approach 방법은 위 사진과 같이 js 코드의 링크를 걸어주는 것만으로 코드를 작성할 수 있다.

 

DOM approach와 link approach 방법 모두 퍼트릴 수 있는 코드를 삽입하는 것은 동일하지만, 해당 코드가 공격자 페이지에 있는지에 대한 여부로 approach 방법에 차이가 있다.

Countermeasures

우리는 지금까지 XSS 공격 방법을 살펴보았다. 그렇다면 이러한 XSS 공격을 막기 위한 방법을 살펴보자.

 

the Filter Approach

첫 번째 방법은 필터링 방법이다. XSS 공격은 결국 사용자의 입력으로 코드가 들어오고, 해당 코드가 실행됨으로써 해킹이 진행되는 것이므로, 사용자의 입력에서 코드 부분을 필터링하는 방법이다.

 

가장 간단한 방법으로는 <script></script>와 같은 태그의 시작 부분부터 끝 부분까지를 입력으로 받지 않는 방법이 있다. 하지만 이 방법을 실제로 도입하기에는 문제가 있다.

 

왜냐하면 입력을 통해 코드를 넣는 방법이 굉장히 다양하기 때문이다. 예를 들어 HTML의 svg 태그는 이미지를 로딩할 수 있는데, 해당 이미지에 대해 onclick라는 속성을 넣어 HTML 내부에 스크립트가 실행되도록 설정할 수 있기 때문이다.

 

따라서 이러한 필터링 방법은 입력으로 코드가 들어올 수 있는 경우가 굉장히 많기 때문에 모든 경우를 찾아 필터링하기가 어렵다.

 

하지만 jsoup와 같은 오픈 소스 라이브러리를 통해 어느 정도의 필터링은 가능하다.

 

The Encoding Approach

두 번째는 인코딩 방법이다. HTML 태그들이 입력을 통해 들어오기 때문에 이러한 입력을 다른 형태로 인코딩하여 동일한 의미를 가진 다른 형태로 만들어주는 것이다.

 

예를 들어 입력으로 <script>가 들어온 경우 &lt;script&gt;와 같이 <> 기호를 인코딩된 값으로 치환해 주는 것이다. 이와 같이 인코딩을 진행하면, 브라우저는 해당 부분이 코드가 아닌 문자열로 판단하기 때문에 코드로서의 실행을 막을 수 있다.

 

Elgg's Approach

Elgg에서는 위에서 언급한 두 가지 방법을 사용한다.

 

PHP module HTMLawed

php에서 제공해주는 라이브러리로 XSS 공격으로부터 HTML을 삭제하는 사용자가 정의 가능한 PHP 스크립트이다.

 

PHP function htmlspecialchars

사용자가 제공한 인코딩 데이터, 사용자 입력의 JavaScript 코드는 브라우저에서 코드가 아닌 문자열로만 해석되게 한다.

 

Defeating XSS using Content Security Policy

 

XSS 공격은 데이터와 코드가 섞여 있기 때문에 공격이 가능하다. Content Security Policy를 헤더에 세팅하면 웹 페이지 내부에 존재하는 코드를 실행하지 못하게 하고, 코드가 링크로만 삽입되어 있는 경우에만 실행하도록 하는 정책을 설정할 수 있다.

 

그리고 페이지 내에 삽입하는 경우를 허용하면 공격자가 마찬가지로 링크를 삽입할 수 있기 때문에 우리는 해당 링크가 공격자가 삽입한 것인지, 우리의 웹 페이지인지 판단할 수 있어야 한다. 이때, 동일한 웹 페이지인지 구분하기 위해 link apporach가 필요하다. 

 

링크되어 있는 웹 페이지의 스크립트들은 관리자의 권한으로 동일한 웹 페이지만 허용할 것인지, 일부 웹 페이지를 허용할 것인지에 대한 정책을 세울 수도 있다.

 

위 사진은 CSP의 예시이다. HTML 헤더에  Content Security Policy를 정의하고, 원하는 정책을 설정할 수 있다. 위의 경우스크립트 소스가 'self' 나 자신, 또는 example.com이나 https://apis.google.com만 허용하겠다는 의미이다.

 

그런데 우리는 앞서 link apporach 방법만을 사용하였다. 하지만 페이지 내에 존재하는 코드를 아예 실행하지 못하도록 만든다면 구현에 제한이 존재한다. 따라서 우리는 안전하게 허용할 수 있는 방법으로 구분 가능한 값을 넣을 수 있다.

 

위 사진과 같이 HTML 헤더에 nonce 값을 랜덤으로 설정해주고, 스크립트 코드에서 해당 값을 가지고 있는 경우에만 실행하도록 정책을 만들 수도 있다. 이때, nonce 값은 랜덤값 뿐만 아니라 코드의 해시값으로도 사용가능하다.

 

CSP 헤더는 위와 같이 구성할 수 있다. 앞서 스크립트를 링크로만 허용할 것인지, 임베딩된 코드도 허용할 것인지에 대해 정책을 설정할 수 있다고 언급하였다.  이때, 임베딩된 코드가 우리가 만든 것인지 아니면 공급자가 선택한 것인지를 구분하기 위해 서버에서만 알 수 있는 랜덤 값을 붙여주어 페이지를 만들 수 있다.

 

위 사진은 CSP 헤더를 설정하는 예제이다. default-src의 경우 js, 이미지, url, html 등의 전반적인 요소들은 self, 즉 자기자신으로부터 온 것만 수용하겠다는 의미이다. 또, script-src의 경우 self, nonce value가 설정된 임베딩된 코드나, example.com으로부터 온 것만 수용하겠다는 의미이다.

 

이러한 정책이 완벽한 것은 아니다. 위와 같은 정책을 우회할 수 있는 방법은 여러 가지가 공개되어 있다. 해당 방법들은 아래 링크를 참고하길 바란다.

https://book.hacktricks.xyz/pentesting-web/content-security-policy-csp-bypass

https://lactea.kr/entry/ctf-%EB%AC%B8%EC%A0%9C%EB%A5%BC-%ED%86%B5%ED%95%B4-CSP-bypass-%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0

 

Web-based Mobile Applications

최근 모바일 앱들도 웹 기반으로 주로 구현이 되어 있다. 이러한 앱도 웹 기반으로 구현되어 있기 때문에 XSS와 같은 공격이 가능하다. 위 사진을 보면 어플리케이션이 단독으로 동작하는 것이 아니라 여러 다른 앱의 입력을 받아 처리해서 보여줄 수 있다. 연락처를 불러와 카카오톡에 친구를 추가하는 것도 그 예가 될 수 있다.

 

입력을 여러 개로부터 받는 경우 그만큼 공격에 취약해질 수 있다. 예를 들어 와이파이의 AP 이름 세팅 시 스크립트 형태로 넣는 경우 실행될 수 있고, 메타 데이터에도 마찬가지로 악성 코드를 넣으면 그대로 실행될 수 있다.

 

웹 기반 어플리케이션이 구현 입장에서 플랫폼과 상관없이 독립적으로 웹을 이용하여 개발할 수 있다는 장점이 존재하지만, 그만큼 외부로부터 오는 입력이 많아져 공격할 수 있는 가능성이 커진다는, 즉 공격 벡터가 넓어질 수 있다는 단점도 존재한다.

 

https://youtu.be/lG7U3fuNw3A?si=PRto5-V_50mJagAZ

위 영상은 구글을 통해 해킹을 진행한 예시를 보여주고 있다.

반응형
LIST