상세 컨텐츠

본문 제목

DVWA : SQL Injection - Impossible level

Vulnerability Assessment/Web Application

by DarkSoul.Story 2025. 1. 14. 12:02

본문

반응형

[ 환경 ]

DVWA v1.9
Burp Suite Community Edition v2024.11.2

1. Source code analysis

[그림 1] Impossible level Source code

 

CSRF 토큰 검증

checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

 

  • checkToken( ) 함수는 CSRF 토큰을 검증한다.
  • 사용자가 요청에 포함한 user_token과 세션에 저장된 session_token을 비교한다,
  • 두 토큰이 다르거나 세션에 유효한 토큰이 없으면 CSRF 공격으로 간주하고 index.php로 리디렉션한다.

사용자 입력값 가져오기

$id = $_GET[ 'id' ];

 

  • 사용자가 입력한 id 값을 GET 요청에서 가져온다.
  • 데이터베이스에서 조회할 id 값을 획득

숫자인지 확인

if(is_numeric( $id )) {
  • is_numeric( ) 함수를 사용하여 입력값이 숫자인지 확인한다.
  • id는 숫자로만 이루어져야 하므로, SQL Injection 등의 악성 입력을 방지하기 위한 기본적인 필터링

데이터베이스 조회

$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();
  • PDO::prepare( )를 사용하여 SQL 쿼리를 준비한다.
  • 쿼리: SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1
  • :id는 플레이스홀더로, 사용자 입력값이 직접 SQL에 삽입되지 않는다.
  • bindParam()으로 사용자 입력값($id)을 플레이스홀더 :id에 바인딩하며, 데이터 타입은 PDO::PARAM_INT로 명시
  • execute()로 쿼리를 실행
  • fetch()로 실행 결과에서 첫 번째 행을 가져온다.
  • SQL Injection 공격 방지
  • 사용자 ID에 해당하는 이름과 성을 데이터베이스에서 가져온다.

결과 행 수 확인

if( $data->rowCount() == 1 ) {
  • rowCount()를 사용해 쿼리 결과 행의 개수를 확인한다.
  • 반환된 결과가 하나일 경우에만 다음 로직을 실행한다.
  • 조회 결과가 정확히 하나의 행인지 확인
  • 다중 결과 반환이나 비정상적인 상황 방지

결과 처리 및 출력

$first = $row[ 'first_name' ];
$last  = $row[ 'last_name' ];

echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
  • $row에서 first_name과 last_name 값을 가져온다.
  • HTML <pre> 태그를 사용하여 사용자에게 결과를 출력
  • ID, First name, Surname이 포함된 포맷으로 출력된다.
  • 사용자 입력값 $id와 조회 결과인 $first, $last를 이스케이프 처리하지 않고 출력되므로, XSS(Cross-Site Scripting) 공격에 취약

CSRF 토큰 생성

generateSessionToken();
  • 새로운 CSRF 토큰을 생성하고 세션에 저장
  • 다음 요청에서도 CSRF 방어를 지속하기 위해 새로운 토큰 생성

 

2. Source code improvements

앞서 살펴본 소스코드는 SQL Injection 공격을 방어할 수 있는지만 XSS 공격에는 취약하다.

XSS 공격 가능성

사용자 입력값 $id를 그대로 출력하기 때문에 XSS 공격에 취약하다. 이를 해결하기 위해 출력 시 htmlspecialchars()를 사용하여 HTML 특수 문자를 이스케이프 처리한다.

echo "<pre>ID: " . htmlspecialchars($id, ENT_QUOTES, 'UTF-8') . "<br />";
echo "First name: " . htmlspecialchars($first, ENT_QUOTES, 'UTF-8') . "<br />";
echo "Surname: " . htmlspecialchars($last, ENT_QUOTES, 'UTF-8') . "</pre>";

 

사용자 피드백 부족

숫자가 아닌 값을 입력하거나 조회 결과가 없을 경우 사용자에게 아무런 피드백을 제공하지 않는다. 이를 해결하기 위해 입력값이 숫자가 아니거나 결과가 없을 경우 적절한 메시지를 출력한다.

if (!is_numeric($id)) {
    echo "<pre>Invalid ID format. Please enter a numeric ID.</pre>";
} elseif ($data->rowCount() === 0) {
    echo "<pre>No user found with the given ID.</pre>";
}

 

개선된 코드

<?php

if( isset( $_GET[ 'Submit' ] ) ) {
    if (!checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' )) {
        die("<pre>Invalid CSRF token. Request denied.</pre>");
    }

    $id = $_GET[ 'id' ];

    if (!is_numeric($id)) {
        echo "<pre>Invalid ID format. Please enter a numeric ID.</pre>";
    } else {
        $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
        $data->bindParam( ':id', $id, PDO::PARAM_INT );
        $data->execute();
        $row = $data->fetch();

        if ($data->rowCount() == 1) {
            $first = htmlspecialchars($row['first_name'], ENT_QUOTES, 'UTF-8');
            $last = htmlspecialchars($row['last_name'], ENT_QUOTES, 'UTF-8');
            echo "<pre>ID: " . htmlspecialchars($id, ENT_QUOTES, 'UTF-8') . "<br />First name: {$first}<br />Surname: {$last}</pre>";
        } else {
            echo "<pre>No user found with the given ID.</pre>";
        }
    }
}

generateSessionToken();

?>

 

요약

CSRF 보호

  • checkToken() 함수로 요청의 유효성을 확인
  • 요청마다 새 CSRF 토큰을 생성

XSS 방지

  • 사용자 입력값과 출력 결과를 htmlspecialchars()로 이스케이프 처리

유효성 검사

  • 입력값이 숫자인지 확인
  • 조회 결과가 없는 경우 사용자에게 메시지를 제공

 

보안 강화

  • SQL Injection 방지: Prepared Statements 사용
  • 출력 값 필터링: 특수 문자 이스케이프
반응형

관련글 더보기