SQL 인젝션 방어: 파라미터 바인딩과 동적 쿼리의 경계

1장 개념과 보호 목표#

SQL 인젝션은 외부 입력이 SQL의 데이터가 아니라 쿼리 구문으로 해석될 때 발생합니다. 핵심 방어는 SQL 구조와 입력값을 분리하는 파라미터 바인딩이며 입력 문자열의 특정 단어를 지우는 필터로 대신할 수 없습니다.


2장 원리와 구분#

Prepared Statement는 값 위치를 안전하게 바인딩할 수 있지만 테이블 이름·컬럼 이름·정렬 방향 같은 구조 요소를 임의 입력으로 바인딩하는 기능은 아닙니다. 이러한 요소는 서버가 정의한 허용 목록과 매핑해야 합니다. ORM도 raw query와 문자열 조합을 사용하면 취약할 수 있습니다. 저장 프로시저 내부의 동적 SQL 역시 확인해야 합니다.


3장 비교와 판단 기준#

입력 위치 처리 방식 피해야 할 구현
검색값·ID 파라미터 바인딩 문자열 SQL 조합
컬럼·테이블 서버 정의 매핑 사용자 입력 직접 삽입
정렬 방향 허용 목록 선택 구문 문자열 무제한 허용
DB 계정 최소 권한 웹 서비스에 관리자 권한

4장 SQL 구조와 데이터를 분리한다는 뜻#

검색어는 문자열 데이터이고 SELECT·WHERE·ORDER BY는 질의 구조입니다. 외부 입력을 구조 문자열에 이어 붙이면 사용자가 예상하지 않은 SQL 문법을 공급할 수 있습니다. 바인딩은 값이 SQL 구문으로 해석되는 경계를 분리하는 핵심 방어입니다.

위험한 생각: 입력의 나쁜 단어를 지운 뒤 문자열로 붙인다
안전한 원칙: SQL 구조를 서버가 정하고 값은 별도 파라미터로 전달한다

5장 Prepared Statement의 처리#

사용하는 드라이버의 준비·파라미터 전달 기능을 적용합니다. 값을 따옴표로 감싸 직접 넣는 코드와 구분해야 합니다. 플레이스홀더의 기호·타입·배열 처리는 DB와 드라이버 규약에 맞춰 확인합니다. 예제를 다른 드라이버에 그대로 복사해도 같은 보호가 된다고 가정하지 않습니다.

같은 요청의 조직·사용자 필터도 신뢰한 서버 값에서 구성합니다. SQL 인젝션이 없다는 사실만으로 타인 데이터의 조회 권한이 맞는 것은 아닙니다.


6장 테이블·컬럼·정렬은 값 자리와 다르다#

일반적인 값 바인딩을 테이블 이름이나 정렬 방향을 자유롭게 선택하는 기능으로 생각하면 안 됩니다. 사용자가 선택한 옵션은 서버의 고정 매핑에서 질의 구조로 변환합니다. 허용하지 않은 값은 거부하고 기본 옵션이 넓은 권한이나 비싼 조회가 되지 않게 합니다.

flowchart LR
 U["검색 요청"] --> V["값: 파라미터 바인딩"]
 U --> O["구조 옵션: 서버 허용 목록"]
 V --> Q["고정된 질의 구조"]
 O --> Q
 Q --> D["최소 권한 DB 계정"]

7장 ORM의 안전한 경로와 raw query#

ORM이 파라미터를 안전하게 전달하는 API를 제공할 수 있지만 raw query·임의 정렬 표현·문자열 조합은 별도 검토 대상입니다. 도구 이름만 보고 직접 만든 질의가 모두 안전하다고 판단하지 않습니다. 실제로 생성되는 쿼리와 파라미터의 분리를 봅니다.

보고서·관리자·배치·데이터 이동 코드는 일반 웹 입력보다 덜 검토되기 쉽습니다. 관리자 입력도 외부 데이터이며 저장된 문자열의 출처가 신뢰할 수 있는지도 확인해야 합니다.


8장 저장된 데이터가 나중에 공격 입력이 된다#

처음 입력을 안전하게 저장했어도 후속 배치가 그 값을 동적 SQL에 결합하면 다시 구조로 해석될 수 있습니다. 데이터의 저장 이력은 이후의 실행 문맥을 안전하게 만들지 않습니다. 각 실행 경계에서 값과 구조를 분리해야 합니다.

문자열의 출처를 따라가는 리뷰와 후속 질의 경로의 검증이 필요합니다. 업로드한 CSV·외부 API 값·DB의 설정 값도 같은 방식으로 조사합니다.


9장 저장 프로시저도 동적 SQL을 확인한다#

프로시저가 있다는 사실과 안전한 구현은 다릅니다. 내부에서 입력을 문자열로 합쳐 실행하면 문제가 남을 수 있습니다. 파라미터 전달·구조 허용 목록·실행 권한을 확인합니다. 애플리케이션과 프로시저 사이의 책임을 문서화합니다.

프로시저 권한이 테이블의 일반 권한보다 넓게 설정된 경우 영향도 검토합니다. 서비스 계정이 어떤 경로로 무엇을 실행할 수 있는지를 최소 권한으로 제한합니다.


10장 LIKE·검색 패턴의 의미#

바인딩은 SQL 문법 해석을 분리하지만 %와 _ 등 검색 와일드카드의 업무 의미는 남습니다. 입력을 문자 그대로 검색할지 패턴으로 허용할지 결정하고 필요한 처리를 DB 규약에 맞춰 적용합니다. 이를 SQL 구문 방어와 같은 문제로 섞지 않습니다.

