Loading

PHP SESSION序列化漏洞

什么是SESSION

先看下百度词条的定义:session(计算机术语)_百度百科 (baidu.com)

对应PHP简单来说就是存储在服务端的用户相关数据,用户访问服务端时则不需要携带这些信息,只需要携带对应的SESSIONid,服务端则会使用对应SESSION中的用户相关数据。SESSION在服务端设置启用时会对用户进行检测,如果携带SESSIONid则会调用相关的SESSION,不携带则会分配一个新的SESSION在服务端和对应的SESSIONid给客户端(Set-Cookie的形式放在Cookie中)。SESSION中的数据在用户和服务端交互时(进行会话)会存储在内存中(也就是全局变量$_SESSION中的内容),而结束交互(会话结束)后会按照特定的规则将SESSION的数据写入文件,下次交互时又会从文件中读取转化为内存中的变量。

类比来说就像商城的寄存柜,当我们去商城购物时(进行会话),我们可以把我们的物品放在寄存柜中(相关信息置于服务端的SESSION中,区别在于SESSION不只是被存放而且会服务端被使用),并获得一个对应的取货吗(SESSIONid),无论我们在商城的哪(访问站点下的不同页面),只要我们持有取货码就能证明对应的寄存柜内的东西是我们的(SESSIONid对应SESSION中的数据属于某一用户),当我们购物结束便能从寄存柜中带走物品(但对于SESSION中的数据,用户会话结束后会以文件形式存储。如果设置了SESSION的超时清除,在SESSION过期(一段时间内未和服务端进行会话)后会被清除;如果未设置,在SESSION过期后只需重新验证相关信息再分配一个新的SESSIONid便能继续使用)。

例如我们第一次访问BUUCTF的题目页面,此时并不携带SESSIONid,访问跳转到登录页面。

但我们成功登录后再去访问,在Cookie中会携带分配给我们的SESSIONid(当然还有其他的数据),此时则能正常访问。

这就是SESSION应用的一个体现。

PHP相关代码解释

[开启session]

会话可自动开启或通过session_start()函数来开启,会话开启后PHP会获取客户端访问时携带的SESSIONid,并从对应的SESSION文件中读取数据存入全局变量$_SESSION之中。如果未携带SESSIONid则会创建一个新的SESSION并生成一个新的SESSIONid,SESSIONid会以Set-Cookie的形式被传给客户端。在服务端储存SESSION的文件夹下会生成对应的SESSION文件,其文件名由sess_加上PHPSESSID的值构成,但并不是所有的PHPSESSID的值都是可行的,其值只能包含a-z、A-Z、0-9、_来构成,所以若需要构造一个特定的PHPSESSID时需要在此范围内构造。

第一次访问(未携带SESSIONid)。

 访问其他开启会话的页面只要携带了正确的SESSIONid,便能通用对应SESSION中的数据。

持有同一SESSIONid,便能在站点下开启会话的不同页面使用同一SESSION中数据。

[清空session]

可以使用session_destroy函数清空当前SESSION中的数据(本次会话中仍有效,但下次会话则被清空),服务端设置了SESSION超时清除时SESSION在过期后会被清除。

 清除SESSION中数据,但本次在会话仍有效。

访问其他页面,此时SESSION已被清除。

[储存/读取session的引擎]

SESSION中的数据在会话开始后从对应SESSION文件中读入存到内存($_SESSION全局变量中),在会话结束后以特定格式存入文件(可在php.ini中设置SESSION存储的位置等设置)。这个文件和内存中的变量互相转化的过程由PHP设定引擎实现(类似于序列化),共有三种引擎,其特征如下图(从其他师傅文章中看到的lemon师傅的表,这里引用下)。

可以直接使用ini_set函数设置"session.serialize_handler"对应值来设置当前页面的SESSION处理引擎,写入文件数据的格式取决于当前页面设置的引擎种类。


 [补][2021-06-02 19:07:44]  session_start函数可以传入会话相关设置组成的数组,这样就可配置当前会话的相关设置。

 即ini_set("session.serialize_handler","php")也可这样对session_start函数传参替代:session_start(["serialize_handler"=>"php"])。

值得注意的是,当前页面会话设置采用的是第一次会话开启时的配置,此后的修改并不会成功。

如先执行session_start(['serialize_handler'=>'php_serialize'])会话会采用php_serialize引擎,但若接着执行session_start(['serialize_handler'=>'php']),会话也并不会切换为php引擎。


 

php引擎(大部分服务端的默认引擎)

php_binary引擎

php_serialize引擎

关于漏洞

漏洞的成因在于SESSION储存和读取时使用了不同的引擎,在这种情况下使用符合特定语句的数据会得到与原本不符的数据。由php_binary引擎或php_serialize引擎执行SESSION的储存(或者可控服务端SESSION文件,自定义构造合适的文件内容),由php引擎执行SESSION的读取时存在可触发的特殊语句。

原因在于php引擎执行读取时会将数据中符号"|"前视为一个变量名的信息,后视为变量值的信息来解析。我们在可以输入可控时,可以控制符号"|"的位置来控制SESSION在读取时获取的值。

这里以php_serialize引擎储存,php引擎读取来做演示(php_binary引擎储存,php引擎读取时同理)。

在正常使用时,变量string的序列化后字符串(以下称目标字符串)只会作为SESSION中的一变量对应的值;但使用漏洞时(在目标字符串前插入符号"|"),php引擎会按照其规则将目标字符串前的所有字符前视为一个变量名的信息,而目标字符串被视为改变量对应的值来解析,多余的无法解析部分则会被忽略。

 

这就意味着在符合条件下,我们可以通过在传入SESSION中的数据合理地插入符号"|"的方法来控制下次SESSION读取后获得值,这样我们传入SESSION的序列化后的字符串最终将会被解析为有效变量而不是一条字符串。

如果想要使用该漏洞,应该满足以下这些条件:

  • 数据存入SESSION处的引擎为php_serialize或php_binary
  • SESSION读取数据处的引擎为php(默认情况下均为php)
  • 数据是有效的序列化后字符串
  • 数据中合理地插入符号"|"(不影响之后SESSION读取数据)

实际上只要符合规范的储存信息都会被解析,多余的无法解析的部分均会被自动忽略。

 

 使用此SESSION后(再次载入该SESSION内容),SESSION中红框的多余部分并不会被解析(但需要保证多余部分不含有符号|,否则会导致解析报错)。

写在最后 

希望该博客对各位师傅有所帮助,如果发现了文中的不对之处,希望各位师傅斧正,参考文章如下:

posted @ 2021-06-01 15:46  Article_kelp  阅读(203)  评论(0编辑  收藏  举报