[시큐어코딩 가이드] 크로스사이트 요청 위조(CSRF)

 

 

 

■ 입력데이터 검증 및 표현

 

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

 

 

크로스사이트 요청 위조(CSRF)

 

■ 개요

 

특정 웹사이트에 대해서 사용자가 인지하지 못한 상황에서 사용자의 의도와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 요청하게 하는 공격을 말한다. 웹 응용프로그램이 사용자로부터 받은 요청에 대해서 사용자가 의도한 대로 작성되고 전송된 것인지 확인하지 않는 경우 발생 가능하고 특히 해당 사용자가 관리자인 경우 사용자 권한관리, 게시물 삭제, 사용자 등록 등 관리자 권한으로만 수행 가능한 기능을 공격자의 의도대로 실행시킬 수 있게 된다.


공격자는 사용자가 인증한 세션이 특정 동작을 수행하여도 계속 유지되어 정상적인 요청과 비정상적인 요청을 구분하지 못 하는 점을 악용한다. 파이썬에서 가장 많이 사용하고 있는 Django 프레임워크와 Flask 프레임워크에서는 각각 CSRF 토큰 기능을 지원하고 있으며, Django는 {% csrf token %} 태그를 이용하여 CSRF 토큰 기능 제공하고 Flask에서는 Flask-WTF 확장 라이브러리를 통해 {{form.csrf_token}} 태그를 이용하여 CSRF 토큰 기능을 제공하여 태그를 사용하는 경우 안전하게 사용할 수 있다.

 

 

■ 안전한 코딩 기법

 

해당 요청이 정상적인 사용자의 정상적인 절차에 의한 요청인지를 구분하기 위해 세션별로 CSRF토큰을 생성하여 세션에 저장하고, 사용자가 작업페이지를 요청할 때마다 hidden값으로 클라이언트에게 토큰을 전달한 뒤, 해당 클라이언트의 데이터 처리 요청 시 전달되는 CSRF토큰값을 체크하여 요청의 유효성을 검사하도록 한다.


Django 프레임워크와 Flask 프레임워크는 미들웨어와 프레임워크에서 기본적으로 CSRF Token을 사용해서 CSRF 공격으로부터 보호하는 기능을 가지고 있다. 해당 기능을 사용하기 위해 form태그 내부에 csrf_token을 사용해야 한다.

 

 

코드예제

 

■ Django 프레임워크 사용

 

Django프레임워크에서는 1.2 버전부터 CSRF 취약점을 방지기능을 기본으로 제공하고 있다. 미들웨어의 CSRF 옵션을 비활성하거나 템플릿에서 csrf_exempt decorator를 사용하는 경우, 크로스사이트 요청 위조 공격에 노출될 수 있다.

 

⦁Django 미들웨어 설정(settings.py) 사례

안전하지 않은 코드의 예
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
# MIDDLEWARE 목록에서 csrf 항목을 삭제 또는 주석처리 하면
# Django 앱에서 csrf 유효성 검사가 전역적으로 제거 됨
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.locale.LocaleMiddleware',
......
]

 

다음은 Django의 csrf 기능을 활성화하기 위한 안전한 미들웨어 설정 예제 이다.
미들웨어의 csrf 기능을 주석, 또는 삭제 처리 하지 않아야 한다. 템플릿 페이지에는 csrf_token을 form 태그 안에 명시해야 미들웨어에서 정상적으로 csrf 기능을 사용할 수 있다.

 

안전한 코드의 예
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
# MIDDLEWARE 목록에서 csrf 항목을 활성화 한다.
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.locale.LocaleMiddleware',
......
]

 

SQLite DB-API 사용에서도 동일하게 정적인 쿼리문을 사전에 생성하고 사용자 입력을 바인딩 하여 안전하게 사용하여야 한다. SQLite에서 매개변수 화된 쿼리(Parameterized Query)를 만들기 위해 “?”를 Placeholder로 사용하거나 “:name” 처럼 Named Placeholder를 사용하는 방법 2가지가 있다.

 

 

⦁Django 뷰 기능 설정(views.py) 사례


미들웨어에 csrf 검증 기능이 활성화 되어 있어도 View에서 csrf기능을 해제 하는 경우에는 해당 요청에 대해서 csrf 검증기능을 사용하지 않게 된다.


