← 전체 Write-Up

Web / Dreamhack

php-1

서로 다른 입력 검증 경로와 PHP 스트림 필터를 분석한 기록.

읽기 안내

문제에서 제공한 코드와 직접 작성한 풀이를 함께 보존했습니다. 원문 페이로드의 reousrce 표기는 그대로 두었습니다. PHP 공식 문서의 올바른 옵션명은 resource입니다.

CODE

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>

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>

설명

   <?php
          include $_GET['page']?$_GET['page'].'.php':'main.php';
      ?>

page의 파라미터를 받으면 그 파라미터를 실행시키고 없으면 자동으로 main.php를 실행시킨다.

다음은 list.php다.

<?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/>";
    }
?>

디렉토리 내를 순회하며 .., ., index.php 를 제외한 모든 파일을 읽어 출력한다.

view.php

 if(preg_match('/flag|:/i', $file)){
        exit('Permission denied');
    }

/flag 문자열이 있으면 Permission denied를 출력한다.

list.php를 통해 flag.php에 접속하면 Permission denied가 뜬다.

php-1 — 실습 화면 1

view.php에서는 flag를 필터링하기 때문에 필터링을 거치지 않는 index.php를 통해 flag.php를 접속해보자.

http://host3.dreamhack.games:20870/index.php?page=../uploads/flag

php-1 — 실습 화면 2

flag가 아닌 can you see $flag? 라는 문장이 나왔다. 이 말은 html 부분에 flag가 있지 않고 실제 php 코드 내부에 있는 것으로 예상된다.

php wrapper 를 사용해 php 내부의 flag를 긁어보자.

php wrapper란? php wrapper는 파일 시스템 함수들을 사용할 때, 파일뿐만 아니라 다양한 프로토콜이나 리소스에 접근할 수 있도록 중간에서 연결을 담당하는 인터페이스를 말한다.

즉, 단순 파일 경로만 여는게 아니라, http:// , ftp://, php://, data://, zip:// 같은 특정 프로토콜을 경로처럼 취급할 수 있게 해주는 기능이다.

Wrapper 설명
file:// 기본 파일 시스템 접근
http://, https:// HTTP, HTTPS 프로토콜 파일 접근
ftp://, ftps:// FTP 파일 접근
php://input, php://memory PHP 내부 입출력 스트림
data:// 데이터 URI 읽기
zip:// ZIP 아카이브 내부 파일 접근
시스템 함수 기능
file:// Accessing local filesystem
http:// Accessing HTTP(s) URLs
ftp:// Accessing FTP(s) URLs
php:// Accessing various I/O streams
zlib:// Compression Streams
data:// Data (RFC 2397)
glob:// Find pathnames matching pattern
phar:// PHP Archive

특히 내가 사용할 php wrapper는 php://filter다. 이 wrapper는 encode/decode 옵션으로 서버 안에 존재하는 문서를 열람할 수 있다. 그리고 zip파일의 압축을 풀고 해당 파일을 실행한다.

페이로드는 다음과 같다.

http://host3.dreamhack.games:20870/index.php?page=php://filter/convert.base64-encode/reousrce=../uploads/flag

php-1 — 실습 화면 3

base64로 인코딩된 문자열이 출력되었다. 이 문자열을 디코딩 해 보면 다음과 같은 내용이 출력된다.

php-1 — 실습 화면 4

플래그가 출력되었다.

작성 기록과 문제 출처

원문과 출처

풀이와 설명: MadEyE / 이선용의 개인 Notion 기록.
문제에서 제공한 코드·서비스 화면과 외부 참고자료의 권리는 해당 작성자와 플랫폼에 있습니다.

원문 본문·코드·실습 증적을 보존했으며, 원문에 포함된 외부 글은 참고 링크로 안내합니다.