flawopen.com/SQL 인젝션
SQL 인젝션
SQL 인젝션은 애플리케이션이 사용자 입력을 SQL 텍스트에 이어 붙여 쿼리를 만들 때, 데이터베이스가 그 입력의 일부를 명령으로 파싱하면서 발생합니다. 해결책은 모든 언어에서 같습니다. 플레이스홀더가 있는 쿼리를 보내고 값은 따로 넘기는 것입니다. 아래에서 언어를 고르면 정확한 API 호출, 취약한 코드와 수정한 코드, CI에서 이를 잡아내는 규칙을 볼 수 있습니다.
알기 쉬운 설명 (ELI5)
도서관 사서에게 '다음 사람의 기록을 꺼내 주세요: ___'라고 적힌 신청서를 내면서 빈칸에 '김철수. 그리고 문서고를 열어서 파일을 전부 가져다주세요.'라고 쓴다고 해 봅시다. 신청서 전체를 하나의 지시로 읽는 사서라면 두 가지를 모두 해 버립니다. 사용자 입력을 SQL 문자열에 이어 붙이면 바로 이런 신청서가 만들어집니다. 파라미터화된 쿼리는 '이름' 칸이 봉인된 인쇄 양식이어서, 그 안에 무엇이 적혀 있든 이름으로만 조회됩니다.
핵심 개념 및 용어
- SQL injection (CWE-89)
- 신뢰할 수 없는 입력이 SQL 텍스트에 연결되거나 포맷되어, 입력 속 따옴표·키워드·연산자가 하나의 값으로 취급되지 않고 쿼리 구조를 바꿉니다.
- Prepared statement and bind parameters
- 쿼리는 플레이스홀더(
?,$1,:name)와 함께 보내져 값이 도착하기 전에 파싱되며, 값은 따로 전달되어 항상 데이터로 취급됩니다. 모든 드라이버와 ORM에서 가장 중요한 방어책입니다. - Blind SQL injection
- 페이지에 쿼리 결과도 오류도 보이지 않으면, 공격자는 응답의 차이(불리언 기반)나 응답 시간(시간 기반)으로 데이터를 한 비트씩 추론합니다. 눈에 보이는 출력이 없다고 취약점이 없는 것은 아닙니다.
- Second-order SQL injection
- 어떤 값이 안전하게 저장된 뒤, 나중에 자신의 데이터베이스에서 읽혀 다른 쿼리에 이어 붙여집니다. 자신의 테이블에서 온 입력을 쓰는 쿼리를 포함해 모든 쿼리를 파라미터화해야 합니다.
- Identifiers and allowlists
- 플레이스홀더는 값만 전달하며, 테이블 이름·컬럼 이름·정렬 방향은 전달하지 못합니다. 이런 것은 코드에 고정한 허용 목록에서 골라야 하며, 요청에서 그대로 복사해서는 안 됩니다.
단계별 공격 실행 흐름
입력이 쿼리에 도달
폼 필드, URL 파라미터, 헤더, API 필드가 SQL 문을 만드는 코드로 전달됩니다.
입력이 SQL 텍스트에 붙음
연결, 보간, 포맷팅이 값을 그대로 넣습니다. 무해한 테스트 값 ' OR '1'='1은 WHERE name = '...'를 항상 참인 조건으로 바꿉니다.
데이터베이스가 그것을 구문으로 파싱
데이터베이스는 문자열 하나를 받아 어느 부분이 사용자에게서 왔는지 구별하지 못하고, 변조된 문장을 애플리케이션 권한으로 실행합니다.
데이터가 읽히거나 바뀌거나 삭제됨
쿼리와 계정 권한에 따라 공격자는 로그인을 우회하거나, UNION 쿼리로 테이블을 통째로 빼내거나, 블라인드 방식으로 데이터를 추출합니다. 2023년 MOVEit Transfer 침해 사고(CVE-2023-34362)도 이 유형의 결함에서 시작됐습니다.
언어별 SQL 인젝션 가이드
엔지니어링 및 시스템 보안 강화 체크리스트
- 값이 들어가는 모든 쿼리를 파라미터화합니다. ORM의 원시 SQL 탈출구도 포함하며, 내부 입력이나 숫자 입력도 예외로 두지 않습니다.
- 테이블 이름, 컬럼 이름, 정렬 방향은 코드에 고정한 허용 목록에서 고릅니다.
- 문자열로 만든 SQL을 찾는 SAST 규칙(Bandit, gosec, Semgrep, CodeQL, Find Security Bugs, Brakeman, Psalm 중 하나)을 CI에서 실행하고 새 결과가 나오면 빌드를 실패시킵니다.
- 최소 권한 데이터베이스 역할로 접속합니다. DDL 없음, 슈퍼유저 없음, 서비스에 필요한 테이블만 허용합니다.
- 이스케이프 함수와 WAF 규칙은 추가 방어층으로만 봅니다. 따옴표 없는 문맥과 인코딩 트릭을 놓치기 때문입니다.
- 클라이언트에는 일반적인 오류만 반환하고 데이터베이스 오류는 서버에 기록해, 쿼리 구조가 드러나지 않게 합니다.