SlowTurtle_

천천히 그러나 끝까지 완주

Webhacking-Write-Up/DVWA

DVWA(LOW) - Insecure CAPTCHA, SQL Injection, SQL Injection(Blind)

SlowTurtle_ 2023. 1. 16. 18:11
728x90

1. Insecure CAPTCHA

Insecure CAPTCHA는 사이트에 접근하려는 시도가 사람이 접근하는건지 컴퓨터 프로그램이 접근하는 것인지 판단하기 위해서 사용한다.

CAPTCHA가 정상적으로 실행가능한 실습 준비를 마쳤다.

우선 low.php의 소스부터 확인해보자. 너무 길어 step1과 step2로 나눠서 보도록 하겠다.

<?php

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
    // Hide the CAPTCHA form
    $hide_form = true;

    // Get input
    $pass_new  = $_POST[ 'password_new' ];
    $pass_conf = $_POST[ 'password_conf' ];

    // Check CAPTCHA from 3rd party
    $resp = recaptcha_check_answer(
        $_DVWA[ 'recaptcha_private_key'],
        $_POST['g-recaptcha-response']
    );

    // Did the CAPTCHA fail?
    if( !$resp ) {
        // What happens when the CAPTCHA was entered incorrectly
        $html     .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
        $hide_form = false;
        return;
    }
    else {
        // CAPTCHA was correct. Do both new passwords match?
        if( $pass_new == $pass_conf ) {
            // Show next stage for the user
            echo "
                <pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
                <form action=\"#\" method=\"POST\">
                    <input type=\"hidden\" name=\"step\" value=\"2\" />
                    <input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
                    <input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
                    <input type=\"submit\" name=\"Change\" value=\"Change\" />
                </form>";
        }
        else {
            // Both new passwords do not match.
            $html     .= "<pre>Both passwords must match.</pre>";
            $hide_form = false;
        }
    }
}

step1에서는 사용자로부터 CAPTCHA인증과 변경할 비밀번호를 받고 정상적인지 검증을 진행한 후 step2로 진행할 수 있도록 출력한다.

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
    // Hide the CAPTCHA form
    $hide_form = true;

    // Get input
    $pass_new  = $_POST[ 'password_new' ];
    $pass_conf = $_POST[ 'password_conf' ];

    // Check to see if both password match
    if( $pass_new == $pass_conf ) {
        // They do!
        $pass_new = mysql_real_escape_string( $pass_new );
        $pass_new = md5( $pass_new );

        // Update database
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
        $result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' );

        // Feedback for the end user
        echo "<pre>Password Changed.</pre>";
    }
    else {
        // Issue with the passwords matching
        echo "<pre>Passwords did not match.</pre>";
        $hide_form = false;
    }

    mysql_close();
}

?>

step2에서는 SQL쿼리를 통해서 비밀번호를 업데이트 한다. 여기서 관점은 step1에서 과정과 step2에서 과정은 서로 상태를 저장하고 있지 않아서 사용자가 step1을 거치고 step2로 왔는지 여부를 모른다는 취약점이 있다.

password를 password에서 qwer1234로 변경하기 위해 요청한 상황이다. step1인 것을 알 수 있고 passowrd_current, password_new, password_conf를 보면 모두 암호화 되어지지 않고 평문으로 담겨있다는 것을 알 수 있다. 모든 것을 확인했을 때 CAPTCHA기능을 통해서 패스워드와 인증이 완료된 상태인 step:"2"로 변경해 요청한다면 검증하지않고 변경이 가능하다는 것을 알 수 있다. 또한 암호화 되지 않은 변경한 새로운 비밀번호가 쉽게 노출된 것을 알 수 있다.

value="2"로 변경하고 input값에 변경할 값들을 value="qwer1234"로 입력해준 후 요청하면

변경된 것을 확인할 수 있다.

 

2. SQL Injection

우선 low.php를 살펴보았다.

$query 부분을 보면 "SELECT first_name, last_name FROM users WHERE user_id ='$id',"의 sql문을 볼 수 있다.

또한 입력받은 숫자를 문자취급하여 쿼리문에 사용되는 것을 알 수 있다.

