bloodhound技术分析

攻击者可以使用BloodHound轻松识别高度复杂的攻击路径。防御者可以使用它来识别和消除那些相同的攻击路径。蓝队和红队都可以使用BloodHound轻松深入了解Active Directory环境中的权限关系。

BloodHound分为两部分,一部分是收集器,通过不同的API调用收集图形所需的信息;另一部分是将集合的信息导入Neo4j数据库中,进行展示分析。

工具使用

环境:

kali 2019 vm虚拟机

安装:

apt-get install bloodhound

安装完毕后启动Neo4j和bloodhound

neo4j console

bloodhound

访问http://localhost:7474修改初始密码,然后登陆bloodhound。

联动cobaltstrike

官方地址:https://github.com/BloodHoundAD/BloodHound

cs插件:https://github.com/C0axx/AggressorScripts

也可以通过sharpbound.exe和ps1单独使用(execute-assembly、powershell-import)。

导入插件,快速导出并下载bloodhound的json文件。

选择任意可用选项

使用powerpick

在view–>download找到下载的文件

导入zip后就可以通过各种关系寻找路线攻击域管或者某台特定机器

寻找攻击域管的最短路径:

由于本地搭建的域环境没有过多的关联信息,所以不能很好看出各台机器的关系逻辑。

其他环境效果图:

查找具有DCSync权限的主体

通过使用该工具,可以在攻击内网的时候有一个明确的路线,大致了解哪些机器及用户的权限是存在价值的,还可以通过当前所获取的权限去判断我们下一步应该去攻击哪些机器,当前的权限是否又与哪些机器有着session交互。

###使用技巧

如果你不想过多了解bloodhound对运行机制,不需要自己编写语法查询,只用bloodhound提供的默认ui显示。那么只需要认识几个图形所代表的意义及一些使用技巧:

1
2
3
4
5
6
7
绿色用户头像:用户

三个黄色头像:用户组

红色小电脑:计算机

绿色小地球:域

默认提供的ui查询

1
2
3
4
5
6
7
8
9
10
11
12
Find all Domain Admins                                查找所有域管理员
Find Shortest Paths to Domain Admins 查找域管理员的最短路径
Find Principals with DCSync Rights 查找具有DCSync权限的主体
Users with Foreign Domain Group Membership 具有外域组成员身份的用户
Groups with Foreign Domain Group Membership 具有外域组成员身份的组
Map Domain Trusts 域信任映射图
Shortest Paths to Unconstrained Delegation Systems 不受约束的委派系统的最短路径
Shortest Paths from Kerberoastable Users 来自Kerberoastable用户的最短路径
Shortest Paths to Domain Admins from Kerberoastable Users 可通过Kerberoastable用户访问域管理员的最短路径
Shortest Path from Owned Principals 已拥有权限最短路径
Shortest Paths to Domain Admins from Owned Principals 已拥有权限到域管理员的最短路径
Shortest Paths to High Value Targets 高价值目标的最短路径

两个关系中的联系解读

鼠标移动到路径并出现闪光时,右键–>HELP。

出现该关系的释义

通过进行关系关联:

如下图,通过点击企业管理员组将会利用闪光列出3条相关联的信息路径。

以高价值目标的最短路径举例进行分析

当选择高价值目标的最短路径UI进行分析(选择其他UI使用方式一样),可以重点关注用户(绿色头像)、计算机(红色小电脑)。当用鼠标指向这两个单位时,会以高亮显示体现出具体的攻击路径。简而言之:

1.对高亮攻击路径中的所有用户分析

2.对高亮攻击路径中的所有计算机进行分析

找到关键计算机或者关键用户时,可以右键设置最短路径到此处,查询可以获取该用户权限的路径信息。

发现从exchange计算机可以获取该用户权限

如同这样,可以遍历默认提供的ui查询中,获取到关键的用户、计算机以及获取它们的相关路径,可以很好的辅助内网渗透中的横向、纵向权限获取。

路径设置

右键设置起点终点,或者在左上角填上起点终点

目标选择和API使用

