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 인 계정은 비밀번호가 암호화가 되어있음

이후의 studentadmin 계정은 암호화 적용 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!@# 으로 바뀐다

 

 

방어 방법

  1. SHA2 함수 사용 
    • 사용자의 입력값이 해시 처리 → 쿼리 구조 변경 어려움
    • SHA2( ) 함수가 모든 입력을 감싸는 구조 → 인젝션 효과 원천 차단
  2. 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 금지