그러냐

웹 보안 가이드라인 1. XSS - 초안 본문

php 시큐어코딩

웹 보안 가이드라인 1. XSS - 초안

관절분리 2017. 3. 20. 17:54
반응형

마크다운으로 작성해서 조금 이상하게 보이는 경우가 있는습니다. gist에도 올려놨습니다.  링크


XSS (Cross-Site Scripting)

크로스 사이트 스크립팅(Cross-Site Scripting, XSS)은 사용자의 입력값에 의해 자신 혹은 타인의 브라우저에 스크립트를 삽입하여 사용자를 공격하는 기법이다. "XSS 취약점이 없는 사이트는 없다."라고 할 만큼 많이 발생하는 취약점이다.

XSS는 공격 유형에 따라 크게 2가지로 분류할 수 있다. 첫 번째로 사용자의 입력값이 즉시 나타나는 비 지속적인 유형이다. 사용자의 입력을 그대로 반사한다는 의미의 Reflected XSS 기법이 이에 해당한다. 두 번째로 사용자의 입력이 데이터베이스(혹은 파일, 브라우저 리소스 등)에 저장되어 지속적으로 발생하는 유형이다. 사용자의 입력값을 저장한다는 의미의 Stored XSS 기법이 이에 해당한다. 한번 삽입되어 지속적으로 발생하는 유형이 비 지속적인 유형보다 높은 위험성을 가진다.

앞으로 XSS를 설명하기 위한 간단한 예시와 개발자가 놓치기 쉬운 XSS 발생 시나리오를 소개한다. 이후 XSS의 영향력과 대응방안 그리고 올바르지 않은 대응방안을 소개한다.

1. XSS 예시

index.html

<html>
 <head>
 <title>Koogle</title>
 </head>
  <body>
   <form method="get" action="search.php">
   Koogle:
   <input type="text" name="keyword">
   <input type="submit" value="submit">
   </form>
 </body>
</html>

search.php

<?php echo $_GET['keyword']; ?>

index.html에서 입력받은 파라미터를 search.php로 전달하여 출력하는 예시이다. 사용자의 입력값을 별도의 검사 없이 사용자에게 출력하고 있다. 이는 XSS의 2가지 유형 중 비 지속적인 유형에 속한다. 사용자의 입력값을 별도의 처리 없이 그대로 출력하는 것은 XSS를 발생시키기 위한 가장 쉬운 조건이다.

(하지만 실제로 위의 코드에 스크립트를 삽입하여도 작동하지 않는 경우가 있을 것이다. 이는 브라우저의 XSS protection에 의해서 필터링 되는 경우이다. 특정한 경우에 브라우저의 필터를 우회할 수 있기에 절대로 브라우저 필터로 안심하지 말 것!)

good_search.php

<?php echo htmlspecialchars($_GET['page']); ?>

good_search.php는 XSS를 방어하기 위하여 search.php를 수정한 코드이다. 사용자의 입력값을 PHP의 htmlspecialchars 함수를 사용하여 HTML Entity로 변환하여 출력했다. 사용자의 입력값을 치환하는 대응방안의 예시이다.

2. XSS 발생 사례

간단한 XSS의 예시를 설명했다. 하지만 XSS는 예상치 못한 다양한 상황에서 발생한다. 실제로 존재했던 사례를 재구성한 다양한 상황을 소스코드와 함께 실제환경에서 실습한다.

예시 코드는 전체 코드의 일부로 모든 예시의 전체 코드는 Docker에 포함되어 있다.
docker install ni5am/sg4d-xss
#service apache2 start
#service mysql start

2.1 JavaScript 영역에 출력되는 경우

사용자에게 입력받는 문자열 혹은 숫자를 JavaScript 영역에 출력하는 경우 XSS가 발생할 수 있다.

if (<?php echo $_GET['alert'];?>) {
    $('.alert').show()
} else {
    $('.alert').hide();
}

사용자의 GET 파라미터의 입력값(true, false)에 따라 경고창을 화면에 나타나는 예제 이다. 사용자가 조절 할 수 있는 파라미터에 XSS 공격코드를 작성하여 삽입해본다.