收集器有几个独立的步骤,它们同时运行以收集图形所需的不同数据。整体细分分为几类:本地管理员,组成员关系,会话,对象属性,ACL和域信任。

本地管理员

本地管理员集合使用两种不同的方法完成,具体取决于是否指定了隐秘选项。没有隐秘选项的本地管理员收集将首先查询Active Directory以获取计算机列表。计算机列表将传递给枚举运行程序,该运行程序将连接到每台计算机并执行以下操作:

  • 在端口445上执行TCP连接以检查主机是否处于活动状态
  • 如果主机处于活动状态,则执行NetLocalGroupGetMembers NETAPI32.aspx) API。
  • 从NetLocalGroupGetMembers调用返回的数据并将SID解析为实际用户,并过滤掉本地帐户。

如果指定了隐秘集合,则SharpHound将向域控制器查询所有组策略容器对象及其对应的gpcfilesyspath属性的列表,该属性指示实际组策略文件在域控制器$ SYSVOL目录中的位置。将枚举每个组策略文件,查找模式S-1-5-32-544__Members,它指示本地Administrators组。处理文件以确定应用这些GPO的计算机。

语法

1
2
3
4
5
6
7
8
9
10
NET_API_STATUS NET_API_FUNCTION NetLocalGroupGetMembers(
LPCWSTR servername,
LPCWSTR localgroupname,
DWORD level,
LPBYTE *bufptr,
DWORD prefmaxlen,
LPDWORD entriesread,
LPDWORD totalentries,
PDWORD_PTR resumehandle
);

参数

servername

指向常量字符串的指针,该字符串指定要在其上执行函数的远程服务器的DNS或NetBIOS名称。如果此参数为NULL,则使用本地计算机。

localgroupname

指向常量字符串的指针,该字符串指定要列出其成员的本地组的名称。有关更多信息,请参阅以下备注部分。

level

指定数据的信息级别。此参数可以是以下值之一。

含义
0 返回与本地组成员关联的 安全标识符(SID)。所述bufptr参数指向的数组 LOCALGROUP_MEMBERS_INFO_0结构。
1 返回与本地组成员关联的SID和帐户信息。所述bufptr参数指向的数组 LOCALGROUP_MEMBERS_INFO_1结构。
2 返回与本地组成员关联的SID,帐户信息和域名。所述bufptr参数指向的数组 LOCALGROUP_MEMBERS_INFO_2结构。
3 返回本地组成员的帐户和域名。所述bufptr参数指向的数组 LOCALGROUP_MEMBERS_INFO_3结构。

NetLocalGroupGetMembers sample code,枚举用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static class NetworkAPI
{
[DllImport("Netapi32.dll")]
public extern static int NetLocalGroupGetMembers([MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string localgroupname, int level, out IntPtr bufptr, int prefmaxlen, out int entriesread, out int totalentries, out int resumehandle);

[DllImport("Netapi32.dll")]
public extern static int NetApiBufferFree(IntPtr Buffer);

// LOCALGROUP_MEMBERS_INFO_1 - Structure for holding members details
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct LOCALGROUP_MEMBERS_INFO_1
{
public int lgrmi1_sid;
public int lgrmi1_sidusage;
public string lgrmi1_name;
}
}
static void Main(string[] args)
{
int EntriesRead;
int TotalEntries;
int Resume;
IntPtr bufPtr;

string groupName = "Administrators";

NetworkAPI.NetLocalGroupGetMembers(null, groupName, 1, out bufPtr, -1, out EntriesRead, out TotalEntries, out Resume);

if (EntriesRead > 0)
{
NetworkAPI.LOCALGROUP_MEMBERS_INFO_1[] Members = new NetworkAPI.LOCALGROUP_MEMBERS_INFO_1[EntriesRead];
IntPtr iter = bufPtr;
for (int i = 0; i < EntriesRead; i++)
{
Members[i] = (NetworkAPI.LOCALGROUP_MEMBERS_INFO_1)Marshal.PtrToStructure(iter, typeof(NetworkAPI.LOCALGROUP_MEMBERS_INFO_1));
iter = (IntPtr)((int)iter + Marshal.SizeOf(typeof(NetworkAPI.LOCALGROUP_MEMBERS_INFO_1)));
Console.WriteLine(Members[i].lgrmi1_name);
}
NetworkAPI.NetApiBufferFree(bufPtr);
}
}

NetLocalGroupGetMembers函数检索安全数据库中特定的本地组,它从安全帐户管理器(SAM)数据库,或在域控制器上(在Active Directory)枚举成员名单。本地组成员可以是用户或全局组。也就是说,在非域控的机器下枚举只能是本地组用户,在域控下运行可以枚举本地组或者全局组。(需要在域内使用,如果是工作组,无法获取远程计算机的成员组用户,只能获取本地成员组用户)

通过遍历DNS或者NetBIOS名称便可以获取所有机器的特定本地组的用户名

扩展思路

1.拿到的用户名可以作为爆破使用。

2.确认域管、域控机器。

会话

无论是否指定隐秘,会话集合都是相同的。BloodHound公开了两种不同的查询计算机会话信息的方法。两种方法都从检查端口445开始,然后发散。

默认会话

会话收集的默认方法使用NetSessionEnum NETAPI32.aspx)调用。它不允许直接查询系统以询问谁登录。相反,它允许查询系统以确定为该系统建立的网络会话以及从何处访问。在访问网络资源(例如文件共享)时创建网络会话。