다음은 Function-Based View에서 csrf 검증 기능을 비활성화 하는 예제이다.

 

안전하지 않은 코드의 예
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt

# csrf.exempt 데코레이터로 미들웨어에서 보호되는 CSRF 기능을 해제한다.
@csrf.exempt
def pay_to_point(request):
user_id = request.POST.get('user_id', '')
pay = request.POST.get('pay', '')
product_info = request.POST.get('product_info', '')
ret = pay(user_id, pay, product_info)
return render(request, '/view_wallet.html', {'wallet':ret})

 

Django는 기본적으로 CSRF 기능을 강제하고 있지만 몇몇 기능에 대해서 CSRF 기능을 해제 하여야 하는 경우는 미들웨어의 csrf 기능을 전역적으로 disable 하기 보다는 미들웨어의 csrf 기능은 활성화 하고 필요한 요청에 대해서만 csrf_exempt 데코레이터를 사용하여야 하고 이 경우에 크로스사이트 요청 위조의 위협에 노출될 수 있으므로 주의를 기울여야 한다.

 

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

# csrf_exempt 데코레이터를 삭제하거나 주석처리한다.
# @csrf_exempt
def pay_to_point(request):
user_id = request.POST.get('user_id', '')
pay = request.POST.get('pay', '')
product_info = request.POST.get('product_info', '')

ret = pay(user_id, pay, product_info)

return render(request, '/view_wallet.html', {'wallet':ret})

 

⦁Django 템플릿 설정 사례


미들웨어에서 csrf 기능을 활성화 하여도 템플릿 페이지에 csrf 토큰을 명시하지 않을 경우 csrf 검증 기능을 사용할 수 없다.

 

안전하지 않은 코드의 예
<!--html page-->
<form action="" method="POST">
<!-- form 태그 내부에 csrf_token 미적용-->
<table>
{{form.as_table}}
</table>
<input type="submit"/>
</form>

 

미들웨어에서 csrf 기능을 활성화한 후에 템플릿 페이지에서는 csrf_token 값을 명시하여야만 정상적인 csrf 검증 기능을 사용할 수 있다.

 

안전한 코드의 예
<!--html page-->
<form action="" method="POST">
{% csrf_token %} <!--csrf_token 사용->
<table>
{{form.as_table}}
</table>
<input type="submit"/>
</form>

 

■ Flask 프레임워크 사용

 

⦁Flask app 설정 사례


Flask의 WTF 패키지를 사용하면 CSRF 보호 기법을 사용 할 수 있다. 아래 예제 코드는 CSRF 설정이 되지 않은 상태이다

 

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

app = Flask(__name__)

 

Flask 프레임워크를 사용하여 웹 애플리케이션을 구축하는 경우, CSRF를 방지하려면 Flask-WTF extension의 CSRFProtect를 사용해야 한다. app에 설정하고 HTML(템플릿) 페이지에는 CSRF토큰을 추가한다.

 

안전한 코드의 예
from flask import Flask
from flask_wtf.csrf import CSRFProtect

# CSRF 설정 추가
csrf = CSRFProtect(app)
app = Flask(__name__)
app.config[‘SECRET_KEY’] = os.environ.get('SECRET_KEY')
csrf.init_app(app)

 

⦁Flask 템플릿 설정 사례

 

위 코드처럼 함수에 CSRF 기능을 활성화 하여도 Html 파일에 csrf_token을 명시 하지 않을 경우 CSRF 검증 기능을 사용할 수 없다.

 

안전하지 않은 코드의 예
<form action="" method="POST">
<!-- form 태그 내부에 csrf_token 미적용-->
<table>
{{as_table}}
</table>
<input type="submit"/>
</form>

 

템플릿 페이지에도 csrf_token 값을 명시해줘야 정상적인 csrf 검증이 수행된다.
FlaskForm 사용 시에는 {{ form.csrf_token }}을 명시해야 하고 템플릿에 FlaskForm을 사용하지 않을 경우에는 form 태그 안에 hidden input 값으로 {{ csrf_token }} 값을 명시해야 한다.

 

안전한 코드의 예
<form action="" method="POST">
<!-- form 태그 내부에 csrf_token 적용-->
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<table>
{{table}}
</table>
<input type="submit"/>
</form>

 

 

댓글

Designed by JB FACTORY