SQL 인젝션이란? 발생 원인과 공격 원리, 안전한 대응 방법

SQL 인젝션의 개념과 발생 원인, 공격 원리, 피해 사례를 알아보고 PreparedStatement, 입력값 검증, 최소 권한 설정을 이용한 안전한 대응 방법을 설명합니다.

웹사이트에서 로그인하거나 게시물을 검색하면 사용자가 입력한 정보가 데이터베이스로 전달됩니다. 이 과정에서 입력값을 안전하게 처리하지 않으면 공격자가 SQL 문법을 삽입해 데이터베이스 명령을 조작할 수 있습니다. 이러한 보안 취약점을 SQL 인젝션(SQL Injection)이라고 합니다.

SQL 인젝션은 오래전부터 알려진 공격이지만, 지금도 웹 애플리케이션과 API에서 지속적으로 발견됩니다. 공격에 성공하면 인증 우회, 개인정보 유출, 데이터 변경 및 삭제와 같은 심각한 피해가 발생할 수 있으므로 개발 단계부터 안전한 데이터베이스 처리 방식을 적용해야 합니다.

SQL 인젝션이란?

SQL 인젝션은 웹 애플리케이션이 사용자의 입력값을 SQL 쿼리에 그대로 연결할 때 발생하는 취약점입니다. 공격자는 입력란이나 URL 파라미터 등에 SQL 문법으로 해석될 수 있는 값을 전달해 원래 작성된 쿼리의 조건이나 구조를 변경합니다.

예를 들어 로그인 기능은 일반적으로 사용자가 입력한 아이디와 비밀번호가 데이터베이스에 존재하는지 확인합니다.

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

입력값이 데이터와 명령어로 명확하게 분리돼 있다면 문제가 없습니다. 그러나 프로그램이 문자열 연결 방식으로 SQL 쿼리를 만든다면 사용자가 입력한 특수문자와 SQL 구문이 단순한 데이터가 아니라 데이터베이스 명령의 일부로 해석될 수 있습니다.

SQL 인젝션의 핵심은 외부 입력값이 SQL 명령문의 구조에 영향을 미친다는 것입니다.

SQL 인젝션이 발생하는 원인

SQL 인젝션의 대표적인 원인은 사용자 입력값과 SQL 명령어를 문자열로 직접 결합하는 것입니다. 다음은 자바에서 작성한 취약한 코드의 예시입니다.

String sql = "SELECT * FROM users WHERE username = '"
        + username + "' AND password = '" + password + "'";

Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);

이 코드에서 usernamepassword는 외부에서 전달되는 값입니다. 프로그램은 입력값을 검증하거나 별도의 데이터로 처리하지 않고 SQL 문장에 직접 연결합니다. 따라서 입력값에 SQL 문법으로 사용되는 특수문자가 포함되면 쿼리 구조가 달라질 가능성이 있습니다.

주요 발생 원인은 다음과 같습니다.

  • 사용자 입력값을 SQL 문자열에 직접 연결하는 경우
  • 동적 쿼리를 안전하지 않은 방식으로 생성하는 경우
  • 입력값의 형식과 길이를 제한하지 않는 경우
  • 데이터베이스 계정에 과도한 권한을 부여한 경우
  • 상세한 데이터베이스 오류 메시지를 사용자에게 노출하는 경우
  • 오래된 프레임워크나 직접 제작한 데이터 접근 코드를 사용하는 경우

입력값 검증만으로 모든 SQL 인젝션을 방어하려고 해서는 안 됩니다. 가장 중요한 대응 방법은 파라미터화된 쿼리를 사용해 SQL 명령어와 사용자 데이터를 구조적으로 분리하는 것입니다.

SQL 인젝션 공격으로 발생할 수 있는 피해

SQL 인젝션 공격이 성공했을 때 발생하는 피해는 애플리케이션 구조와 데이터베이스 계정의 권한에 따라 달라집니다.

첫 번째 피해는 인증 우회입니다. 로그인 조건이 조작되면 정상적인 계정 정보 없이도 인증 절차가 우회될 수 있습니다.

두 번째는 개인정보 유출입니다. 회원 이름, 이메일 주소, 전화번호, 주소와 같은 정보뿐만 아니라 비밀번호 해시와 내부 업무 데이터가 노출될 수 있습니다.

세 번째는 데이터 변조와 삭제입니다. 공격자가 데이터 변경 명령을 실행할 수 있는 환경이라면 게시물, 회원정보 또는 상품가격 등이 임의로 변경될 수 있습니다. 데이터 삭제 권한까지 존재하면 서비스 운영에 직접적인 장애가 발생할 수 있습니다.

네 번째는 데이터베이스 구조 노출입니다. 테이블 이름, 컬럼 이름, 데이터베이스 종류와 버전 등이 확인되면 공격자가 시스템 구조를 분석하는 데 악용할 수 있습니다.

따라서 SQL 인젝션은 단순한 로그인 오류가 아니라 웹서비스의 기밀성, 무결성, 가용성에 모두 영향을 줄 수 있는 중요한 취약점입니다.

PreparedStatement를 이용한 대응 방법

자바에서는 PreparedStatement를 사용해 SQL 명령어와 사용자 입력값을 분리할 수 있습니다.

String sql =
    "SELECT * FROM users WHERE username = ? AND password = ?";

PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);

ResultSet rs = pstmt.executeQuery();

위 코드의 물음표는 나중에 값이 들어갈 위치를 나타내는 바인딩 변수입니다. setString()으로 전달한 값은 SQL 명령어가 아니라 데이터로 처리됩니다. 입력값에 SQL에서 사용하는 특수문자가 포함돼 있더라도 쿼리 구조를 변경하는 명령으로 실행되지 않습니다.

