핵심 요약
- 러스트의 특수한 메모리 수명 규칙인 `'static` 라이프타임을 쿼리 인자에 강제 지정해, 동적 문자열 연결 시 컴파일 자체가 실패하도록 만드는 트릭이 소개되었습니다.
- 개발자가 플레이스홀더 대신 `format!` 등으로 쿼리문을 직접 조립할 경우 빌드 단계에서 에러가 발생해 취약점 발생을 사전에 막을 수 있습니다.
- 메모리 릭을 유도하는 함수 등으로 우회는 가능하지만, 잘못된 코딩 방식을 원천적으로 까다롭게 만든다는 점에서 정적 분석의 유용성을 보여줍니다.
요약 SQL 인젝션(SQL Injection)은 사용자 입력을 처리하는 웹 애플리케이션과 데이터베이스가 결합된 이래 개발자들을 괴롭혀 온 대표적인 보안 취약점입니다. 공격자가 악의적으로 조작한 문자열을 쿼리에 그대로 이어 붙일(문자열 연결) 때 발생하며, 이를 막으려면 변수 데이터를 쿼리문 자체와 완전히 분리하는 '플레이스홀더(placeholder)'나 '프리페어드 스테이트먼트(prepared statement)'를 사용하는 것이 정석입니다.
대다수 현대 SQL 라이브러리가 이 기능을 지원하지만, 개발자의 부주의나 실수로 `format!` 같은 매크로를 써서 동적으로 쿼리를 생성하면 여전히 취약점이 발생할 수 있습니다. 일반적인 환경에서는 이런 실수를 저질러도 코드가 정상 빌드되므로, 별도의 코드 리뷰나 감사 전까지는 버그를 발견하기 어렵습니다.
글쓴이는 러스트(Rust) 언어의 고유한 메모리 관리 개념인 '라이프타임(Lifetime)'을 역이용해, 이 같은 실수를 컴파일 단계에서 원천 차단하는 기발한 묘수를 제시합니다. 기본적으로 함수 파라미터의 라이프타임은 컴파일러가 자동 추론하지만, 쿼리 실행 함수인 `sql_query`의 쿼리 문자열 인자에 프로그램 전체 실행 시간 동안 유지되어야 하는 `'static` 라이프타임을 강제(`query: &'static str`)하는 방식입니다.
이렇게 지정하면 런타임에 동적으로 조합되는 가변 문자열은 스택이나 힙에 머무는 유효 수명이 짧아 컴파일러의 `'static` 조건을 만족하지 못하고 빌드 에러(`borrowed value does not live long enough`)를 내뿜으며 컴파일이 중단됩니다. 결국 개발자는 `"SELECT * FROM users WHERE username=?"`처럼 정적 리터럴 문자열을 쓰고, 사용자 파라미터는 플레이스홀더를 통해 전달해야만 코드가 정상 컴파일되도록 강제되는 셈입니다.
작성자는 이후 Rust 1.26에 추가된 `Box::leak` 등을 통해 메모리를 해제하지 않는 방식으로 `'static`을 우회할 수는 있겠지만, 여전히 개발자가 '잘못된 방식'으로 코딩하는 것을 컴파일러 차원에서 까다롭게 만들어 보안성을 높여준다는 점에서 러스트 정적 검사의 강력함을 보여주는 좋은 사례라고 평가했습니다.
Sponsored · 광고