SQL 삽입과 안전한 질의
문자열 결합과 매개변수 바인딩의 차이, Blind 유형과 대응을 이해합니다.
1. SQL 삽입의 성립 조건
SQL 삽입(SQL Injection)은 외부 입력을 데이터로 처리하지 않고 SQL 문자열 구조에 결합하여 DB가 코드 일부로 해석하게 만드는 취약점이다. 입력에 작은따옴표가 있다는 사실보다 질의 구조와 값이 분리되어 있는지가 중요하다.
# 취약한 문자열 결합 예시
sql = "SELECT id, name FROM member WHERE name = '" + name + "'"
cur.execute(sql)
외부 값이 로그인 폼에만 들어오는 것은 아니다. 검색어·URL·쿠키·헤더 등 SQL 생성에 도달하는 입력도 대상이 된다. 영향은 실제 질의와 DB 계정 권한에 따라 정보 노출·변경·삭제 등으로 달라진다.
2. 매개변수 바인딩
Prepared Statement·Parameterized Query는 SQL 구조를 정의하고 외부 값은 별도 매개변수로 전달한다.
# Python sqlite3 예시: cur는 준비된 DB 커서
name = "O'Reilly"
cur.execute(
"SELECT id, name FROM member WHERE name = ?",
(name,)
)
작은따옴표가 포함된 정상 이름도 하나의 값으로 처리한다. 이를 위해 이름에서 작은따옴표를 제거할 필요가 없다. 괄호 안 (name,)은 원소가 하나인 튜플이며 ?에 대응하는 값을 전달한다.
DB API마다 ?, :name, $1 등 표기가 다르다. 특정 기호를 사용했는지보다 API를 통해 구조와 값을 분리했는지를 확인한다. 값을 먼저 문자열에 넣고 그 문자열을 prepare하면 원인이 남는다.
3. 식별자와 정렬 방향은 허용 목록으로 선택한다
일반적으로 테이블·열 이름, ASC·DESC 같은 SQL 구조는 값 매개변수처럼 바인딩할 수 없다. 외부 문자열을 바로 연결하지 않고 개발자가 정한 후보로 매핑한다.
sort_map = {"name": "name ASC", "newest": "created_at DESC"}
order_clause = sort_map.get(user_sort)
if order_clause is None:
raise ValueError("허용되지 않은 정렬 기준")
sql = "SELECT id, name FROM member ORDER BY " + order_clause
cur.execute(sql)
최종 질의에 들어가는 것은 사용자가 임의로 만든 SQL이 아니라 고정된 두 후보 중 하나다. 일반 검색 조건의 값은 계속 매개변수 바인딩을 사용한다.
4. 관찰 방식에 따른 공격 유형
| 유형 | 구분 기준 |
|---|---|
| UNION 기반 | 다른 조회 결과가 원래 결과에 결합되어 응답에 노출 |
| 오류 기반 | DB 오류에서 구조·데이터 단서를 획득 |
| Boolean 기반 Blind | 페이지·성공 여부 등 반응 차이로 참·거짓 추론 |
| Time 기반 Blind | 조건에 따른 응답시간 차이로 추론 |
UNION 기반과 오류 기반은 같은 요청·응답 경로에서 결과를 확인하는 In-band의 대표 형태다. Blind는 값을 직접 출력하지 않아도 반응으로 알아내는 방식이다. 따라서 오류 페이지를 숨겼다고 SQL 삽입이 사라지는 것은 아니다.
5. 저장 프로시저·ORM의 한계
저장 프로시저도 내부에서 입력을 연결하여 동적 SQL로 실행하면 취약할 수 있다. ORM도 raw SQL이나 질의 문자열에 입력을 직접 붙이면 안전하지 않다.
입력 형식·길이·범위 검증은 필요하지만 바인딩을 대체하지 않는다. 이스케이프나 금지 문자열 목록은 DBMS·문맥 차이와 누락이 있으므로 근본 대응으로만 의존하지 않는다.
6. 예방과 피해 제한을 구분한다
| 통제 | 역할 |
|---|---|
| 매개변수 질의·고정 질의 구조 | 입력이 SQL 문법이 되는 원인 제거 |
| 최소 DB 권한 | 취약점 발생 시 접근·변경 피해 제한 |
| 일반화된 외부 오류 | 구조·데이터 단서 노출 감소 |
| 안전한 내부 로그 | 진단·반복 이상 시도 추적 |
| WAF | 일부 공격 패턴의 추가 탐지·차단 |
조회만 필요한 응용 계정에 계정 생성·권한 변경·테이블 삭제 권한을 주지 않는다. 로그에도 비밀번호·세션·DB 접속 비밀값을 남기지 않는다. WAF·오류 숨김·최소 권한만 적용한 채 위험한 문자열 결합을 유지하지 않는다.
개념 확인 문제
문제를 누르면 바로 아래에서 정답과 해설을 확인할 수 있습니다.
01정렬 열 이름을 값 매개변수 하나로 바인딩하면 되는가?
일반적으로 열 이름·정렬 방향은 값처럼 바인딩할 수 없다. 외부 선택값을 개발자가 정한 고정 식별자·정렬식 목록에 매핑하고 일반 조건 값은 바인딩한다.
02DB 오류 메시지를 숨기면 SQL 삽입이 제거되는가?
아니다. 정보 노출은 줄지만 Boolean·시간 차이로 결과를 추론하는 Blind가 가능할 수 있다. 질의 구조와 값을 분리하는 근본 수정이 필요하다.