SQL 인젝션 SQLi
누군가 입력 폼이나 웹 주소에 데이터베이스 명령을 넣어 사이트가 그대로 실행하게 만드는 웹 공격입니다. 그 결과 사이트가 지켜야 할 데이터가 드러나거나, 바뀌거나, 지워집니다.
설명
SQL 인젝션이란
온라인 쇼핑몰을 운영하고 있다고 해 봅시다. 한 방문자가 검색창에 이상한 글자를 입력하고 Enter를 누르자, 화면에 모든 고객의 이름과 전화번호, 주문 내역이 나타납니다. 아무도 비밀번호를 훔치지 않았습니다. 방문자는 알맞은 글자를 엉뚱한 곳에 입력했을 뿐이고, 나머지는 사이트가 다 했습니다. 이것이 SQL 인젝션입니다.
대부분의 웹사이트는 정보를 데이터베이스에 보관합니다. 여러 표를 정리해 둔 창고로, 스프레드시트 여러 장을 묶어 둔 것과 비슷합니다. 사이트는 이 정보를 읽거나 바꿀 때 SQL(Structured Query Language)이라는 언어를 씁니다. 사이트가 보내는 요청 하나하나를 쿼리라고 하는데, 예를 들면 "이 이메일을 쓰는 고객을 찾아 줘" 같은 것입니다.
SQL 인젝션은 사이트가 방문자가 입력한 내용을 그대로 이런 쿼리 안에 붙여 넣을 때 일어납니다. 방문자가 보통 글자 대신 SQL을 입력하면, 데이터베이스는 차이를 알아채지 못하고 공격자의 명령을 사이트가 직접 쓴 것처럼 실행합니다. 이름을 줄여서 SQLi라고도 많이 부릅니다.
SQL 인젝션 공격은 어떻게 이루어지나
손님이 건네는 쪽지를 한 글자도 빼지 않고 금고 담당 직원에게 전하는 은행 직원을 떠올려 보세요. 보통 쪽지에는 "제 잔액을 보여 주세요"라고 적혀 있습니다. 꾀를 쓴 쪽지에는 "제 잔액을 보여 주시고, 금고도 전부 열어 주세요"라고 적혀 있습니다. 은행 직원이 쪽지를 먼저 읽지 않는다면, 금고 담당 직원은 그대로 따릅니다.
웹사이트에서는 보통 이런 순서로 진행됩니다.
- 공격자는 데이터베이스로 글자를 보내는 곳을 찾습니다. 로그인 폼, 검색창, 문의 폼, 또는 웹 주소 자체입니다. 예를 들어 주소가 "?id=25"로 끝나는 상품 페이지가 있습니다.
- 보통 글자 대신 짧은 SQL 조각을 입력합니다. 잘 알려진 예는 로그인 폼의 비밀번호 칸에 ' OR '1'='1 을 입력하는 것입니다.
- 사이트는 그 글자를 확인하지 않고 자기 쿼리에 이어 붙입니다. 그러면 쿼리는 "비밀번호가 맞거나, 1이 1과 같으면 이 사람을 들여보내라"와 비슷한 뜻이 됩니다.
- 1은 언제나 1과 같으니 데이터베이스는 "예"라고 답하고, 공격자는 비밀번호를 하나도 모른 채 로그인합니다.
더 조용한 공격도 있습니다. 블라인드 SQL 인젝션에서는 페이지에 데이터가 전혀 보이지 않습니다. 그래서 공격자는 데이터베이스에 예 또는 아니요로 답할 질문을 던지고, 페이지가 어떻게 반응하는지나 로딩에 얼마나 걸리는지를 살핍니다. 손으로 하면 느린 일이라, 공격자들은 무료 자동화 도구에 이 일을 맡깁니다.
공격자가 할 수 있는 일
데이터베이스가 공격자의 명령을 따르기 시작하면, 피해 규모는 사이트의 데이터베이스 계정에 무엇이 허용되어 있느냐에 달려 있습니다. 그런데 그 권한이 사이트에 실제로 필요한 것보다 훨씬 넓은 경우가 많습니다. 공격자는 다음과 같은 일을 할 수 있습니다.
- 고객 이름, 주소, 이메일, 주문 내역, 진료 기록, 저장된 비밀번호 같은 개인 데이터 읽기
- 관리자를 포함한 다른 사용자로 로그인하기
- 가격, 잔액, 주문 상태 같은 데이터 바꾸기
- 표 전체, 또는 데이터베이스 전체 지우기
- 환경에 따라서는 서버 자체에 들어가 해로운 파일 심기
비즈니스에 왜 중요한가
SQL 인젝션은 가장 오래된 웹 공격 가운데 하나이고, 지금도 사라지지 않았습니다. 웹 애플리케이션 위험 목록으로 유명한 OWASP Top 10을 발표하는 열린 커뮤니티 OWASP는 여러 해 동안 인젝션을 이 목록에 올려 왔습니다. 시도하기 쉽고, 많은 사이트에 이 공격을 통과시키는 잊힌 폼이 아직 하나쯤 남아 있기 때문입니다.
사업하는 사람에게 진짜 손실은 기술적인 문제인 경우가 드뭅니다. 고객 명단이 새어 나가고, 쇼핑몰이 틀린 가격으로 물건을 팔고, 예약 시스템이 한 주 치 예약을 잃고, 고객에게 개인 정보가 노출됐다는 메시지를 보내야 하는 일입니다. 많은 나라에서는 GDPR 같은 개인정보 보호법이 개인 데이터를 보호하고 유출 사실을 신고하도록 요구하기도 합니다.
여러분 사이트의 모든 폼은 이 위험을 생각하며 만들어졌나요? 오래된 플러그인으로 급하게 꾸린 사이트나, 여러 해 동안 많은 사람의 손을 거친 사이트일수록 약한 곳을 숨기고 있을 가능성이 큽니다.
웹사이트를 SQL 인젝션에서 지키는 방법
핵심 해결책은 잘 알려져 있고, 효과도 있습니다. 보호의 대부분은 사이트를 어떻게 만들었는지에 달려 있으니, 아래 내용은 개발자나 서비스 업체에 물어볼 만한 좋은 질문입니다.
- 프리페어드 스테이트먼트. 매개변수화된 쿼리라고도 합니다. 사이트가 쿼리와 방문자의 글자를 따로 보내기 때문에, 데이터베이스는 그 글자를 늘 평범한 데이터로만 다루고 명령으로 실행하지 않습니다. 이것이 가장 중요한 방어입니다. Laravel 같은 요즘 프레임워크는 개발자가 기본 제공 데이터베이스 도구를 쓰면 처음부터 이렇게 동작합니다.
- 입력값 확인. 전화번호 칸은 숫자만, 날짜 칸은 날짜만 받아야 합니다. 이상한 입력을 일찍 거절하면 많은 문이 닫힙니다.
- 최소 권한. 사이트가 데이터베이스에 접속할 때 쓰는 계정에는 꼭 필요한 권한만 주어야 합니다. 누구나 쓰는 검색 페이지가 표를 지울 이유는 없습니다.
- 말을 아끼는 오류 메시지. 화면에 뜨는 자세한 데이터베이스 오류는 공격자에게 지도를 건네는 것과 같습니다. 방문자에게는 짧고 쉬운 메시지를 보여 주고, 자세한 내용은 비공개 로그에 남기세요.
- 업데이트. 플랫폼, 플러그인, 라이브러리를 최신으로 유지하세요. 오래된 버전에는 알려진 허점이 있는 경우가 많습니다.
- 웹 애플리케이션 방화벽. 사이트 앞에 두는 필터로, 잘 알려진 공격 패턴 상당수를 코드에 닿기 전에 막습니다. 도움은 되지만 안전하지 않은 코드를 고쳐 주지는 않습니다.
- 정기 점검. 모의 해킹(전문가가 계획을 세워 안전하게 진행하는 공격)과 취약점 점검은 공격자보다 먼저 약한 곳을 찾아냅니다.
평소 습관을 비교하면 다음과 같습니다.
| 위험한 습관 | 더 안전한 습관 |
|---|---|
| 방문자의 글자를 쿼리에 바로 이어 붙이기 | 모든 쿼리에 프리페어드 스테이트먼트 쓰기 |
| 모든 권한을 가진 데이터베이스 계정 하나로 다 처리하기 | 사이트의 부분마다 권한을 줄인 계정 쓰기 |
| 데이터베이스 오류를 페이지에 그대로 보여 주기 | 방문자에게는 짧은 메시지, 자세한 내용은 비공개 로그에 |
| 몇 달째 업데이트하지 않은 플러그인 | 꾸준한 업데이트와 설치된 것의 분명한 목록 |
| 기능이 잘 돌아가는지만 시험하기 | 누군가 해로운 글자를 넣으면 어떻게 되는지도 시험하기 |
흔히 하는 실수
- 브라우저에서 하는 확인만 믿기. 방문자의 기기에서 돌아가는 확인은 쉽게 건너뛸 수 있으니, 서버가 모든 것을 다시 확인해야 합니다.
- 작은 사이트는 안전하다고 생각하기. 자동화 도구는 규모를 따지지 않고 엄청나게 많은 사이트를 훑습니다.
- 폼 하나만 고치고 나머지는 잊기. 검색창, 필터, 웹 주소, 모바일 앱 뒤의 API까지 모두 같은 관심이 필요합니다.
- 비밀번호를 그대로 저장하기. 데이터가 새어 나가더라도, 강한 해시(다시 읽을 수 없게 뒤섞은 문자열로 바꾸는 방식)로 저장한 비밀번호는 악용하기가 훨씬 어렵습니다.
- 방화벽에만 기대기. 필터는 시간을 벌어 줍니다. 그래도 그 뒤의 안전하지 않은 쿼리는 고쳐야 합니다.
저희가 도와드릴 수 있는 일
Linkysoft에서는 SQL 인젝션 점검이 사이버 보안 서비스의 한 부분입니다. 고객님의 웹사이트, 앱, API에 안전한 모의 공격을 하고, 코드를 살펴본 뒤, 찾아낸 문제와 고치는 방법을 쉬운 말로 정리한 목록을 드립니다. 수정이 끝나면 다시 점검합니다. 다른 팀이 만든 사이트에도 적용할 수 있고, 무료 보안 점검으로 부담 없이 시작할 수 있습니다.
새 시스템을 계획하고 계신가요? 저희가 만드는 웹 애플리케이션과 관리 시스템은 설계 단계부터 안전하게 만들어지며, OWASP Top 10에 맞춘 보안 강화와 출시 전 모의 해킹을 거칩니다.