이처럼 PreparedStatement가 SQL 인젝션을 방지하는 이유는 특수문자를 단순히 제거하기 때문이 아닙니다. 데이터베이스가 미리 정해진 SQL 구조와 나중에 전달되는 입력 데이터를 구분해 처리하기 때문입니다.

다만 테이블 이름이나 정렬 방향처럼 바인딩 변수를 사용할 수 없는 영역은 허용 목록을 만들어 처리해야 합니다. 예를 들어 정렬 기준을 외부 입력으로 받는다면 name, date, price 등 사전에 허용한 값만 선택할 수 있도록 제한해야 합니다.

추가로 적용해야 할 보안 대책

파라미터화된 쿼리를 적용한 뒤에도 여러 방어 대책을 함께 사용하는 것이 좋습니다.

입력값 검증

아이디, 이메일, 날짜, 숫자 등 입력 항목마다 허용되는 형식과 최대 길이를 지정합니다. 숫자만 필요한 값은 정수형으로 변환하고, 선택 항목은 허용 목록 방식으로 검사해야 합니다.

데이터베이스 최소 권한

웹 애플리케이션이 사용하는 데이터베이스 계정에는 업무에 필요한 권한만 부여해야 합니다. 단순 조회 기능이라면 불필요한 테이블 변경이나 삭제 권한을 제거합니다. 취약점이 발생하더라도 피해 범위를 줄일 수 있습니다.

오류 메시지 숨김

데이터베이스 오류 내용을 화면에 그대로 출력하면 테이블명, 컬럼명, SQL 문장과 같은 내부 정보가 노출될 수 있습니다. 사용자에게는 일반적인 오류 안내만 제공하고 상세 오류는 접근이 통제된 서버 로그에 기록해야 합니다.

비밀번호 안전하게 저장하기

비밀번호를 SQL 인젝션으로부터 보호하는 것과 안전하게 저장하는 것은 별개의 문제입니다. 비밀번호는 평문으로 저장하거나 단순 암호화하지 말고, 검증된 비밀번호 해시 알고리즘과 솔트를 사용해야 합니다.

보안 점검과 로그 모니터링

개발 단계에서는 코드 검토와 정적 분석을 수행하고, 운영 단계에서는 비정상적으로 반복되는 요청과 데이터베이스 오류를 확인해야 합니다. 보안 점검은 반드시 소유하거나 명시적으로 허가받은 시스템에서 진행해야 합니다.

SQL 인젝션 점검 시 확인할 항목

SQL 인젝션 취약점을 점검할 때는 공격 문자열 입력 여부만 확인해서는 부족합니다. 다음과 같은 코드와 설정을 함께 살펴봐야 합니다.

  • 외부 입력값이 SQL 쿼리에 직접 연결되는가?
  • Statement보다 PreparedStatement를 사용하고 있는가?
  • 모든 바인딩 가능한 값에 파라미터화된 쿼리를 적용했는가?
  • 동적 정렬과 테이블 선택에 허용 목록을 적용했는가?
  • 데이터베이스 오류 정보가 사용자 화면에 노출되는가?
  • 애플리케이션 데이터베이스 계정에 불필요한 권한이 있는가?
  • 비밀번호를 안전한 해시 방식으로 저장하고 있는가?
  • 비정상적인 요청과 오류가 서버 로그에 기록되는가?

자주 묻는 질문

입력값에서 작은따옴표를 제거하면 안전한가요?

아닙니다. 특정 문자만 제거하는 방식은 인코딩, 데이터베이스 종류, 쿼리 작성 방법에 따라 우회될 수 있습니다. 문자 제거보다 PreparedStatement와 같은 파라미터화된 쿼리를 우선 적용해야 합니다.

ORM을 사용하면 SQL 인젝션이 완전히 사라지나요?

ORM이 제공하는 안전한 파라미터 바인딩 기능을 올바르게 사용하면 위험을 크게 줄일 수 있습니다. 그러나 문자열로 직접 쿼리를 만들거나 Native Query에 외부 입력값을 연결하면 ORM 환경에서도 취약점이 발생할 수 있습니다.

웹 방화벽만 설치하면 SQL 인젝션을 막을 수 있나요?

웹 방화벽은 의심스러운 요청을 탐지하거나 차단하는 보조 수단입니다. 우회 가능성이 있으므로 근본적인 해결책으로 볼 수 없습니다. 애플리케이션 코드에서 파라미터화된 쿼리를 적용하는 것이 우선입니다.

마무리

SQL 인젝션은 사용자 입력값이 SQL 명령문의 구조를 변경할 수 있을 때 발생합니다. 가장 효과적인 대응 방법은 PreparedStatement와 같은 파라미터화된 쿼리를 사용해 명령어와 데이터를 분리하는 것입니다.

여기에 입력값 검증, 데이터베이스 최소 권한, 오류 메시지 관리, 안전한 비밀번호 저장, 로그 모니터링을 함께 적용하면 위험을 더욱 낮출 수 있습니다. 개발자는 모든 외부 입력값을 신뢰하지 않는다는 원칙을 세우고, 데이터베이스에 전달되는 과정을 코드 수준에서 점검해야 합니다.

핵심 요약: SQL 문자열에 사용자 입력값을 직접 연결하지 말고, 파라미터화된 쿼리를 사용해 입력값을 데이터로 처리해야 합니다.

핵심 키워드: SQL 인젝션, SQL Injection, SQL 인젝션 공격, SQL 인젝션 대응 방법, PreparedStatement

태그: 웹 보안, 웹 취약점, SQL 인젝션, 시큐어 코딩, 데이터베이스 보안

댓글 남기기