1. 문제
-- 정확한 값을 조회할 때
SELECT * FROM TABLE WHERE ID = 'asdf';
-- 모든 값을 조회할 때
SELECT * FROM TABLE WHERE ID LIKE '%';
ID 값을 조건으로 데이터를 조회하는 SQL 쿼리가 있다. 이 두 쿼리를 상황에 따라 다르게 사용해야 한다. 하지만 쿼리문 자체를 동적으로 수정하기 어렵기 때문에, ID 조건의 앞부분은 고정하고 뒤에서 사용하는 값을 프론트엔드에서 분기문으로 처리하여 쿼리에 삽입하는 작업이 있었다.
if( 조건1 ) {
var condition = "= asdf";
} else {
var condition = "LIKE '%'";
}
프론트엔드에서 ID 값을 동적으로 "= asdf" 또는 "LIKE '%'"로 설정하면, 동일한 쿼리 구조를 유지하면서 조건만 변경할 수 있다.
-- 모든 값을 조회할 때
SELECT * FROM TABLE WHERE ID LIKE ' % '
... 중간 생략
그런데 문제가 프론트에서 처리를 해서 값을 넘기다 보니까, 특수 문자 중에 single quotation이 제대로 넘어가질 않아서 실제 쿼리가 위와 같이 작성되었다.
2. 문제 원인
Single Quotation(')이 **'**로 변환되는 것은 HTML 엔티티 변환 또는 HTML 인코딩이라 부른다. 웹 브라우저는 HTML 문서의 특수문자를 해석할 때, 악의적 코드 실행이나 레이아웃 깨짐을 방지하기 위해 특정 문자를 HTML 엔티티로 변환하거나 디코딩한다. 아래는 chat gpt가 설명해주는 HTML 엔티티이다.
- HTML 엔티티(HTML Entity):
- 특수문자를 표현하기 위한 코드.
- '는 ASCII 코드 39번(=Single Quotation)을 나타냄.
- 예:
- < → <
- > → >
- & → &
- HTML 인코딩(HTML Encoding):
- 텍스트를 HTML 엔티티로 변환하는 과정.
- Single Quotation이 '로, Double Quotation이 "로 변환됨.
- Escape Characters:
- HTML 내에서 해석을 피하기 위해 특정 문자를 다른 형태로 표현하는 과정.
- 이는 브라우저가 해당 문자를 HTML 코드로 인식하지 않게 함.
내가 사용하는 템플릿 문법은 Django에서 사용하는 Jinja2 문법과 유사하다. 값을 선언할 때는 {{ }} 머스태시(Mustache)로 감싸고, 분기문은 {% if %}와 {% endif %} 같은 방식으로 작성한다. 이 문법에서는 Filter라는 개념이 있다. 필터는 파이프(|, pipe symbol)라는 특수문자를 사용하여 변수를 변환할 수 있다. 예를 들어, 데이터를 원하는 형식으로 처리하거나 출력할 때 활용한다.
<!-- lower는 소문자로 바꾸는 Filter -->
{{ var str = "HELLO WORLD" | lower }}
위 str을 console이나 로그로 찍어보면 대문자가 모두 소문자로 변환되어 출력된다.
그리고 Flask 공식 문서에 자동변환(Autoescaping) 제어하기가 있는데
자동변환(Autoescaping)은 자동으로 특수 문자들을 변환시켜주는 개념이다. 특수문자들은 HTML (혹은 XML, 그리고 XHTML) 문서 에서 &, >, <, " , ' 에 해당한다. 이 문자들은 해당 문서에서 특별한 의미들을 담고 있고 이 문자들을 텍스트 그대로 사용하고 싶다면 “entities” 라고 불리우는 값들로 변환하여야 한다. 이렇게 하지 않으면 본문에 해당 문자들을 사용할 수 없어 사용자에게 불만을 초래할뿐만 아니라 보안 문제도 발생할 수 있다.
즉 {{ }} 안에 &, >, <, " , ' 를 사용하면 자동으로 HTML Entity로 변환한다. 이는 보안 때문에 이렇게 사용해야 한다.
Jinja2 : https://jinja.palletsprojects.com/en/stable/templates/#filters
Flask : https://flask-docs-kr.readthedocs.io/ko/latest/templating.html#id3
3. 문제 해결
필터에 raw라고 있는데 특수 문자를 모두 이스케이프 하지 않도록 변환한다. 즉 이스케이프 된것을 다시 특수문자로 돌려준다.
// 결과 : LIKE ' % '
{{ var = "Like '%'" }}
// 결과 : LIKE '%'
{{ var = "Like '%'" | raw }}
이렇게 필터를 적용해서 쿼리에 제대로된 조건을 삽입할 수 있었다.
무척 간단해 보이지만, 앞에 무수히 많은 에러를 잡고 돌고 돌아 해당 문제를 해결했다. 다른 방법도 있을 텐데 나중에 찾아봐야 겠다. 사용하는 pebble 문법이 js도 아니고 java도 아닌 애매르송해서 뭔가 제대로 동작하게 만들기 쉽지 않다.
4. 여담 : RAW 필터 막 사용하면 안되는 이유
질문 주제: Django raw 필터 사용 시 XSS 공격 예시
raw 필터는 Django 템플릿에서 사용자 데이터를 HTML로 렌더링하도록 처리하며, HTML 이스케이프를 방지한다. 이는 잘못 사용하면 XSS (Cross-Site Scripting) 공격에 취약해질 수 있다.
예시: raw 필터로 인한 XSS 공격
1. 공격 가능한 코드
다음은 Django 템플릿에서 raw 필터를 부주의하게 사용한 경우:
<!-- templates/example.html --> <p>사용자 메시지: {{ user_input | raw }}</p>
2. 사용자가 입력한 데이터
만약 사용자가 다음과 같은 악성 데이터를 입력했다고 가정:
<script>alert('XSS 공격!');</script>
3. 렌더링된 HTML 결과
템플릿에서 raw 필터를 사용했기 때문에, Django는 이 데이터를 이스케이프 처리하지 않고 그대로 출력:
html복사편집
<p>사용자 메시지: <script>alert('XSS 공격!');</script></p>
따라서, 이때는 raw를 지양해서
이 경우 입력값 <script>alert('XSS');</script> 는 다음과 같이 이스케이프 처리됨:
<p>사용자 메시지: <script>alert('XSS');</script></p>
즉 입력값에 자바스크립트를 의도적으로 삽입해서 보안에 문제가 생길 수 있기 때문에 잘 생각하고 사용해야한다.
[결론] 설명하는게 쉽지가 않네,,,