
얼마 전 작은 쇼핑몰의 검색 기능을 점검하다가 검색어가 SQL 문장에 그대로 붙는 코드를 봤습니다. 서버 자원은 넉넉했고 방화벽도 켜져 있었지만, 그 부분에서는 입력값 하나가 쿼리의 뜻을 바꿀 수 있었습니다. sql 인젝션 공격 막는법을 묻는다면 저는 서버 요금제보다 먼저 애플리케이션이 SQL을 만드는 방식을 확인합니다.
입력값과 SQL 문장을 분리한다
SQL 인젝션은 사용자가 넣은 글자가 데이터가 아니라 명령의 일부로 해석될 때 생깁니다. 예를 들어 상품 검색어를 SELECT 문 뒤에 문자열로 이어 붙이면 따옴표나 연산자가 원래 조건을 바꿀 수 있습니다. 로그인, 검색뿐 아니라 게시글 번호, 관리자 목록의 필터, 파일에서 읽어 온 값도 같은 경로가 될 수 있습니다.
기본 처방은 준비된 쿼리와 바인딩 매개변수입니다. 쿼리에는 WHERE id = ?처럼 자리를 남겨 두고, 실제 id 값은 데이터베이스 드라이버의 매개변수로 따로 전달합니다. 숫자처럼 보이는 값도 문자열을 이어 붙이지 않는 편이 안전합니다. ORM을 쓰더라도 원시 SQL을 직접 실행하는 부분은 별도로 확인해야 합니다.
입력값에서 따옴표를 지우거나 특정 단어를 막는 방식은 방어의 중심이 될 수 없습니다. 정상 데이터에도 그런 문자가 들어갈 수 있고, 인코딩과 구문 차이 때문에 빠뜨리는 경우가 생깁니다. 입력 길이와 형식을 검사하는 일은 필요하지만, 값이 쿼리 구조에 섞이지 않게 만드는 일이 먼저입니다.
정렬 기준과 테이블 이름은 허용 목록으로 고른다
매개변수는 보통 검색어와 숫자 같은 값에 쓸 수 있습니다. 반면 ORDER BY에 들어가는 열 이름이나 정렬 방향, 테이블 이름은 같은 방식으로 바인딩할 수 없는 경우가 많습니다. 그래서 게시글 목록에서 사용자가 보낸 sort 값을 SQL 뒤에 그대로 붙이는 코드가 자주 문제를 만듭니다.
이럴 때는 입력값을 서버에 미리 적어 둔 선택지로 바꿉니다. 예를 들어 최신순은 생성일 열의 내림차순, 인기순은 조회수 열의 내림차순처럼 두 가지 쿼리 조각만 코드에서 선택합니다. 예상하지 못한 값은 거절하거나 기본 정렬을 적용합니다. 사용자가 보낸 글자를 검사한 뒤 그대로 SQL에 넣는 것보다 선택 가능한 쿼리 구조를 제한하기 쉽습니다.
저장 프로시저도 내부에서 문자열을 조립해 실행한다면 안전하다고 볼 수 없습니다. 관리자 화면, 예약 작업, 데이터 가져오기 작업에서 동적 SQL을 쓰는 부분까지 찾아야 합니다. 처음 입력될 때는 문제없던 값이 데이터베이스에 저장됐다가 나중에 다른 쿼리에 붙는 경우도 있습니다.
데이터베이스 계정 권한으로 피해 범위를 줄인다
코드를 고쳐도 운영 계정이 모든 테이블에 읽기·쓰기 권한을 갖고 있으면 사고의 범위가 커집니다. 상품 목록만 읽는 서비스에 테이블 삭제 권한을 줄 이유는 없습니다. 애플리케이션별로 계정을 나누고, 필요한 테이블에 필요한 작업만 허용하는 편이 낫습니다. 데이터베이스 관리자 계정으로 웹서비스를 연결하는 구성은 먼저 바꿔야 합니다.
웹서버와 데이터베이스를 분리했다면 접속 가능한 서버도 제한합니다. 데이터베이스를 외부에 공개할 필요가 없는 구조에서는 애플리케이션 서버에서만 연결되게 설정합니다. 다만 네트워크 차단이나 웹 방화벽이 취약한 쿼리를 안전하게 바꿔 주지는 않습니다. OWASP도 매개변수화와 최소 권한을 각각 권고합니다.
백업은 별개로 확인해야 합니다. 매일 백업 파일이 생긴다는 사실보다 실제로 복원할 수 있는지가 중요합니다. 저는 백업 시각, 보관 기간, 별도 위치의 사본, 복원에 걸린 시간을 함께 기록합니다. 공격으로 데이터가 바뀐 시점을 찾지 못하면 최신 백업에도 손상된 데이터가 들어 있을 수 있습니다.
점검과 장애 대응 순서를 미리 정한다
점검할 때는 사용자 입력이 데이터베이스 호출까지 가는 경로를 먼저 찾습니다. 검색창처럼 눈에 띄는 곳 외에 URL의 숫자 매개변수, 정렬 메뉴, 관리자 기능, 외부 데이터 수집 작업을 살핍니다. 코드에서 문자열 연결로 SQL을 만드는 부분을 찾고, 수정 뒤에는 정상 검색과 잘못된 입력이 모두 예상대로 처리되는지 확인합니다. 운영 서비스에 무리한 공격 문장을 반복해서 보내기보다 테스트 환경에서 검증하는 편이 장애 위험이 작습니다.
- 의심스러운 요청이 몰리면 접근 기록에서 시간대, 요청 경로, 응답 코드의 변화를 확인합니다.
- 데이터베이스 오류가 늘었다면 애플리케이션 로그와 함께 확인하되, 오류 내용이나 개인정보가 사용자 화면에 노출되지 않게 합니다.
- 침해가 의심되면 관련 기능을 제한하고 로그와 데이터 상태를 보존한 뒤, 영향받은 계정의 자격 증명을 교체합니다.
- 데이터 변경 여부를 확인하고 복원이 필요하면 검증된 백업으로 테스트한 다음 서비스에 적용합니다.
트래픽이 적은 사이트도 공개된 입력창 하나로 데이터가 손상될 수 있습니다. 서버 사양을 올리기 전에 쿼리의 매개변수 처리, 계정 권한, 복원 절차를 확인하는 쪽이 운영에서는 훨씬 값어치가 큽니다.
