본문으로 건너뛰기
개발 머꼬
개발 노트SQL
hohyeon.dev22

쿼리를 문자열로 이어붙였다가 SQL Injection이 뚫린 사고

  • #Database
  • #Engineering Note
  • #Security

문제 발생

로그인 폼의 아이디 입력란에 특수한 문자열을 넣었더니, 비밀번호 없이도 로그인이 되는 취약점이 발견됐습니다.

# 위험한 코드
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
cursor.execute(query)

사용자가 username' OR '1'='1을 입력하면, 최종 쿼리는 다음과 같이 완성됩니다.

SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '...'

원인 분석

문자열 이어붙이기로 쿼리를 만들면, 사용자 입력이 데이터가 아니라 SQL 문법의 일부로 해석될 수 있습니다. 위 예시에서 입력값 안의 작은따옴표(')가 원래 의도한 문자열 리터럴의 경계를 깨뜨리고, OR '1'='1'이라는 항상 참인 조건을 쿼리에 삽입해 인증 로직 전체를 무력화합니다.

이건 로그인 우회의 아주 기본적인 형태일 뿐이고, 실제로는 UNION SELECT로 다른 테이블의 데이터를 통째로 빼내거나, 여러 문장을 세미콜론으로 이어붙여 데이터를 삭제하는 등 훨씬 심각한 공격으로 이어질 수 있습니다.

해결 방안

**Prepared Statement(파라미터화 쿼리)**를 항상 씁니다. 쿼리의 구조(SQL 문법)와 실제 값(사용자 입력)을 데이터베이스 드라이버 레벨에서 완전히 분리합니다.

# 안전한 코드
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))

이렇게 하면 username에 어떤 문자열이 들어오든, 드라이버는 그것을 "이 자리에 들어갈 값"으로만 취급합니다 — 그 값 안에 'OR 같은 SQL 문법 요소가 있어도 절대 쿼리 구조의 일부로 해석되지 않습니다. 이건 입력값을 "이스케이프 처리"하는 것과는 다릅니다 — 애초에 값과 구조가 전송 프로토콜 레벨에서 분리되어 있어 우회할 방법 자체가 없습니다.

  1. 문자열 포맷팅(f-string, %, .format(), 문자열 +)으로 쿼리를 조립하는 코드를 만나면 예외 없이 의심합니다 — 어떤 언어/프레임워크든 원칙은 동일합니다.
  2. 사용하는 ORM(Drizzle, JPA, SQLAlchemy 등)의 쿼리 빌더를 쓰면, 대부분 내부적으로 자동으로 파라미터화된 쿼리를 생성해줍니다 — 다만 ORM에서도 raw SQL을 문자열로 직접 이어붙이는 기능(예: 일부 ORM의 .raw() 계열 메서드)을 쓰면 이 보호가 깨질 수 있으므로 그런 경우는 특히 주의해야 합니다.
  3. 테이블명이나 컬럼명처럼 값이 아니라 구조 자체를 동적으로 바꿔야 하는 경우(파라미터화가 적용되지 않는 부분)는, 사용자 입력을 직접 쓰지 말고 미리 정의된 화이트리스트 중에서만 고르도록 강제합니다.
  4. 정기적으로 코드베이스에서 SQL 문자열 조합 패턴을 정적 분석 도구로 스캔하는 것도 이런 취약점을 배포 전에 잡는 데 도움이 됩니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.