SQL Injection

2025. 6. 17. 15:07·Backend/DB
목차
  1. 1. SQL Injection 원리
  2. 핵심 원리: 사용자 입력이 SQL 쿼리에 직접 결합
  3. PreparedStatement를 통한 보호 방법
  4. ORM (Object- Relational Mapping) 을 통한 보호 방법
  5. 결론
728x90

SQL Injection은 웹 애플리케이션 보안에서 가장 흔하고 치명적인 취약점 중 하나.
원리부터 PreparedStatement, ORM을 통한 보호 방법까지 설명하려 함

1. SQL Injection 원리

SQL Injection은 웹 애플리케이션의 입력 폼이나 URL 파라미터 등을 통해 악의적인 SQL 구문(Query)을 주입(Injection)하여, 데이터베이스를 비정상적으로 조작하거나 민감한 정보를 탈취하는 공격 기법

핵심 원리: 사용자 입력이 SQL 쿼리에 직접 결합

애플리케이션이 사용자로부터 입력받은 값을 아무런 검증이나 처리 없이 SQL 쿼리 문자열에 직접 삽입하여 실행할 때 발생

SELECT * FROM users WHERE username = '사용자_입력_ID' AND password = '사용자_입력_PW';

공격자는 사용자_입력_ID 부분에 SQL 예약어 (OR, AND, ', -- , etc..) 를 포함하여 주입


예시1: 인증 우회

  • 공격자가 사용자 ID에 ' OR 1=1 --를 입력한다고 가정 ( -- : 주석)
  • 원래 쿼리: SELECT * FROM users WHERE username = '' OR 1=1 --' AND password = '사용자_입력_PW';
  • 이 쿼리는 username=' ' 조건이 거짓이어도 OR 1=1 부분이 항상 참이 되므로, 비밀번호를 몰라도 로그인 성공
    👉 -- 뒤의 내용은 주석 처리되어 무시

예시2: 데이터 탈취

  • 공격자가 검색 폼에 ' ; DROP TABLE users' --와 같은 쿼리를 입력한다고 가정
  • 원래 쿼리: SELECT * FROM products WHERE name LIKE '%'; DROP TABLE users; --%';
  • 이러면 정상적인 쿼리 검색 후, users 테이블이 삭제되는 심각한 문제 발생

예시3: 정보 노출

 

  • 공격자가  ' UNION SELECT password, username FROM users -- 와 같은 쿼리를 입력한다고 가정
  • 원래 쿼리: SELECT name FROM products WHERE category = '' UNION SELECT password, username FROM users --';
  • UNION SELECT를 통해 products 테이블의 검색 결과와 함께 users 테이블의 사용자 이름과 비밀번호가 함께 반환
❓ 왜 작은 따옴표로 시작하는가
= 공격자는 먼저 애플리케이션이 원래 의도했던 문자열 리터럴을 주입된 작은따옴표로 강제로 닫아버림

PreparedStatement를 통한 보호 방법

PreparedStatement (또는 Parameterized Query, 바인딩 변수 방식)는 SQL Injection을 방어하는 가장 효과적이고 권장되는 방법

원리:

  • SQL 쿼리 구조와 사용자 입력 데이터를 완전히 분리하여 처리
  • SQL 쿼리는 미리 컴파일되거나  DB 전달 시 구조 고정
  • 사용자 입력 데이터는 쿼리 내의 ? 또는 :이름에 값으로 바인딩 됨
    이 데이터는 SQL 구문이 아닌 순수한 데이터 값으로만 인식 됨
장점 설명
SQL Injection 방어 사용자 입력이 SQL 구문의 일부로 해석되지 않으므로, 악의적인 SQL 코드 주입 근본적으로 차단
성능 향상 쿼리 구조가 미리 정의 & 컴파일 되므로, 동일한 쿼리를 여러 번 실행할 때 오버헤드를 줄여 성능 향상

작동 방식

1. SQL 쿼리 정의 

String sql = "SELECT * FROM users WHERE username = ? AND password = ?;";
PreparedStatement pstmt = connection.prepareStatement(sql);

 

? 는 플레이스홀더로, 실제 값이 들어갈 위치를 나타냄

DB는 SELECT * FROM users WHERE username = [값] AND password = [값]; 형태의 쿼리 구조를 인식


2. 데이터 바인딩

pstmt.setString(1, userInputId);     // 첫 번째 ? 에 사용자 입력 ID 바인딩
pstmt.setString(2, userInputPw);     // 두 번째 ? 에 사용자 입력 PW 바인딩

 

  • setString() 메소드는 userInputId와 userInputPw를 문자열 데이터 값으로 처리하여 쿼리에 삽입
  • 이 값 안에 SQL 예약어(예: OR 1=1 --)가 포함되어 있더라도, 데이터베이스는 문자열로만 인식 (SQL 구문 X)
  • 예시:  userInputId가 ' OR 1=1 -- 이더라도, 데이터베이스는 username = ''' OR 1=1 --'
    (문자열 리터럴로 처리되어 단일 인용부호가 이스케이프 됨)으로 인식하여 공격을 무력화

ORM (Object- Relational Mapping) 을 통한 보호 방법

ORM은 객체 지향 프로그래밍 언어의 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 기술

대표적인 ORM: Java의 Hibernate/JPA, Python의 SQLAlchemy, Node.js의 Sequelize 

ORM의 SQL Injection 보호 원리:

  • 대부분의 최신 ORM 프레임워크는 내부적으로 PreparedStatement와 같은 파라미터 바인딩 방식을 사용
  • 직접 SQL 쿼리 문자열을 조작할 필요 없이, 객체 지향적인 방식으로 DB 작업을 수행하도록 추상화 계층을 제공
장점  
높은 생산성 SQL 쿼리 직접 작성X, 개발 생산성 향상
유지보수 용이 객체지향적 코드로 DB 작업을 수행하므로, 가독성 & 유지보수성 좋음
기본적인 SQL Injection 방어 기본적으로 파라미터 바인딩을 사용, 별도의 조치 없이도 SQL Injection 공격 방어

작동 방식

1. 객체 모델 정의

// Java (JPA/Hibernate 예시)
@Entity
public class User {
    @Id
    private Long id;
    private String username;
    private String password;
    // ... getters and setters
}

2. 데이터베이스 작업 수행 (ORM API 사용):

// 사용자 ID로 User 객체 조회
User user = entityManager.createQuery("SELECT u FROM User u WHERE u.username = :username", User.class)
                         .setParameter("username", userInputId) // ORM이 내부적으로 파라미터 바인딩
                         .getSingleResult();

 

 

  • 개발자는 SQL 쿼리를 직접 작성하는 대신 JPQL이나 HQL과 같은 ORM 전용 쿼리 언어를 사용하거나
    메서드 호출(예: userRepository.findByUsername(userInputId))만으로 DB 작업 수행
  • 이 과정에서 ORM 프레임워크가 개발자를 대신하여 안전한 PreparedStatement 기반의 SQL 쿼리 생성 & 실행

주의 사항

  • 원시 SQL(Native SQL) 사용 시: ORM을 사용하더라도 때로는 복잡한 쿼리나 성능 최적화를 위해 직접 SQL 쿼리(Native Query)를 작성하여 실행해야 할 때가 존재
    이 경우 반드시 PreparedStatement의 원리를 적용하여 파라미터 바인딩을 수동으로 해주어야 함. 
    ∵ ORM의 보호를 벗어나기 때문
// ORM에서 Native Query 사용 시 주의 (JPA 예시)
Query nativeQuery = entityManager.createNativeQuery("SELECT * FROM users WHERE username = ?");
nativeQuery.setParameter(1, userInputId); // 반드시 파라미터 바인딩 사용!
  • 동적 쿼리 생성 시: 개발자가 ORM의 API를 통해 동적으로 쿼리 조건을 생성할 때, 문자열 결합 방식으로 필터링 조건을 구성하면 SQL Injection 취약점이 발생할 수도 있음
    항상 ORM이 제공하는 안전한 필터링/조건 추가 API를 사용

결론

SQL Injection은 사용자 입력 값을 SQL 쿼리에 직접 문자열 결합하는 방식에서 비롯됨.

이를 방어하는 가장 확실한 방법은 PreparedStatement(파라미터 바인딩)를 사용하여 쿼리 구조와 데이터 값을 분리

ORM은 이 PreparedStatement의 원리를 내부적으로 활용하여 개발자가 SQL Injection에 노출될 위험을 크게 줄여주는 훌륭한 도구지만, ORM을 사용하더라도 원시 SQL을 사용하거나 부적절하게 동적 쿼리를 생성할 때는 여전히 SQL Injection에 주의해야 함

 

728x90
  1. 1. SQL Injection 원리
  2. 핵심 원리: 사용자 입력이 SQL 쿼리에 직접 결합
  3. PreparedStatement를 통한 보호 방법
  4. ORM (Object- Relational Mapping) 을 통한 보호 방법
  5. 결론
'Backend/DB' 카테고리의 다른 글
  • 트랜잭션 분산 환경 처리
  • DB 분산 구조
  • Redis -2
  • Redis - 1
0woy
0woy
Algorithm, CS, Web 등 배운 내용을 기록합니다.
  • 0woy
    0woy dev
    0woy
  • 전체
    오늘
    어제
  • 🌐 LANGUAGE
    • 분류 전체보기 (80)
      • Backend (21)
        • JAVA (7)
        • DB (11)
        • Spring (1)
        • Spring Security (2)
      • Computer Science (22)
        • 네트워크 (9)
        • 운영체제 (5)
        • 보안 (7)
      • Frontend (15)
        • HTML5 (1)
        • CSS (1)
        • JS (4)
        • Vue 3 (9)
      • PS (16)
        • LeetCode (2)
        • Baekjoon (1)
        • Programmers (1)
        • 알고리즘 (12)
      • Dev Trivia (6)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    트리
    RDB
    DP
    dfs
    속성
    대칭키
    공개키
    Graph
    Filter
    leetcode
    function
    그래프
    shortestpath
    가용성
    select
    CA
    BFS
    java
    set
    Props
    https
    Vue3
    tcp
    javascript
    JDBC
    Spring
    security
    PreparedStatement
    비밀키
    JS
  • 최근 댓글

  • 최근 글

  • 250x250
  • hELLO· Designed By정상우.v4.10.3
0woy
SQL Injection

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.