본문 바로가기
개발자 일지/Phython

[파이썬 기초]XSS(Cross-Site Scripting) 개념 및 방어 방법

by 네빌링 2021. 4. 4.
반응형

-보안공격인 XSS(Cross-Site Scripting)에 대해 알아보고 이를 파이썬에서 막는 방법을 살펴본다.

-CSRF(Cross-Site Request Forgery)와의 차이점을 간단하게 살펴본다.

-생활코딩 파이썬 강의를 바탕으로 학습 및 보충학습 하였다.

-파이썬은 3.9.2 ver를 사용한다.


XSS란?

 가장 흔한 보안 공격 중 하나로 공격자가 웹사이트에 스크립트 태그(<script/>)를 사용하여 쿠키탈취나 특정 코드를 실행하여 공격하는 보안 공격을 말한다. 학습 중 CSRF(Cross-Site Request Forgery)라는 보안 공격이 생각나서 차이점을 찾아보았다.

 

CSRF의 개념 및 XSS와 CSRF 차이점

 

 CSRF는 해석하면 사이트 간 요청 위조이다. 사용자가 자신의 의지와 무관하게 공격자가 의도한 특정 행위(수정,삭제,등록 등)을 특정 웹사이트에 요청하게 하는 공격이다. 

 

차이점은 다음과 같다.

 

XSS CSRF
1.공격대상이 클라이언트이다.
2.사이트변조, 백도어 등으로 클라이언트에 대한 악성공격
1.공격대상이 서버이다.
2.요청을 위조하여 사용자 권한을 이용해 서버에 대한 악성공격

 

XSS 공격 예시

 아래와 같이 글을 쓴 후 저장해본다.

 

description 부분에 스크립트 태그로 alert함수 삽입

 

 저 상태에서 제출(저장)을 하면 스크립트 코드가 실행되어 생성된 XSS Attack 목록을 누를 때마다 'XSS Attack!'이라는 alert창이 뜨게 된다.

 

XSS Attack 목록 클릭시마다 얼럿 발생

 

 위와 같이 스크립트 태그가 실행된다는 점을 응용하여 특정 사이트로 보내버리거나(location.href) 쿠키를 보여주는 등 (document.cookie) 여러가지 보안 공격을 할 수 있다. 보통 웹사이트들은 이런 취약 공격에 대처하기 위해 여러가지 XSS 방어 방법을 사용하여 보안을 강화한다.

 

