2024. 1. 29. 17:21ㆍ보안, 해킹/웹
SQL injection이란?
SQL injection이란, 서비스가 데이터베이스를 조작하는 부분에 공격자가 임의의 SQL 쿼리를 삽입하여 악의적인 동작을 수행하는 공격이다.
SQL injection의 기본적인 내용들에 대해서는 여기로..
https://securitychan.tistory.com/25
SQL injection이란? (기본편)
SQL이란? SQL은 Structured Query Language의 약자로, 데이터베이스(특히 관계형 데이터베이스)에서 데이터를 추출하고 조작하는 데에 사용하는 데이터 관리 언어다. 기본적으로 명령(query)을 전달하면,
securitychan.tistory.com
특수한 상황에서의 SQL injection
SQL injection이라는 방법을 통해서 인증 우회 등의 동작을 수행할 수 있는 것은 맞지만, 기본적으로 SQL injection은 데이터베이스를 대상으로 하는 공격이기 때문에 일반적으로 행하는 동작은 데이터를 추출하는 것이다.
데이터 추출을 하려면 당연히 select 구문을 사용해서 데이터를 읽어와야 하겠지만, 대부분의 경우 원하는 데이터를 가져왔다고 해서 그 데이터를 곧이 곧대로 보여주지만은 않는다.
어찌보면 당연하다. 로그인 페이지를 생각해보자. 로그인 페이지에서는 사용자의 아이디, 비밀번호를 입력받아 데이터베이스 내에서 회원정보를 가져온다. 만약 아이디 또는 비밀번호가 일치하지 않는다면 페이지는 회원정보가 일치하지 않는다는 오류를 발생시킨다.
이때, 로그인 페이지는 올바른 회원정보를 입력했다고 하더라도 굳이 데이터베이스에 데이터가 어떻게 저장되어 있는지를 페이지에 보여줄 필요가 없다. 서버는 해당 회원정보가 데이터베이스에 있는 것만 확인이 되면 알아서 로그인 처리를 하면 되기 때문이다. 애초에 데이터를 표시하는 것이 로그인 페이지의 원래 기능이 아니기도 하고.
그러면, 데이터가 표시되지 않는데 대체 어떻게 DB 내부에 어떤 데이터가 있는지 알아낸다는 건가. 이에 집요한 해커들은 Blind SQL injection이라는 해결책을 내놓는다.
Blind SQL injection! 무슨 뜻이지?
Blind SQL injection은 말 그대로 Blind한 상황에서 이루어지는 SQL injection이라는 뜻이다. 보이지 않는 상황. 데이터를 우리 눈으로 직접 확인할 수 없는 상황에서 SQL injection을 통해 데이터를 알아내겠다는 것이다.
기본 원리 - Blind SQL injection은 스무고개다.
SQL을 사용한다는 것은, 우리가 데이터베이스(DBMS)에 질의응답을 하는 것과 같다. DB에 접근할 수 있는 관리자가 DB에 질문을 하면, DBMS는 이에 대한 답변을 해준다는 것이다.
예를 들어, 'A라는 유저에 대한 정보를 보여줘' 라는 질문을 하기 위해 쿼리를 작성해서 DBMS에 보내면, DBMS는 A라는 유저에 대한 정보를 반환한다. 이때 쿼리는 관리자의 질문, 결과정보는 DBMS의 답이다.
그런데 위에서 언급했던 로그인 페이지같은 경우에는 SQL 쿼리의 결과를 웹서비스 이용자가 직접적으로 확인할 방법이 없다. 애초에 로그인 페이지에서 SQL은 사용자가 입력한 데이터가 실제 DB에 존재하는지를 검증하기 위해서만 사용되기 때문이다. 그렇기 때문에 서버는 쿼리의 결과를 근거로 클라이언트에게 '로그인 성공 유무'만을 알려준다. Blind SQL injection은 이러한 예/아니오(성공 유무)라는 답변만으로도 정보를 알아낼 수 있지 않을까? 라는 발상에서 시작된다.
스무고개라는 놀이가 있다. 한 명이 어떤 단어를 생각하면, 다른 사람들이 스무 번의 질문을 통해 생각한 단어를 맞추는 놀이다. 이때, 질문에 대한 답은 항상 예/아니오여야 한다.
Blind SQL injection은 스무고개와 유사하다. 공격자는 질문자가 되어 서버에 SQL 쿼리를 삽입한 요청을 보낸다. 서버는 SQL 쿼리의 결과를 바탕으로 두 가지 다른 답변(로그인 페이지를 다시 예로 들면 로그인 성공/실패 응답)을 할 것이다. 공격자는 이 응답을 바탕으로 DB의 데이터에 관한 정보를 알아내는 것이 목표다.
Blind SQL injection의 쿼리는 어떤 형태인가
Blind SQL injection의 쿼리는 일반적으로 다음과 같은 형태로 이루어진다.
[default값]' and [조건] and '1'='1
쿼리문의 구조에 대해
여기서 [default값]은 정상적인 동작을 발생시키는 값을 의미한다. 그리고 [조건]은 알아내려는 데이터에 관한 쿼리문인데, 이 쿼리문은 true 또는 false라는 값을 반환하는 쿼리문이다. 설명이 조금 더 필요하다는 생각이 들 것이다.
Blind SQL injection의 '예/아니오'를 구분하는 응답은 '정상 동작 여부'이다. 또, 또 로그인 페이지를 예시로 들면, 로그인 페이지는 회원정보를 입력했을 때 로그인 성공/실패만을 사용자에게 알려준다. 그렇다면 이러한 동작이 공격자가 원하는 질의문에 대한 대답처럼 동작하게 하려면? 공격자의 질문에 대한 답이 참이라면 로그인 성공, 거짓이라면 로그인 실패라는 응답을 하도록 쿼리를 만들면 된다.
[default값]은 로그인 성공(정상 동작)을 유발하는 값이다. 내가 만약 guest/guest1234라는 아이디/비밀번호를 사용하고 있다면 (그리고 아이디 부분에 SQL injection을 수행한다고 하면) guest라는 값이 default값에 해당한다.
[조건]은 공격자가 진짜 원하는 데이터에 대해 true, false라는 값을 반환하는 쿼리다. 이 조건이 만약 true라면, 정상적으로 로그인이 될 것이고, false라면 로그인 실패를 응답할 것이다.
그렇다면 [조건]은 어떻게 만들어질까.
사실 컴퓨터나 프로그램을 조금 다뤄보았다면 서버를 상대로 스무고개를 한다는 것이 얼마나 터무니없는 소리인지 알 수 있다. 스무고개는 '동물인가요?'라는 형태의 질문이 가능하지만, 일반적인 DBMS가 이런 고차원적(?)인 질문을 이해할 리가 없다. guest라는 단어가 DB에 저장되어 있다고 가정했을 때, DBMS는 guest가 어떤 카테고리인지를 정의하지 않았다면 이런 질문을 이해하지 못할 뿐더러, 애초에 guest라는 단어 자체도 무슨 뜻인지 모른다. DBMS는 애초부터 ai 챗봇처럼 머리가 좋지 않다. 그렇기 때문에 질문의 형태는 정말 단순해진다.
바로 몇 번째 자리에 무슨 글자가 있는지 물어보는거다. 놀랍게도. DBMS의 종류에 따라 다르지만, DBMS 자체적으로 지원하는 ascii, substr 등의 함수가 존재한다. 이를 활용하여 select 구문으로 가져온 데이터를 한 글자씩 순차적으로 비교하여 알아내는 것이다. 이러한 조건문이 참이라면 정상 동작을 할 것이고, 아니라면 원래 기능이 동작하지 않을 것이다. 너무 번거롭지 않냐고? 우린 코드짜면 그만이니까.
Blind SQL injection 적용
아래 사이트는 dreamhack 워게임에서 제공하는 SQL injection 취약점이 존재하는 서비스이다.
이때 실제 존재하는 데이터를 입력하여 로그인을 시도하면 서버는 다음과 같은 응답을 보낸다. 편의상 burp suite의 repeater 기능을 활용하였다.
이때, 다음과 같은 두 조건을 삽입하여 공격자가 삽입한 SQL 구문이 유효하게 작용한다는 점을 확인할 수 있다.
이때 다음 쿼리를 중간에 삽입하여 다음과 같은 payload를 보낼 것이다.
guest" and (substr((select name from sqlite_master where type='table' limit 0,1),1,1))>'a' and "1"="1
(참고로, 여기서 사용된 DBMS는 SQLite이다. 알던 거랑 달라서 많이 당황했다.)
설명을 하자면, 데이터베이스 내에 존재하는 모든 테이블 이름 중 첫번째 테이블 이름을 가져와서 첫번째 문자를 a와 비교한다. 이때 첫번째 문자의 아스키 코드값이 'a'보다 클 경우 true, 작다면 false가 되어 로그인 성공 유무로 결과가 나타날 것이다.
이 동작으로 데이터베이스 내에 존재하는 첫 번째 테이블 이름의 첫 번째 문자가 u라는 사실을 알 수 있다. (참고로 원래 테이블명은 users다. 사실 워게임 문제파일엔 테이블 이름을 알려준다.)
이러한 동작을 문자 하나하나 수행하여 데이터베이스 내의 데이터를 뜯어내는 것이 Blind SQL injection의 목적이다.
너무 번거롭지 않나요?
당연히 번거롭다. Burp suite의 repeater 기능을 사용하더라도 한글자 한글자 바꿔가며 하는 것도 한세월이다. 자체 기능인 Intruder도 community 버전은 속도제한이 있고, pro 버전을 쓰더라도 오래 걸린다.
그렇기 때문에 Blind SQL injection은 일반적으로 파이썬 코드로 자동화하여 진행한다.
Process of Blind SQL injection
Blind SQL injection을 수행하는 절차에 관한 것이지만, 사실 UNION SQL injection의 절차와 상당히 유사하다.
1. SQL injection point 찾기
당연하다. SQL injection을 수행하기 위해서는 SQL 쿼리가 사용되는 곳을 찾아야 한다. 페이지 중에서 데이터베이스에 존재하는 데이터를 가져오는 페이지를 찾아야 한다.
SQL injection point를 찾았다면, 해당 포인트에서 사용자의 입력값이 쿼리로 작동할 수 있는지 여부를 확인해야 한다. 일반적으로 사용하는 payload는 아래와 같다.
default' and '1'='1
default' and '1'='2
만약 우리의 입력값이 SQL 구문으로서 해석된다면, 위의 payload에 대해서는 default라는 데이터에 대한 처리를 정상적으로 수행하지만 아래의 payload에 대해서는 default라는 데이터를 처리하지 않을 것이다.
2. SELECT 구문 사용 가능 여부 확인
일종의 추가검증이다. SQL injection이 발생할 여지가 있는 point라 하더라도 여러 다른 이유로 SELECT 구문이 동작하지 않을 수 있다. 따라서 삽입한 SELECT 구문이 동작하는지 추가로 확인하는 과정이다.
default' and ((select 'a')='a') and '1'='1
default' and ((select 'a')='b') and '1'='1
바로 위 과정과 원리는 비슷하다. true/false의 위치가 payload 중앙으로 이동된 것 뿐이다.
SELECT 구문이 정상적으로 작동하면 위 payload에 대해서는 정상적으로 처리될 것이다.
3. payload 작성
중요한 과정이다. Blind SQL injection 특성상 payload가 길고 조잡해질 수 밖에 없기 때문이다. 그리고 자동화 과정에서 필요한 과정이다.
그리고 사실 괄호가 너무 많아 헷갈린다.
- 아래에서 설명하는 payload는 MySQL에서 사용하는 쿼리이다. 서버에서 사용중인 DBMS 종류에 따라 쿼리는 달라질 수 있다. https://portswigger.net/web-security/sql-injection/cheat-sheet를 참고하면 도움이 된다.
SELECT 구문 사용 가능 여부까지 확인했다면 쿼리의 기본 틀은 아래와 같다.
default' and () and '1'='1
다음으로, 아스키코드 비교 조건을 삽입한다.
default' and (ascii()>65) and '1'='1
(참고로 65는 임의의 수다. 자동화할 때 저 부분을 바꿔가며 처리하면 된다.)
이후 substr 함수를 삽입
default' and (ascii(substr((),1,1))>65) and '1'='1
마지막으로 substr의 첫 번째 인자에 위치한 괄호 안에 쿼리를 삽입하면 된다.
4. 데이터베이스 이름 확인
여기부터는 UNION SQL injection과 동일하다. 현재 데이터베이스의 이름을 알아내려면
default' and (ascii(substr((select database()),1,1))>65) and '1'='1
또는 모든 데이터베이스의 이름을 알아내려면
default' and (ascii(substr((select schema_name from information_schema.schemata limit 0,1),1,1))>65) and '1'='1
위에서는 그냥 넘어갔었지만, 중요한 점은 이 경우 삽입한 쿼리에 limit 구문이 붙었다는 것이다. limit 0, 1이 붙었다는 건 여러 개의 결과 중 첫 번째(0번째 index) 데이터만 가져온다는 뜻이다.
왜 들어갔는지는 예상이 갈 것이다. substr 함수 때문이다. 하나의 데이터가 들어가야 첫 번째 문자만 떼오든 말든 할 것이니까.
5. 테이블 이름 확인
이 역시 UNION에서와 동일하다.
그래도 쿼리를 알려주자면
default' and (ascii(substr((select table_name from information_schema.tables where table_schema='[DB명]' limit 0,1),1,1))>65) and '1'='1
6. Column 이름 확인
default' and (ascii(substr((select column_name from information_schema.columns where table_name='[테이블명]' limit 0,1),1,1))>65) and '1'='1
7. 데이터 추출
위에서 얻은 정보를 바탕으로 마음껏 데이터를 뽑아먹으면 된다.
정리
지금까지 Blind SQL injection에 대해 알아보았다. Blind SQL injection이란 화면에 SQL 쿼리의 결과가 직접적으로 표시되지 않는 페이지 환경에서 데이터를 추출하는 공격이다. 실제로 SQL injection 사례에서 대다수의 경우는 Blind SQL injection이기도 하고, SQL을 사용하는 페이지이기만 하면 데이터를 추출할 수 있다는 점에서 아주 위험한 공격이다.
'보안, 해킹 > 웹' 카테고리의 다른 글
CSRF(Cross Site Request Forgery)란? (0) | 2024.03.24 |
---|---|
XSS(Cross-Site Scripting)란? (1) | 2024.02.05 |
SQL injection 심화편(1) - Union SQL injeciton (1) | 2023.12.31 |
SQL injection이란? (기본편) (1) | 2023.12.03 |
SQL injection이란? (실습편) (0) | 2023.11.18 |