该函数提供有关在服务器上建立的会话的信息。

语法

1
2
3
4
5
6
7
8
9
10
11
NET_API_STATUS NET_API_FUNCTION NetSessionEnum(
LMSTR servername,
LMSTR UncClientName,
LMSTR username,
DWORD level,
LPBYTE *bufptr,
DWORD prefmaxlen,
LPDWORD entriesread,
LPDWORD totalentries,
LPDWORD resume_handle
);

参数

servername

指向字符串的指针,该字符串指定要在其上执行函数的远程服务器的DNS或NetBIOS名称。如果此参数为NULL,则使用本地计算机。

UncClientName

指向字符串的指针,该字符串指定要为其返回信息的计算机会话的名称。如果此参数为NULL,则 NetSessionEnum将返回服务器上所有计算机会话的信息。

username

指向字符串的指针,该字符串指定要为其返回信息的用户的名称。如果此参数为NULL,则 NetSessionEnum将返回所有用户的信息。

level

指定数据的信息级别。此参数可以是以下值之一。

含义
0 返回建立会话的计算机的名称。所述bufptr参数指向的数组 SESSION_INFO_0结构。
1 返回计算机的名称,用户的名称以及打开计算机上的文件,管道和设备。所述bufptr 参数指向的数组 SESSION_INFO_1结构。
2 除了为级别1指示的信息之外,还返回客户端的类型以及用户如何建立会话。所述bufptr参数指向的数组 SESSION_INFO_2结构。
10 返回计算机的名称,用户的名称以及会话的活动和空闲时间。所述bufptr参数指向的数组 SESSION_INFO_10结构。
502 返回计算机的名称; 用户名; 打开计算机上的文件,管道和设备; 以及客户端正在使用的传输的名称。所述bufptr参数指向的数组 SESSION_INFO_502结构。

例如,API调用可以像这样运行:

1
NetSessionEnum("primary.testlab.local", null, null, 10, out IntPtr ptrInfo, -1, out int EntriesRead, out int _, ref resumeHandle);

API调用的第四个参数是API调用的级别,其中10是唯一以未经身份验证的方式为BloodHound提供所需数据的级别。(级别1、2需要administrators或者Server Operators本地组的成员身份)。因此,使用该函数时不需要高权限。

对具有已安装的共享驱动器的域控制器运行此操作将显示类似于以下的结果:

1
2
3
4
sesi10_cname - 192.168.1.10
sesi10_username - rvazarkar
sesi10_time - 0
sesi10_idle_time - 0

该sesi10_cname参数指示,其中会话是从哪里来的,所以主机名可以让我们的会话到远程主机相关解析这个IP地址。这通常是您可能看不到已知存在的登录会话的原因。如果外部网络会话不存在,则SharpHound的未经身份验证的收集方法无法枚举此数据。同样重要的是要注意,username参数没有与之关联的域,这意味着会话信息包含涉及猜测的元素。SharpHound使用全局编录来尝试消除冲突用户的冲突,并确定哪个域是正确的域,但不能保证它会选择正确的域。

