Post

SSRF 취약점 실습 - 내부 파일 탈취

SSRF(Server-Side Request Forgery)는 서버가 공격자가 지정한 URL로 요청을 보내도록 유도하는 취약점이다. 내부 네트워크 자원에 대한 접근이나 서버 내부 파일 탈취 등에 악용될 수 있어 크리티컬한 취약점 중 하나.

실습 환경

취약한 기능 추가

사실 내가 전에 만들어둔 APM 게시판에는 SSRF 취약점이 없었다… URL을 파라미터로 받는 곳이 없는 초 심플한 게시판이라서. 그래서 게시글 작성 시 외부 이미지 URL을 파라미터로 받아 서버에 저장하는 기능을 억지로(?) 추가했다.

추가한 내용을 간단히 정리하면:

  • DB에 img_url, fetched_img 컬럼 추가
  • 게시글 저장 로직(member_insert.php)에 아래 코드 추가
1
2
3
4
5
6
7
8
9
10
11
// 외부 이미지 URL fetch → 로컬 저장
$img_url = $_POST["img_url"] ?? "";
$fetched_img_name = "";

if ($img_url) {
    $img_data = file_get_contents($img_url);  // SSRF 발생 지점
    if ($img_data !== false) {
        $fetched_img_name = "img_" . date("Y_m_d_H_i_s") . ".dat";
        file_put_contents($upload_dir . $fetched_img_name, $img_data);
    }
}

핵심은 file_get_contents($img_url) — 사용자 입력값을 아무 검증 없이 서버가 직접 요청을 날리는 부분이다.

정상 동작 확인

외부 이미지 주소를 첨부하면 게시글 조회 시 이미지가 표시된다.

외부 이미지 fetch 확인 이미지 URL 첨부 기능 정상 동작 확인


취약점 분석

1단계: 내부 서버 응답 확인

이미지 URL 입력란에 http://localhost/를 입력하여 게시글을 저장한다.

SSRF 취약점 확인 이미지 URL에 http://localhost/ 입력

게시글을 조회하면 서버가 localhost로 HTTP 요청을 날리고, 그 응답이 .dat 파일로 저장되어 화면에 노출된다.

Apache2 서버 응답 노출 Burp Suite 캡처 - Apache2 Ubuntu Default Page 응답값이 그대로 노출됨

Burp Suite 분석:

  • 요청: GET /logintest/file/img_2026_03_22_01_33_53.dat
  • 응답: <title>Apache2 Ubuntu Default Page: It works</title>

서버가 내부 localhost로 HTTP 요청을 보내고, 그 응답이 파일로 저장되어 클라이언트에게 노출되는 것이 확인됐다. SSRF 취약점 존재 확인.

2단계: 내부 파일 탈취 시도 (http:// 스킴)

따라서 이 SSRF 취약점을 이용해 내부 PHP 파일의 소스코드를 탈취할 수 있을 것 같았다.

근데… 로그인 기능을 처리하는 login_process.php로 시도했을 때 실제 소스코드가 아닌 login.php의 HTML이 저장됐다.

왜 그런지 생각해보니 당연한 거였음.

PHP는 서버에서 실행되는 언어니까, http:// 스킴으로 요청하면 아파치가 PHP를 실행한 결과를 반환한다.

1
2
file_get_contents("http://127.0.0.1/logintest/login_process.php") 요청 시:
클라이언트(member_insert.php) → 아파치 → PHP 실행 → 결과 HTML 반환

login_process.php는 POST 데이터 없이 GET으로 접근하면 로그인 실패 → login.php로 리다이렉트 하도록 짜여있으니까, 소스코드가 아니라 리다이렉트된 login.php의 HTML이 저장되는 것…

그래서 file:// 스킴을 쓰기로 했다.

3단계: file:// 스킴으로 소스코드 탈취

file:// 스킴은 HTTP 요청이 아니라 파일 시스템을 직접 읽기 때문에 PHP 실행 없이 소스코드 원본을 가져올 수 있다.

이미지 URL 입력란에 file:///var/www/html/logintest/login_process.php를 입력한다.

file:// 스킴 입력 이미지 URL에 file:///var/www/html/logintest/login_process.php 입력

그리고 저장된 게시글을 조회하면,

login_process.php 소스코드 노출 Burp Suite 캡처 - login_process.php 소스코드 전체 노출

login_process.php 노출 내용:

1
2
3
<?php
// MySQL 연결
$mysqli = mysqli_connect('localhost', 'test22', 'pass22', 'logintest');

DB 계정 정보가 포함된 로그인 처리 로직이 통째로 노출됐다.

4단계: DB 설정 파일 탈취

별도로 분리해서 관리하던 db.php 파일도 시도해봤다.

file:///var/www/html/logintest/db.php를 입력한다.

db.php 탈취 시도 이미지 URL에 file:///var/www/html/logintest/db.php 입력

db.php 소스코드 노출 Burp Suite 캡처 - db.php DB 계정 정보 전체 노출

db.php 노출 내용:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
// 데이터베이스연결 설정
$host = 'localhost';
$username = 'test22';
$password = 'pass22';
$database = 'logintest';

// MySQL 데이터베이스에연결
$conn = mysqli_connect($host, $username, $password, $database);

// 연결 오류 처리
if (mysqli_connect_errno()) {
    echo '데이터베이스연결 실패: ' . mysqli_connect_error();
    exit();
}
?>

DB 호스트, 계정명, 패스워드, DB명까지 전부 노출됐다.


취약점 정리

SSRF

  • 위치: member_insert.php의 file_get_contents($img_url)
  • 원인: 사용자 입력 URL에 대한 검증 없이 서버가 직접 요청
  • 영향:
    • http:// 스킴 → 내부 네트워크 서비스 접근 및 응답 확인
    • file:// 스킴 → 서버 내부 파일 원본 탈취
    • DB 계정 정보(test22 / pass22) 노출

대응 방안

1. URL 검증 및 화이트리스트 적용

  • 허용된 도메인/IP만 요청 가능하도록 화이트리스트 설정
  • http://, https:// 외의 스킴(file://, gopher://, dict:// 등) 차단
1
2
3
4
5
$parsed = parse_url($img_url);
$allowed_schemes = ['http', 'https'];
if (!in_array($parsed['scheme'], $allowed_schemes)) {
    die('허용되지 않는 스킴입니다.');
}

2. 내부 IP 접근 차단

  • 127.0.0.1, localhost, 10.x.x.x, 192.168.x.x 등 내부 IP 범위 차단

3. 네트워크 분리

  • 웹 서버가 내부 서비스에 직접 접근하지 못하도록 네트워크 단에서 격리

4. 기능 자체 제거

  • 불필요한 경우 URL을 파라미터로 받는 기능 자체를 없애는 게 제일 깔끔함 (내 야매 게시판처럼…)

결론: file_get_contents()에 사용자 입력을 그대로 넣는 순간 SSRF에 매우 취약해진다. http:// 스킴으로 내부 서비스 스캔, file:// 스킴으로 소스코드 탈취까지 가능하니 URL을 서버에서 직접 fetch하는 기능은 반드시 스킴과 대상 호스트를 검증해야 한다.

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