본문 바로가기

IT Security/DVWA(Damn Vulnerable Web App)

[DVWA] SQL injection

안녕하세요 Retain0입니다. 이번 시간에는 " DVWA "라는 취약한 웹 애플리케이션을 통한 SQL injection 공격을 해보도록 하겠습니다.

 

SQL injection 이란

  • 웹 어플리케이션에서 입려 되는 사용자의 입력값을 조작하여 해당 서버의 데이터베이스를 공격하는 기법으로 주로 사용자가 입력한 값을 제대로 검증하지 못할떄 발생합니다. 이를 통해 DB내의 정보 즉 사이트 내에 회원정보를 갈취하고 DB내용을 변조할 수도 있으며 관리자급 권한을 획득할 수도 있습니다. OWASP top 10 중에서도 빈도수가 가장 높은 공격에 속하는데 입력값 검증을 하지 않을 시 아주 간단한 구문 하나만으로도 취약점을 찾아내어 큰 타격을 줄 수도 있습니다.

SQL injection 의 대표적인 종류들

  • Error Based : 논리적 에러를 이용한 SQL injection 공격으로 가장 많이 쓰고 대중적인 공격입니다. 조작된 쿼리 입력을 통해 에러 메시지를 노출시키는데 그 안에 주로 DB에 관한 정보를 담아 노출시키게 됩니다.
  • Union Based : Union 키워드는 두 개의 쿼리문에 대한 결과를 통합해서 하나의 테이블로 보여주게 하는 키워드입니다. 정상적인 쿼리문에 Union 키워드를 사용하여 삽입시 원하는 쿼리문을 실행 실수 있게 됩니다.
  • Boolean Based : 특정한 값이나 데이터를 전달받지 않고, 단순히 참과 거짓의 정보만 알 수 있을 때 사용합니다.
  • Time Based : 서버의 특정한 응답 대신 참 혹은 거짓의 응답을 통해서 데이터베이스의 정보를 유추하는 기법입니다. 사용되는 함수는 MySQL 기준으로 " SLEEP " 과 " BENCHMARK " 가 있습니다.

① Low

(사진 1) 1 이라는 ID를 가진 사용자 

현재 실습환경에서는 1~5까지의 사용자 ID가 있습니다. 사용자의 입력값을 검증하지 않는다는 가정하에 해당 사용자의 ID를 입력하는 폼에 " ' " 작은따옴표를 입력해서 MySQL 에러를 일으켜 보도록 하겠습니다.

(사진 2) MySQL 에러

(사진 2)를 보시면 MySQL 에러가 뜬것을 확인하실 수가 있습니다. 해당 구문이 나올 경우 SQL injection에 취약하다는 것을 의미하며 추가적으로 공격을 진행할 수 있게 됩니다.

(사진 3) LOW 레벨 소스코드

(사진 3)을 보시면 user_id='$id', '"라고 적혀져 있는 부분이 있습니다. $id라는 변수는 이미 작은따옴표로 둘러 쌓여져 있기 때문에 작은 따옴표 하나만 입력하게 되면 WHERE user_id=''' 3개가 되기 때문에 에러가 발생하게 된 것입니다.

(사진 4) 사용자 계정들 노출

(사진 4) WHERE 구문 우회 기법인 1' OR '1'='1을 입력해보면 ID가 1을 포함한 DB에 있는 모든 ID 값과 name을 도출시키게 됩니다. or 구문으로 '1'='1' 참값이 되었기에 모든 계정을 도출시키게 된 겁입니다.

(사진 5) union 구문인 1' union select 1# 을 입력해서 컬럼갯수를 알아내 보도록 하겠습니다. 컬럼갯수가 정확히 맞지 않는 이상 구문 에러가 발생할 것입니다. 

1' union select 1#

(사진 6) 도출

1' union select 1,1# 을 입력하여 컬럼수를 올려본결과 합집합의 의미를 담고 있는 union 구문으로 인해 우리가 따로 select로 추가했던 First name : 1 / Surname : 1 도 출력된것을 확인할 수가 있습니다.

1' union select 1,1#

컬럼수를 하나 더 높여서 1' union select 1,1,1# 을 입력해보면 다시 구문에러가 발생합니다. 즉 컬럼이 2개 사용되고 있다는것을 알수가 있습니다.

(사진 7) order by 절 사용해보기

union 구문 말고도 order by 구문을 통해서도 컬럼 갯수를 알아낼 수가 있습니다. order by 구문의 사용 목적은 특정한 컬럼을 기준으로 정렬할 때 사용하는 키워드입니다. 즉 컬럼의 갯수보다 큰 값을 찾아내게 되면 정렬을 할 수 없기 때문에 에러를 발생시키게 될 것입니다.

(사진 8) order by 절 사용해보기

(사진 8) order by 절을 사용해본 결과 에러가 발생하지 않고 정상적으로 출력되었습니다. 그럼 에러가 발생할 때까지 숫자를 증가시키도록 입력해야 합니다.

1' order by 1#