session的获取主要通过域控及共享

NetSessionEnum sample code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static SESSION_INFO_10[] EnumSessions(string server)
{
IntPtr BufPtr;
int res = 0;
Int32 er = 0, tr = 0, resume = 0;
BufPtr = (IntPtr)Marshal.SizeOf(typeof(SESSION_INFO_10));
SESSION_INFO_10[] results = new SESSION_INFO_10[0];
do
{
res = NetSessionEnum(server, null, null, 10, out BufPtr, -1, ref er, ref tr, ref resume);
results = new SESSION_INFO_10[er];
if (res == (int)NERR.ERROR_MORE_DATA || res == (int)NERR.NERR_Success)
{
Int32 p = BufPtr.ToInt32();
for (int i = 0; i < er; i++)
{

SESSION_INFO_10 si = (SESSION_INFO_10)Marshal.PtrToStructure(new IntPtr(p), typeof(SESSION_INFO_10));
results[i] = si;
p += Marshal.SizeOf(typeof(SESSION_INFO_10));
}
}
Marshal.FreeHGlobal(BufPtr);
}
while (res == (int)NERR.ERROR_MORE_DATA);
return results;
}

扩展思路

1.通过服务器建立session信息搜集IP资产

LoggedOn会话

LoggedOn收集方法是通过询问是谁在对系统实际登录的计算机返回会话信息的更精确的收集方法。需要注意的是,此级别的集合需要您要从中收集数据的主机的管理权限。此会话集非常适合防御者,或在获得域管理员后进行其他数据收集。该LoggedOn收集方法采用两种不同的方法来收集数据。第一种方法是使用NetWkstaUserEnum NETAPI32.aspx) API调用。

该函数可以列出当前登录到该工作站的所有用户的信息。此列表包括交互式,服务和批量登录。

语法

1
2
3
4
5
6
7
8
9
10
11
NET_API_STATUS NET_API_FUNCTION NetSessionEnum(
LMSTR servername,
LMSTR UncClientName,
LMSTR username,
DWORD level,
LPBYTE *bufptr,
DWORD prefmaxlen,
LPDWORD entriesread,
LPDWORD totalentries,
LPDWORD resume_handle
);

参数

servername

指向字符串的指针,该字符串指定要在其上执行函数的远程服务器的DNS或NetBIOS名称。如果此参数为NULL,则使用本地计算机。

UncClientName

指向字符串的指针,该字符串指定要为其返回信息的计算机会话的名称。如果此参数为NULL,则 NetSessionEnum将返回服务器上所有计算机会话的信息。

username

指向字符串的指针,该字符串指定要为其返回信息的用户的名称。如果此参数为NULL,则 NetSessionEnum将返回所有用户的信息。

level

指定数据的信息级别。此参数可以是以下值之一。

含义
0 返回建立会话的计算机的名称。所述bufptr参数指向的数组 SESSION_INFO_0结构。
1 返回计算机的名称,用户的名称以及打开计算机上的文件,管道和设备。所述bufptr 参数指向的数组 SESSION_INFO_1结构。
2 除了为级别1指示的信息之外,还返回客户端的类型以及用户如何建立会话。所述bufptr参数指向的数组 SESSION_INFO_2结构。
10 返回计算机的名称,用户的名称以及会话的活动和空闲时间。所述bufptr参数指向的数组 SESSION_INFO_10结构。
502 返回计算机的名称; 用户名; 打开计算机上的文件,管道和设备; 以及客户端正在使用的传输的名称。所述bufptr参数指向的数组 SESSION_INFO_502结构。

此API调用的示例如下:

1
NetWkstaUserEnum("primary.testlab.local", 1, out IntPtr intPtr, -1, out int entriesRead, out int _, ref resumeHandle);

API调用的第二个参数是API调用的级别,其中1个返回的数据多于0。对系统执行此操作会产生如下数据:

