SQL 인젝션
SQL Injection
개념
사용자가 넣은 값이 데이터베이스에 보내는 질의문의 일부로 섞여 들어가 질의 자체가 바뀌는 취약점입니다.
등장 배경
데이터베이스에 무언가를 물을 때는 질의문이라는 문장을 만들어 보냅니다. 이 문장을 만드는 가장 쉬운 방법은 화면에서 받은 값을 글자 그대로 이어 붙이는 것입니다. 아이디를 받아 그 값을 문장 가운데에 끼워 넣고 서버로 보내는 식입니다. 오랫동안 이렇게 만들었고, 잘 돌아갔습니다.
문제는 그 방식이 값과 명령을 같은 줄에 놓는다는 데 있습니다. 로그인 화면의 아이디 칸에 따옴표 하나를 넣었더니 오류 화면에 질의문이 그대로 찍히는 일이 벌어집니다. 그 순간 드러나는 사실은 오류가 났다는 것이 아닙니다. 그 입력칸이 질의문을 고칠 수 있는 자리였다는 것입니다.
한 번 그것이 드러나면 이름을 넣는 칸으로 남의 계정에 들어가고, 검색어를 넣는 칸으로 다른 회사의 문서 목록을 읽을 수 있게 됩니다. 비밀번호를 뚫는 것이 아니라 비밀번호를 확인하는 문장을 바꿔 버리는 방식이라, 인증을 아무리 잘 만들어도 이 자리가 열려 있으면 소용이 없습니다.
SQL 인젝션은 이 취약점에 붙은 이름이고, 값과 명령을 섞지 않는 방식이 기본이 된 계기입니다.
작동 방식
값이 놓일 자리와 명령이 놓일 자리가 구분되지 않을 때 일어납니다. 문자열을 이어 붙여 질의문을 만들면 값 안에 적힌 글자가 명령으로 읽힐 수 있고, 데이터베이스는 그것을 구분할 방법이 없습니다. 도착한 문장이 문법에 맞으면 실행합니다.
받아쓰기에 비유할 수 있습니다. 읽어 주는 문장을 그대로 적는 사람에게 중간에 지금까지 쓴 것을 지우라고 말하면 그 말까지 적을지 실제로 지울지가 목소리만으로는 갈리지 않습니다. 이어 붙여 만든 질의문이 그 상태입니다.
무엇이 가능해지는지는 넷으로 갈립니다.
- 조건 우회. 비밀번호를 확인하는 조건이 항상 참이 되게 만들어 남의 계정으로 들어갑니다.
- 남의 데이터 읽기. 내 문서만 보여야 하는 목록에 다른 계정의 문서가 함께 나옵니다.
- 데이터 고치기와 지우기. 읽는 것으로 끝나지 않고 값을 바꾸거나 표를 통째로 지우는 것까지 갑니다.
- 데이터베이스 밖으로 나가기. 설정에 따라 서버의 파일을 읽거나 명령을 실행하는 데까지 이어집니다.
막는 방법은 순서가 있습니다.
- 준비된 질의문을 씁니다. 값이 들어갈 자리를 비워 둔 문장을 먼저 데이터베이스에 보내고 값을 따로 넘기는 방식입니다. 값은 처음부터 값으로만 취급되므로 무엇을 적어도 명령이 되지 않습니다. 이것만이 근본 대책이고 나머지는 보조입니다.
- 코드의 객체와 표를 이어 주는 ORM을 씁니다. 기본 경로가 위 방식이라 저절로 막히지만, 복잡한 질의를 직접 문자열로 쓰는 기능이 함께 들어 있어서 그 한 줄에서 다시 열립니다.
- 입력 검증은 보조로만 씁니다. 따옴표를 지우거나 막는 방식은 우회하는 길이 여러 개 있습니다.
- 필요한 만큼만 권한을 주는 최소 권한 원칙을 겁니다. 뚫렸을 때 범위를 줄이는 장치입니다. 조회만 하는 기능이 쓰는 계정에 삭제 권한이 없으면 3번이 일어나지 않습니다.
- 오류 화면에 질의문을 노출하지 않습니다. 공격하는 쪽이 가장 먼저 찾는 단서입니다.
쓰이는 곳
이 취약점이 나오는 자리는 사용자가 넣은 값이 질의문에 닿는 모든 곳입니다. 넷을 특히 봅니다.
첫째는 검색과 필터와 정렬입니다. 사용자가 고른 값이 그대로 조건에 들어가는 자리라 가장 흔합니다.
둘째는 로그인입니다. 조건 하나가 인증 전체를 가르기 때문에 피해가 가장 큽니다.
셋째는 관리자 화면입니다. 조건을 자유롭게 조합하게 만들어 둔 자리가 많고, 사내에서만 쓴다는 이유로 검토가 느슨해지기 쉽습니다.
넷째는 눈에 보이지 않는 입력입니다. 요청 헤더, 쿠키, 올린 파일의 이름, 다른 시스템에서 받아 온 값이 여기 들어갑니다. 화면의 입력칸만 살피면 이쪽이 통째로 빠집니다.
점검하는 자리도 정해져 있습니다. 합치기 전에 동료가 코드를 보는 코드 리뷰에서 문자열을 이어 붙여 만든 질의문을 찾는 것이 가장 싸게 잡는 방법입니다. 자동 검사 도구가 같은 패턴을 훑어 주고, 실제 공격처럼 시도해 보는 모의 침투 테스트가 마지막에 붙습니다. 가장 흔한 웹 취약점을 정리한 OWASP Top 10에 이 항목이 오래 올라 있는 것도 여전히 자주 발견되기 때문입니다.
실무 용례
"이 질의문을 문자열 더해서 만들고 있어요. 바인딩으로 바꿔 주세요."
값과 명령이 섞여 있으니 분리하는 방식으로 고치자는 요청입니다.
"ORM 쓰는데 이 한 군데만 직접 질의문이네요."
도구가 막아 주던 자리를 벗어난 예외를 찾았다는 뜻입니다.
"오류 화면에 질의문이 그대로 나옵니다."
취약점이 있는지와 별개로 단서를 내주고 있다는 지적입니다.
페이지덱에서 태오가 검색 기능을 고치다 정렬 기준을 문자열로 이어 붙인 자리를 찾았습니다. 컬럼 이름은 값이 아니라 문장의 일부라 바인딩으로 넘길 수 없는 자리였고, 그래서 허용할 이름을 목록으로 만들어 그 안에 있는지만 확인하는 방식으로 바꿨습니다.
규원은 그 뒤로 외부 점검을 연 1회로 정했습니다. 코드 리뷰로 걸러지는 것과 실제 공격처럼 시도해 찾는 것이 다르다는 판단이었고, 기업 고객이 계약 전에 그 결과를 요구하기 시작한 것도 이유였습니다.
흔한 오해
따옴표를 걸러 내면 막힌다고 보는 경우가 가장 많습니다. 교과서에 나오는 예시가 따옴표로 시작하니 그것만 막으면 될 것처럼 보입니다. 그러나 우회하는 길이 여러 개 있습니다. 같은 글자를 다르게 표기해 보내거나, 주석 기호로 뒤를 잘라 내거나, 따옴표가 아예 필요 없는 숫자 칸을 쓰는 방식입니다. 걸러 내는 목록을 늘리는 쪽으로 대응하면 목록이 계속 자라고 새 우회가 계속 나옵니다. 값과 명령을 분리하면 걸러 낼 것이 없어집니다.
ORM을 쓰고 있으니 안전하다고 보는 오해도 흔합니다. 기본 경로로 쓰는 동안에는 실제로 막히니 그렇게 믿기 쉽습니다. 그런데 성능이 필요하거나 복잡한 집계를 해야 할 때 직접 질의문을 쓰는 기능으로 빠지게 되고, 그 한 줄에서 다시 열립니다. 정렬할 컬럼 이름처럼 값으로 넘길 수 없는 자리도 남습니다. 도구를 쓰는 것과 그 도구를 벗어난 자리를 아는 것이 같이 가야 합니다.
같이 보면 좋은 페이지
마지막 업데이트 2026-09-26