SQL Injection이란?
by 핫도구SQL Injection은 현재까지도 가장 오래되었지만 치명적인 웹 취약점이다. 오늘 SQL Injection이 왜 발생하는지 파악하고 어떻게 하면 막을 수 있는지에 대한 방법에 대해 알아 볼 예정이다.
SQL Injection이란?
SQL Injection은 사용자 입력값이 SQL 쿼리에 그대로 삽입되면서 공격자가 의도한 SQL 구문이 실행되는 취약점이다. 이는 간단하게 이야기하면 데이터가 들어와야 할 값들이 코드로 해석되는 것의 문제이다.
<?
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username' and passwod = '$password'";
$result = mysqli_query($conn, $sql);
?>
사용자가 admin/1234를 입력하면 아래의 쿼리가 만들어진다.
SELECT * FROM users WHERE username = 'admin' AND password = '1234'
하지만 공격자가 username에 ' or '1'='1' --를 입력하면 결과는 이상해진다.
SELECT * FROM users WHERE username = '' OR '1'='1' --' AND password = ''
공격 유형별 알아보면?
Union-based Injection
테이블 구조를 파악한 뒤, UNION SELECT로 다른 테이블의 데이터를 빼내는 방식이다.
<?
$id = $_GET['id'];
$sql = "SELECT name, price FROM products WHERE id = $id";
?>
공격자가 id파라미터에 다음 값을 넣으면
1 UNION SELECT username, password FROM users --
실행되는 쿼리는 아래와 같다.
SELECT name, price FROM products WHERE id = 1 UNION SELECT username, password FROM users --
이러면 상품정보와 함께 사용자 정보가 그대로 노출된다.
Blind SQL Injection
쿼리 결과가 화면에 직접 표시되지 않을 경우, 참과 거짓 응답 차이나 응답 시간을 이용해 정보를 추론하는 방식이다. 에러 메시지를 숨겼다고 안전한 것이 아닌 쿼리 자체가 취약하면 Blind 방식으로 뚫리게 된다.
# Boolean-based: 응답 차이로 판별
1 AND (SELECT LENGTH(password) FROM users WHERE username='admin') > 5
# Time-based: 응답 시간으로 판별
1 AND IF((SELECT SUBSTRING(PASSWORD, 1, 1) FROM users WHERE username='admin')= 'a', SLEEP(3), 0)
어떻게 한 글자씩 추출되는가?
admin 계정의 비밀번호가 php1234라고 가정하면 공격자가 아래와 같이 질문을 반복한다.
비밀번호 1번째 글자가 'a'인가? → 응답 즉시 도착 (거짓)
비밀번호 1번째 글자가 'b'인가? → 응답 즉시 도착 (거짓) ...
비밀번호 1번째 글자가 'p'인가? → 응답 3초 지연 (참!) → 첫 글자는 'p'
비밀번호 2번째 글자가 'a'인가? → 거짓 ...
비밀번호 2번째 글자가 'h'인가? → 참! → 두 번째 글자는 'h'
실제 쿼리로 보면 아래와 같다.
-- 1번째 글자 확인
1 AND IF(SUBSTRING((SELECT password FROM users WHERE username = 'admin'), 1, 1) = 'p', SLEEP(3), 0)
-- 2번째 글자 확인
1 AND IF(SUBSTRING((SELECT password FROM users WHERE username = 'admin'), 2, 1) = 'h', SLEEP(3), 0)
-- 3번째 글자 확인
1 AND IF(SUBSTRING((SELECT password FROM users WHERE username = 'admin'), 3, 1) = 'p', SLEEP(3), 0)
이게 사람이 하면 엄청난 작업을 해야하지만 sqlmap과 같이 이진탐색으로 최적화하면 한 글자당 7~8번의 요청이면 된다.
이처럼 Blind SQL Injection이 탐지하기 어려운 이유는 서버에 에러가 발생하지 않고 로그에는 짧은 시간 안에 비슷한 GET, POST 요청이 많이 찍히는데 전부 정상으로 나가기 때문에 만약 모니터링 도구가 에러 기반으로만 동작한다면 공격 사실을 모를 수 있다.
Second-order Injection
입력 시점이 아닌 저장된 데이터가 나중에 쿼리에 사용될 때 발생된다.
<?
// 회원가입
$username = "admin'--";
$sql = "INSERT INTO users (username) VALUES ('$username'");
// 프로필 수정
$row = mysqli_fetch_assoc($result);
$sql = "UPDATE profiles SET bio = '...' WHERE username = '{$row['username']}'";
mysqli_query($conn, $sql);
?>
위처럼 회원가입을 하고 프로필 수정을 하면 실제로 실행되는 쿼리는 아래와 같다.
UPDATE profiles SET bio = '...' WHERE username = 'admin' --'
이렇게 되어 db에 꺼낸 값 자체 또한 검증 할 필요가 있다.
코드 취약점 점검하기
<?
// 1. 변수를 쿼리 문자열에 직접 삽입
$sql = "SELECT * FROM users WHERE id = $id";
// 2. 따옴표 안에 변수 삽입
$sql = "SELECT * FROM users WHERE username = '$username'";
// 3. 문자열 결합
$sql = "SELECT * FROM products WHERE name = '".$name."'";
// 4. sprinf로 조합
$sql = sprintf("SELECT * FROM users WHERE id = %s", $id);
// 5. real_escape_string을 사용했지만 따옴표 없이 사용
$id = mysqli_real_escape_string($conn, $_GET['id']);
$sql = "SELECT * FROM users WHERE id = $id";
이와 같은 패턴의 코드가 존재하면 SQL Injection에 취약하다.
1. 변수를 쿼리 문자열에 직접 삽입
변수에 1 or 1=1 을 넣으면 sql injection 발생
2. 따옴표 안에 변수 삽입
변수에 ' or '1' = '1' --를 넣으면 sql injection 발생
3. 문자열 결합
2번과 같음
4. sprinf로 조합
sprinf는 단순한 문자열 치환뿐이기 때문에 1번과 같음
5. 따옴표 없이 real_escape_string 사용
real_escape_string은 ', ", \와 같은 특수문자 앞에 \를 붙여주는 함수인데 변수 자리에 따옴표가 없으면 '\'를 붙여주지 않기 때문에 1번과 같은 경우가 발생한다.
다음에는 sql injection 방어 중 하나로 prepared statement에 대해 알아 볼 예정이다.
'서버 > 보안' 카테고리의 다른 글
| 필터로 막은 SQL Injection이 뚫리는 이유 — 다층 방어의 한계 (0) | 2026.09.02 |
|---|---|
| 네트워크 레벨에서 SQL Injection을 막는 방법 (0) | 2026.03.11 |
| SQL Injection 방어를 위한 Prepared Statement (0) | 2026.03.04 |
블로그의 정보
AquaMan
핫도구