(사진 9) 컬럼 갯수 알아내기

1' order by 3#

1' order by 3# 에서 에러 페이지가 노출되었습니다. 이것을 토대로 해당 컬럼은 2개를 사용한다는 것을 알 수 있게 됩니다.

union 구문과 order by 구문의 차이점이라면 union 구문의 경우 컬럼의 갯수가 정확히 맞을 때까지 하나씩 대입해봐야하 하지만 order by 절을 사용하면 컬럼의 갯수가 입력값보다 큰지 안 큰지 유무를 통해 확인할 수있기떄문에 금방 알아낼 수 있습니다.

 

(사진 10) DB명 획득

 

1' union select schema_name,1 from information_schema.schemata#

MySQL 은 information_schema라는 데이터베이스에서 정보 등을 관리하고 있습니다. schema_name 구문을 통해 데이터베이스의 이름을 조회해본 결과 모든 DB 이름들이 도출된 것을 확인할 수가 있습니다.(admin, information_schema, dvwa) 

(사진 11) Table 명 획득

 

1' union select table_schema, table_name from information_schema.tables where table_schema = 'dvwa' #

DB명을 획득하였으니 이제 상위의 구문을 통해 Table 명을 획득해 보도록 하겠습니다. (사진 11)을 보시다시피(admin, guest book, users)라는 3개의 테이블들이 조회되었습니다.

 

(사진 12) Column 획득

 

1' union select table_name, column_name from information_schema.columns where table_schema = 'dvwa' and table_name = 'users'#

