주요정보통신기반시설가이드/Web 취약점 점검
[SI (상)] 5. SQL 인젝션
JITSU
2025. 5. 12. 09:27
작업 환경
- VMware Workstation Pro (17.6.2 ver)
- Server
- Web 서버 : Rocky_Linux(8.10 ver, VMnet 2, PHP로 구성)
- DNS 서버 : Rocky_Linux(8.10 ver, VMnet 1)
- Client
- Kali Linux : Web 취약점 점검용 (VMnet 1)
- GNS 구성
- 해당 점검은 비밀번호 수정 페이지에 한해서 진행됨
SI (상) | 5. SQL 인젝션 |
취약점 개요 | |
점검 내용 | 웹페이지 내 SQL 인젝션 취약점 존재 여부 점검 |
점검 목적 | 대화형 웹 사이트에 비정상적인 사용자 입력 값 허용을 차단하여 악의적인 데이터베이스 접근 및 조작을 방지하기 위함 |
보안 위협 | 해당 취약점이 존재하는 경우 비정상적인 SQL 쿼리로 DBMS 및 데이터(Data)를 열람하거나 조작 가능하므로 사용자의 입력 값에 대한 필터링을 구현하여야 함 |
판단 기준 |
양호 : 임의로 작성된 SQL 쿼리 입력에 대한 검증이 이루어지는 경우 취약 : 임의로 작성된 SQL 쿼리 입력에 대한 검증이 이루어지지 않는 경우 |
참고 | OWASP TOP 10 항목 중 A03 : Injection 에 해당한다고 판단 |
취약한 세팅
기존 쿼리에는 회원가입시 SHA2 암호화가 있었으나
테스트를 위해 SHA2 암호화 진행 없이 평문으로 DB에 아이디, 비밀번호 저장
기존의 user_id = 1 인 계정은 비밀번호가 암호화가 되어있음
이후의 student 와 admin 계정은 암호화 적용 X
점검 방법
- Step 1) 사용자 입력 값에 특수문자나 임의의 SQL 쿼리를 삽입하여 DB 에러 페이지가 반환되는지 확인
- 입력값 : ') ← 비정상 괄호 조합 유도
현재 오류 발생 시 DB 내부의 처리 로직이 부족하여 아무런 페이지가 반환되지 않음
- Step 2) 사용자 입력 값에 임의의 SQL 참, 거짓 쿼리를 삽입하여 참, 거짓 쿼리에 따라 반환되는 페이지가 다른지 확인
- 거짓 입력값 : 123' AND 1=2 --
- 참 입력값 : Step 3) 참고
현재 오류 발생 시 DB 내부의 처리 로직이 부족하여 아무런 페이지가 반환되지 않음
- Step 3) 비밀번호 변경 페이지에 참이 되는 SQL 쿼리를 전달하여 비밀번호가 변경 되는 지 확인
- 입력 값 : 123!@#' WHERE '1'='1' --
모든 유저의 비밀번호가 123!@# 으로 변경된 것을 확인할 수 있음(SQLi 취약)
발생한 이유?
- 비밀번호 변경 처리 부분 쿼리
// 비밀번호 변경 부분
# 5. SQLi Test 위해 취약하게 수정 5/10
#$update_sql = "UPDATE users SET user_pw = SHA2('$pw', 512) WHERE user_id = '$user_id'";
$update_sql = "UPDATE users SET user_pw = '$pw' WHERE user_id = '$user_id'";
if(mysqli_query($conn, $update_sql)) {
echo "<script>alert('비밀번호 변경 성공!'); location.href='/proc/logout.php';</script>";
}else{
echo "<script>alert('비밀번호 변경 실패 : " . mysqli_error($conn) . "'); history.back();</script>";
}
입력값 123!@#' WHERE '1'='1' -- 이 실행되면
UPDATE users SET user_pw = '123!@#' WHERE '1'='1' -- WHERE user_id = '$user_id'";
위와 같이 공격자가 입력한 쿼리문이 들어가면서 기존의 쿼리는 주석으로 처리되어 모든 사용자의 비밀번호가 123!@# 으로 바뀐다
방어 방법
- SHA2 함수 사용
- 사용자의 입력값이 해시 처리 → 쿼리 구조 변경 어려움
- SHA2( ) 함수가 모든 입력을 감싸는 구조 → 인젝션 효과 원천 차단
- Prepared Satement 사용
// Prepared Statement 사용
$sql = "UPDATE users SET user_pw = SHA2(?, 512) WHERE user_id = ?";
$stmt = mysqli_prepare($conn, $sql);
if ($stmt) {
mysqli_stmt_bind_param($stmt, "ss", $pw, $user_id);
if (mysqli_stmt_execute($stmt)) {
echo "<script>alert('비밀번호 변경 성공!'); location.href='/proc/logout.php';</script>";
} else {
echo "<script>alert('비밀번호 변경 실패: " . mysqli_error($conn) . "'); history.back();</script>";
}
mysqli_stmt_close($stmt);
} else {
echo "<script>alert('쿼리 준비 실패'); history.back();</script>";
}
- mysqli_prepare( ) : SQL 구문을 먼저 고정한 후 실행하게 해줌
- ? : 사용자 입력이 삽입될 자리에 플레이스홀더 사용
- mysqli_stmt_bind_param( ) : 입력 값을 따로 바인딩 → SQL 구조 변조 불가능
- "ss" : 하나의 파라미터 타입을 나타낸다 (mapping 으로 생각)
- 첫 번째 ? 에 String (문자열) : $pw
- 두 번째 ? 에 String (문자열) : $user_id
타입 문자 종류 (MySQLi 기준)
문자 | 타입 | 설명 |
s | string | 문자열 (예: abc, password123) |
i | integer | 정수 (예: 42, 0) |
d | double | 실수 (예: 3.14, 2.718) |
b | blob | 바이너리 데이터 (파일 등) |
MySQLi 는 C 스타일의 바이너리 인터페이스를 사용하므로, 변수의 타입을 명시해줘야 SQL 쿼리를 안전하게 컴파일 할 수 있음
수정 후 재공격
공격하는 쿼리문을 비밀번호 "값"으로 처리하여 DB에 저장함
보안 설정 방법
- SQL 쿼리에 사용되는 문자열의 유효성을 검증하는 로직 구현
- 아래와 같은 특수문자를 사용자 입력 값으로 지정 금지
- Dynamic SQL 구문 사용을 지양하며 파라미터에 문자열 검사 필수적용
- 시스템에서 제공하는 에러 메시지 및 DBMS에서 제공하는 에러 코드가 노출되지 않도록 예외처리
- 웹 방화벽에 인젝션 공격 관련
참고
📌 SQL 인젝션 이란?
공격자가 사용자 입력에 SQL 구문을 삽입하여
애플리케이션의 쿼리를 조작하거나 DB에서 권한 없는 데이터에 접근하거나 파괴하는 취약점
- 입력값이 제대로 필터링되지 않은 상태에서 SQL에 삽입
- 인증 우회, 데이터 조회, 수정, 삭제, DB 탈취 등 가능
- 매우 파괴력 높은 고위험 취약점
🔐 OWASP TOP 10(2021)과의 연관
1. A03: Injection
- 사용자 입력이 SQL에 직접 사용됨
- 입력값에 대한 필터링/검증 없음
- 데이터 유출, 조작, 권한 탈취 등 심각한 결과 유발
- A03에 포함된 대표 인젝션 종류
- SQL 인젝션
- LDAP 인젝션
- OS 명령 인젝션
- NoSQL 인젝션
- XML/XPath 인젝션
🛡️ 방어 방법
- Prepared Statement : 쿼리 구조와 데이터를 분리해 인젝션 차단
- ORM 사용 : 대부분 자동으로 Prepared Statement 사용
- 입력 필터링 : 숫자, 문자열, 길이, 패턴 제한 등
- 최소 권한 원칙 : DB 계정에 읽기/쓰기만 허용, DROP/DELETE 금지