SQL Injection
검색·인증 입력이 SQL 구조를 바꾸는 과정
발생 상황
로그인, 게시판 검색, 기간 조회, 정렬 기능에서 입력을 SQL 문자열에 연결할 때 발생합니다. 화면에 보이는 검색창뿐 아니라 URL, 쿠키, 요청 본문도 쿼리 입력이 될 수 있습니다.
주요 유형
로그인 우회는 인증 조건 변조, UNION은 결과 결합, Error-based는 오류 메시지 노출을 이용합니다. Boolean-based Blind는 응답 차이, Time-based Blind는 조건부 지연을 비교합니다. 결과를 관측하는 통로에 따라 기법이 달라집니다.
분석할 때 주의할 점
따옴표 하나에 오류가 났다고 SQLi로 확정할 수 없습니다. 동일 조건을 반복하고 참·거짓 대조군을 확인하세요. UNION은 열 개수가 같아야 하며, 대응하는 열의 자료형은 DBMS의 규칙에 따라 호환되어야 합니다. Oracle·MySQL·PostgreSQL의 함수와 주석 구문은 같지 않습니다.
대응 방안
값은 매개변수화된 쿼리로 전달합니다. 컬럼명·테이블명·ASC/DESC처럼 값으로 바인딩할 수 없는 SQL 구조는 서버의 고정 허용목록에 매핑합니다. DB 계정 권한을 최소화하고 상세 오류는 내부 로그에만 남깁니다.
자주 놓치는 부분과 특이사항
ORM을 사용해도 raw query나 문자열 연결을 섞으면 취약할 수 있습니다. 저장된 입력이 나중에 다른 쿼리에 사용되는 Second-order SQLi도 있습니다. WAF나 따옴표 치환만으로 근본 원인을 해결할 수는 없습니다.
SQL Injection · 로그인과 UNION 더 알아보기
사용자 입력이 SQL의 데이터 경계를 벗어나 조건이나 쿼리 구조로 해석될 때 발생합니다. 로그인 우회는 인증 조건이 바뀌는 문제이고, UNION 기반 추출은 원래 조회 결과에 다른 SELECT 결과를 결합하는 문제입니다.
UNION은 열 개수가 같아야 하며, 대응하는 열의 자료형은 DBMS의 규칙에 따라 호환되어야 합니다. 먼저 정상 검색의 결과와 오류를 비교하고, 테이블명 → 컬럼명 → 데이터 순으로 구조를 파악합니다. Prepared Statement는 값 바인딩에 사용하며 동적인 테이블명·정렬 컬럼은 별도 허용목록으로 제한합니다.
Error-based SQL Injection 더 알아보기
쿼리 결과가 화면에 직접 나오지 않아도 데이터베이스 오류에 표현식의 결과가 포함되면 정보를 알아낼 수 있습니다. Oracle의 CTXSYS 계열 오류처럼 DBMS마다 이용 가능한 함수와 오류 형식이 다릅니다.
정상 응답과 오류 응답의 상태 코드·본문을 비교합니다. 한 번의 오류에 여러 행을 넣을 수 있는지, 문자열 길이 제한은 어떤지 확인해야 합니다. 상세 DB 오류를 사용자에게 전달하지 않는 조치와 쿼리 매개변수화가 함께 필요합니다.
Blind SQL Injection · Boolean과 Time 더 알아보기
참/거짓에 따른 게시글 수나 문구 차이, 조건에 따른 응답 지연을 이용합니다. 출력이 없어도 LENGTH·SUBSTR·ASCII 같은 연산으로 길이와 문자를 단계적으로 판별할 수 있습니다.
날짜 검색의 TO_DATE 인자, 숫자형 ID, ORDER BY 표현식도 입력 지점입니다. 자동화 전에 참인 조건과 거짓인 조건이 안정적으로 구분되는지 확인합니다. 지연은 네트워크 변동과 구분해야 하며, URL의 +는 폼 디코딩 시 공백이고 문자 + 자체는 %2B입니다.
요청과 구현 비교
문자열 연결 대신 값 바인딩
Python sqlite3 예시입니다. 플레이스홀더 문법은 드라이버마다 다릅니다. 정렬 컬럼은 값 바인딩 대신 고정 매핑을 사용하고, 알 수 없는 값은 입력 오류로 처리합니다. ValueError는 웹 요청 처리 계층에서 일관된 400 입력 오류로 변환하고 내부 예외 원문을 노출하지 마세요.
# 취약: 입력이 SQL 구조에 합쳐짐
query = "SELECT id FROM users WHERE name='" + name + "'"
# 수정: 구조와 데이터를 분리
rows = db.execute('SELECT id FROM users WHERE name = ?', (name,))
order = {'recent': 'created_at DESC', 'name': 'name ASC'}.get(sort_key)
if order is None:
raise ValueError('허용되지 않은 정렬 기준')
# 요청 처리 계층: ValueError를 포착해 일관된 HTTP 400 반환대조군으로 응답 비교
정상 검색의 결과 수·본문을 먼저 기록합니다. 참·거짓 조건의 결과를 같은 세션에서 반복 비교하고 네트워크 실패는 별도 기록합니다. 지연 한 번이나 HTTP 200만으로 취약점을 확정하지 않습니다. 수정 후에는 같은 입력이 데이터로만 처리되는지, 정상 검색은 유지되는지 확인합니다.
적용 조건과 재검증
DBMS와 오류 정책
실습 SQL 문제는 SQLite 기반입니다. 예시에 나오는 함수와 오류 문구를 다른 DBMS에 그대로 적용할 수는 없습니다. 동적 정렬값은 서버 허용목록에 매핑하고, 허용되지 않은 값은 내부 SQL 오류 대신 일정한 입력 오류로 처리해야 합니다. 정상 검색도 계속 동작하는지 확인하세요.