SQL Injection 방지: psycopg2의 sql.Identifier와 sql.Literal

Sue·2025년 7월 24일
0
post-thumbnail

SQL Injection 방지

  • psycopg2의 sql.Identifiersql.Literal 개념 정리

목차

  1. SQL Injection이란?
  2. 왜 위험한가?
  3. 전통적인 SQL 구성 방식의 문제점
  4. psycopg2.sql 모듈 소개
  5. sql.Identifier와 sql.Literal의 차이
  6. 실전 예제 비교
  7. 정리 및 보안 실천 팁

1. SQL Injection이란?

SQL Injection은 외부 입력값이 SQL 쿼리 안에 직접 삽입되면서, 악의적인 명령이 실행되는 보안 취약점을 의미한다.

이는 데이터 유출, 삭제, 인증 우회 등 심각한 보안 사고로 이어질 수 있다.


2. 왜 위험한가?

예를 들어, 사용자가 로그인 폼에서 다음과 같은 값을 입력했다고 가정하자.

Username: admin' OR '1'='1

아래와 같은 방식으로 SQL 쿼리를 문자열로 구성하면:

username = "admin' OR '1'='1"
query = f"SELECT * FROM user WHERE username = '{username}'"

실제 실행되는 쿼리는 다음과 같다:

SELECT * FROM user WHERE username = 'admin' OR '1'='1'

이는 항상 참(OR '1'='1')이 되어 인증 없이 모든 사용자 정보를 가져올 수 있게 된다. 심한 경우, DROP TABLE 같은 파괴적인 명령도 삽입될 수 있다.


3. 전통적인 SQL 구성 방식의 문제점

초보 개발자들은 흔히 다음과 같이 문자열 포맷팅(f-string, %s, .format())을 사용해 SQL 쿼리를 만든다:

email = input("Email: ")
query = f"SELECT * FROM user WHERE email = '{email}'"

이 방식은 SQL 명령어와 사용자 데이터 간의 경계가 없기 때문에, 사용자 입력이 쿼리 문법 자체를 바꿀 수 있다. 이로 인해 SQL Injection에 매우 취약하다.


4. psycopg2.sql 모듈 소개

psycopg2는 PostgreSQL에 연결하기 위한 Python 라이브러리다. 이 라이브러리는 안전한 SQL 생성을 위한 psycopg2.sql 모듈을 제공한다.

이 모듈은 SQL 쿼리의 문자열 조합 방식 대신, 구조적으로 안전하게 SQL을 작성할 수 있도록 도와준다.

중요한 도구는 다음 두 가지이다:

  • sql.Identifier(...)
    테이블명, 컬럼명 등 식별자(identifier)를 안전하게 삽입

  • sql.Literal(...)
    문자열, 숫자 등 값(value)을 안전하게 삽입


5. sql.Identifier vs sql.Literal

항목설명예시
sql.Identifier()SQL 구문 내의 테이블명, 컬럼명, 스키마명 등 구조 요소 삽입"user", "project_id"
sql.Literal()SQL 조건절에서 비교에 사용되는 값 삽입'Suyeon', 123, '2024-01-01'

두 도구 모두 내부적으로 SQL 구문에 대한 이스케이프 처리를 수행하여 Injection을 원천 차단한다.


6. 실전 예제 비교

6.1. 잘못된 예 (Injection 위험 있음)

project_id = "1; DROP TABLE project;"
query = f"SELECT * FROM project WHERE id = '{project_id}'"

→ 실행될 수 있는 쿼리:

SELECT * FROM project WHERE id = '1'; DROP TABLE project;'

6.2. 안전한 예 (psycopg2.sql 사용)

from psycopg2 import sql

project_id = "1; DROP TABLE project;"
query = sql.SQL("SELECT * FROM project WHERE id = {}").format(
    sql.Literal(project_id)
)

→ 실제 SQL 내부적으로는 다음과 같이 이스케이프된다:

SELECT * FROM project WHERE id = '1; DROP TABLE project;'

이렇게 되면 DROP TABLE은 실행되지 않고 단순 문자열로 처리된다.

6.3. 식별자에 대한 예

table_name = "user"
query = sql.SQL("SELECT * FROM {}").format(sql.Identifier(table_name))

→ 결과:

SELECT * FROM "user"

여기서 user는 PostgreSQL의 예약어이기 때문에, 반드시 따옴표로 감싸야 정상 동작한다.
sql.Identifier()는 이러한 처리를 자동으로 해준다.


7. 정리 및 실천 팁

핵심 요약:

  • SQL 쿼리는 문자열 붙여쓰기 방식으로 절대 만들지 말 것
  • 사용자 입력값은 반드시 sql.Literal()로 감쌀 것
  • 테이블명, 컬럼명 등의 구조 요소는 sql.Identifier()로 감쌀 것
  • psycopg2.sql 모듈은 구조적인 SQL 쿼리 생성을 도와준다

추가 팁:

  • 쿼리문 생성이 복잡해질 경우 sql.SQL(...).format(...) 패턴을 중첩해서 사용해도 된다
  • Pydantic과 함께 사용할 경우, 입력 데이터의 타입과 유효성을 미리 검증한 뒤 SQL로 넘기면 안정성이 더욱 높아진다
  • SQLAlchemy ORM 또는 Query Builder를 사용하는 것도 대체 수단이 될 수 있지만, psycopg2 수준의 정밀 제어가 필요할 경우 sql.Identifier, sql.Literal 조합이 가장 효과적이다

이상으로 SQL Injection의 원리와 psycopg2를 이용한 안전한 SQL 쿼리 작성법에 대해 정리하였습니다.

profile
AI/ML Engineer

0개의 댓글