템플릿에 auto-escape (htmlspecialchars 자동 적용) 기능 추가#1267
Closed
kijin wants to merge 3 commits into
Closed
Conversation
Contributor
|
👍 이러한 방식의 접근은 생각해 보지 못했어요. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
비번 암호화 방식과 세션 쉴드에 이어서, 보안관련 깜짝PR 제3탄입니다.
(주의: 스크롤 압박 쩔어요.)
실제 코드가 준비되지 않은 상태에서는 심각한 변화를 제안하지 않는다는 개인적인 원칙에 따라, 이슈가 아닌 PR로 올립니다. 반드시 이 코드로 적용해 달라는 의도가 아닙니다. 첨부된 커밋은 논의를 시작하기 위한 proof of concept일 뿐입니다. 상세한 문법이나 구현방식은 필요에 따라 얼마든지 바뀔 수 있습니다.
개요
최근 XE의 보안패치들을 보면 XSS(크로스 사이트 스크립팅) 문제가 절반 이상을 차지하고 있습니다. 사용자가 어딘가에 스크립트를 삽입할 수 있는 취약점이죠. 가장 흔한 종류의 취약점이기도 하지만, 여차하면 관리자 세션 탈취로 이어질 수 있으므로 가장 위험한 취약점이기도 합니다.
XSS 취약점을 방지하기 위해서는 사용자가 입력하고 화면에 출력되는 모든 변수에서
<script>처럼 위험한 태그 또는 속성들을 제거해야 합니다. 닉네임이나 글 제목처럼 HTML 사용이 필요없는 경우에는htmlspecialchars()로 모든 태그를 무력화시키고, 글 내용처럼 HTML 사용이 필요한 경우에는 안전한 태그와 위험한 태그를 구분하는 필터링 작업이 필요하죠. 현재 XE는 두 가지 모두 잘 되고 있지만, 들어오고 나가는 변수가 워낙 많다 보니 가끔씩 필터링되지 않은 변수가 그대로 출력되어 XSS 취약점으로 이어지기도 합니다.최근 해외에서 들어오는 프레임워크들은 이런 문제를 원천봉쇄하기 위해 화면에 출력하는 모든 변수를 기본적으로 필터링(escape)하고, 글 내용처럼 반드시 필요한 경우만을 예외로 취급하는 추세입니다. 혹시 변수 하나를 잊고 필터링하지 않았더라도 안전하도록 만드는 거죠.
사실 일반적인 템플릿에서 100개의 변수를 출력한다고 해도 그 중 HTML 태그 보존이 필요한 경우는 1
2개뿐이고, 나머지는 모두 필터링해 버려도 되거든요. 12개의 특별한 변수 때문에 모든 변수를 XSS의 위험에 노출시킬 필요는 없다는 것이 핵심입니다.auto-escape가 적용된 프레임워크에서는 아래와 같은 템플릿 문법이
아래와 같이 단순한
echo문으로 컴파일되는 것이 아니라,아래와 같이 필터링 함수를 호출하는 형태로 컴파일됩니다.
만약 HTML 태그를 그대로 출력해야 한다면 아래와 같이 특별한 문법을 사용해서
이 변수는 필터링(escape)해주지 않아도 된다는 것을 템플릿 핸들러에게 알려줍니다. (물론 해당 변수는 사전에 확실히 필터링을 해줬어야겠죠?)
이런 기능을 흔히 auto-escape라고 부릅니다.
적용 방식 선택
그런데 지금의 XE에 auto-escape를 적용한다면 큰 문제가 발생할 것이 분명합니다. 오랫동안 auto-escape 기능 없이 템플릿을 작성해 왔기 때문이죠. 당장 엄청나게 많은 모듈과 위젯, 스킨들이 HTML을 표시하지 못해 난리가 나겠죠.
그래서 일괄적으로 적용하는 것은 어렵고, 템플릿 작성자가 원하는 경우에만 auto-escape를 적용할 수 있도록 선택권을 주는 것이 바람직할 것 같습니다. 일단 코어에 관련 문법을 추가해 두고, 코어에 포함된 템플릿들부터 auto-escape를 사용하도록 점차 전환하고, 새로 작성되는 써드파티 템플릿들도 auto-escape를 사용하도록 권장하면... 시간은 좀 걸리더라도 별다른 문제 없이 XSS 취약점을 많이 막을 수 있겠죠.
해외 PHP 개발자들이 많이 쓰는 Twig 템플릿 엔진, 파이썬 사용자라면 한 번쯤 들어봤을 Django 프레임워크도 비슷한 방식을 취하고 있습니다. 각 템플릿에서 auto-escape 기능을 사용하겠다고 선언할 수 있고, 선언하지 않으면 적용되지 않고, 위에서 선언했더라도 중간에 잠깐 중지했다가 다시 사용할 수 있죠.
예를 들어 Django에서는 아래와 같은 템플릿을 작성하면
var1은 자동으로 필터링되지만var2는 기존 방식대로 그대로 출력됩니다.물론 템플릿 파일의 맨 위에서 선언하면 그 파일 전체에 적용이 되죠. 마치 최근 자바스크립트에서
"use strict"를 선언하거나 예전 VB6에서Option Strict를 선언하던 것 같이... (오 추억의 VB6 ㅋㅋㅋ) 중간에 계속 껐다 켰다 하는 것보다는, 가능하면 파일 전체에 적용하는 것을 권장하기도 하고요.아무튼 XE도 이런 방식으로 적용하는 것이 기존 템플릿과의 호환성 유지를 위해 꼭 필요할 것 같습니다.
템플릿 핸들러 건드려보기
위와 같은 문법을 XE의 템플릿 핸들러에 적용해 본 것이 이 PR입니다. 가능한 XE 고유의 문법과 잘 어울리도록 해보았지만, 더 좋은 의견이 있다면 언제든지 바꿀 수 있겠고요...
우선,
<load>나<include>처럼 XE의 템플릿 핸들러에게 특별한 의미를 갖는 태그 하나를 더 추가해 보았습니다. auto-escape 외에도 앞으로 다양한 용도로 사용할 것을 염두에 두고...위와 같이 해주면 그 다음부터 화면에 출력하는 변수들은 모두 자동으로 필터링됩니다.
만약 필터링 없이 그대로 출력해야 하는 변수가 있다면
이렇게 해 주거나, 아니면 그 변수만
이렇게 표시해 주면 됩니다. (Django의 필터 문법을 따랐으나, Django에서 사용하는
safe라는 용어는 오해의 소지가 있어서noescape로 바꾸었습니다. 위험을 무릅쓰고 HTML을 출력하는데, 안전하다는 뜻의safe라니 이상하잖아요.)auto-escape 기능을 사용하지 않는 기존의 템플릿들이 컴파일되는 방식에는 전혀 변화가 없습니다. 그러나 그런 템플릿에서도 아래와 같은 문법을 사용할 경우,
언제든지 특정 변수를 필터링할 수 있습니다. 귀찮게 템플릿 안에서
htmlspecialchars()를 호출하지 않아도 된다는 거죠. (Ruby on Rails의 한 글자짜리h함수조차 귀찮아서 안 쓰는 경우가 종종 있습니다. 개발자를 귀찮게 하면 안됩니다. 보안은 편해야 돼요. 편해야 잘 쓰게 됩니다.)auto-escape 설정은 해당 파일에만 적용됩니다. 인클루드하는 다른 파일에는 적용되지 않습니다. (템플릿 핸들러가 싱글턴을 사용하도록 되어 있어서, 그걸 우회하느라고 애 좀 먹었습니다 ㅎㅎ)
참고로 위의
<config>문법은 다른 어떤 용도로 사용해도 무방합니다. 예를 들어 아래와 같이 쓰면템플릿 내에서
$this->config->foo라고 접근 가능합니다. (같은 파일에서만 가능)미해결 과제
필터링 소요시간
변수를 출력할 때마다
htmlspecialchars()함수를 자동으로 호출한다면 페이지당 수백 번, 많으면 수천 번씩 해당 함수가 호출될지도 모릅니다. 페이지 로딩 속도에 영향을 미칠 수 있다는 거죠.물론 위의 함수는 사실
str_replace()나 마찬가지이기 때문에, 짧은 문자열 수백 개를 치환하더라도 눈에 띌 만큼 긴 시간이 걸리지는 않을 것으로 생각됩니다. 실제로 보안이 뛰어난 해외의 여러 템플릿 엔진들도 별다른 문제 없이 저렇게 사용하고 있고요.그래도 추측보다는 실제 테스트 결과가 중요하니까, 다양한 환경에서 성능 테스트가 필요할 것 같습니다.
이미 필터링된 변수들과의 호환성
auto-escape 기능은 변수를 처음 입력받을 때 필터링하지 않고, 나중에 출력할 때 필터링하는 것을 원칙으로 합니다. 처음 입력받을 때 필터링하더라도 내부적으로 여러 단계의 처리를 거치다 보면 필터링한 것이 망가질 수도 있고, 어느 변수가 필터링한 것이고 어느 변수가 아직 안 한 것인지 헷갈려서 XSS 취약점이 발생하기 쉽기 때문입니다. AJAX로 JSON 데이터를 받아와서 jQuery로 페이지에 입력하는 일이 잦은 요즘 웹사이트들은, jQuery의
text()메소드가 알아서 다 처리해 주니까 굳이 서버단에서 태그를 필터링할 필요를 못 느끼기도 하고요.(물론 이런 경우에도 글 내용처럼 HTML을 최대한 보존하면서 위험한 태그만 걸러내는 작업에는 서버 자원이 많이 소요되기 때문에 미리 필터링해서 저장하는 일이 많습니다.)
그러나 XE는 지난 몇 년간 변수를 처음 입력받을 때 필터링하는 정책을 써 왔습니다. XSS 취약점의 원인이 되어 온 몇몇 변수들을 제외하면, 거의 대부분이 이미 필터링된 상태로 DB에 저장되어 있죠. 예를 들어 제목에
&라는 문자를 사용하면 DB에 저장할 때는 이미&로 변환된 상태입니다.문제는, 이걸 또다시 auto-escape하게 되면
&amp;가 되어버리죠.htmlspecialchars()함수의 double escaping 기능 때문입니다. 이미 필터링했든 안 했든 무조건 필터링하는 거예요. 만약 중간에 실수로 또 한번 필터링이 들어가면&amp;amp;가 나올 수도 있습니다 ㅡ.ㅡ;;사실 입력받을 때 필터링하든 출력할 때 필터링하든 아무튼 한 번만 필터링해야지, 여러 번 필터링하면 이런 문제가 생기는 것이 당연합니다. 필터링 많이 한다고 다 좋은 게 아니예요. 과유불급이예요. (XSS 취약점을 급히 고치느라고 템플릿에서 필터링하는 것으로 땜빵해 둔 부분도 있더군요. 그러나 이런 경우는 소수입니다.)
그래서 일단은 double-escape 옵션을
false로 해두었습니다. 처음부터 auto-escaping을 제대로 해온 프레임워크들의 입장에서 보면 이상한 조치이고, 나중에 또다른 호환성 문제를 일으킬 수도 있을 것 같긴 한데... 여러분의 의견은 어떠신가요?필터링해서는 안 되는 변수?
일단 레이아웃의
{$content}문법은 예외로 처리하도록 해두었습니다. 이것까지 auto-escape하게 되면 페이지가 왕창 깨져서 나올 게 뻔하니까요. 혹시 그 밖에도 auto-escape해서는 안 되는 경우가 있을까요?