
정보를 추출하기 위해 반드시 화면 표시가 필요한 것은 아니다
요약
화면에 결과가 직접 표시되지 않는 환경에서도 데이터를 추출할 수 있는 Blind SQL Injection 공격 기법을 설명합니다. 애플리케이션의 반응 패턴, 에러 발생 여부, 응답 지연 시간을 단서로 삼아 정보를 탈취하는 세 가지 수법을 다룹니다.
핵심 포인트
- Blind SQLi는 화면에 데이터가 보이지 않아도 공격이 가능함
- 수법 1: 조건에 따른 화면 메시지 변화를 이용해 데이터 확인
- 수법 2: 조건 충족 시 의도적인 에러를 발생시켜 유무로 판단
- 수법 3: 응답 시간의 지연(Time-based)을 통해 데이터 추출
이 시리즈에서는 직접 만든 취약한 애플리케이션을 대상으로 SQL 인젝션 (SQLi)을 실연해 왔다. 제1회는 사실 보여줄 생각이 없었는데 유출되어 버린 이야기, 제2회는 전혀 관계없는 페이지에서 개인정보를 빼내는 이야기였다.
다만, 지금까지는 공통된 전제가 있었다. 공격의 결과가 화면에 제대로 나타난다는 것이다. 상품이 나오거나, 비밀번호가 나온다. 그래서 "빼냈다"는 것을 알 수 있었다.
이번에는 좀 더 기괴한 이야기를 하려 한다. 화면에는 아무것도 표시되지 않는데, 비밀번호가 한 글자씩 빼내지는 공격이다. "블라인드 SQLi (Blind SQLi)"라고 불린다.
결과가 보이지 않는데, 어떻게 빼내는가
보통 생각하면 결과가 화면에 나오지 않는다면 아무것도 훔칠 수 없을 것처럼 보인다.
하지만 공격자는 다른 것을 단서로 삼는다. 애플리케이션의 "반응"이다.
이 수법에서 공격자는 "비밀번호의 첫 글자는 a인가?"라고 데이터베이스에 질문한다. 애플리케이션의 반응이 "예"라면 첫 글자는 a라고 확정된다. 아니라면 b, c……와 같이 전수 조사를 한다. 이를 글자 수만큼 반복하여 보이지 않는 비밀번호를 한 글자씩 조립해 나간다.
문제는 "예 / 아니오"를 어떻게 읽어내느냐이다. 여기에는 단계가 있으며, 단서가 점점 가늘고 교활해진다. 실제로 직접 만든 애플리케이션으로 시도한 세 가지 수법을 순서대로 살펴보겠다.
수법 ①: 표시가 변한다
가장 알기 쉬운 것은 화면의 표시가 변하는 패턴이다. 예를 들어, 조건이 맞으면 "Welcome back"이라고 표시되고, 틀리면 아무것도 나오지 않는 동작이 있다고 가정하자. (본래는 Cookie 등으로 두 번째 방문인지 여부를 판단하는 경우가 많다)
그곳에 다음과 같이 입력한다.
' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a
복잡해 보이지만 의미는 "관리자의 비밀번호 첫 글자가 a라면, 올바른 것이라고 해줘"이다. SUBSTRING(password,1,1)이 "비밀번호의 첫 글자만 추출한다"는 지정이 된다.
이때 "Welcome back"이 나오면 첫 글자는 a. 나오지 않으면 다르다. 'a' 부분을 'b', 'c'로 바꾸며 표시가 변하는 문자를 찾는다. 맞춘 문자를 찾으면 SUBSTRING의 숫자를 2, 3으로 진행하며 두 번째 글자, 세 번째 글자……와 같이 반복한다.
표시라는 알기 쉬운 "예 / 아니오"다.
수법 ②: 에러가 발생한다 / 발생하지 않는다
다음은 표시조차 일절 변하지 않는 패턴이다. "Welcome back"과 같은 친절한 반응이 없다.
그럼에도 공격자는 포기하지 않는다. 이번에는 "조건이 맞을 때만 일부러 에러를 일으키는" 장치를 사용한다. 구체적으로는 일부러 "1 나누기 0"과 같은 계산을 시킨다. 이는 수학적으로 불가능하므로 데이터베이스가 에러를 내뱉는다.
' AND (조건이 맞을 때 1/0을 계산하게 하는 장치)--
조건이 맞으면 에러(화면이 500 에러가 됨), 틀리면 정상적으로 표시. 에러의 유무가 이번의 "예 / 아니오"가 된다. 표시가 변하지 않더라도 에러가 발생하는지 여부로 내용을 맞출 수 있다.
수법 ③: 반응이 지연된다
그리고 가장 교활한 것이 표시도 변하지 않고, 에러도 발생하지 않는, 사면초가의 케이스다.
여기서 공격자가 사용하는 마지막 수단이 시간이다. "조건이 맞을 때만 일부러 몇 초 기다리게 하는" 명령을 심는다.
' || (조건이 맞을 때 pg_sleep(5)로 5초 기다리게 함)--
pg_sleep(5)는 "5초 기다려라"라는 명령이다. 조건이 맞으면 응답이 5초 늦어지고, 틀리면 바로 돌아온다. 즉, 응답 속도만이 단서가 된다.
화면에는 정말 아무 일도 일어나지 않는다. 표시도, 에러도, 완전히 똑같다. 그런데 "응답이 느린가 빠른가"만으로 비밀번호가 한 글자씩 유출된다. 이를 알았을 때는 상당히 놀랐다. "결과를 표시하지 않으니까 안전하다", "에러를 숨겼으니까 안전하다"는 전혀 안전하지 않다. 조금이라도 단서가 하나라도 남아 있다면 그곳에서 빼내진다.
너무나 지루한 작업을 툴이 순식간에
그렇다고는 해도 이 공격에는 약점이 있다. 죽을 만큼 시간이 걸린다.
비밀번호가 20글자라면, 한 글자당 최대 36회(az 및 09)의 질문을 20글자만큼 반복한다. 손으로 "상대의 눈치"를 살피며 수백 번 질문한다. 실제로 손으로 해보았지만, 이것은 인간이 하기에는 너무나 단조롭고 시간이 많이 걸린다.
하지만 세상에는 무엇에 대해서든 자동화 도구(Automation Tool)가 존재한다. 주입(Injection)할 수 있는 위치와 상대방의 정보를 전달하면, 방금 전의 "한 글자씩 질문해서 맞히기"를 전자동으로, 고속으로 수행해 준다. 실제로 실행하면, 이와 같이 비밀번호가 하나씩 복원되어 간다.
retrieved: administrator
retrieved: (비밀번호가 한 글자씩 복원되어 간다)
손으로 수십 분이 걸렸던 작업이, 그냥 내버려 두는 것만으로 끝난다.
도구만 있으면 충분한 것은 아니다
여기까지 읽으면 "도구가 있으면 전부 자동으로 찾아낼 수 있는 것 아닌가"라고 생각할지도 모른다. 하지만 그렇지 않다.
자동화 도구가 잘하는 것은, "이곳이 수상하다"라고 전달받은 장소를 깊게 공략하는 것이다. 반대로 잘 못하는 것은, "애초에 어디에 구멍이 있는지"를 제로(Zero)에서부터 찾아내는 것이다. 로그인 너머에 숨겨진 기능이나, 이 시리즈의 첫 회에서 다루었던 것과 같은 "해당 앱 고유의 로직의 허점"("타인의 데이터를 가로챌 수 있다", "결제 체크를 우회할 수 있다" 등)은 도구의 눈에 보이지 않는다. 왜냐하면, 그것들은 "정상적인 조작"과 구별할 수 없기 때문이다.
따라서 현실의 진단은 자동과 수동의 이단계(Two-step) 체제로 이루어진다. 도구로 넓고 얕게 훑고, 인간이 수상한 곳을 깊게 판다. 도구로 나오는 부분은 누가 해도 똑같지만, "그 앱 특유의 허점을 찾는" 데에 인간이 검토할 의미가 있다. (현재로서는 그렇지만, AI의 진화에 따라 미래는 알 수 없다)
이번 내용의 요약
이번에 전달하고 싶었던 것은, "결과를 숨긴다", "에러를 지운다"만으로는 방어가 되지 않는다는 사실이다. 표시도, 에러도, 응답 시간도, 미세한 차이가 단 하나라도 남아 있다면, 그것을 단서로 정보는 한 글자씩 빼앗겨 간다.
그리고 대책은 지금까지와 다르지 않다. 입력을 데이터로 취급하여 명령으로 변하지 않게 하는 것(Prepared Statement, 준비된 문구). 반응의 차이를 없애는 것이 아니라, 애초에 주입시키지 않는 것이 근본적인 대책이 된다.
덧붙여, 이것은 "사용자가 입력하는 부분"에만 국한된 이야기가 아니다. 한 번 DB에 저장한 값이나, 외부에서 가져온 값이 나중에 SQL 문에 사용되는 상황에서도 주입은 발생할 수 있다. 그러므로 "입력 여부"에 따라 대책을 세울 곳을 골라내는 것이 아니라, SQL 문을 구성할 때는 항상 플레이스홀더(Placeholder, 자리 표시자/바인드 변수)로 구현한다고 생각하는 것이 근본적인 해결책이다 (IPA도 동일한 방침을 제시하고 있다).
이 시리즈에서 반복해서 써왔듯이, AI는 "작동하는 코드"는 깔끔하게 작성해 준다. 하지만 "화면에 아무것도 나오지 않는데, 응답 속도만으로 정보가 유출되는" 것과 같은 공격자의 관점까지는, 현재로서는 우리가 가지고 있을 필요가 있다.
당신의 앱을 위한 체크리스트
- 에러나 검색 결과를 "표시하지 않도록 했다"는 것만으로 대책을 세웠다고 생각하고 있지는 않은가
- 상세한 에러 메시지를 그대로 화면에 반환하고 있지는 않은가
- DB 주변에서 SQL 문을 문자열 연결(String Concatenation)로 구성하고 있는 부분이 남아 있지 않은가 (입력에 국한되지 않고, 저장된 값이나 외부에서 온 값을 사용하는 곳을 포함하여 모두 플레이스홀더 + 바인드 변수로 되어 있는가)
- 자동 스캔 도구를 돌려본 것만으로 안전하다고 판단하고 있지는 않은가
- 로그인 너머나, 자신의 앱 특유의 기능을 공격자 관점에서 한 번 재검토했는가
"우리 앱도 걱정된다"는 분은 편하게 문의해 주세요. AI로 만든 제작물의 보안에 대해서도 앞으로 계속 써 내려가겠습니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기