개요
SQL(Structured Query Language)은 관계형 데이터베이스를 조작하는 표준 언어입니다. 그러나 웹 애플리케이션이 사용자 입력을 그대로 SQL 문에 결합하면 공격자가 악성 명령을 주입해 데이터 열람, 삭제, 변조 등 심각한 피해를 유발할 수 있습니다. 이 가이드는 SQL 인젝션의 동작 원리, 취약점 예제, 실무에서 적용할 수 있는 방어 기법을 단계별로 설명합니다.
중요 용어: SQL 인젝션 — 공격자가 악성 SQL 구문을 입력 필드나 파라미터로 삽입해 데이터베이스가 의도하지 않은 동작을 하게 만드는 공격 기법.
SQL 인젝션은 어떻게 발생하는가
공격자는 웹 폼, URL 파라미터, 쿠키, 헤더 등 사용자로부터 들어오는 데이터를 통해 SQL 문을 조작합니다. 취약한 애플리케이션은 사용자 입력을 이스케이프하지 않거나, 쿼리와 데이터를 분리하지 않을 때 공격에 노출됩니다.
가장 흔한 방식은 조건문을 조작해 인증 우회나 데이터 반환을 유도하는 것입니다. 예를 들어 로그인 폼에서 다음과 같은 PHP 코드가 있다고 가정합니다.
위 코드는 사용자가 입력한 문자열을 그대로 쿼리에 결합합니다. 만약 공격자가 패스워드 필드에 다음과 같은 값을 입력하면:
‘ OR ‘a’=’a
결과 쿼리는 다음과 같이 변형됩니다.
SELECT * FROM users WHERE username='computer' AND user_password='' OR 'a'='a';
문장 ‘a’=’a’는 항상 참이므로, 쿼리는 빈 결과가 아닌 레코드를 반환하게 되고 인증을 우회할 수 있습니다.
방어 원칙 요약
- 입력 검증(Validation) 및 정규화(Normalization)
- 이스케이프 처리(Escaping)와 파라미터화(Prepared Statement)
- 최소 권한 원칙(Least Privilege)
- 내부 오류 상세 메시지 노출 금지
- 보안 로깅 및 모니터링
실무 방어 기법
1) 입력 검증과 정규화
서버 측에서 모든 입력을 검증하세요. 길이, 형식, 허용 문자 집합(화이트리스트)을 사용합니다. 예: 이메일 입력은 이메일 문자만 허용하고, 숫자 필드는 오직 0-9만 허용합니다. 클라이언트 측 검증은 UX를 위해 필요하지만, 서버 측 검증은 필수입니다.
팁: 화이트리스트(허용 목록)가 블랙리스트(금지 목록)보다 안전합니다.
2) 이스케이프 처리
이스케이프는 특수 문자가 쿼리의 문법으로 해석되지 않도록 만듭니다. PHP의 경우 mysqli_real_escape_string() 같은 함수를 사용해 특수 문자를 이스케이프할 수 있습니다.
다음은 원본 예제에 이스케이프를 적용한 코드입니다.
주의: 이스케이프는 사용 중인 드라이버과 문자 인코딩에 따라 올바르게 적용되어야 합니다. 또한 이스케이프는 단독으로는 완전한 보호 수단이 아닙니다. 가능한 경우 파라미터화된 쿼리를 사용하세요.
3) 파라미터화된 쿼리(Prepared Statements)
가장 권장되는 방법은 쿼리와 데이터를 분리하는 것입니다. 파라미터화된 쿼리는 입력값을 데이터로 취급하므로 SQL 문법과 섞이지 않습니다.
PHP(PDO) 예제:
prepare('SELECT * FROM users WHERE username = :username AND user_password = :password');
$stmt->execute(['username' => $_POST['username'], 'password' => $_POST['password']]);
$user = $stmt->fetch();
?>
위 방식은 입력값이 쿼리 구조에 영향을 주지 않으므로 SQL 인젝션을 효과적으로 방지합니다.
4) 최소 권한 원칙
애플리케이션이 데이터베이스에 접속할 때는 필요한 권한만 부여하세요. 예를 들어 단순 조회용 기능에 대해서는 INSERT/UPDATE/DELETE 권한을 제거합니다. 이렇게 하면 공격자가 쿼리 실행 권한을 얻더라도 할 수 있는 행동이 제한됩니다.
Microsoft SQL Server 예시 권한 제한:
deny select on sys.tables to sqldatabasepermit;
deny select on sys.packages to sqldatabasepermit;
deny select on sys.sysobjects to sqldatabasepermit;
권한을 설계할 때는 역할 기반 접근 제어(RBAC)를 도입해 계정별 최소 권한을 설정하세요.
5) 오류 메시지와 정보 노출 방지
데이터베이스 오류 메시지는 내부 구조, 테이블명, 컬럼명 등 민감 정보를 유출할 수 있습니다. 운영 환경에서는 상세한 DB 오류를 사용자에게 노출하지 말고, 내부 로그에만 기록하세요.
6) 보안 로깅과 모니터링
의심스러운 쿼리 패턴(예: 연속된 작은따옴표, OR 1=1, 주석 토큰 ‘–‘)을 탐지하도록 쿼리 로그와 웹서버 로그를 모니터링하세요. 자동화된 경보를 설정해 이상 징후가 있으면 즉시 조사합니다.
방어 기법 비교 요약
- 이스케이프: 기존 코드에 상대적으로 쉽게 적용 가능하지만 완전하지 않음.
- 파라미터화(Prepared Statement): 가장 강력하고 권장되는 방식.
- ORM 사용: 안전한 쿼리 생성을 돕지만 ORM 자체가 완전 무결한 것은 아님. 쿼리 빌더를 사용할 때도 인자 바인딩을 확인하세요.
- 최소 권한: 공격 성공 시 피해 범위를 축소.
실전 팁: 개발자와 운영자가 할 일
개발자 체크리스트:
- 모든 입력은 서버 측에서 검증한다.
- SQL 쿼리는 파라미터화한다.
- ORM이나 쿼리 빌더 사용 시 바인딩을 확인한다.
- 예외 메시지를 클라이언트로 직접 전송하지 않는다.
DBA/운영 체크리스트:
- 애플리케이션 DB 계정에 최소 권한만 부여한다.
- 감사 로그(감사 추적)를 활성화한다.
- 주기적으로 취약점 스캔과 펜테스트를 수행한다.
보안 테스트(침투 테스터) 체크리스트:
- 모든 입력 지점에 다양한 인젝션 페이로드를 시도한다.
- 에러 기반, 블라인드, 시간 기반 인젝션을 모두 테스트한다.
- 쿼리 실행 로그와 애플리케이션 로그를 대조해 증거를 수집한다.
사고 대응(Incident Response) 기본 흐름
- 탐지: 이상 트래픽/로그에서 의심스러운 패턴 확인.
- 격리: 영향 범위를 제한하기 위해 애플리케이션 또는 DB 연결을 일시 정지.
- 분석: 공격 벡터, 사용된 페이로드, 유출 또는 변조 여부 조사.
- 복구: 백업을 이용한 데이터 복원, 취약점 패치 적용.
- 보고: 내부·외부 관련자에게 사건 보고 및 재발 방지 계획 수립.
간단한 롤백 기준: 데이터베이스 스키마가 변조되었거나 대량 삭제가 확인되면 즉시 읽기 전용 모드로 전환하고 백업으로 복원하는 것이 안전합니다.
공격이 실패하는 경우와 한계
- 파라미터화된 쿼리를 전면 적용하면 대부분의 구조적 인젝션은 실패합니다.
- 그러나 애플리케이션 레벨의 로직 결함(예: 동적 SQL을 문자열로 조립하는 경우)이나 외부 시스템 호출 등은 여전히 위험합니다.
- ORM에서 Raw Query를 사용할 때는 개발자의 실수로 취약점이 다시 생길 수 있습니다.
따라서 다층 방어(Defense in Depth)를 적용해야 합니다.
보안 하드닝 체크리스트 요약
- 파라미터화 사용 여부 점검
- 입력 화이트리스트 적용
- DB 계정 권한 최소화
- 상세 오류 메시지 비노출
- 쿼리/앱 로그 모니터링 및 인텔리전스 기반 경보
- 주기적 펜테스트 및 코드 리뷰
간단한 헬프풀 스니펫(언어별)
PHP(PDO):
// 안전한 로그인 예시
$stmt = $pdo->prepare('SELECT id FROM users WHERE username = :username AND user_password = :password');
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);
$stmt->execute();
$user = $stmt->fetch();
Node.js (mysql2):
const [rows] = await pool.execute('SELECT * FROM users WHERE username = ? AND user_password = ?', [username, password]);
Prepared Statement를 사용하면 드라이버가 자동으로 타입과 이스케이프를 처리합니다.
위험 매트릭스와 완화 방안
- 고위험: 인증 우회, 데이터 유출(고감도 데이터) — 완화: 파라미터화, 최소권한, 감지/알림
- 중간위험: 데이터 변조, 무결성 훼손 — 완화: 트랜잭션 검증, 감사 로그, 무결성 체크
- 낮은위험: 서비스 거부를 유도하는 무거운 쿼리 — 완화: 쿼리 타임아웃, 자원 제한
1줄 용어집
- 파라미터화: 쿼리 구조와 데이터를 분리해 입력을 데이터로만 취급하는 기법.
- 최소 권한 원칙: 계정에 필요한 최소한의 권한만 부여하는 보안 원칙.
요약
SQL 인젝션은 여전히 널리 사용되는 공격 벡터이지만, 적절한 코딩 습관과 운영 정책으로 위험을 크게 낮출 수 있습니다. 핵심은 입력을 신뢰하지 않고, 쿼리와 데이터를 분리하며, 권한을 제한하고, 오류/로그를 안전하게 처리하는 것입니다. 정기적인 테스트와 모니터링을 통해 방어 체계를 유지하세요.
중요: 이 문서의 기법들은 대부분의 공격을 차단하지만, 조직의 특수 환경에 따라 추가 조치가 필요할 수 있습니다. 펜테스트와 코드 리뷰를 정기적으로 수행해 실제 적용 상태를 확인하세요.