페이지 크기·긴 검색어·넓은 조건·동시성의 비용 제한도 필요합니다. 인젝션을 막았어도 비싼 정상 질의 반복은 서비스에 영향을 줄 수 있습니다.


11장 최소 권한과 오류 메시지#

웹 조회 계정이 DB 관리자·파일 접근·불필요한 변경 권한을 가질 필요가 있는지 확인합니다. 권한 제한은 피해를 줄이는 보완 통제이며 바인딩을 대신하지 않습니다. DB 오류를 사용자에게 그대로 반환해 구조·계정·민감 데이터를 노출하지 않습니다.

로그는 사건 식별자와 필요한 진단 정보를 남기고 원문 비밀·전체 민감 질의는 제한합니다. 조사할 수 있는 정보와 불필요한 노출을 함께 설계합니다.


12장 코드 리뷰에서 데이터를 끝까지 추적한다#

입력의 출처 실행 경계 확인할 통제
검색·ID WHERE 값 파라미터 바인딩
정렬 옵션 컬럼·방향 서버의 고정 매핑
CSV·외부 API 후속 배치 SQL 실행 시 재분리
프로시저 입력 동적 질의 내부 바인딩·권한

13장 정상 입력과 실패 입력을 함께 시험한다#

따옴표가 있는 정상 이름·유니코드·빈 값·경계 타입이 데이터로 처리되는지 확인합니다. 미지원 구조 옵션은 거부되어야 하고 다른 조직의 자원은 정상 인증 상태에서도 허용되면 안 됩니다. 오류·쿼리·권한과 성능을 각각 기록합니다.

WAF의 차단은 보완 증거입니다. 최종 수정은 질의 구조와 값을 안전하게 분리하고 업무 권한·비용을 올바르게 처리하는 서버 구현에 연결해야 합니다.


14장 적용 사례와 운영 방법#

검색 서비스에서 검색어는 바인딩하고 정렬 옵션은 price·created_at 같은 사전 정의된 값으로 매핑합니다. 데이터베이스 계정의 최소 권한과 오류 메시지 제한은 피해 범위를 줄이는 보완 통제입니다. WAF는 탐지·차단을 도울 수 있지만 취약한 쿼리 조합을 수정하는 일을 대체하지 못합니다.


15장 검색값은 바인딩하고 정렬 구조는 매핑한다#

다음 Python DB-API 스타일 예시는 두 입력을 다르게 처리하는 원리를 보여 줍니다. 플레이스홀더 문법은 드라이버마다 다르므로 사용하는 드라이버의 방식으로 적용해야 합니다.

# 정렬 구조는 서버가 정의한 값만 선택한다.
sort_columns = {"recent": "created_at", "price": "price"}
sort_column = sort_columns.get(requested_sort)
if sort_column is None:
    raise ValueError("지원하지 않는 정렬")

# 값은 SQL 문자열에 합치지 않고 별도 파라미터로 전달한다.
sql = f"SELECT id, price FROM products WHERE category = ? ORDER BY {sort_column}"
cursor.execute(sql, (category_value,))

위 예시에서 문자열 삽입이 허용되는 이유는 sort_column이 사용자 입력 원문이 아니라 서버의 고정 목록에서 나온 값이기 때문입니다. 요청값을 그대로 대입하거나 목록에 임의 SQL 표현을 등록하면 이 조건이 깨집니다. 페이지 크기·권한·검색 비용은 별도 제한이 필요합니다.


16장 코드 리뷰에서 찾을 위험 경로#

검색·관리자 보고서·배치·저장 프로시저까지 외부 데이터가 질의에 들어가는 경로를 찾습니다. 과거에 DB에 저장된 문자열도 나중에 동적 SQL에 결합되면 위험해질 수 있습니다. 입력 검증을 통과했다는 사실만으로 구문 결합을 안전하게 만들지는 않습니다.

검증에서는 따옴표가 포함된 정상 검색어가 데이터로 처리되는지, 정렬 옵션의 미지원 값이 거부되는지, 서비스 계정이 업무에 불필요한 변경 권한을 갖는지 확인합니다. 오류·로그에는 비밀과 전체 민감 질의를 노출하지 않으면서 조사 가능한 식별자를 남깁니다.


17장 적용 전 확인할 사항#

  • 외부 입력이 SQL 구문에 합쳐지는 지점을 찾는다
  • 값 바인딩과 구조 요소 허용 목록을 구분한다
  • DB 권한과 오류·로그 노출을 제한한다

18장 자주 묻는 질문#

입력값에 따옴표가 없으면 안전한가요?#

숫자·정렬·식별자 등 여러 문맥에서 취약점이 생길 수 있습니다. 특정 문자 유무보다 쿼리 구조와 데이터가 분리되는지 검증해야 합니다.

LIKE 검색도 파라미터 바인딩만 하면 업무 의미까지 안전한가요?#

SQL 구문 분리와 와일드카드의 검색 의미는 다릅니다. %와 _를 문자로 검색할지 패턴으로 허용할지 정책을 정하고 필요한 이스케이프를 드라이버·DB 규칙에 맞춰 적용해야 합니다.

ORM을 쓰면 raw query 점검은 없어도 되나요?#

ORM의 안전한 API와 직접 조합한 SQL은 다릅니다. raw query·정렬 표현·저장 프로시저 등 구문 생성 경로를 계속 점검해야 합니다.


함께 읽기#