原创 · 2024年12月21日 0

[HCTF 2018] WarmUp 题解

前言:题目来源BUUCTF

目录:

  1. 题目
  2. 解析

一、题目

靶机主页为一张无关图片,不可互动。代码如下:

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <!--source.php-->
    
    <br><img src="https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg" /></body>
</html>

二、解析

检查上述主页代码,发现有注释中提到一个文件 source.php 。尝试访问这个文件看看有什么输出:

http://靶机地址/source.php

一般情况下,用户在向网站服务器请求任何php文件时,服务器会直接运行php文件,而不是向用户显示文件内容。也就是说php文件是“无法被下载”的,这也是出于网络安全的设计。

但是访问本题靶机的 source.php 时,却出现了如下所示的代码原文,我们来看看这是怎么回事:

source.php
<?php
    highlight_file(__FILE__);
    class emmm
    {
        public static function checkFile(&$page)
        {
            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
            if (! isset($page) || !is_string($page)) {
                echo "you can't see it";
                return false;
            }

            if (in_array($page, $whitelist)) {
                return true;
            }

            $_page = mb_substr(
                $page,
                0,
                mb_strpos($page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }

            $_page = urldecode($page);
            $_page = mb_substr(
                $_page,
                0,
                mb_strpos($_page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }
            echo "you can't see it";
            return false;
        }
    }

    if (! empty($_REQUEST['file'])
        && is_string($_REQUEST['file'])
        && emmm::checkFile($_REQUEST['file'])
    ) {
        include $_REQUEST['file'];
        exit;
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
    }  
?>

发现这是一段PHP代码,现在分段来分析它的内容:

PHP
highlight_file(__FILE__);

这表示此文件被访问时,服务器直接输出源代码文本。正因为它,我们得以看到代码。

然后是一个类 emmm ,其中只有一个函数 checkFile 。看名字似乎是一个文件检查函数,其具体内容如下:

PHP
class emmm
    {
        public static function checkFile(&$page)
        {
            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
            if (! isset($page) || !is_string($page)) {
                echo "you can't see it";
                return false;
            }

            if (in_array($page, $whitelist)) {
                return true;
            }

            $_page = mb_substr(
                $page,
                0,
                mb_strpos($page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }

            $_page = urldecode($page);
            $_page = mb_substr(
                $_page,
                0,
                mb_strpos($_page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }
            echo "you can't see it";
            return false;
        }
    }

看来这个函数干了以下几件事:

  1. 接受一个不知何处传来的值 $page ;
  2. 设置了一个白名单(叫 $whitelist 的数组),里面有两个文件名,分别是 source.php 和 hint.php ;
  3. 从文件名上看,hint.php内可能包含提示信息。鉴于我们已经在看 source.php 了,可以猜测到服务器内还存在另一个文件 hint.php ;
  4. 检查用户请求的 $page 值是不是符合“某些条件”。

“某些条件”具体如下:

  • $page 值中的第一个问号后的内容将被忽略;
  • $page 值是否在白名单中?
  • $page 值经过url解码后是否还在白名单中?

看到这里真是让人一头雾水,我们继续往下看这个所谓的 $page 值到底是什么:

PHP
    if (! empty($_REQUEST['file'])
        && is_string($_REQUEST['file'])
        && emmm::checkFile($_REQUEST['file'])
    ) {
        include $_REQUEST['file'];
        exit;
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
    }  

这段代码的意思是,从URL中接受用户给出的 file 值(比如?file=xxx),如果此值是字符串,而且此值通过了类emmm中checkFile函数的检查,就在页面中包含用户给出的这个 file 值。从这一点来看,file 值实际上应该是一个文件名。

到这里就真相大白了。前面那个函数所接收的 $page 值,实际上是用户在URL中给出的GET请求的值。

现在我们整理一下此网站的运行逻辑:

  1. 用户在URL中给出了file值,是一个文件名,比如 xxx.php ;
  2. 这个值作为变量 $page 被提交给函数 checkFile ;
  3. 函数先忽略文件名中第一个问号之后的部分;
  4. 接下来函数检查此文件是否:
    • 在白名单中?
    • 经过url解码后是否还在白名单中?
  5. 一旦通过检查,就将此文件显示给用户。

到这里我们基本上确定,题目要我们绕过这个检查机制,找到一个目标文件。

可是目标文件是什么?我们试着访问以下 hint.php ,看看出题人给出了什么提示:

此php文件的输出为一句话:“flag不在这,而在ffffllllaaaagggg”

看来我们应该试着找一下叫做ffffllllaaaagggg的文件。构造URL如下:

http://靶机地址/?file=source.php

这个URL是能通过检查的,因为 source.php 确实是白名单中的文件;

在此基础上添加一个问号,问号后添加一个相对路径:

http://靶机地址/?file=source.php?./ffffllllaaaagggg

这个URL也能通过检查。为什么?

此时函数获取到的“文件名”实际上是:source.php?./ffffllllaaaagggg 。检查时,问号后面的内容被系统忽略,只会检查问号前的内容,而这里问号前的内容是能通过检查的;

那么这个URL能不能获得输出?也能,但前提是此路径下真的存在文件ffffllllaaaagggg。为什么?

大多数服务器的操作系统是Linux。在Linux中,路径“./”指当前路径,“../”指父级路径。此时,对于系统获取到的“文件名” source.php?./ffffllllaaaagggg ,实际上是两个文件:source.phpffffllllaaaagggg 。所以只要存在此文件,就会被输出。

但是浏览器并没有输出内容,我们在在此基础上进一步修改URL:

http://靶机地址/?file=source.php?../ffffllllaaaagggg

我们尝试到上级目录寻找这个文件。请注意不能用通配符“*”代替文件名ffffllllaaaaagggg,因为PHP并不能像shell命令一样识别通配符。如果还是失败,可以继续叠加“../”来向更上层的目录寻找。

经过反复尝试,最终我们在此路径下获取到了目标文件:

http://靶机地址/?file=source.php?../../../../../ffffllllaaaagggg

成功获取flag。