[시큐어코딩 가이드] 크로스 사이트 스크립트(XSS)

 

 

 

■ 입력데이터 검증 및 표현

 

프로그램 입력 값에 대한 검증 누락 또는 부적절한 검증, 데이터의 잘못된 형식지정, 일관되지 않은 언어셋 사용 등으로 인해 발생되는 보안약점으로 SQL 삽입, 크로스사이트 스크립트(XSS) 등의 공격을 유발할 수 있다.

 

 

크로스 사이트 스크립트 (XSS)

 

■ 개요

 

크로스사이트 스크립트 공격(Cross-site scripting Attacks)은 웹사이트에 악성 코드를 삽입하는 공격 방법이다. 공격자는 대상 웹 응용 프로그램의 결함을 이용하여 악성코드(일반적으로 클라이언트 측 JavaScript 사용)를 사용자에게 보낸다. XSS 공격은 일반적으로 애플리케이션의 호스트 자체를 대상으로 하지 않고 애플리케이션의 사용자를 목표로 삼는다.


XSS는 공격자가 웹 응용프로그램을 속여 사용자의 브라우저에서 실행할 수 있는 형식의 데이터를 보 때 발생한다. 일반적인 HTML과 공격자가 제공한 XSS코드의 조합 뿐만 아니라 악성코드 다운로드, 플러그인 또는 미디어 콘텐츠를 이용하기도 한다. 사용자가 양식에 입력한 데이터 또는 서버에서 클라이언트 소프트웨어(브라우저 또는 WebKit등)의 종료점(endpoint)으로 전달된 데이터가 적절한 검증 없이 사용자에게 표시되도록 허용하는 경우 발생한다.


XSS공격은 크게 세가지 유형의 공격방법이 있다.


⦁유형 1 : Reflective XSS (or Non-persistent XSS)
- Reflective XSS는 공격 코드를 사용자의 HTTP 요청에 삽입시킨 뒤, 해당 공격 코드가 서버 응답 내용에 그대로 반사 (Reflected)되어 브라우저에서 실행시키는 공격기법이다. Reflective XSS를 악용하려면 공격자가 사용자 속여 대상 사이트로 데이터를 보내도록 해야 한다. 이 방법은 종종 사용자가 악의적으로 제작된 링크를 클릭하도록 속임으로써 수행 된다.

 

대부분의 경우 Reflective XSS공격 메커니즘은 공개적으로 게시되거나 피해자 전송되는 피싱(Phishing) 이메일 또는 단축 URL 또는 모호한 URL에 매개 변수로 포함하는 방법이다. 이러한 방식으로 구성된 URL은 많은 피싱 체계의 핵심을 구성하며, 공격자는 피해자가 취약한 사이트를 참조하는 URL을 방문하도록 유도한다. 피해자가 링크를 방문하면 스크립트가 피해자의 브라우저에서 자동으로 실행됩니다.


⦁유형 2 : Persistent XSS (or Stored XSS)
- Persistent XSS는 신뢰할 수 없거나 확인되지 않은 사용자 입력이 대상 서버에 저장될 때 발생한다. Persistent XSS의 일반적인 대상에는 게시판 글, 댓글 또는 방문자 로그가 포함된다. 이 기능들은 인증되거나 인증되지 않은 다른 사용자가 공격자의 악성 콘텐츠를 볼 수 있는 기능이다. 소셜(Social) 미디어 사이트 및 회원 그룹에서 흔히 볼 수 있는 것과 같이 공개적으로 표시되는 프로필 페이지는 Persistent XSS의 손쉬운 공격 대상 중 하나입니다. 공격자는 프로필 입력박스에 악성 스크립트를 입력할 수 있으며 다른 사용자가 프로필을 방문하면 브라우저에서 자동으로 코드가 실행되도록 할 수 있다.

 

⦁유형 3 : DOM 기반 XSS (or Client-Side XSS)
- DOM 기반 XSS은 공격 스크립트가 포함된 악성 URL을 통해 전달되는 경우가 많기 때문에 Reflective XSS와 어느정도 유사하다. 그러나 신뢰할 수 있는 사이트의 HTTP 응답에 페이로드를 포함하는 대신 DOM 또는 문서 개체 모델을 수정하여 브라우저에서 독립적으로 완전한 공격을 실행한다. 이는 웹 페이지에 있는 사용자 입력 값을 적절하게 처리하기 위한 JavaScript의 검증 로직을 무효화 하는 것을 목표로 한다.