공격코드 예시 true){}alert('XSS test');if(1

공격코드를 서버에 전송하면 화면에 alert창이 뜬것을 확인 할 수 있다. 사용자의 입력값이 반사되는 Reflected XSS의 예시이기도 하다.

alert 창을 띄우는건 스크립트가 삽입될 가능성을 확인한 것이다. 만약 alert 창 대신에 악성 JavaScript를 삽입한다면 어떠한 공격이 가능할지 상상해보자.

예시와 같이 사용자의 입력값이 JavaScript에 출력되는 경우 브라우저의 XSS protection을 쉽게 우회할 수 있다. 따라서 XSS 공격을 막기 위해서는 브라우저의 XSS protection에 의존하면 안된다.

(참고 : 브라우저(IE 상위버전)에 따라 XSS protection에 차단될 수 있다.)

2.2 HTTP Reqeust Header에 삽입되는 경우

사용자의 입력값이 HTTP Header에 속하는 경우이다. Header를 통해 입력되는 값 역시 브라우저의 XSS protection을 우회할 수 있다.

다음 코드는 사용자의 HTTP Request Header인 User-Agent와 Referer를 출력하는 코드이다.

접속하신 브라우저는 : <?php echo $_SERVER['HTTP_USER_AGENT']; ?> 입니다.
접속하신 경로는 : <?php echo $_SERVER['HTTP_REFERER']; ?> 입니다.

공격 코드 예시 Referer: http://www.nisamlab.com:64321/ <svg/onload=alert(document.domain)>;

공격코드 예시를 Request Header의 Referer에 삽입하여 서버에 전송하면 화면에 alert창이 뜬다. 사용자의 입력값이 파라미터에만 입력된다는 고정관념을 깰 수 있는 사례이다.

2.3 HTTP Response Header에 출력되는 경우

사용자의 입력값이 Response Header 영역에 출력되는 경우 Header의 끝을 의미하는 CRLF(Carriage Return, Line Feed)을 삽입하여 다른 공격자가 원하는 Header를 삽입할 수 있다.

공격 예시

http://www.nisamlab.com?user=nisam%0D%0AX-XXS-Protection : 0%0D%0A%0D%0A<script>alert(document.domain)</script>

응답 예시

HTTP/1.1 200 OK
Host: www.nisamlab.com
Connection: close
Content-type: text/html
User : nisam
X-XXS-Protection : 0

<script>alert(document.domain)</script>
X-Powered-By: PHP/5.5.36

해당 예시는 사용자의 입력값이 Response Header에 삽입되는 예시다. 이와 같이 유저의 입력값이 Header 영역에 삽입될 때 CR와 LF(/r/n)를 주입하여 Response 헤더를 조작하고 Body에 스크립트를 삽입 할 수 있다.

2.4 JSON을 통해 전송되는 경우

사용자의 입력값을 JavaScript를 통해 JSON객체로 만들어 서버에 전송하여 처리하는 경우에도 XSS가 발생할 수 있다.

var data = new Object();
data.name = "switch";
data.state = state; // 사용자의 입력값

var dataJson = JSON.stringify(data);

$.ajax({
    url : 'json_api.php',
    type : 'POST',
    data : "switch="+dataJson,
    dataType:'json'
});

위의 예시 코드는 사용자의 입력을 JSON객체로 만들어 서버에 전송하는 예시이다. 별도의 조작이 없으면 JSON은{"name":"switch","state":false}와 같이 만들어진다. 공격을 위해 {"name":"switch","state":"<script>alert('XSS test');</script>}로 변조하여 전송하였다.

$json = json_decode($_POST['switch']);

if($stmt = $mysqli->prepare("INSERT INTO xss_json (session, state) VALUES (?, ?)")){

    $stmt->bind_param("ss", session_id(), $json->state);
    $stmt->execute();

    $stmt->close();
}

사용자가 Ajax로 전송한 데이터를 파싱하여 데이터베이스에 저장하는 예시이다.

지금 방에 불이 켜져 있나요 ? 컴퓨터 : <?php echo $state;?>

데이터베이스에서 유저가 입력한 값을 출력하는 예시이다. 공격코드가 저장되었다면 스크립트가 실행 될 것이다. 해당 예시는 사용자의 입력값이 데이터베이스에 저장되어 출력되는 Stored XSS이다.

사용자의 입력이 JavaScript가 만든 JSON객체를 통해 전송된다고 쉽게 조작할 수 없다고 생각하면 안된다. 사용자의 입력값은 언제나 변조될 수 있음을 염두해야한다.

(예시는 JSON으로 설명하였지만, 자체적인 포맷 혹은 XML 등으로 사용자의 입력을 JavaScript를 통해 컨트롤하는 경우에 모두 해당될 수 있다.)

2.5 Event Handler에 삽입되는 경우

사용자의 입력이 태그의 Event Handler에 삽입되어 XSS가 발생하는 사례이다.

function callComment() {
    $.ajax({
        url : 'ajax_api.php',
        type : 'GET',
        success : function (data) {
            $('#commentBox').append(data);
        }
    });
}

<a href="javascript:callComment()" class="btn btn-default" role="button">댓글을 가져와</a>

a태그를 누를때마다 새로운 댓글을 가져와 삽입하는 기능을 하는 예시이다.

<?php
// 데이터베이스에서 꺼내온 값의 예시
$id = 1;
$comment = '댓글입니다. ");alert("XSS Attack!';
?>
<div class="well">
    <?php echo htmlspecialchars($comment);?>. <a href="#" onclick='deleteComment("<?php echo $id;?>","<?php echo htmlspecialchars($comment);?>");'>삭제</a>
</div>

사용자의 요청을 반환하는 예시이다. 데이터베이스에서 댓글을 하나 꺼내와 내용을 HTML entity encoding하여 출력한다.

사용자의 입력값을 치환했으니까 안전할꺼라 생각할 수 도 있다. 하지만 취약점은 a tag의 onclick 이벤트 핸들러에서 발생한다.

<div class="well">
    댓글입니다. &quot;);alert(&quot;XSS Attack!. <a href="#" onclick='deleteComment("1","댓글입니다. &quot;);alert(&quot;XSS Attack!");'>삭제</a>
</div>

최종적으로 사용자가 응답받는 HTML이다. 응답값은 Jquery의 append를 통해 HTML에 삽입된다.

새로 추가된 댓글의 삭제 버튼을 누르면 공격 코드가 실행된다. 사용자의 입력값을 치환하였지만 이벤트 핸들러에 속하는 부분을 브라우저가 렌더링하여 원래 문자열로 돌아갔기 때문이다. 만약 이벤트 핸들러에 속하지 않고 자바스크립트 영역이나 HTML부분에 속했다면 정상적으로 방어할 수 있었을 것이다.

3. 영향력

XSS의 예시와 다양한 사례를 소개했다. 직접 경험한 것과 같이 XSS의 영향력은 브라우저의 자바스크립트로 할 수 있는 모든 행위이다. 대표적으로 쿠키를 가로채거나 사용자에게 특정한 행위를 유도 할 수 있다. 이는 CSRF(Cross-site request forgery)취약점과 연계될 수 있다. 그 외에도 브라우저 취약점을 이용한 악성코드 다운로드, 사용자 브라우징 감시, 사용자 리다이렉트와 같은 영향력이 있다.

4. 대응방안

4.1 사용자 입력값을 제한

사용자의 입력값을 제한하여 스크립트를 삽입하지 못하도록 하는 방법이다.

http://www.nisamlab.com?page=1

위와 같은 URL에서 사용자의 입력값인 page가 화면에 바로 뿌려지는 상황을 가정해보자. 이때 page의 값을 숫자만 허용한다면 사용자의 입력값에 숫자외의 값이 출력되지 않는다. 숫자 이외에도 사용자의 입력값이 정해져 있을때 사용자의 입력값을 제한하는 방법을 통해 XSS를 대응할 수 있다.

4.2 사용자의 입력값을 치환

HTML 입력이 필요 없다면 사용자의 입력을 치환하는 방법을 사용한다. 스크립트에서 주로 사용되는 특수문자를 치환한다.

< → &lt;
> → &gt;
" → &quot;
' → &#39;

위 박스는 스크립트에 주로 사용되는 특수문자와 해당 문자에 해당하는 HTML entity이다. 특수문자를 HTML entity로 치환하면 브라우저가 자체적으로 렌더링하여 특수문자로 보여지지만 스크립트로 실행되지 않게 방어할 수 있다.

4.3 스크립트 영역에 출력을 자제

JavaScript가 실행될 수 있는 영역인 스크립트 영역, 이벤트 핸들러 영역에 스크립트가 삽입되는 경우 보호기법들을 우회할 수 있다. 해당 영역에 사용자의 입력을 출력하는 것을 최대한 자제하고 필요한 경우 다른 대응방안과 함께 사용해야 한다.

4.4 XSS 필터 사용

유저에게 HTML을 입력받을때 주로 사용하는 방법이다. 다양한 방법으로 공격이 가능한 XSS는 블랙리스트 방식으로 방어하기에 굉장히 어렵다. 유저에게 허용된 HTML만 입력이 가능하도록 필터를 구성해야한다.

LucyXSS는 JAVA로 작성된 오픈소스이다. DOM을 파싱하여 사전에 입력된 화이트리스트 기반의 설정값에 따라 입력값을 청소해준다.

4.5 HTTP Security Header 헤더 사용

XSS 공격을 막기 위해 브라우저에서 구현된 기능들을 사용하기 위한 HTTP Header가 존재한다. 모든 헤더가 모든 브라우저와 모든 버전을 지원하지 않으므로 헤더의 설정은 보조적인 수단으로 사용해야한다.

4.5.1 X-XSS-Protection

헤더에서 설정한 값에 따라서 유저의 입력을 브라우저에서 필터한다.

옵션기능
X-XSS-Protection : 0브라우저의 XSS-Filter 비활성화
X-XSS-Protection : 1브라우저의 XSS-Filter 활성화
X-XSS-Protection : 1, mode=block브라우저의 XSS-Filter 비활성화
전체 페이지의 응답 내용을 # 문자로만 보여줌

대표적으로 구글과 트위터가 X-XSS-Protection: 1; mode=block 옵션을 사용하고 있다. 유저의 입력값이 그대로 노출되는 공격만 막을 수 있으므로 보조적인 수단으로 사용해야한다.

4.5.2 Content Security Policy(CSP)

CSP 헤더는 헤더에 설정된 경로에서 삽입되는 리소스만 허용한다. 현재 표준화 작업중에 있으며, 지원하는 브라우저 버전에 따른 헤더와 지시자는 Quick Reference Guide에서 확인 할 수 있다.

아래는 페이스북에서 사용하는 예시이다. content-security-policy: default-src * data: blob:;script-src *.facebook.com *.fbcdn.net *.facebook.net *.google-analytics.com (생략..); 이미지 태그와 같은곳에 삽입되는 리소스는 허용하지만 스크립트가 삽입될 수 있는 script 태그는 허용된 도메인에서만 입력이 가능하도록 설정한 예시이다. CSP 헤더를 잘 설정한다면 실제로 XSS가 발생하더라도 피해를 줄일 수 있다.

4.5.3 X-Frame-Options

현재 보여지는 페이지에서 사용하는 프레임을 제한하는 헤더이다. XSS공격을 통해 사용자에게 프레임을 삽입하는 2차 공격을 예방할 수 있다.

옵션기능
X-Frame-Options : DENY프레임을 사용하지 못한다.
X-Frame-Options : SAMEORIGIN같은 도메인의 프레임만 허용한다.
X-Frame-Options : ALLOW FROM https://example.com/설정된 도메인만 허용한다.

많은 웹사이트에서 X-Frame-Options: DENY 옵션을 기본으로 사용하고 있다.

5. 올바르지 않은 대응방안

쉽게 우회될 수 있는 올바르지 않은 대응방법이다.

5.1 잘못된 치환

사용자 입력을 잘못된 방법으로 치환하는 경우이다.

<?php
    echo str_replace("<script", "", $_GET['xss']);
?>

블랙리스트로 정의된 <script를 공백으로 치환하는 소스코드이다. 만약 입력값이 <scr<scriptipt>alert('test');</script>로 입력된다면 블랙리스트에 정의된 문자열이 공백으로 치환되면서 <script>alert('test');</script>라는 정상적인 공격구문을 완성한다. 이처럼 블랙리스트 방법을 통한 필터는 구현하기 어려우며 다양한 우회기법에 대응하기 어렵다.

6. 끝으로

XSS 공격의 다양한 방어방법과 공격방법을 소개했다. 간혹 XSS는 영향력이 없는 취약점이라 말하는 사람들이 있다. 하지만 JavaScript를 충분히 이해하고 있는 사람이라면 XSS의 위험도를 충분히 인지할 수 있을것이다.

끝으로 XSS를 잘 예방하기 위해서는 사용자가 직접 입력하는 값 뿐만 아니라 서버가 처리하는 모든 입력값을 검사하는 습관을 가져야 한다.

내용상 틀린부분이 있을 수 있습니다. 피드백 주시면 반영하도록 하겠습니다.

Thanks to wooeong, reset, regdoll



출처: http://nisam.tistory.com/8 [니삼 블로그]

반응형