1
2
3
4
wkui1_username - rvazarkar
wkui1_logon_domain - TESTLAB
wkui1_oth_domains -
wkui1_logon_server - PRIMARY

NetWkstaUserEnum sample code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static WKSTA_USER_INFO_1[] EnumWkstaUser(string server)
{
IntPtr Bufptr;
int nStatus = 0;
Int32 dwEntriesread = 0, dwTotalentries = 0, dwResumehandle = 0;

Bufptr = (IntPtr)Marshal.SizeOf(typeof(WKSTA_USER_INFO_1));
WKSTA_USER_INFO_1[] results = new WKSTA_USER_INFO_1[0];
do
{
nStatus = NetWkstaUserEnum(server, 1, out Bufptr, 32768, out dwEntriesread, out dwTotalentries, ref dwResumehandle);
results = new WKSTA_USER_INFO_1[dwEntriesread];
if ((nStatus == NERR_SUCCESS) || (nStatus == ERROR_MORE_DATA))
{
if (dwEntriesread > 0)
{
IntPtr pstruct = Bufptr;
for (int i = 0; i < dwEntriesread; i++)
{
WKSTA_USER_INFO_1 wui1 = (WKSTA_USER_INFO_1)Marshal.PtrToStructure(pstruct, typeof(WKSTA_USER_INFO_1));
results[i] = wui1;
pstruct = (IntPtr)((int)pstruct + Marshal.SizeOf(typeof(WKSTA_USER_INFO_1)));
}
}
else
{
//Console.WriteLine("A system error has occurred : " + nStatus);
}
}

if (Bufptr != IntPtr.Zero)
NetApiBufferFree(Bufptr);

} while (nStatus == ERROR_MORE_DATA);
return results;
}

LoggedOn集合方法使用的辅助枚举方法是使用远程注册表。SharpHound将尝试打开远程注册表的用户配置单元(如果已启用),并将查找与 SID 格式匹配的子项,这些对应于登录用户将获取的 SID 转换成用户名即可。一般来说,需要域管权限去操作,而在极少数情况下,无需管理员权限即可访问此数据 。

Reg sample code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static IEnumerable<string> GetRegistryLoggedOn(string server)
{
var users = new List<string>();
try
{
// 远程打开注册表配置单元,如果它不是我们当前的配置单元
RegistryKey key = RegistryKey.OpenRemoteBaseKey(RegistryHive.Users, server);
// 找到与我们的正则表达式匹配的所有子项
var filtered = key.GetSubKeyNames().Where(sub => SidRegex.IsMatch(sub));
foreach (var subkey in filtered)
{
users.Add(subkey);
}
}
catch (Exception)
{
yield break;
}
foreach (var user in users.Distinct())
{
yield return user;
}
}

组成员关系

所有域组成员资格集合都通过LDAP完成。SharpHound将向域控制器询问域中每个组,用户和计算机对象的列表,并使用MemberOf属性来解析组成员身份。组成员关系集合不需要触及域控制器以外的任何系统。

ACL

所有AD对象ACL集合都是通过LDAP完成的。SharpHound将向域控制器询问域中每个用户,组,计算机和域对象的列表,并使用NTSecurityDescriptor属性来解析访问控制列表。ACL集合不需要触及域控制器以外的任何系统。

域信任关系

信任收集使用DsEnumerateDomainTrusts NETAPI32.aspx) API调用执行。运行此查询的示例:

1
DsEnumerateDomainTrusts("testlab.local", 63, out IntPtr ptr, out int domainCount);

第二个参数是一组标志,用于指定要返回的信任类型。63对应于所有可能的标志:

  • DS_DOMAIN_IN_FOREST
  • DS_DOMAIN_DIRECT_OUTBOUND
  • DS_DOMAIN_TREE_ROOT
  • DS_DOMAIN_PRIMARY
  • DS_DOMAIN_NATIVE_MODE
  • DS_DOMAIN_DIRECT_INBOUND

这将返回所有可能的域类型

对象属性

所有属性集合都通过LDAP完成。SharpHound将向域控制器询问域中每个用户和计算机对象的列表,并为每个对象请求几个不同的属性。