악성 스크립트가 삽입되면 공격자는 다양한 악성 활동을 수행 할 수 있다. 공격자는 세션 정보를 포함 한 쿠키와 같은 개인 정보를 피해자의 컴퓨터에서 공격자에게 전송할 수 있다. 공격자는 피해자의 정로를 사용하여 웹 사이트에 악의적인 요청을 보낼 수 있으며, 피해자가 해당 사이트를 관리 할 수 있는 관리자 권한이 있는 경우 사이트에 특히 위험할 수 있다. 피싱(Phishing) 공격은 신뢰할 수 있는 웹 사이트를 모방하고 피해자가 암호를 입력하도록 속여 공격자가 해당 웹 사이트에서 피해자의 계정을 손상시킬 수 있다.


파이썬에서 가장 많이 사용하고 있는 Django 프레임워크와 Flask 프레임워크에서는 각각 Django 템플릿과 Jinja2 템플릿을 사용할 시 크로스사이트 스크립트 공격에 사용될 수 있는 위험한 HTML 문자에 대해 HTML 특수문자 (HTML Entities)로 치환하는 기능을 제공하고 있어 프레임워크에서 제공하는 템플릿을 사용하는 경우 안전하게 사용할 수 있다.

 

■ 안전한 코딩 기법

 

외부 입력값 또는 출력값에 스크립트가 삽입되지 못하도록 문자열 치환 함수를 사용하여 &<>*‘/()등을 &amp; &lt; &gt; &quot; &#x27; &#x2F; &#x28; &#x29;로 치환하거나, html라이브러리의 escape()를 사용한다.

 

HTML 태그를 허용하는게시판에서는 허용되는 HTML 태그들을 화이트리스트로 만들어 해당 태그만 지원하도록 한다. 파이썬에서 가장 많이 사용하는 프레임워크인 Django, Flask등을 사용하는 경우 외부 입력값에 악의적인 스크립트가 삽입되지 못하도록 프레임워크 자체에서 크로스사이트 스크립트 공격에 사용될 수 있는 문자를 HTML 특수문자(HTML Entities)로 치환하여 응답 페이지를 생성하므로 크로스사이트 스크립트로부터 보호된다.

 

프레임워크에서 크로스사이트 스크립트 공격으로부터 보호해 주는 기능이 있더라도 완전하지 않은 경우도 있고 개발자의 실수로 보호기능이 무효화 되는 경우가 있어 주의를기울여야 한다.

 

 

코드예제

 

■ Django 예제

 

Django 프레임워크는 크로스사이트 스크립트 공격에 대한 보안기능을 내장하고 있지만 유의해야 할 사항이 몇 가지 있다. Django의 “safestring(django.utils.safestring)”의 기능을 오용할 경우 Django의 크로스사이트 스크립트 보호 정책이 무력화 될 수 있다.

 

안전하지 않은 코드의 예
from django.shortcuts import render
from django.utils.safestring import mark_safe

def profile_link(request):
# 외부 입력값을 검증 없이 HTML 태그 생성의 인자로 사용
profile_url = request.POST.get('profile_url')
profile_name = requst.POST.get('profile_name')

object_link = '<a href="{}">{}</a>'.format(profile_url, profile_name)
# mark_safe함수는 Django의 XSS escape 정책을 따르지 않는다.
object_link = mark_safe(object_link)

return render(request, 'my_profile.html',{'object_link':object_link})

 

Django 프레임워크는 템플릿 생성 시 HTML에서 위험한 것으로 간주되는 특수 문자(“<”, “>”, “‘”, “””, “&”)를 모두 html 엔티티로 치환 하지만 mark_safe를 사용할 경우 이 정책을 따르지 않는다.


따라서 mark_safe 함수를 사용 할 경우에는 각별한 주의가 필요하고 신뢰할 수 없는 데이터에 대해서는 mark_safe 함수를 사용하지 않는다.

 