" users "라는 Column 정보를 획득하기 위해 상위의 구문을 사용하였습니다. 결과적으로 (admin, user_id, first_name, user, password 등의 계정 정보에 대한 컬럼이 확인되었습니다.

 

(사진 13) union 구문을 통한 계정정보 획득

1' union select user,password from users#

상위 구문을 통해 최종 사용자 아이디와 패스워드를 추출하였습니다. 패스워드 부분은 " Hash " 값으로 암호화되어 있습니다. 추가적으로 암호를 획득하기 위해선 " hash-identifier "을 통해 종류를 파악하고 암호화를 깨트리도록 추가 조취를 취해야 합니다.

(사진 14) Blind SQLi 활용

1' and 1=1#
   OR
SELECT user from users whwere id='1' and 1=2#

(사진 15) Blind SQLi 활용

참과 거짓 값을 구분할 수 있는 SQL 쿼리문 삽입을 통해 숫자 형태의 데이터까지 파악할 수 있는 기법입니다.

1' and 1=1#이라는 참 값과 1' and 1=2# 거짓 값 입력을 통해 해당 웹 어플리케이션에서 보여지는 결과가 틀리면 Blind SQL injection에 취약하다고 볼 수 있습니다. 만일 대상 백그라운드에서 SQL 쿼리문이 실행되고 있다면 SELECT user from users where id='1' and 1=1# 이 될 것입니다.

Blind SQLi 의경우 상위처럼 결과를 화면을 통해 직접적으로 획득할 수는 없으나 존재하느냐 없느냐의 식으로 뒤에 and를 사용해 해당 구문이 참인지 아닌지 파악하는 용으로 사용됩니다.

(사진 16) Time based SQLi 활용

Blind SQLi 에 대한 대응방법을 우회할 수 있는 기법입니다. 즉 참과 거짓 값을 구분하려는데 응답 결과가 동일하다면 이럴 땐 " 응답 시간의 차이 "를 주면 성공할 수 있습니다. 예를 들어 참일 경우 sleep이나 wait for을 삽입하여 응답을 몇 초 늦게 오도록 해주는 것입니다.

1' and sleep(5)#

상위의 구문을 삽입해 본 결과 하단의 검정 박스를 보시다시피 5020ms 즉 5초 정도 응답이 있다가 반응하는 것을 확인할 수 있습니다.

(사진 17) Time Based SQLi 활용

6' and sleep(5)#

거짓 값 인 상위의 구문을 입력해본 결과 전 과는 다른 응답 시간의 차이를 주고 있는 것을 확인하실 수가 있습니다.

이를 토대로 id 가 1이라는 사용자가 존재한다는 것을 알 수가 있고 id 가 6인 사용자는 존재하지 않는다는것을 알수 있습니다.

(사진 18) sqlmap 도구 활용

sqlmap -u "http://192.168.0.20/dvwa/vulnerabilities/sqli/?id=1&submit=submit#" --cookie="security=low; PHPSESSID=jhvvikku5tqods1h6bb9car73l"

SQLi 공격을 수행하는데 제일 많은 사랑을 받고 있는 대중화된 도구이며 아직까지 많은 실무자들도 애용할 정도로 강력한 도구입니다. 

-u : 타겟 주소를 넣기 전에 사용되는 옵션으로 로그인이 된 상태에서 진행하게 될 시 --cookie=COOKIE 옵션을 사용하여 쿠키 정보를 추가적으로 입력해줘야 sqlmap 도 로그인이 된 것처럼 진행함

--cookie : 쿠키값 입력 (F12를 눌러 console 탭 하단에 " document.cookie "를 입력하면 편하게 확인 가능

(사진 20) 결과

(사진 20) 3719개의 http 요청을 자동으로 보냈으며 위에서 수동으로 입력했던 union, orderby, Blind, Time 등 의 기법들을 무작위로 삽입하여 결과를 얻어냅니다. 추가적으로 하단에 MySQL 버전, 웹서버 버전 등 을 파악할 수 있습니다.

 


② Mideum

(사진 21) Mideum 소스코드 확인

(사진 21) Low 레벨에서 사용한 방식 그대로 하면 에러 메시지만 도출될 뿐 추가적인 정보를 획득할 수가 없습니다. 소스코드를 확인해 본 결과 user_id=$id;"; 부분이 Low 방식과 틀립니다. id부분에 ' 작은따옴표 가 없는 걸 보니 숫자만 입력받겠다고 설정한 것으로 확인됩니다.

(사진 22) 작은따옴표 제거후 삽입

(사진 22)를 보시다시피 프록시도구를 통해 잡은 후 구문 삽입을 작은따옴표 없이 제거하여 삽입해 보도록 하겠습니다.

(사진 23) 결과 도출

Mideum 소스코드를 파악하고 작으따옴표를 제거 후 쿼리문을 입력해보니 정상적으로 결과가 도출된 것을 확인할 수가 있습니다.

(사진 24) union 구문 사용

1 union select user,password from users#

합집합의 의미를 가진 union 구문 사용에서도 마찬가지입니다. 작은따옴표를 제거해주면 정상적으로 원하는 결과를 획득하실 수 있습니다.

 

(사진 25) 결과 도출

위의 구문을 통해 user_id와 password를 도출할 수 있습니다.

 


③ High

(사진 26) High 레벨

" Click here to change your ID 링크를 클릭해본 결과 Submit이라는 입력 폼이 새로 생겼습니다. 여기에 값을 대입하면 전송되어 저장되는 형태인 것 같습니다. 우선 소스코드를 확인해보도록 하겠습니다.

 

(사진 27) High 소스코드

id='$id'=LIMIT 1, "; 부분이 있습니다. LIMIT 1이라는 것을 통해 출력되는 레코드의 수를 1개로 조절하여 여러 값들이 나오지 않게끔 하고 있습니다. 하지만 $id 뒤에 " # "을 붙이게 되면 #뒷부분은 모두 주석처리가 되기 때문에 이러한 제한을 우회할 수가 있습니다.

(사진 28) High 레벨 우회

1' or '1'='1'#

상위의 구문을 입력해주면 뒷부분은 전부 주석처리가 되기 때문에 LIMIT 제한을 우회하여 모든 값을 추출시킬 수 있게 됩니다.

(사진 29) union 구문 우회

1' union select user,password from users#

상위의 구문 마지막에 " # "을 붙임으로써 모든 계정의 값들을 추출시킬 수 있게 됩니다.

 


◆ 대응방안

(사진 30) impossible 소스코드

(사진 30)을 보시면 현재 dvwa에서는 id 가 1,2,3,4,5 형식으로 되어있습니다. 그렇기에 입력되는 ID 가 숫자인지 아닌지를 먼저 구분해야 하며 문자를 입력해야 할 경우 허용되는 문자만 사용할 수 있도록 제한을 걸어둬야 합니다. 해당 서비스에 필요한 데이터 형식을 제대로 숙지하고 있지 못할 경우 다양한 우회 기법을 통해 SQLi를 성공시킬 것입니다. 

또한 " Check the database " 하단을 보시면 prepare , bindParam, excute 같은 함수를 여러 번 호출하는 형식으로 쿼리문을 실행시키고 있습니다. Low 나 Mideum, High 레벨의 경우 입력값을 받은 다음에 바로 MySQL_query로 호출하고 있는데 런 식의 방식들을 동적 쿼리라고 합니다. 

한마디로 동적 쿼리보단 impossible 레벨처럼 " 파라미터 쿼리 " 에서는 prepare 함수에다가 미리 쿼리 문의 형태를 작성해두며 id부분에서만 bindparam을 해주는데 이럴 경우 DB에서는 어떤 것이 쿼리이고 문자인지를 구분할 수 있기 때문에 우리가 입력했던 SQL injection 구문들을 막을 수 있습니다.

이외에도

java 기반의 " preparestatment " 와. NET 기반의 " sqlCommand, OleDbCommand "을 통해서도 대응할 수가 있습니다.

'IT Security > DVWA(Damn Vulnerable Web App)' 카테고리의 다른 글

[DVWA] XSS(Cross Site Scripting)  (0) 2020.05.12
[DVWA] Weak Session ID  (0) 2020.05.11
[DVWA] insecure CAPTCHA  (0) 2020.04.23
[DVWA] File Upload Vulnerability  (0) 2020.04.21
[DVWA] File inclusion  (0) 2020.04.20