파이썬 웹 어플리케이션 보안 점검 가이드
📅 March 08, 2017
•⏱️5 min read
이 포스팅은 Jacob Kaplan-Moss가 2013년 호주 pycon에서 발표한 자료를 바탕으로 하며, OYT님이 최신화하여 정리해주신 자료를 참고하였습니다.
OWASP Top 10
먼저, OWASP(The Open Web Application Security Project)는 오픈소스 웹 애플리케이션 보안 프로젝트로, 주로 웹에 관한 정보노출, 악성 파일 및 스크립트, 보안 취약점 등을 연구하며, 웹 애플리케이션의 취약점 중에서 빈도가 많이 발생하고, 보안상 영향을 크게 줄 수 있는 것들의 10대 취약점들을 발표합니다. 보통 3년을 주기로 Top 10 리스트를 발표하는데 2017년에도 발표할 예정이라고 합니다. Top 10 항목들은 다음과 같습니다.
- injection
- Broken auth and session managment
- XSS(cross site scripting)
- Insecure direct object reference(bad url)
- Security misconfiguration(read official secret guideline)
- Sensitive data exposure
- Missing function-level access control(decorator)
- CSRF(Cross site request forgery)
- Components with known vulnerabilities(version check!)
- Unvalidated redirects
이제 파이썬 웹 어플리케이션에서 이를 어떻게 대응할 수 있는지 알아보겠습니다.
1. SQL injection
SQL 인젝션이란, 사용자가 입력한 값이 개발자가 의도치 않은 db query 결과를 초래하는 것, 또는 그것을 이용한 공격을 말합니다. 예를 들면, 아래와 같이 user_id를 string format 쿼리로 넣으면 이러한 공격에 취약할 수 있습니다.
@app.route("/user/<user_id>")
def show_user(user_id):
cur = db.cursor()
query = "SELECT * FROM user_table where user = %s"%user_id
c.execute(query)
return c.fetchall()
- 단순 string formatted된 query가 있는지 확인
- raw query 사용 시 bind parameter를 사용했는지 확인
2. Session Management
세션 데이터는 항상 안전하지 않다고 생각해야 합니다. db에 저장되더라도 안전하지 않은건 마찬가지입니다. 특히 예기치 않은 공격에 대비하기 위해 서버 측에서 세션을 관리 하는 것이 필요합니다.
Flask에서는 Flask-Session
이라는 확장 패키지를 통해 이를 쉽게 구현할 수 있습니다.
특히 PERMANENT_SESSION_LIFETIME
이라는 변수를 통해 일정 시간이 지나면 세션을 자동 파기할 수 있습니다.
- 코드 상에서 공격자가 직접 열람, 추가, 변경 할 수 없는 서버 단 세션 변수를 사용
- 일정 시간이 지나면 세션을 자동 파기
- 중복 로그인 방지
3. XSS (Cross-site-scripting)
XSS란, 웹페이지에 관리자가 의도하지 않는 스크립트(주로 javascript)를 사용자가 넣을 수 있는 상황을 말합니다. 예를 들면 비정상적인 페이지가 보이게하여 타 사용자의 사용을 방해하거나 쿠키 및 기타 개인정보를 특정 사이트로 전송하는 등의 문제가 이에 해당합니다.
@app.route('/hi/<user>')
def hi(user):
return "<h1>hello, %s!</h1>"%user
# 위와 같은 간단한 라우팅에서 아래와 같이 공격할 수 있습니다.
# GET /hi/alert("hacked!")
# <h1> hello, alert("hacked!") </h1>
# 이걸 본 유저는 javascript alert창이 나타난다
- 유저 입력을 받는 html코드는 항상 escape 할 것
- 아무리 똑똑해도 escape 기능을 끄지 말 것
4. Insecure Direct Object References
일명 직접 객체 참조, 또는 Bad url은 개발자가 파일, 디렉토리, DB 키와 같은 내부 구현 객체를 참조하는 것을 노출시킬 때 발생합니다. 아래의 코드를 통해 예를 들어보겠습니다.
# GET /jobs/application/6337
@app.route(/jobs/application/<job_id>)
def find_job(job_id):
SELECT * FROM job where id = job_id ...
# 대응방안으로는 flask-login 등을 사용하여 간접 참조하는 방법이 있습니다.
from flask.ext.login import login_required, current_user
@app.route("/mypage/<id>")
@login_required
def mypage(id):
...
- user_id 등 db와 직접 관계된 값을 GET parameter로 사용하고 있는지 확인
- 웹 방화벽에서 상위 혹은 하위 디렉토리로 이동하는 특수문자를 필터링할 것
- 파일을 다운로드하는 소스코드에서는 직접적으로 파일의 경로 및 파일명을 파라미터로 받는 것을 피할 것
5. Security Misconfiguration
기본으로 제공되는 값은 종종 안전하지 않기 때문에 보안 설정은 정의, 구현 및 유지되어야 합니다. 대표적으로 코드 난독화 가 이에 해당합니다.
파이썬에서는 Base64
패키지를 통해 Encoding/Decoding 하는 방법이 있습니다.
더 나아가 pycrypto
패키지를 사용하면 Crypto 모듈을 통해 AES 알고리즘으로 암호화할 수 있습니다.
- http://flask.pocoo.org/docs/security/ 를 정독해볼 것
- debug 모드를 off 하였는지 확인
6. Sensitive data exposure
많은 웹 애플리케이션들이 신용카드, 개인 식별 정보 및 인증 정보와 같은 중요한 데이터를 제대로 보호하지 않습니다. 공격자는 신용카드 사기, 신분 도용 또는 다른 범죄를 수행하는 등 약하게 보호된 데이터를 훔치거나 변경할 수 있습니다. 따라서, 중요 데이터가 저장 또는 전송 중이거나 브라우저와 교환하는 경우 특별히 주의하여야 하며, 암호화해야 합니다.
REST API에서는 JSON으로 데이터를 통해 통신하기 때문에 JWE (JSON Web Encryption) 를 통해 JSON을 암호화해주는 방법이 있습니다.
파이썬의 python-jose
또는 PyJWE
를 참고하시면 쉽게 구현할 수 있습니다.
JOSE(Javascript Object Signing and Encryption)
규격을 통해 데이터 암호화- 모든 암호를 bcrypt or PBKDF2로 해시 암호화 하였는지 확인
- password 생성 rule을 정하였는지 확인
7. Missing function-level access control
요청에 대해 적절히 확인하지 않을 경우 공격자는 적절한 권한 없이 기능에 접근하기 위한 요청을 위조할 수 있게 됩니다. 따라서, 관리자 페이지 등에 대하여 유저 권한을 클라이언트가 아닌 서버에서 판단 해야 하며 역할에 기반한 별도의 인증절차를 요구하도록 만들어야 하고 권한이 없는 유저들은 접근 불가하게 코딩해야 합니다.
@app.route("/mypage/<id>")
@jwt_required(scope='admin')
def mypage(id):
...
- 로그인, 토큰 등 접근 관련 flow에서 hard-coding하지 않고
decorator
등을 사용했는지 확인 - OAuth와 같이 Token 기반의 인증시스템을 통해 접근 권한을 부여
8. CSRF(Cross site request forgery)
CSRF는 로그인 된 사용자의 웹 애플리케이션에 세션 쿠키와 기타 다른 인증정보를 자동으로 포함하여 위조된 HTTP 요청을 강제로 보내도록 하는 것입니다. 공격자는 주로 상태(DB, 세션 등)를 변경하는 공격을 합니다. XSS를 방지하면 어느정도 커버되기도 합니다.
<!-- 버튼이나 input을 제출하면 주식이 팔린다! -->
<form action='/stock/sell' method='get'>
<input type=submit value=sell_stock>
</form>
<a href="/stock/sell/"> click me!</a>
<form action='/stock/sell' method='post'>
<input type=submit value=sell_stock>
</form>
<!-- 플라스크에서는 wtf 패키지를 통해 입력 폼을 검증하고 CSRF를 방지가능 -->
<form method="POST" action="/">
{{ form.csrf_token }}
{{ form.name.label }} {{ form.name(size=20) }}
<input type="submit" value="Go">
</form>
- GET 요청으로 상태를 바꾸는 것이 없는지 확인
- POST에 CSRF Token을 달아놓았는지 확인 (공격자가 주입해도 서버에서 거절)
9. Components with known vulnerabilities
컴포넌트, 라이브러리, 프레임워크 및 다른 소프트웨어 모듈은 대부분 항상 전체 권한으로 실행되어 취약한 컴포넌트를 악용하여 공격하는 경우 심각한 데이터 손실이 발생하거나 서버가 장악될 수 있습니다.
- outdated 된 라이브러리가 있는지, 지속적인 업데이트
- 사용되지 않는 기능들을 최대한 비활성화 할 것
10. Unvalidated redirects
웹에서 종종 사용자들을 다른 페이지로 리다이렉트 하거나 포워드하기 위해 신뢰할 수 없는 데이터를 사용하는 경우가 많습니다. 적절한 검증 절차가 없으면 공격자는 피해자를 피싱 또는 악성코드 사이트로 리다이렉트 될 수 있기 때문에 주의해야 합니다.
- CORS(cross origin resource sharing)시 host whitelist를 관리하고 있는지
- flask-redirect는 언제나 안전하지 않음을 인지하고 사용하고 있는지
정리 및 관련 링크
저도 그렇고 학생 때는 대부분 보안을 고려하지 않은 웹 어플리케이션을 개발하는 경우가 많은데, 이 자료가 많은 도움이 되었으면 좋겠습니다!