id부분을 이용하여 문제를 풀어나가면 될 것 같다. User_ID에 1' or 1=1# 을 입력하여 1또는 1은 1일 때 뒤는 모두 주석처리하도록 했다.

user_id와 상관없이 언제나 참이기때문에 모든 회원정보를 조회할 수 있었다. 회원정보가 더 있었다면 더 나왔을 것이다.

위와 같은 결과를 보아 SQL구문 삽입이 가능한 것을 알 수 있다. 이번에는 UNION질의를 이용해서 SQL구문삽입을 통해 테이블 내용을 조회해보자.

우선 UNION은 앞, 뒤 SELECT문에서 출력되는 컬럼의 수가 동일해야 함을 인지하자. 따라서 SELECT문의 컬럼을 하나씩 늘려가며 조회해보겠다.

User ID에 1' UNION SELECT 1#을 입력하여 SELECT문에 1개 컬럼을 가져와보려했지만 different number of columns라는 경고문이 떴다. 앞, 뒤 SELECT문컬럼의 수가 서로 다르다는 뜻 같다.

그래서 1' UNION SELECT 1,2#을 입력해보았다.

출력되는 것을 보아 SELECT에서 컬럼의 수는 두 개라는 것을 알 수 있었다. 위에  UNION의 질의는 앞과 뒤 SELECT문에서 컬럼의 수가 동일해야하는 것을 컬럼의 수가 2개라는 것을 확인했으니 user_id와 password를 알아보자

1' UNION SELECT user,password FROM users#

위와 같이 입력해봤다.

user_id와 password를 얻을 수 있었다. 그러나 password가 암호화되어 있었다. password 암호화를 한 번 풀어보고 싶어서 더욱 알아봤다. hash-identifier라는 도구를 알게 되었다. hash-identifier라는 도구는 해시 알고리즘 종류를 알아내주는 툴이다. #hash-identifier로 실행시키고 HASH: 값을 넣으면 가능한 해쉬를 알 수 있다.

admin의 패스워드를 넣어 실행결과 MD5가 가능함을 알 수 있었다.

그 후 john thre ripper를 이용하여 처음에 학습했던 brute force로 MD5로 인코딩된 문자열들을 디코딩해보겠다.

우선 사용하기위해 5개의 user_id와 password를 (:)으로 묶어 hash.txt파일을 만들었다. 

무작위대입을 해보았는데 admin의 패스워드가 디코딩안되었다. 이유를 찾아봤지만 아직 해결하지못했다.

admin을 해결하지 못한 것이 아쉽지만 우선 4개의 user_id의 해독된 password를 알 수있었다.

 

3. SQL Injection(Blind)

SQL Injection(Blind)는 쿼리의 결과를 참과 거짓으로만 출력하는 페이지에서 사용하는 공격이다.

바로 전 SQL Injection과 초기화면이 같아서 냅다 1부터 넣어봤다.

이번엔 Blind SQL Injection이라 User ID가 db에 존재한다는 것만 알려줬다. 즉 참과 거짓으로만 구별된다는 것을 확인했다

우선 User_ID에 순서대로 1' or 1=1#, ' or 1=2#을 넣어보았다.

참일 때 존재하고 거짓일 때 존재하지 않는 다는 것을 알았다. 즉, sql에 주입이 통하는 것을 확인할 수 있었다.

UNION으로 컬럼의 갯수를 확인해보겠다. 'union select 1,2#를 입력해보았다.

존재한다는 것을 보아 컬럼이 2개인 것을 확인할 수 있었다.

어느 때와 같이 low.php를 열어봤다.

<?php

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

	switch ($_DVWA['SQLI_DB']) {
		case MYSQL:
			// Check database
			$query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
			$result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ); // Removed 'or die' to suppress mysql errors

			$exists = false;
			if ($result !== false) {
				try {
					$exists = (mysqli_num_rows( $result ) > 0);
				} catch(Exception $e) {
					$exists = false;
				}
			}
			((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
			break;
		case SQLITE:
			global $sqlite_db_connection;

			$query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
			try {
				$results = $sqlite_db_connection->query($query);
				$row = $results->fetchArray();
				$exists = $row !== false;
			} catch(Exception $e) {
				$exists = false;
			}

			break;
	}

	if ($exists) {
		// Feedback for end user
		$html .= '<pre>User ID exists in the database.</pre>';
	} else {
		// User wasn't found, so the page wasn't!
		header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

		// Feedback for end user
		$html .= '<pre>User ID is MISSING from the database.</pre>';
	}

}

