什么是SESSION?
官方Session定义:在计算机中,尤其是在网络应用中,称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。主要有以下特点:
session保存的位置是在服务器端
session通常是要配合cookie使用
因为HTTP的无状态性,服务端产生了session来标识当前的用户状态
本质上,session就是一种可以维持服务器端的数据存储技术。即session技术就是一种基于后端有别于数据库的临时存储数据的技术
SESSION的工作流程
以PHP为例,理解session的原理
PHP脚本使用 session_start()时开启session会话,会自动检测PHPSESSID
如果Cookie中存在,获取PHPSESSID
如果Cookie中不存在,创建一个PHPSESSID,并通过响应头以Cookie形式保存到浏览器
初始化超全局变量$_SESSION为一个空数组
PHP通过PHPSESSID去指定位置(PHPSESSID文件存储位置)匹配对应的文件
存在该文件:读取文件内容(通过反序列化方式),将数据存储到$_SESSION中
不存在该文件: session_start()创建一个PHPSESSID命名文件
程序执行结束,将$_SESSION中保存的所有数据序列化存储到PHPSESSID对应的文件中
PHP session序列化机制
根据php.ini中的配置项,我们研究将$_SESSION中保存的所有数据序列化存储到PHPSESSID对应的文件中,使用的三种不同的处理格式,即session.serialize_handler定义的三种引擎:
处理器 | 对应的存储格式 |
---|---|
php(默认的) | 键名 + 竖线 + 经过 serialize() 函数反序列处理的值 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值 |
php_serialize (php>=5.5.4) | 经过 serialize() 函数反序列处理的数组 |
php处理器
首先来看看默认session.serialize_handler = php时候的序列化结果,代码如下
1 |
|
文件名为sess_mpnnbont606f50eb178na451od,其中mpnnbont606f50eb178na451od就是后续请求头中Cookie携带的PHPSESSID的值
2、文件内容
php处理器存储格式
键名 | 竖线 | 经过 serialize() 函数反序列处理的值 |
---|---|---|
$_SESSION[‘name’]的键名:name | | | s:6:“harden”; |
php_binary处理器
使用php_binary处理器(二进制),即session.serialize_handler = php_binary
1 |
|
文件名都一样
键名的长度对应的 ASCII 字符 | 键名 | 经过 serialize() 函数反序列处理的值. |
---|---|---|
$ | namenamenamenamenamenamenamenamename | s:6:“harden”; |
php_serialize 处理器
使用php_binary处理器,即session.serialize_handler = php_serialize
1 |
|
文件内容为反序列化后的数组:a:1:{s:4:”name”;s:6:”harden”;}
小结
以上三种为php序列化的机制,如果一个服务器有2个页面用的是不同的处理器很可能就会出现SESSION反序列化漏洞,这个现在先不谈,我还没做到那里
php中的seesion_upload_progress
php.ini下有这些默认选项
- session.upload_progress.enabled = on
- session.upload_progress.cleanup = on
- session.upload_progress.prefix = “upload_progress_”
- session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS”
- session.upload_progress.freq = “1%”
- session.upload_progress.min_freq = “1”
- enabled=on表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;
- cleanup=on表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要;
- name:当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控;
- prefix+name将表示为session中的键名
- session.upload_progress.prefix就是字符串”upload_progress_”
prefix+name
当我们POST传入session.upload_progess.name的同名变量(即别称)PHP_SESSION_UPLOAD_PROGRESS时,$_SESSION数组内会新增一个键值对
- 键名:就是session.upload_progress.prefix+session.upload_progress.name
这里的session.upload_progress.name也就是PHP_SESSION_UPLOAD_PROGRESS的值
- 键值:接下来引入一个例子来讲
案例一
在这我们写一个提交文件的代码:
1 | <form action="upload.php" method="POST" enctype="multipart/form-data"> |
这里面可以直接换成 PHP_SESSION_UPLOAD_PROGRESS
- ini_get:就是获取php.ini里面参数的值
这里的123就是PHP_SESSION_UPLOAD_PROGRESS的值
上传后我们$_SESSION数组中就会多出以下的键值对:
1 |
|
这里的键名”upload_progress_123”,就是由我们上面所说的session.upload_progress.prefix+session.upload_progress.name
其中session.upload_progress.prefix是upload_progress_,session.upload_progress.name是123
这时候session文件的内容里面会有一段
1 | a:1:{s:19:"upload_progress_123";........................等等 |
这里面我们的可控参数就是session.upload_progress.name:123了,我们可以通过修改session.upload_progress.name的值来达到命令执行的目的
这里需要补充一下,写入session文件的是序列化后的内容
总结:
- 注入点1:session.upload_progress.name
- 注入点2:name
- 注入点3:field_name
以上这三个都是我们可以控制的值,我们只需要把这三个地方修改成需要执行的代码即可
利用session_upload_progress进行文件包含
原理
原理就和上面讲的一样,session.upload_progess.name给我们提供了注入点
当网页的代码中有文件包含函数require(),require_once(),include(),include_once()时,我们就可以包含文件,执行内部的代码,达到我们的目的
条件竞争
所谓的条件竞争
- 第一种:当函数体内有判断函数时,判断正确与否是需要时间的,我们通过脚本用多个线程运行这段代码,我们线程越多速度就越快,可以从判断这段时间间隙中去绕过判断
- 第二种:session文件一般默认是执行完函数就删除内容的,但是删除内容也需要时间,和第一种一样,用多个线程跑,利用时间差去把数据存入session文件
CTFSHOW——Web82
1 | if(isset($_GET['file'])){ |
这一题一不能使用2个伪协议,二不能使用包含日志文件,这一题考虑条件竞争
- 首先我们明确第一点,session文件的名字我们是可以控制的
如果我们COOKIE传入PHPSESSID=aaaa,那么对应的session文件的名字就是sess_aaaa
所以第一点,文件名称可以控制
其次文件的内容我们可以通过上面的介绍可以知道,我们用SESSION_UPLOAD_PROGRESS可以把内容写进session文件里
最后写入了文件内容,我们需要include包含这个文件,达到命令执行的效果
方法一:
首先利用一个小脚本去给这个靶场上传一个文件,然后进行抓包,
脚本:
1 |
|
抓包:
可以看到已经有PHP_SESSION_UPLOAD_PROGRESS的值,我们把他的值改为我们要执行的代码,如
再加入一个COOKIE:PHPSESSID=flag
然后我们再抓一个get类型的包,就是在靶场get传入一个file=1:
然后把file改成file=/tmp/sess_flag,然后POST和GET同时进行爆破,这边我选了10线程
ps:这边爆破就是随便找个没用的内容,然后往里面爆破字典达到多次跑代码的效果
结果:
诺,可以看到已经出来答案了,fl0g.php和index.php就是当前目录的文件
分析一下
我们来看看回显这一段代码:
1 | upload_progress_fl0g.php |
看到没,这边就是序列化后的内容,和我们上面说的一样,会给$_SESSION数组带来一个由session.upload_progress.prefix+session.upload_progress.name组成的键名
- 这边session.upload_progress.prefix=’upload_progress_’
- session.upload_progress.name=fl0g.phpindex.php|a:5{s:10:”start_time”;i:1662324411;s:14:”content_length”;i:455;s:15:”bytes_processed”;i:455;s:4:”done”;b:0;s:5:”files”;a:1:{i:0;a:7:{s:10:”field_name”;s:4:”file”;s:4:”name”;s:5:”1.php”;s:8:”tmp_name”;N;s:5:”error”;i:0;s:4:”done”;b:0;s:10:”start_time”;i:1662324411;s:15:”bytes_processed”;i:455;}}}
这边为什么session.upload_progress.name不是system(‘ls’)呢,这是因为system(‘ls’)被执行了,所以它的回显替代了它
结果
用同样的方法,我们把system(‘ls’)改为system(tac fl0g.php),读取flag文件:
1 | Content-Length: 592 |
还是完全符合上述规则的!
方法二:
这边我们原理还是一样的,只不过是用python脚本去跑一下了,都有注释:
1 | import requests |
出现perfect值的时候就说明已经将内容写入了1.php,这时候直接访问1.php,进行远程rce,这边rce的时候,py脚本一定要保持运行,进行条件竞争:
得到结果
浅分析
这里利用的思路是在session文件中写入<?php eval($_POST[1]);?>
然后再POST传递参数1:=file_put_contents(‘/var/www/html/1.php’,’‘);
将<?php eval($_POST[2]);?>
写入1.php中
最后访问1.php去进行远程RCE,密码就是2
这就是整体的思路
About this Post
This post is written by Boogipop, licensed under CC BY-NC 4.0.