对于用户对象,使用以下属性:

  • SamAccountName
  • DistinguishedName
  • SaMAccountType
  • PwdLastSet
  • LastLogon
  • SidHistory
  • UserAccountControl
  • Mail
  • ObjectSid
  • ServicePrincipalName
  • DisplayName

对于计算机对象,使用以下属性:

  • SaMAccountName
  • DistinguishedName
  • SaMAccountType
  • ObjectSid
  • UserAccountControl
  • DNSHostName
  • OperatingSystemServicePack
  • OperatingSystem

每种收集方法的目标是什么系统?

SharpHound根据提供的标记显着改变目标选择。SharpHound使用的默认收集方法非常粗暴,触及可到达的域上的每个系统。使用隐形标志可显着降低目标系统的数量。

本地管理员 - 非隐秘

没有隐秘标志的本地管理员集合将覆盖每个可到达的域计算机以收集数据。这提供了可靠和准确的结果。

本地管理员 - 隐秘

具有隐秘标志的本地管理员集合完全依赖于组策略设置,并且不需要触摸除SYSVOL文件夹中包含相关文件的域控制器之外的任何系统。隐秘收集返回的数据质量因域而异,因为某些域不使用GPO来管理本地管理员设置,而其他域则仅使用它。GPO不会反映所做的本地更改,因此使用此方法将无法找到添加到本地管理员组的其他原则。

会话 - 非隐秘

没有隐秘标志的会话集合将覆盖每个可到达的域计算机以收集数据。

会话 - 隐秘

具有隐秘标志的会话集合极大地限制了用于收集的系统的数量。SharpHound将使用LDAP中的UserAccountControl属性将所有标记为域控制器的计算机作为目标。还将请求具有任何HomeDirectoryScriptPathProfilePath属性集的所有Active Directory对象的列表。从这些属性创建一组唯一的服务器名称,以标识会话收集的其他目标。平均而言,隐形会话收集将收集域中约50-60%的会话信息。这可能会因域的结构而异,但大多数网络会话通常指向域控制器或文件服务器。

组- 隐秘与非隐秘

组集合仅需要与域控制器通信以请求LDAP数据。

ACL - 隐秘和非隐秘

ACL集合仅需要与域控制器通信以请求LDAP数据。

信任关系 - 隐秘和非隐秘

信任收集需要与映射的每个域中的一个域控制器进行通信。

对象属性 - 隐秘和非隐秘

对象属性集合需要与域控制器通信以请求LDAP数据。

技术总结

Cheat Sheets

Collection Method API Call Default Targets Stealth Targets
Session NetSessionEnum.aspx) All Computers Domain Controllers + “Share Servers”
LocalGroup Modified NetLocalGroupGetMembers.aspx) All Computers GPO Files
Group Ldap All User,Group, and Computer Objects All User,Group, and Computer Objects
Trusts DsEnumerateDomainTrusts NETAPI32.aspx) All Domain and TrustedDomain objects All Domain and TrustedDomain objects
LoggedOn NetWkstaUserEnum NETAPI32.aspx) + Remote Registry All Computers Domain Controllers + “Share Servers”
ACL Ldap All user, group, computer, and domain objects All user, group, computer, and domain objects
ObjectProps Ldap All user and computer objects All user and computer objects
API Call Protocol Port RPC Interface UUID Named Pipe RPC Method
NetSessionEnum.aspx) [MS-SRVS]: Server Service Remote Protocol TCP 445 4B324FC8-1670-01D3-1278-5A47BF6EE188 \PIPE\srvsvc NetrSessionEnum
NetWkstaUserEnum.aspx) [MS-WKST]: Workstation Service Remote Protocol TCP 445 6BFFD098-A112-3610-9833-46C3F87E345A \PIPE\wkssvc NetrWkstaUserEnum

参考

https://blog.cptjesus.com/posts/sharphoundtechnical

https://blog.cptjesus.com/posts/sharphoundtargeting

Author: rootrain
Link: https://rootrain.me/2020/02/29/bloodhound技术分析/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.