본문 바로가기

Dreamhack/웹해킹

[LEVEL 1] php-1

웹 서비스에 접속하면 상단에는 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';

 

  1. URL에 page 파라미터가 있을 때: 사용자가 URL에 ?page=list와 같이 page 파라미터를 입력하면, 그 값(list)을 가져와서 뒤에 .php를 붙여 list.php 파일을 불러옵니다.
  2. 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