안전한 코드의 예
from django.shortcuts import render

def profile_link(request):
# 외부 입력값을 검증 없이 HTML 태그 생성의 인자로 사용
profile_url = request.POST.get('profile_url')
profile_name = requst.POST.get('profile_name')

object_link = '<a href="{}">{}</a>'.format(profile_url, profile_name)
# 신뢰 할 수 없는 데이터에 대해서는 mark_safe 함수를 사용하지 않는다

return render(request, 'my_profile.html',{'object_link':object_link})

 

다음은 Django 프레임워크에서의 템플릿 예제이다. autoescape 블록을 사용 시 설정값을 off로 할 경우와 개별 변수에 대해서 safe 필터를 사용할 경우 크로스사이트 스크립트 공격에 노출될 수 있다.

 

안전하지 않은 코드의 예
<!doctype html>
<html>
<body>
<div class="content">
{% autoescape off %}
// autoescape off로 해당 블록내의 데이터는 XSS 공격에
// 노출될 수 있음.
{{ content }}
{% endautoescape %}
</div>
<div class="content2">
//safe 필터 사용으로 XSS 공격에 노출될 수 있음.
{{ content | safe }}
</div>
</body>
</html>

 

신뢰할 수 없는 입력값 또는 동적 데이터에 대해서는 autoescape 옵션 값을 on으로 하고 safe 필터를 부득이 하게 사용할 경우 추가적인 보안대책이 필요하다.

 

안전한 코드의 예
<!doctype html>
<html>
<body>
<div class="content">
{% autoescape on %}
// autoescape on로 해당 블록내의 데이터는 XSS 공격에
{{ content }}
{% endautoescape %}
</div>
<div class="content2">
//검증되지 않은 데이터에는 safe 필터를 사용하지 않는다.
{{ content }}
</div>
</body>
</html>

 

autoescape 블록을 사용할 경우 많은 주의를 기울여야 한다. autoescape 옵션값을 off로 설정한 템플릿 페이지를 include 또는 extends하는 템플릿까지 영향이 확장된다. 공통적으로 사용하는 템플릿페이지에 off로 설정할 경우 많은 템플릿 페이지가 크로스사이트 스크립트 공격에 노출될 수 있다.

다.

 

 

■ Flask에서의 예제

 

사용자의 요청에 포함된 값 또는 DB에 저장된 값 또는 내부의 연산을 통해서 생성된 값을 포함 한 값은 동적 웹 페이지 생성에 사용하는 경우 크로스사이트 스크립트(XSS) 공격이 발생할 가능성이 있어 위험하다. 아래 예제는 Flask 프레임워크를 사용한 안전하지 않은 예제이다.

 

안전하지 않은 코드의 예
from flask import Flask, request, render_template

@app.route('/search', methods=['POST'])
def search():
search_keyword = request.form.get('search_keyword')
# 사용자의 입력을 아무런 검증 또는 치환 없이 동적 웹 페이지에 사용하고 있어
# 크로스 사이트 스크립트가 발생할 수 있다
return render_template('search.html', search_keyword=search_keyword)

 

동적 웹 페이지 생성에 사용되는 데이터를 HTML 엔티티 코드 (Entity Code)로 치환하여 안전하게 표현하여야 한다. html.escape 메소드는 문자열의 &, < 및 > 특수문자를 HTML에서 안전한 값으로 변환한다. 옵션 값의 quote 값이 True이면 문자 (“)와 (‘)도 변환된다. <a href=”…“>에서처럼 따옴표로 구분된 HTML 어트리뷰트 값에 포함하는 문자열을 포함할 경우 사용할 수 있다.

 

안전한 코드의 예
import html
from flask import Flask, request, render_template

@app.route('/search', methods=['POST'])
def search():
search_keyword = request.form.get('search_keyword')

# 동적 웹 페이지 생성에 사용되는 데이터는
# HTML 엔티티코드로 치환하여 표현해야한다
escape_keyword = html.escape(search_keyword)
return render_template('search.html', search_keyword=escape_keyword)

 

 

댓글

Designed by JB FACTORY