?>

끝에 다 다르때 쯤 문장들이 눈에 들어왔다. User ID가 존재할 때는 존재한다는 문장을 출력하는데 존재하지 않을 때 서버에 404 Not Found를 출력해내고 우리에게 보여지는 것은 User ID가 존재하지 않는 다는 문장인 것을 확인했다.

보통적인 Blind SQL Injection을 할 때인 쿼리문으로 하는 것보다  Sqlmap을 이용하는 방법을 보고 흥미를 느껴서 Sqlmap을 사용하여 이해하며 진행해보았다.

sqlmap -u "http://localhost/DVWA-master/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=bfv8637i30oorh0u2vk4re1s3h"

터미널에 #sqlmap -u URL --cookie=COOKIE로 해당 사이트를 살펴 볼 수 있다. cookie는 현재 security가 low고 PHPSESSID는 bfv8637i30oorh0u2vk4re1s3h로 입력했다.

boolean-based blind 및 time-based blind 가능함을 확인했고 MySQL을 사용함을 알 수 있었다.

그러면 데이터베이스 명을 확인하기 위해 아래 코드를 사용했다. 첫 문장에 --current-db 추가해 현재 db를 확인했다. 

sqlmap -u "http://localhost/DVWA-master/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=bfv8637i30oorh0u2vk4re1s3h" --current-db

데이터베이스 명이 'dvwa'인 것을 확인할 수 있었다. 이제 dvwa의 테이블을 알아보자.

기본적인 부분은 같고 -D db_name --tables 형식으로 db_name대신 위에 알아본 dvwa를 입력했다.

sqlmap -u "http://localhost/DVWA-master/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=bfv8637i30oorh0u2vk4re1s3h" -D dvwa --tables

그 결과 guestbook과 users라는 두 테이블이 존재한다는 것을 알았다.

우리는 guestbook보다 users의 테이블의 칼럼을 출력해보자. 

db_name과 table_name을 알았으니 -D db_name -T table_name --columns 형식으로 출력했다.

sql문은 생각보다 직관적으로 알 수 있는 명령어를 사용하여 길이가 길지만 이해하기 쉬웠다.

sqlmap -u "http://localhost/DVWA-master/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=bfv8637i30oorh0u2vk4re1s3h" -D dvwa -T users --columns

총 8개의 컬럼을 알 수 있었는데 그 중 password와 user_id가 눈에 띈다. user_id와 password를 잘 매칭하면 정보를 획득할 수 있을 것 같다. 당장 해보자!

우선 user_id의 데이터를 dump로 값을 얻어봤다.

sqlmap -u "http://localhost/DVWA-master/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=bfv8637i30oorh0u2vk4re1s3h" -D dvwa -T users -C user_id --dump

처음에 1을 넣었을 때 db에 존재한다는 것을 확인했던 기억이 떠올랐다. user_id에 1이 존재함을 직접 확인했다. 

이제 password를 알아보겠다. 

sqlmap -u "http://localhost/DVWA-master/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie="security=low; PHPSESSID=bfv8637i30oorh0u2vk4re1s3h" -D dvwa -T users -C password --dump

결과를 출력했다. sqlmap을 사용하는데 암호화 되어 있던 값들이 취약하고 잘 알려진 MD5라는 것을 캐치하고 나에게 디코딩해서 할 지를 y / n로 대답하라고 한다. 그래서 y로 대답을 했더니 알아서 디코딩하는 과정을 보여주며 알려주는 친절함을 보고 반했다. 위와 같이 password를 알 수 있었다. 아마 1,2,3,4,5와 순서가 같을 것 같다. 즉 1의 password는 letmein일 것이라고 추측해본다. 과정이 길었지만 흥미롭게 할 수 있었던 Blind SQL Injection 실습이어서 좋았다.

728x90