【翻译】BloodHound Active Directory Automation Pentest
使用 BloodHound CE 实现 Active Directory 渗透测试自动化
BloodHound 是每个渗透测试人员和红队成员必备的工具之一。随着 BloodHound CE 的发布,BloodHound 获得了一些非常好的实用性改进。尽管 BloodHound 最为人所知的是通过图形可视化攻击路径,但通过直接利用底层数据库可以收集到大量信息。本文将展示一些如何使用底层数据库或新的 API 来自动发现 Active Directory 环境中许多基本漏洞的示例。
该脚本已发布在我们的 Github 仓库 bloodhound-adAnalysis。如果您有任何问题或反馈,请随时与我联系。
简介
在几乎每次遇到 Active Directory (AD) 的渗透测试中,我们都会使用 BloodHound。它可以可视化复杂的 Active Directory 结构,找出可能的攻击路径,并对环境有一个很好的概览。八月初,新版本 BloodHound CE 发布,带来了一些新功能和显著的性能改进。一些不错的新增功能包括 API 和 docker 部署。另一个变化是不再将对象标记为"高价值",而是现在标记为"Tier Zero"。这是一个很好的改进,因为现在所有 Tier Zero 资产都在 GUI 中标记,使它们更容易识别,与 BloodHound Legacy 相比标记的资产也更多。Specter Ops 在这篇博客文章中将 Tier Zero 资产定义为所有对企业身份和其安全依赖项具有控制权的资产。由于这仍是一个早期访问版本,一些功能尚未实现,如导入自定义查询。对于一些缺失的功能,仍可以使用 BloodHound Legacy,例如将对象标记为已拥有或在转发 docker 中的 neo4j 数据库端口时清除数据库。
目前,我们正在致力于自动化在任务中经常发现的某些问题,如禁用的 SMB 签名或没有 LAPS 的计算机。在使用 BloodHound CE 时,我决定开始编写一个简单的 Python 脚本来自动化其中的一些发现。由于现在有四种与 BloodHound 交互的方式,我认为有必要对它们进行一下比较,并展示它们各自的用例。
BloodHound CE GUI / API
BloodHound CE GUI 在识别攻击路径或寻找有趣目标方面非常出色。它提供了所有 AD 对象及其之间关系的概览。每个对象都有大量可用信息并可以可视化,例如"用户可以 RDP 到哪些主机?用户有哪些对象控制权?"GUI 最大的优势是可以可视化较长的链条,并且能够轻松看到链条中每个关系如何被利用。BloodHound CE 现在在后台使用 API,也可以直接使用。设置非常简单,提供的python 脚本为使用 API 提供了很好的基础。API 也可以进行测试,并在 GUI 中有文档说明,这使得入门变得非常舒适。
Neo4j Web/Bolt 接口
访问 BloodHound 数据的另一种方式是直接通过 neo4j。数据无法像 GUI 那样可视化,但对于某些用例来说,基于文本的原始结果是我更喜欢的方式。此外,web 接口还提供了将数据导出为 csv 文件的选项,这在向客户提供受影响资源的信息时非常有用,特别是当资源数量众多时。我最喜欢使用 neo4j 的场景之一是浏览所有描述(是的,这是大量数据)。浏览 AD 描述可以揭示一些有趣的信息,例如主机的用途或公司内部使用的技术。这在 GUI 中并不太可行,因为需要单独访问每个对象。通过 neo4j(通过 web 或 bolt 接口)访问数据允许我们更舒适地检索某些信息,如使用 count() 获取结果数量,或者只获取特定属性,这些可以更容易地写入文件,例如用于密码喷洒的用户名。
实践示例
现在让我们进入使用 BloodHound CE 的有趣部分,看看我们如何实现一些自动化。
根据特定条件生成用户列表
许多工具都能为给定域生成用户列表,但使用 BloodHound CE API 或 neo4j 数据库有一个很大的优势:能够根据特定条件进行过滤。基于特定条件,我们可以过滤出最有趣的用户或可能产生最大成功率的用户。我们的脚本生成 4 个用户文件:
-
enabledUsers.txt -
enabledTierZeroUsers.txt -
enabledInactiveUsers.txt -
enabledPotentialAdminUsers.txt
enabledUsers.txt 将使用以下查询生成:
MATCH (u:User) WHERE u.enabled = true RETURN u.name
这将简单地过滤掉所有禁用的用户。通过过滤掉禁用的用户,我们可以大幅减少在下一次攻击(例如密码破解)中需要使用的用户数量。在最近的一次渗透测试中,这种方法使用户数量减少了超过 50%。enabledTierZeroUsers.txt 仅包含已启用的 Tier Zero 用户。
MATCH (u:User) WHERE u.enabled = true AND u.system_tags = 'admin_tier_0'RETURN u.name
由于新的 system_tags 属性,这个查询相当简单。它可以与 grep -f 结合使用,用来查看 Tier Zero 用户的密码是否已被成功破解。enabledInactiveUsers.txt 文件非常有趣,因为它包含了在过去 90 天内没有登录记录的已启用用户。在许多情况下,这意味着该用户已不再使用(例如员工已离职),但由于账户未被禁用,因此仍然可以使用。这些用户是密码攻击的理想目标,因为在大多数情况下锁定这些账户的风险要小得多。这个查询稍微复杂一些:
MATCH (u:User) WHERE u.enabled = true AND u.lastlogon < (datetime().epochseconds - (90 * 86400)) AND u.lastlogontimestamp < (datetime().epochseconds - (90 * 86400)) RETURN u.name
要检查用户是否可以被视为非活动状态,我们需要检查 lastlogon 和 lastlogontimestamp 这两个属性。这两个属性都包含最后一次登录的时间戳,但 lastlogon 是在数据收集期间查询的域控制器上的登录时间,而 lastlogontimestamp 是从所有其他域控制器复制的时间戳。在这种情况下,这两个值都必须低于设定的阈值:从运行查询时起的 90 天前。这可能会产生一个副作用,即如果在稍后再次执行查询,可能会返回不同的数据。
MATCH (u:User) WHERE (u.name =~ '(?i).*adm.*' OR u.description =~ '(?i).*admin.*') AND u.enabled = true RETURN u.name
enabledPotentialAdminUsers.txt 包含所有名称中包含子字符串 adm 的用户(这种命名方式经常用于管理员用户),或描述中包含单词 admin 的用户。这个文件应该包含一些潜在的有趣用户,这些用户不一定是 Tier Zero 用户,但很可能在某些系统上具有较高权限。所有展示的查询只能直接在 neo4j 中使用。虽然通过 API 实现这些查询是可能的,但在某些场景下需要额外的步骤。让我们以 kerberoasting 为例,比较一下 neo4j 和 API 的使用方式。
API 与 neo4j 数据库的对比:以 kerberoasting 为例
BloodHound 中用于查找可 kerberoast 用户的默认查询是:
MATCH (n:User) WHERE n.hasspn=trueRETURN n
这是一个非常简单的查询,但请注意返回的用户包括已禁用的用户和用户 krbtgt。我们可以使用以下 Python 代码通过 API 请求相同的数据:
response = client._request('POST', '/api/v2/graphs/cypher', bytes('{"query": "MATCH (n:User) WHERE n.hasspn=true RETURN n"}', 'ascii'))
响应是一些包含所有返回节点的 json 数据,具有以下信息:
-
label:节点名称 -
kind:节点类型,例如 User -
objectId:节点的对象 ID -
isTierZero:true 或 false -
lastSeen:令人惊讶的是,这不是用户的最后登录时间,而是数据摄取的日期;这可能是由于与 BloodHound Enterprise 共享代码库造成的
在我们当前的报告风格中,客户会收到一个包含所有可 kerberoast 用户的 csv 文件,其中包含通过以下针对 neo4j 数据库的查询生成的一些额外信息:
MATCH (n:User) WHERE n.hasspn=true AND n.samaccountname <> 'krbtgt' RETURN n.name, n.objectid, n.serviceprincipalnames, n.system_tags
通过 API,我们可以获取除 serviceprincipalnames (SPNs) 之外的所有相同信息。为了通过 API 获取 SPN,我们需要再次请求每个可 kerberoast 用户来检索此信息。Python 代码看起来会像这样:
response = client._request('POST', '/api/v2/graphs/cypher', bytes('{"query": "MATCH (n:User) WHERE n.hasspn=true RETURN n"}', 'ascii'))data = response.json()['data']for node in data['nodes']: oid = data['nodes'][node]['objectId'] responseUser = client._request('GET', f'/api/v2/users/{oid}') spns = responseUser.json()['data']['props']['serviceprincipalnames']
在用于自动化此发现的脚本中,使用了以下函数:
defcheckKerberoastableUsers(driver): print('===+++===+++===+++===+++===') print(' Checking Kerberoastable Users') print('===+++===+++===+++===+++===') q = "MATCH (n:User) WHERE n.hasspn=true AND n.samaccountname <> 'krbtgt' RETURN count(n) " kerberoastable, _, _ = driver.execute_query(q, database_="neo4j", routing_=RoutingControl.READ) q2 = "MATCH (n:User) WHERE n.hasspn=true AND n.samaccountname <> 'krbtgt' AND n.system_tags='admin_tier_0' RETURN count(n) " kerberoastableTierZero, _, _ = driver.execute_query(q2, database_="neo4j", routing_=RoutingControl.READ) print(f'There is a total of {kerberoastable[0]["count(n)"]} kerberoastable Users. This includes {kerberoastableTierZero[0]["count(n)"]} Tier Zero Accounts!')if kerberoastable[0]["count(n)"] > 0: print("Generating csv-file for: Affected Resources") q3 = "MATCH (n:User) WHERE n.hasspn=true AND n.samaccountname <> 'krbtgt' RETURN n.name, n.objectid, n.serviceprincipalnames, n.system_tags " kerberoastableData, _, _ = driver.execute_query(q3, database_="neo4j", routing_=RoutingControl.READ) writeCsvFile('kerberoastableUsers.csv', kerberoastableData)
该函数执行三个查询以收集以下信息:
-
所有可 kerberoast 用户的数量 -
所有 Tier Zero 可 kerberoast 用户的数量 -
所有可 kerberoast 用户的名称、对象 ID、SPN 和系统标签(Tier Zero)
如果我们发现可 kerberoast 的用户,我们还会为客户生成 csv 文件。在我们的版本中,我们还会为报告生成 PoC 和描述,但这里不包含这些内容。
筛选强制密码更改的目标
如果我们询问 BloodHound CE 如何滥用 GenericWrite 边缘,它会告诉我们三种可能的攻击:Targeted Kerberoast、Force Change Password 和 Shadow Credentials 攻击。根据具体情况,我们可能想要执行 Force Change Password 攻击,但不知道哪些用户可以安全地攻击,因为他们可能处于活动状态,而且我们可能会中断客户的生产环境。让我们使用 cypher 查询来检查哪些用户是此攻击的潜在候选者。在 BloodHound GUI 中,我们可以在节点的实体面板中看到所有出站对象控制,但如果数量太多且新的安全措施阻止绘制图形,我们该如何过滤或显示它们?用户 [email protected] 的相应 cypher 查询(并仅针对其他用户的出站控制进行过滤)是:
MATCH p=(u:User {name: '[email protected]'})-[r1:MemberOf*0..]->(g)-[r2]->(n:User) WHERE r2.isacl=trueRETURN p
好的,现在我们可以添加一些在其他查询中已经使用过的过滤条件来查找潜在目标:
-
n.enabled = true
因为我们无法使用已禁用的用户进行登录 -
u.lastlogon < (datetime().epochseconds - (90 * 86400)) AND u.lastlogontimestamp < (datetime().epochseconds - (90 * 86400))
因为我们想要找到一段时间内未登录的用户(这里是 90 天)
现在我们可以将所有条件组合起来,搜索强制密码更改攻击的最佳候选目标。
MATCH p=(u:User {name: '[email protected]'})-[r1:MemberOf*0..]->(g)-[r2]->(n:User) WHERE r2.isacl=true AND n.enabled = true AND u.lastlogon < (datetime().epochseconds - (90 * 86400)) AND u.lastlogontimestamp < (datetime().epochseconds - (90 * 86400))RETURN p
由于测试环境的 Active Directory 是生成的,因此没有登录数据,结果与上图相同。但在真实环境中,结果应该会更少。现在我们可以查看所有返回的用户,识别最有价值的目标,并在不太担心锁定用户账户的情况下更改他们的密码。
使用新 API 上传数据
新 API 的一个很好的用例是自动将收集的数据上传到 BloodHound。Python 中的基本函数可以是这样的:
defuploadData(client, dirToJson): postfix = ['_ous.json', '_gpos.json', '_containers.json', '_computers.json', '_groups.json', '_users.json', '_domains.json'] response = client._request('POST', '/api/v2/file-upload/start') uploadId = response.json()['data']['id']for file in postfix: filename = glob.glob(dirToJson + '/*' + file) print(f'Uploading: {filename}')with open(filename[0], 'r', encoding='utf-8-sig') as f: data = f.read().encode('utf-8') response = client._request('POST', f'/api/v2/file-upload/{uploadId}', data) response = client._request('POST', f'/api/v2/file-upload/{uploadId}/end') print('Waiting for BloodHound to ingest the data.') response = client._request('GET', '/api/v2/file-upload?skip=0&limit=10&sort_by=-id') status = response.json()['data'][0]whileTrue:if status['id'] == uploadId and status['status_message'] == "Complete":breakelse: time.sleep(15) response = client._request('GET', '/api/v2/file-upload?skip=0&limit=10&sort_by=-id') status = response.json()['data'][0] print('Done! Continuing now.')
dirToJson 变量是一个简单的字符串,包含了 json 文件的路径(不包含末尾的 /),例如 /customer/bloodhound。首先,我们必须使用 /api/v2/file-upload/start API 端点来创建一个新的文件上传任务。然后,我们将收集到的 json 文件上传到 /api/v2/file-upload/{file_upload_id},请求体中包含我们的 json 文件内容。所需的 file_upload_id 将在 /api/v2/file-upload/start 响应中返回。上传所有文件后,我们需要通知 BloodHound 上传已完成,数据可以被导入到数据库中。现在我们定期使用 API 端点 /api/v2/file-upload?skip=0&limit=10&sort_by=-id 并检查新创建的任务状态是否为 Completed。完成导入后,我们就可以开始分析数据了。
从已控用户到 Tier Zero 的最短路径
新的 Tier Zero 标签允许我们进一步扩展攻击路径搜索,但由于查询所需时间比如 到 Domain Admins 的最短路径 更长,这通常会导致超时。通过对 到高价值/Tier Zero 目标的最短路径 进行小幅修改,可以使用目标起始点运行此查询,并有望在超时前完成:
MATCH p=shortestPath((n {name: '[email protected]'})-[:Owns|GenericAll|GenericWrite|WriteOwner|WriteDacl|MemberOf|ForceChangePassword|AllExtendedRights|AddMember|HasSession|Contains|GPLink|AllowedToDelegate|TrustedBy|AllowedToAct|AdminTo|CanPSRemote|CanRDP|ExecuteDCOM|HasSIDHistory|AddSelf|DCSync|ReadLAPSPassword|ReadGMSAPassword|DumpSMSAPassword|SQLAdmin|AddAllowedToAct|WriteSPN|AddKeyCredentialLink|SyncLAPSPassword|WriteAccountRestrictions*1..]->(m))WHERE m.system_tags = "admin_tier_0" AND n<>mRETURN p
在这个例子中,我们将起始点设置为名为 [email protected] 的用户,但我们也可以选择计算机或组名称。如果我们在 BloodHound Legacy 中或使用 CrackMapExec 等其他工具将用户标记为已控,我们可以将 {name: '[email protected]'} 更改为 {owned: true},并同时从多个起始点进行查找。这可能会导致超时,但允许我们找到更多潜在的攻击路径。
自动化脚本
提供的脚本涵盖了我们在渗透测试中经常遇到的一些基本发现,这些发现很容易实现自动化。目前将执行以下任务:
-
使用 BloodHound API 收集有关用户总数、组等基本信息 -
生成不同的用户列表 -
检查是否在所有计算机对象上启用了 LAPS -
检查计算机是否使用不受支持的 Windows 版本 -
检查不活跃的用户和计算机 -
检查 krbtgt 密码的年龄 -
检查敏感用户(Domain Admins 和 Tier Zero)的数量,以及他们是否在 Protected Users 组中 -
检查访客账户是否处于活动状态 -
检查可 kerberoast 和可 AS-REP-roast 的用户 -
检查活动的 Tier Zero 会话 -
检查 Kerberos 委派(约束、非约束和基于资源的约束委派) -
检查非 Tier Zero 对象的 DCSync -
生成包含所有描述的文件
所有这些发现主要是为了识别缺失的最佳实践。这些发现在渗透测试期间通常需要大量时间。手动运行所有必要的测试并记录它们是很痛苦的。尽可能地自动化这个过程可以在测试期间留出更多时间来入侵 AD 或更详细地测试其他目标。为了运行这个脚本,你需要遵循以下步骤:
-
使用提供的 docker-compose 文件 设置 BloodHound CE 并启用 neo4j 端口 -
从 GUI 生成 API token 并下载当前版本的 SharpHound -
将你的 API token(如果适用,还有更改后的 neo4j 密码)输入到脚本中 -
从域加入的主机运行 SharpHound -
解压 .zip 文件 -
运行以下命令
python -m venv adAnalysissource adAnalysis/bin/activatepython -m pip install neo4j requestspython adAnalysis.py -d <pathToJsonDir>
该脚本将打印所有发现并在当前目录中写入文件。以下数据将写入 csv 文件:
-
laps.csv:计算机名称、计算机 objectid -
unsupportedOs.csv:计算机名称、计算机 objectid -
inactiveUsers.csv:用户名、用户 objectid、用户是否启用(true 或 false)、用户是否为管理员(true 或 false) -
inactiveComputers.csv:计算机名称、计算机 objectid、计算机是否启用(true 或 false) -
domainAdmins.csv:用户名、用户 objectid -
tierZeroUsers.csv:用户名、用户 objectid -
kerberoastableUsers.csv:用户名、用户 objectid、用户服务主体名称、用户系统标签(Tier Zero 或 NULL) -
asrepRoastableUsers.csv:用户名、用户 objectid、用户系统标签(Tier Zero 或 NULL) -
tierZeroSessions.csv:用户名、用户 objectid、计算机名称、计算机 objectid -
dcsync.csv:用户名、用户 objectid -
constrainedDelegation.csv:用户名、用户 objectid、计算机名称、计算机 objectid -
unconstrainedDelegation.csv:对象名称、对象 objectid -
resourcebasedConstrainedDelegation.csv:对象名称(允许操作)、对象 objectid(允许操作)、对象名称(目标对象)、对象 objectid(目标对象)
结论
新版 BloodHound CE 看起来非常有前途,尽管它仍处于早期访问阶段,但相比传统版本已经有了一些不错的改进。新的 API 提供了另一种与 BloodHound 交互的方式,可用于自动化某些任务或以文本形式检索数据进行处理。未来功能的计划也看起来非常有趣,例如使用 BloodHound 收集和分析 AD CS。虽然自动化基本任务可以显著减少渗透测试期间的工作量,但仍然需要进行一些手动分析来识别更复杂的漏洞。但在测试过程中有更多时间让我们能够更深入地研究其他组件,或者模拟不同的攻击场景,比如提升权限以访问敏感文件或其他关键系统。
原文始发于微信公众号(securitainment):BloodHound Active Directory 自动化渗透测试
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论