작은 메모장
18. SQL Injection 본문
SQL Injection
DMZ등 어떤 서비스가 사용자가 요청한 서비스의 정보를 얻으려면 SQL문으로 DB(DataBase)에 요청해야한다.
SQL Injection이란, 이 SQL 질의를 처리하는 과정에서 예상치 못한 입력 값에 의해 DBMS 정보가 노출, 특정 명령어 실행등이 발생하는 취약점을 의미한다.
공격자는 정상적인 값 대신 자신이 원하는 값을 웹에 요청하고, 웹은 데이터베이스에 요청을 한 뒤 결과값이 웹을 통해 노출되거나 유추할 수 있는 형태로 출력된다.
SQL Injection에는 3가지 접근법이 존재한다.
1. 에러 베이스 기반 (Error Based SQLi)
의도적인 오류를 일으켜 생기는 오류를 이용한 접근법
대표적으로 싱글쿼터(')나 더블쿼터(")를 사용하여 의도적인 오류를 일으킨다.
위 사진에서 보듯 싱글쿼터 하나만 입력 했더니 SQL 오류를 Output으로 알려주는 것을 확인할 수 있다.
이러한 오류는 어째서 생기는 것일까? 이 Input처리의 소스코드를 보면 이유를 알 수 있다.
이건 해당 페이지의 소스코드로, 입력했던 값을 title 값으로 받아 전달하는 것을 확인할 수 있다.
143번 줄에서, title 대신에 세미콜론이 들어간다고 생각하면, 다음과 같을 것이다.
$sql = "SELECT * FROM movies WHERE title LIKE '%" . ' . "%'";
이렇게 되면 세미콜론의 불균형이 생겨 오류가 생기게 된다.
오류가 생기는 것은 좋은데, 이걸 어떻게 사용할 것인가?
입력값을 다음과 같이 바꿔보자
입력값을 세미콜론 하나 대신, ' or 1=1#으로 변경하였다.
입력하였더니 모든 데이터를 보여주는 모습.
변경한 코드를 다시 살펴보면
$sql = "SELECT * FROM movies WHERE title LIKE '%" . ' or 1=1# . "%'";
이는 세미콜론이 강제적으로 sql문을 끊어버리고 그 뒤에 조건문을 넣어 제어를 시도하고 있다.
공격자의 입장에서 이 문구가 에러가 나오지 않았다는 것은, 넣었던 조건문들이 동작이 잘 한다는 뜻.
이 공격이 통한다는 것을 확인했으니, 과감하게 다른 것도 시도해볼 수 있다.
order by 명령으로 각각 정렬을 시도하였다.
1~7까지 공격이 통하는 것을 확인할 수 있는데, 8 이상부터는 명령어가 들어가지 않는 것을 확인할 수 있다.
즉, 이 DB의 column수는 7개라는 것을 알 수 있다.
column수가 7개라는 것을 알았으니, 다른 것을 시도해볼 수 있다.
union 명령을 통해 select 1~7의 명령을 합쳐 아래에 표시하도록 하였다.
즉, select된 명령은 앞의 DB 호출과 함께 같이 표기될 것이다.
사진을 보면 결과 맨 아래쪽에 이상한 숫자들이 같이 표기된 것을 확인할 수 있는데, 이 번호가 select문으로 호출된 column 번호를 의미한다.
결과적으로 각각의 column 번호를 알아낸 셈과 동일하다.
지금은 7개의 column밖에 없기에 탐색에 쉬웠지만, 한 50개가 넘어가는 column이 있거나 이 작업을 다량으로 해야한다면 상당히 힘든 작업일 것이다.
때문에 Burp Suite를 사용하여 반복작업을 수행할 것이다.
여기서 Send to Repeater를 누른다
그럼 이렇게 Repeater 창이 하나 생기는데, 여기서 단순 변경으로 인한 비교를 쉽게 할 수 있다.
즉, 아까처럼 1, 2, 3 고쳤을 때 그 비교를 쉽게 할 수 있다는 것.
Response의 Render를 누르면 렌더링 화면으로도 보여준다.
Send to Intruder를 누르면 Intruder창이 생기는데, 여기서 매크로 설정을 할 수 있다.
여기서는 § 사이에 있는 값을 지속적으로 변경하면서 요청을 전송한다.
반복작업에 매우 탁월한 기능을 가지고 있는 것.
공격자가 원하는 것은 ' order by ?#에서 ?에 있는 값만 변경하기를 원한다.
따라서, title 파라미터에 있는 1만 변경할 값으로 지정한다.
Attack type은 4가지가 있지만 2개만 알면 되는데, Sniper과 Cluster bomb이다.
Sniper은 파라미터, 즉 변경하려는 값이 하나일 때 쓰는 옵션으로, 값 하나에 파라미터 하나가 할당이 된다.
즉, A B C D... 이런식으로 진행한다.
Cluster bomb은 변경하려는 값이 두개 이상일 때 쓴느 옵션으로, 모든 파라미터의 경우의 수를 전부 할당한다.
즉, Aa Ab Ac Ad ... Ba Bb Bc Bd... 이런식으로 진행한다.
Position을 정했으면, 변경할 값에 넣을 데이터를 정해야 한다.
위의 경우는 숫자를 순차적으로 집어넣을 것이므로, Payload type은 Numbers, 범위는 1~20까지 설정하고 돌린다.
확인해보면, Payload 8부터 데이터 전체 길이가 확 떨어지는 것을 확인할 수 있다.
이는 요청했던 DB정보의 table이 빠진 것으로, payload 7까지만 DB를 가져오는 것을 확인할 수 있다.
즉, column 7까지만 존재함을 또 다르게 확인할 수 있는 것이다.
이 사이트에서도 시도해보자.
똑같이 검색창에 SQL Injection을 시도한다.
그럼 오류창이 뜨는 것을 확인할 수 있는데, 이는 SQL 공격이 먹힌다는 것을 암시한다.
' or 1=1#을 입력했을때의 모습이다. 조건문이 통과되어 모든 데이터를 가져오는 모습을 볼 수 있다.
' order by 1#또한 어느것이 정렬이 되었는지는 모르겠으나, 일단 정렬 자체는 되어 나온 결과가 보인다.
이제 반복작업으로 column 갯수를 알아내보자. Burp Suite로 조종한다.
Payload 24부터 데이터 길이가 뚝 떨어지는 것으로 보아, column의 갯수는 24개임을 확인할 수 있다.
더 많은 것을 해보자.
' union select 1,2,3,4,5,6,7#문은 각각의 column뒤에 추가적인 정보를 달아주는 문구였다.
이 문구상에서는 그냥 각각의 column번호를 뒤에 달아주는거였지만, 이걸 바꿔서 유용한 정보를 빼낼 수 있다.
칸이 넉넉한 column 2번 자리에 버전 정보를 요청하는 @@version을 요청했다.
그랬더니 버전 정보가 정상적으로 출력되는 모습을 확인할 수 있다.
버전 정보를 요청하는 것 말고도 다른 유용한 정보또한 요청할 수 있다.
' union select 1,database(),3,4,user(),6,7#를 이용한 결과다.
이에 데이터베이트 이름과 유저 정보를 보여주는 모습.
혹시나 위의 정보가 뜨는게 불편하다면,
-1111 같이 있지 않을 법한 데이터를 요청하여 데이터 요청을 없애면 된다.
기본적으로 SQL의 DB는 각각의 테이블 정보를 schema 형태로 운용하고 있으며, 이 schema를 저장하고 관리하는 테이블이 따로 존재한다.
왠만해서는 이 schema table은 잘 변경되지 않으며(수정하기가 극도로 귀찮기 때문), 공격자들 또한 여기를 노리는 경우가 많다.
이유는 간단하다. 이 schema table에는 현재 보고있는 DB말고도 다른 DB의 schema까지 들어있기 때문이다.
모든 DB의 schema를 알아낼 수 있다면 구조를 파악하기 아주 쉬워지기에 여기를 우선적으로 노린다.
-1111' union select 1,table_name,3,4,5,6,7 from information_schema.tables# 명령을 사용하여 schema tables에서 모든 테이블 정보를 가져온 모습이다.
-1111' union select 1,column_name,3,4,5,6,7 from information_schema.columns# 명령으로 columns 정보 또한 가져와봤다.
약간의 가공을 거치면, where table_name='users'를 붙이면 사용자가 사용하는 columns만 가져올 것이다.
다른 정보를 더 가져와보자.
-1111' union select 1,login,password,4,5,6,7 from users#의 명령을 모든 user의 아이디와 비밀번호를 가져와봤다.
아이디는 읽을 수 있는 값으로 되어 있으나, 비밀번호는 HASH화 되어 읽을 수 없게 되어 있는 것을 확인할 수 있다.
이건 어떻게 해결하는가?에 대한 현실적인 해법이 있는데, 바로 Rainbow Table을 사용하는 것이다.
수학적으로 HASH 암호화는 역암호화가 현실적으로 불가능한 암호화로, 사람들은 이걸 풀기보단 다른 방법을 생각해냈다.
바로 단어 하나하나마다 해시를 하나씩 매칭시킨 결과를 하나로 모아서 어중간한 단어에도 매칭될 수 있게 큰 데이터를 만드는 것.
엄청 무식한 방법이긴 한데, 이 모든 매칭 결과를 모아놓은 것이 바로 Rainbow Table이라고 한다.
Rainbow Table에도 종류가 있는데 용량과 성공률이 제각각이다.
이걸 이용해서 얻어온 비밀번호 해시값을 대입하여 비밀번호를 얻어내는 것이 일반적인 수법이다.
취약한 암호는 바로바로 튀어나오겠지만, 어려운 암호같은 경우엔 대략 6개월이 걸린다(연구 결과임).
때문에 사이트가 암호를 3개월마다 바꾸라고 하는 이유.
상기의 Injection은 정말 다양하게 사용할 수 있다.
만약 Input 방식이 아닌 Select 방식을 사용해서 값을 집어넣을 수 없다면?
주소창에 Input 데이터 넣는 곳이 그대로 있으므로 거기에 집어넣으면 된다.
POST 방식이여서 주소창에도 넣을 공간이 없다면?
Burp suite로 가로채서 저기에 넣으면 된다.
즉,SQL Injection은 값을 입력받는 경우 왠만하면 일어날 수 있다는 것이 핵심이다.
2. 블라인드 베이스 기반 (Blind Based SQLi)
결과를 보여주지 않는 곳에서 사용하는 접근법
대충 이런 느낌
에러가 나면 에러를 보여주지 않고 데이터를 보여주지 않는 식으로 사이트를 구성했을 때 접근하는 방법이다.
공략하는 방법은 상당히 천천히 진행되는데, 지속적인 질의를 통해 천천히 내부를 추측하는 형태로 진행된다.
가령, 가장 확실하게 알아낼 수 있는 정보부터 시작해 천천히 깊고 중요한 질문으로 진행하는 것이다.
일단 가장 확실하게 알 수 있는 정보부터 시작한다.
' or 1=1 and length(database())=1# 명령으로 지금 이 데이터베이트의 이름의 길이를 묻는 것으로 시작했다.
데이터베이스의 이름이 bwAPP이므로 길이가 5가 될 것이다.
' or 1=1 and length(database())=5# 명령으로 5글자냐고 물어볼 때 맞다고 이야기하는 모습
글자수를 알아내었으니 이제 이름을 알아내야할차례
이를 위해서 substring을 사용한다.
' or 1=1 and substring(database(),1,1)='a'# 명령으로 첫 글자가 a냐를 물어보는 모습.
이름이 bwAPP이기 때문에, 거짓이 나오는 모습
' or 1=1 and substring(database(),1,1)='b'# 명령으로 첫 글자가 b냐를 물어보는 모습.
첫 글자가 맞기 때문에, 참이 나온다.
이 과정은 5글자여도 상당히 귀찮은 작업이다.
때문에, 아스키코드로 변환하여 burp의 매크로를 돌리면 편할 것이다.
' or 1=1 and ascii(substring(database(),1,1))=98#으로 아스키코드로 가공하였다.
이제 매크로를 돌리면 될 것이다.
상당히 번거로운 과정이긴 하나, 이 방식으로 schema table도 알아낼 수 있다.
' or 1=1 and length((select table_name from information_schema.tables where table_schema='bWAPP' limit 0, 1))=4#
이 명령은 "bWAPP" table schema가 있는 table에서 schema.table의 정보를 가져오되, 첫 번째 줄을 가져오라는 명령이다.
테이블 이름 정보가 4글자임을 알았으니, 이제 아스키코드로 하나씩 돌려보면 된다.
' or 1=1 and ascii(substring((select table_name from information_schema.tables where table_schema='bWAPP' limit 0, 1),1,1))=98#
이 명령으로 table schema의 첫 번째 table 이름이 "b"로 시작되는 것을 알 수 있다.
이 명령으로 매크로를 돌리면 될 것이다.
3. 시간 베이스 기반 (Time Based SQLi)
어떠한 반응 또한 없을 때, SQL 주입이 동작하는지 확인하는 방법
보통 여기까지 온다면 SQL 쿼리에 대한 방어를 잘 하고 있는 것이다.
에러건 반응이건 아주 잘 방어하고 있기 때문.
때문에 이 시간 베이스 기반 방식은 SQL Injection을 시도해볼 수 있는 마지막 가능성이라고 생각하면 된다.
원리는 간단하다. 오류도 안보여주고, 결과조차 안보여주므로, 시간 지연을 고의로 발생시켜 이를 통해 참/거짓의 결과를 알아내겠다는 것.
' or 1=1 and sleep(5)# 명령을 사용하였다.
보통 검색버튼을 눌렀을 때 곧바로 잘 가져오지만, SQL Injection이 유효할 때는 곧바로 가져오지 않고 로딩이 꽤 오래 걸린다.
즉, 저 sleep으로 인해 시간이 지연되었고, SQL 주입이 되었음을 암시한다.
다시말해, 로딩이 곧바로 진행되면 조건문이 거짓, 로딩이 지연되면 조건문이 참이라는 뜻이다.
' or 1=1 and length(database())=5 and sleep(1)# 명령을 사용하였다.
이 명령은 위의 Blind Based의 명령어에 and로 sleep만 넣은 것이다.
보다시피 계속 로딩이 돌아가는 것을 보아 명령이 잘 작동하는 것을 확인할 수 있다.
SQL Injection Tool
상단의 과정은 상당히 번거롭고 짜증나는 과정이다.
다시말해, 손이 많이 간다는 뜻이다.
당연하게도, 이 귀찮은 과정을 전부 자동화해버린 도구들이 있으니, 이 도구들을 소개할 것이다.
SQLmap
이름 그대로 자동으로 SQL Injection을 수행하고 데이터를 수집하는 도구다.
사용법은 상당히 간단하면서 복잡한데, 이유는 옵션이 상당히 많기 때문이다.
칼리리눅스를 사용하여 Burp로 가져온 세션값과 데이터 값을 이용해 데이터를 가져올 것이다.
동작이 끝나면 database 이름들과 버전 정보, OS 정보, DBMS 정보들, 그리고 파일의 위치까지 전부 가져온 것을 확인할 수 있다. 옵션 넣기가 많이 까다롭지만 상당히 편리한 도구.
아까 데이터베이스 목록 중에 bWAPP이 있었으니, 그 테이블 정보를 알고 싶다고 명령한다.
그럼 이렇게 테이블 정보를 정상적으로 가져오는 것을 확인할 수 있다.
users의 columns 정보 또한 가져오라고 해봤다.
잘 가져오는 모습
이 정보를 이제 dump를 떠서 저장할 것이다.
dump 과정에서 SQLmap은 다양한 옵션을 제공한다.
가령, 해시 복호화와 결과 저장 타입 등 다양한 옵션으로 결과를 용이하게 저장할 수 있다.
'실더스 루키즈 교육' 카테고리의 다른 글
20. AWS 클라우드 개요 (0) | 2023.12.26 |
---|---|
19. 파일 업로드 취약점 (0) | 2023.12.21 |
17. XSS 취약점 (0) | 2023.12.19 |
16. BeeBox와 인증처리미흡 취약점 (0) | 2023.12.18 |
15. 웹 쉘 (0) | 2023.12.15 |