XSS 방어 방법 - 치환하기

 가장 기본적인 방법으로 각 언어마다 갖고 있는 replace() 함수를 활용하여 '<'나 '>' 등의 특수문자를 HTML Entity(HTML 문자참조)로 변환하여 사용하는 것이다. HTML Entity는 특정한 문자들(< & " , >등)이 HTML 태그로 예약되어 있기 때문에 마크업문자와 충돌을 방지하기 위해 쓰이는 문자들이다. 예를들어 <는 &lt;라고 입력하면 실제 사용자에게는 <로 보여지지만 HTML 문서에서는 &lt;라고 보여지기 때문에 일반문자로 인식되어 스크립트 실행이 되지 않는다. 

 

파이썬 예제에서 특수문자 치환은 아래와 같이 한다.(이전 파이썬 포스팅 예제 이어서 사용)

 

index.py 일부

#3.a태그 클릭시 id값에 따른 pageId 분기처리
form = cgi.FieldStorage()
if 'id' in form:
    title = pageId = form["id"].value
    description = open('data/'+pageId,'r').read() #open함수로 내용 불러오기
    description = description.replace('<','&lt;') #XSS공격을 막기 위한 특수문자 치환
    description = description.replace('<','&gt;') #XSS공격을 막기 위한 특수문자 치환

    update_link = '<a href="update.py?id={}">update</a>'.format(pageId)
    delete_action= '''
        <form action="process_delete.py" method="post">
            <input type="hidden" name="pageId" value="{}">
            <input type="submit" value="delete">
        </form>
    '''.format(pageId)  #delete버튼을 form방식으로 만든다
else :
    title = pageId = 'welcome'
    description = 'Welcome!'
    update_link = ''
    delete_action = ''

 

 예제의 index.py에서 목록메뉴를 누를 때마다 실행되는 코드이다. open() 함수로 파일 내용을 읽어온 후, replace 처리를 해주어 특수문자를 일반문자로 해석하게 변경한 후 보여준다. 이러면 script태그가 실행되지 않고 아래와 같이 일반문자로 보이게 된다.

 

스크립트 태그로 인한 alert이 실행되지 않고 아래와 같이 그냥 일반문자로 보여지게 된다

 

 

XSS 방어 방법 - HTML_Sanitizer 사용하기

 XSS를 방어하기 위해 html_sanitizer라는 패키지를 사용할 수도 있다. 이를 사용하기 위해서 Pypi에서 패키지를 다운로드 받아야 한다.

 

 


PypiPython Package Index의 약자로 파이썬으로 만든 각종 패키지들을 다운로드 받을 수 있는 저장소이다. pip라는 명령어를 통해 Pypi에서 원하는 패키지를 받을 수 있다.


 

 윈도우 사용자이기 때문에 CMD창에서 다음과 같이 pip install html_sanitizer라고 입력하면 설치된다. 이미 설치한 경우 아래와 같이 Requirement already satisfied라고 뜬다.

 

 설치할 때 defaulting to user installation because normal site-packages is not writeable라는 에러가 발생하였었는데, 관리자 권한으로 실행하니 설치가 되었다. 또한 설치 이후에 제대로 패키지가 실행되지 않았는데, 기존에 이전 버전 파이썬이 중복 설치되어 있었다. 이런 점들을 잘 확인한 후 설치하면 정상적으로 설치가 될 것이다.

 

 아래와 같이 index.py 일부를 html_sanitizer를 사용하여 수정한다.

 

index.py 일부

#1.모듈
import cgi, os, view, html_sanitizer #html_sanitizer를 import
sanitizer = html_sanitizer.Sanitizer() #sanitizer사용


#3.a태그 클릭시 id값에 따른 pageId 분기처리
form = cgi.FieldStorage()
if 'id' in form:
    title = pageId = form["id"].value
    description = open('data/'+pageId,'r').read() #open함수로 내용 불러오기
    #description = description.replace('<','&lt;')
    #description = description.replace('<','&gt;')
    description = sanitizer.sanitize(description) #sanitizer사용

    update_link = '<a href="update.py?id={}">update</a>'.format(pageId)
    delete_action= '''
        <form action="process_delete.py" method="post">
            <input type="hidden" name="pageId" value="{}">
            <input type="submit" value="delete">
        </form>
    '''.format(pageId)  #delete버튼을 form방식으로 만든다
else :
    title = pageId = 'welcome'
    description = 'Welcome!'
    update_link = ''
    delete_action = ''

 

 html_sanitizer를 import한 후, description을 sanitize함수 인자로 넣어서 사용한다. replace치환없이도 XSS가 실행되지 않는 것을 확인할 수 있다. 또한 title이나 기타 XSS공격을 받을 수 있는 부분은 모두 처리해주는 것이 좋다. 최종적인 index.py는 다음과 같다.

 

index.py

#!Python
print("Content-Type: text/html")
print()

#1.모듈
import cgi, os, view, html_sanitizer #html_sanitizer를 import
sanitizer = html_sanitizer.Sanitizer() #sanitizer사용


#3.a태그 클릭시 id값에 따른 pageId 분기처리
form = cgi.FieldStorage()
if 'id' in form:
    title = pageId = form["id"].value
    description = open('data/'+pageId,'r').read() #open함수로 내용 불러오기
    #description = description.replace('<','&lt;')
    #description = description.replace('<','&gt;')
    description = sanitizer.sanitize(description)  #sanitizer사용
    title = sanitizer.sanitize(title) #sanitizer사용

    update_link = '<a href="update.py?id={}">update</a>'.format(pageId)
    delete_action= '''
        <form action="process_delete.py" method="post">
            <input type="hidden" name="pageId" value="{}">
            <input type="submit" value="delete">
        </form>
    '''.format(pageId)  #delete버튼을 form방식으로 만든다
else :
    title = pageId = 'welcome'
    description = 'Welcome!'
    update_link = ''
    delete_action = ''

#4.화면 그리기
print('''<!doctype html>
<html>
<head>
  <title>WEB1 - Welcome</title>
  <meta charset="utf-8">
</head>
<body>
  <h1><a href="index.py">WEB</a></h1>
  <ol>
    {listStr}
  </ol>
  <a href="create.py">create</a>
  {update_link}
  {delete_action}
  <h2>{title}</h2>
  <p>{desc}
  </p>
</body>
</html>
'''.format(
    title=title,
    desc=description,
    listStr=view.getList(), #view모듈에서 getList() 호출
    update_link=update_link,
    delete_action=delete_action))

 

 

 

 

출처 : blog.outsider.ne.kr/380, glasgowkiss.tistory.com/16, book.coalastudy.com/python-basic/6/stage-3-pip, www.kisa.or.kr/uploadfile/201312/201312161355109566.pdf

 

반응형