[DB] SQL Injection

do_it·2025년 11월 5일

database

목록 보기
7/11

1. 기본 개념 이해 (What & Why?)

SQL Injection이란?

사용자의 입력값이 데이터로 처리되지 않고, 개발자가 의도하지 않은 DB를 조작하는 SQL 쿼리(명령어)의 일부로 실행되는 웹 보안 취약점

  • 정상적인 상황
    웹사이트가 사용자에게 ID를 물어보면, 사용자는 my_user_id 같은 데이터(값)를 입력
    서버는 이 값을 받아 "이 ID를 가진 사용자를 찾아줘"라는 쿼리를 실행
  • 공격 상황
    공격자는 my_user_id 대신 admin' OR '1'='1 같은 명령어(쿼리문)를 입력
    서버가 이 입력을 제대로 거르지 못하면, 이 값이 그대로 쿼리문에 포함되어 개발자가 의도하지 않은 명령이 실행됨.

왜 발생하는가? (핵심 원인)

동적 쿼리(Dynamic Query)

사용자 입력을 검증 없이 문자열로 이어붙여(+ 또는 concat) 쿼리문을 만들 때 주로 발생함

공격자는 쿼리문의 구문(Syntax)를 깨뜨리는 특수문자를 삽입하여 쿼리를 조작함


2. 주요 공격 유형 (Types)

공격자가 “어떻게” 정보를 빼내는지에 따라 유형이 나뉨.

1) In-band

공격자가 보낸 쿼리의 결과를 현재 웹 페이지 화면을 통해 직접 볼 수 있을 때 사용함

Union-based

UNION 연산자는 두 개 이상의 SELECT문 결과를 하나로 합치는 명령어

공격자는 이를 악용해 원래 쿼리 결과에 추가로 다른 테이블의 데이터를 붙여서 탈취

  • e.g.
    게시판 검색 쿼리를 조작하여 users 테이블의 id, pw를 탈취
    …WHERE title=’검색어’ UNION SELECT id, pw FROM users;

Error-based

고의로 SQL 쿼리 오류를 발생시켜, 반환되는 오류 메시지를 통해 데이터베이스의 구조(테이블명, 컬럼명 등)를 알아내는 방식

만약 서버가 DB 오류 메세지를 화면에 그대로 노출하도록 설정되어 있다면, 공격자는 이 오류 메세지를 통해 DB의 버전, 테이블 이름, 칼럼명 등 민감한 DB 구조 정보를 알아낼 수 있음

2) Inferential (Blind SQL Injection)

추론 방식, 서버가 공격 결과나 메세지를 전혀 보여주지 않고, 단지 정상/ 오류 페이지만 보여줄 때 사용하는 고난도 기법

공격자는 참/ 거짓 반응 또는 응답 시간의 차이를 이용해 데이터를 한 글자씩 빼냄

Boolean-based

참 / 거짓일 때의 페이지 반응이 다른 점을 이용함

  • 공격: ‘admin 계정의 비밀번호 첫 번째 글자가 a인가?’의 쿼리를 보냄
  • 결과: 게시물이 존재하는 경우 페이지가 뜨면 → T
  • 공격: admin 계정의 비밀번호 첫 번째 글자가 b인가?
  • 결과: 게시물이 없습니다의 페이지가 뜨면 → F
  • 이런 식으로 한 글자, 한 글자 모든 비밀번호를 알아냄 (느리지만 자동화 스크립트로 가능)

Time-based

DB에 내장된 SLEEP(), WAITFOR 등의 함수를 사용해, 응답 시간을 기준으로 데이터를 유추

  • 공격: 첫 글자가 'a'이면 5초 대기
  • 결과: 서버 응답이 5초 이상 걸리면 → T / 서버 응답이 즉시 오면 → F
  • 응답 시간을 측정하여 데이터를 빼냄

3. 방어 기법 (Prevention)

1) Prepared Statement (매개변수 기반 쿼리)

작동원리

쿼리의 틀(Template) & 사용자 값(Data)을 분리해서 DB에 전송함.

DB는 쿼리 틀을 먼저 컴파일하고, 값은 나중에 대입하므로 입력값이 절대로 명령어로 해석되지 않음.

  1. 컴파일 (틀 전송)
    먼저 SELECT * FROM users WHERE id = ? AND pw = ?; 와 같이, 값이 들어갈 자리를 ? (placeholder)로 표시한 '쿼리 틀'을 DB로 보냄
    DB는 이 틀을 미리 컴파일 / 분석함
  2. 실행 (값 전송)
    DB는 이미 틀을 분석해두었기 때문에, 나중에 전송된 admin’—값을 명령어로 절대 해석하지 않음
    ? 자리에 들어가야 할 단순한 문자열 데이터로만 취급함
  • Java의 PreparedStatement (JDBC), Python의 cursor.execute(query, params), PHP의 PDO

2) ORM (Object-Relational Mapping)

  • ORM은 내부적으로 Prepared Statement를 사용하여 SQL Injection을 원천적으로 방어해 줍니다.
    개발자가 userRepository.findById("admin"); 처럼 객체지향 코드를 작성하면, 내부적으로 안전한 Prepared Statement를 생성하여 DB와 통신함.
    개발자가 실수로 동적 쿼리를 만들 여지를 원천적으로 줄임.
  • Java(Spring)의 JPA/Hibernate, Python의 SQLAlchemy

입력값 검증 (Input Validation)

서버단에서 사용자의 입력이 예상된 형식인지 검사

최소 권한 원칙 (Least Privilege)

DB에 접속하는 웹 애플리케이션 계정에 필요한 최소한의 권한만 부여함

웹사이트는 데이터를 읽고(SELECT), 쓰고(INSERT), 수정(UPDATE)할 뿐 테이블을 삭제(DROP)할 필요는 없음

DROP 권한을 아예 주지 않으면, 공격자가 DROP TABLE 쿼리를 주입해도 권한 없음 오류로 실패하게 됨

0개의 댓글