ZakiPedio/BridgeHead

GitHub: ZakiPedio/BridgeHead

纯 C++20 实现的 ADWS 协议栈静态库,无需 .NET/WCF/HTTP 即可通过 9389 端口直接访问和操作 Active Directory。

Stars: 28 | Forks: 0

# BridgeHead **BridgeHead** 是一个 C++20 静态库,直接通过 TCP 实现了完整的 **Active Directory Web Services (ADWS)** 协议栈。它以 AD bridgehead 服务器(目录流量流经的网关)命名,为你的 C++ 代码提供了与 PowerShell 的 `Get-ADUser` 和 `Get-ADComputer` 底层使用的相同的 9389 端口低级访问权限。 ## 目录 - [协议栈](#protocol-stack) - [快速开始](#quick-start) - [API 参考](#api-reference) - [构建](#build) - [集成](#integration) - [平台支持](#platform-support) - [依赖项](#dependencies) - [已知限制](#known-limitations) - [协议参考](#protocol-references) - [许可证](#license) ## 协议栈 传输层通过通用的 `bridgehead::transport::IByteStream` 接口封装其下层协议。`NbfseCodec` 是一个编解码器工具,由 `AdwsClient` 调用,用于在组帧前后对 SOAP 消息进行编码/解码: ``` AdwsClient WS-Enumeration + WS-Transfer [MS-ADDM] ├── NbfseCodec .NET Binary Format for SOAP [MC-NBFSE] (encode/decode) └── NmfFramer .NET Message Framing [MC-NMF] (send/receive frames) └── NnsSession .NET NegotiateStream [MS-NNS] └── TcpSocket raw TCP/IP ``` 使用者的入口点是 `bridgehead::adws::AdwsClient`。 ## 快速开始 ### 查询,枚举所有用户 ``` #include "bridgehead/adws/AdwsClient.hpp" // NTLM (works on any host) auto client = bridgehead::adws::AdwsClient::EnumerationClient( "192.168.1.10", // DC IP or hostname "DC01.corp.local", // DC FQDN, used in NMF Via header and Kerberos SPN "CORP", // NetBIOS domain name "Administrator", // username "Passw0rd" // password ); // Kerberos (username hidden on the wire; requires DC reachable on port 88) auto client = bridgehead::adws::AdwsClient::EnumerationClient( "192.168.1.10", "DC01.corp.local", "CORP", "Administrator", "Passw0rd", bridgehead::adws::AuthPackage::Kerberos ); auto users = client.Query( "(objectClass=user)", {"sAMAccountName", "distinguishedName", "memberOf"} ); for (auto& obj : users) std::cout << obj.FirstValue("sAMAccountName") << '\n'; ``` ### 流式处理大型结果集 ``` client.Enumerate( "(objectClass=computer)", {"dNSHostName", "operatingSystem"}, "", // empty = domain root base DN [](const bridgehead::adws::LdapObject& obj) { std::cout << obj.FirstValue("dNSHostName") << '\n'; return true; // return false to stop early (sends wsen:Release) } ); ``` ### 范围和分页 ``` // OneLevel scope, 50 objects per Pull round-trip client.Query( "(objectClass=user)", {"sAMAccountName"}, "OU=Admins,DC=corp,DC=local", 50, bridgehead::adws::SearchScope::OneLevel ); ``` ### 二进制属性,objectGUID, objectSid ``` auto objs = client.Query("(objectClass=user)", {"objectGUID", "objectSid"}); for (auto& obj : objs) { if (auto* b = obj.FirstBytes("objectGUID")) std::cout << bridgehead::adws::ParseGuid(*b) << '\n'; // {XXXXXXXX-...} if (auto* b = obj.FirstBytes("objectSid")) std::cout << bridgehead::adws::ParseSid(*b) << '\n'; // S-1-5-... } ``` ### 安全描述符解码 ``` #include "bridgehead/adws/SecurityDescriptor.hpp" auto objs = client.Query("(objectClass=user)", {"nTSecurityDescriptor"}); if (auto* raw = objs[0].FirstBytes("nTSecurityDescriptor")) { auto sd = bridgehead::adws::ParseSecurityDescriptor(*raw); std::cout << "Owner: " << sd.ownerSid << '\n'; for (auto& ace : sd.dacl.aces) std::cout << " type=" << (int)ace.type << " mask=0x" << std::hex << ace.mask << " sid=" << ace.sid << '\n'; } ``` ### 写入属性 (Resource endpoint) ``` auto rc = bridgehead::adws::AdwsClient::ResourceClient( "192.168.1.10", "DC01.corp.local", "CORP", "Administrator", "Passw0rd"); // Modify attributes rc.Put("CN=Alice,OU=Users,DC=corp,DC=local", { {"description", {"managed by bridgehead"}}, {"telephoneNumber", {"555-1234"}}, }); // Clear an attribute (both values and bytes empty = delete) rc.Put("CN=Alice,OU=Users,DC=corp,DC=local", { {"telephoneNumber", {}}, }); // Read back auto obj = rc.Get("CN=Alice,OU=Users,DC=corp,DC=local", {"description", "telephoneNumber"}); // Delete object rc.Delete("CN=TempUser,OU=Users,DC=corp,DC=local"); ``` ### LDAP 修改类型, Add / Replace / Delete `Put` 默认为 `Replace`(覆盖所有现有值)。在多值属性上需要进行细粒度控制时,请使用 `ModifyOperation`: ``` using bridgehead::adws::LdapModification; using bridgehead::adws::ModifyOperation; rc.Put("CN=Alice,OU=Users,DC=corp,DC=local", { // Append a value to an existing multi-valued attribute LdapModification{"otherTelephone", {"555-9999"}, {}, ModifyOperation::Add}, // Remove one specific value (leave others intact) LdapModification{"otherTelephone", {"555-0000"}, {}, ModifyOperation::Delete}, // Replace is the default, explicit here for clarity LdapModification{"description", {"updated"}, {}, ModifyOperation::Replace}, }); ``` ### 移动 / 重命名 ``` // Move to a different OU rc.Move( "CN=Alice,OU=OldOU,DC=corp,DC=local", // current DN "CN=Alice,OU=NewOU,DC=corp,DC=local" // new DN ); // Rename in place (same parent, new CN) rc.Move( "CN=Alice,OU=Users,DC=corp,DC=local", "CN=AliceSmith,OU=Users,DC=corp,DC=local" ); ``` ### 写入二进制属性 在 `LdapModification` 的 `bytes` 字段中提供二进制值。它们会在线上自动进行 base64 编码: ``` std::vector thumbnail = loadFile("photo.jpg"); rc.Put("CN=Alice,OU=Users,DC=corp,DC=local", { LdapModification{"thumbnailPhoto", {}, {thumbnail}}, }); ``` ### 创建对象 (ResourceFactory endpoint) ``` auto rf = bridgehead::adws::AdwsClient::ResourceFactoryClient( "192.168.1.10", "DC01.corp.local", "CORP", "Administrator", "Passw0rd"); rf.Create( "CN=NewUser,OU=Users,DC=corp,DC=local", "user", {{"sAMAccountName", {"newuser"}}, {"userAccountControl", {"512"}}} ); ``` ### 异步操作 ``` #include "bridgehead/adws/AdwsClientAsync.hpp" auto ac = bridgehead::adws::AdwsClientAsync::EnumerationClient( "192.168.1.10", "DC01.corp.local", "CORP", "Administrator", "Passw0rd"); auto future = ac.QueryAsync("(objectClass=user)", {"sAMAccountName"}); // ... do other work while the query runs ... auto users = future.get(); // blocks until complete; re-throws any exception ``` 对响应缓慢的 DC 设置超时: ``` if (future.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) { // query is still running } ``` 所有操作都有异步变体:`QueryAsync`, `EnumerateAsync`, `GetAsync`, `PutAsync`, `DeleteAsync`, `CreateAsync`, `MoveAsync`。 ### 连接池(高频 / 多线程工作负载) ``` #include "bridgehead/adws/AdwsClientPool.hpp" // Enumeration pool, Query / Enumerate bridgehead::adws::AdwsClientPool pool({ .host = "192.168.1.10", .fqdn = "DC01.corp.local", .domain = "CORP", .username = "Administrator", .password = "Passw0rd", .maxSize = 4, // up to 4 concurrent authenticated sessions }); auto users = pool.Query("(objectClass=user)", {"sAMAccountName"}); // Resource pool, Get / Put / Delete bridgehead::adws::AdwsClientPool resPool({ .host = "192.168.1.10", .fqdn = "DC01.corp.local", .domain = "CORP", .username = "Administrator", .password = "Passw0rd", .maxSize = 4, .endpoint = bridgehead::adws::PoolEndpoint::Resource, }); auto obj = resPool.Get("CN=Alice,OU=Users,DC=corp,DC=local", {"mail"}); resPool.Put("CN=Alice,OU=Users,DC=corp,DC=local", {{"mail", {"alice@corp.local"}}}); // ResourceFactory pool, Create bridgehead::adws::AdwsClientPool rfPool({ .host = "192.168.1.10", .fqdn = "DC01.corp.local", .domain = "CORP", .username = "Administrator", .password = "Passw0rd", .endpoint = bridgehead::adws::PoolEndpoint::ResourceFactory, }); rfPool.Create("CN=NewUser,OU=Users,DC=corp,DC=local", "user", {{"sAMAccountName", {"newuser"}}}); ``` ## API 参考 所有公共头文件都位于 `include/bridgehead/` 下。完整的 API 文档可以使用 Doxygen 生成(参见 [构建](#build))。 ### `bridgehead::adws::AdwsClient` | 方法 | Endpoint | 描述 | |--------|----------|-------------| | `EnumerationClient(host, fqdn, domain, user, pass [, auth, timeoutMs, opTimeoutMs, limits])` | `/Enumeration` | 工厂,连接并认证 | | `ResourceClient(...)` | `/Resource` | 用于 Get / Put / Delete / Move 的工厂 | | `ResourceFactoryClient(...)` | `/ResourceFactory` | 用于 Create 的工厂 | | `Query(filter, attrs [, baseDN, maxElems, scope])` | Enumeration | 将所有结果收集到 `vector` 中 | | `Enumerate(filter, attrs, baseDN, callback [, maxElems, scope])` | Enumeration | 通过回调流式传输结果 | | `Get(dn, attrs)` | Resource | 读取单个对象的属性 | | `Put(dn, modifications)` | Resource | 修改属性 | | `Delete(dn)` | Resource | 删除对象 | | `Create(dn, objectClass, attrs)` | ResourceFactory | 创建新对象 | | `Move(dn, newDn)` | Resource | 移动或重命名现有对象 | ### `bridgehead::adws::AdwsClientAsync` 基于 `std::future` 的异步封装器。具有与 `AdwsClient` 相同的工厂方法;每个操作返回一个 `std::future`: ``` std::future> f = ac.QueryAsync(...); std::future e = ac.EnumerateAsync(filter, attrs, baseDN, callback); std::future g = ac.GetAsync(...); std::future h = ac.PutAsync(...); std::future i = ac.DeleteAsync(...); std::future j = ac.CreateAsync(...); std::future k = ac.MoveAsync(...); ``` 对于跨多个连接的并发查询,请使用 `AdwsClientPool`。 ### `bridgehead::adws::AdwsClientPool` 线程安全的预认证会话池。连接采用延迟创建,并在调用间复用。 ``` pool.IdleCount(); // sessions currently idle pool.TotalCount(); // idle + checked-out pool.MaxSize(); // configured maximum pool size pool.Move(dn, newDn); // Move/rename (Resource pool) ``` ### `bridgehead::adws::SearchScope` ``` enum class SearchScope { Base, OneLevel, Subtree /*default*/ }; ``` ### `bridgehead::adws::LdapObject` / `LdapAttribute` ``` struct LdapAttribute { std::string name; std::string syntax; // LdapSyntax OID, empty if absent std::vector values; // text values (raw base64 for binary) std::vector> bytes; // decoded binary; parallel to values }; struct LdapObject { std::vector attributes; // all returned attributes const LdapAttribute* Find(const std::string& name) const; // case-insensitive std::string FirstValue(const std::string& name) const; const std::vector* FirstBytes(const std::string& name) const; }; ``` ### 二进制辅助工具 ``` std::string ParseGuid(const std::vector& bytes); // → "{XXXXXXXX-XXXX-...}" std::string ParseSid (const std::vector& bytes); // → "S-1-5-..." ``` ### `FilterValue` 和过滤器构建辅助工具 `FilterValue` 是一个类型安全的包装器,在构造时会转义 RFC 4515 §3 的元字符(`\`, `*`, `(`, `)`, NUL)。将其与构建器辅助工具结合使用,可以从结构上杜绝 LDAP 注入: ``` // UNSAFE, raw string concatenation, easy to forget escaping client.Query("(sAMAccountName=" + username + ")", ...); // SAFE, FilterValue escapes on construction; FilterEq composes the assertion FilterValue user = username; client.Query(FilterEq("sAMAccountName", user), ...); // Compose complex filters client.Query( FilterAnd({ FilterEq("objectClass", FilterValue::Raw("user")), // Raw() for safe literals FilterOr({ FilterEq("sAMAccountName", user), FilterEq("mail", FilterValue(email)), }), FilterNot(FilterPresent("userAccountControl")), }), {"sAMAccountName", "mail"} ); ``` 当你需要手动构建过滤器字符串时,`EscapeLdapFilter(str)` 仍可作为较低层级的原语使用。 ### `DnValue` 和 DN 构建辅助工具 模式与 `FilterValue` 相同,但用于 Distinguished Name RDN 值 (RFC 4514 §2.4)。转义 `,`, `+`, `"`, `\`, `<`, `>`, `;`, NUL,以及开头/结尾的 `#` 和空格: ``` // UNSAFE, comma injection breaks the DN structure rc.Get("CN=" + username + ",OU=Users,DC=corp,DC=local", attrs); // SAFE DnValue cn = username; // auto-escaped auto dn = BuildDn({DnAttr("CN", cn), "OU=Users", "DC=corp", "DC=local"}); rc.Get(dn, attrs); // DnValue::Raw() for values you control auto dn2 = BuildDn({DnAttr("CN", DnValue::Raw("Service Account")), "OU=SvcAccounts", "DC=corp", "DC=local"}); ``` ### `bridgehead::adws::SecurityDescriptor` ``` SecurityDescriptor ParseSecurityDescriptor(const std::vector& bytes); struct SecurityDescriptor { uint16_t control; // SdControl::* flags std::string ownerSid; std::string groupSid; bool hasDacl; Acl dacl; bool hasSacl; Acl sacl; }; struct Acl { std::vector aces; }; struct Ace { uint8_t type; // AceType::* uint8_t flags; // AceFlags::* uint32_t mask; std::string sid; std::string objectType; // GUID string, object ACEs only std::string inheritedObjectType; // GUID string, object ACEs only }; ``` 常量命名空间:`AceType::*`, `AceFlags::*`, `SdControl::*`。 ### `bridgehead::ConnectionError` / `AuthenticationError` / `ProtocolError` 定义在 `include/bridgehead/Exceptions.hpp` 中(由 `AdwsClient.hpp` 传递包含)。这三者都继承自 `std::runtime_error`: | 类型 | 抛出时机 | |------|-------------| | `ConnectionError` | TCP 层级故障:拒绝、超时、I/O 错误 | | `AuthenticationError` | NTLM/Kerberos 协商失败 | | `ProtocolError` | 任何协议层的格式错误服务器响应 | ``` try { auto client = bridgehead::adws::AdwsClient::EnumerationClient(...); auto users = client.Query(...); } catch (const bridgehead::ConnectionError& e) { // unreachable DC, wrong port, timeout } catch (const bridgehead::AuthenticationError& e) { // bad credentials, KDC unreachable } catch (const bridgehead::ProtocolError& e) { // unexpected server response } catch (const bridgehead::adws::SoapFault& e) { // DC returned a SOAP fault (e.g. invalid filter) } // or coarse-grained: // } catch (const std::runtime_error& e) { ... } ``` ### `bridgehead::adws::SoapFault` 当 DC 返回任何 `s:Fault` 响应时抛出,而不是抛出普通的 `std::runtime_error`: ``` struct SoapFault : std::runtime_error { std::string code; // e.g. "Sender" std::string subcode; // e.g. "InvalidEnumerationContext", empty if absent std::string reason; // human-readable text }; ``` ### `bridgehead::adws::LdapModification` 由 `Put` 和 `Create` 使用: ``` enum class ModifyOperation { Replace /*default*/, Add, Delete }; struct LdapModification { std::string name; std::vector values; // text values std::vector> bytes; // binary values (base64-encoded on the wire) ModifyOperation operation = ModifyOperation::Replace; }; ``` 当 `values` 和 `bytes` 均为空时,无论 `operation` 为何,该属性都会被清除/删除。 ### `bridgehead::adws::ProtocolLimits` 所有工厂方法、`AdwsClientAsync` 工厂和 `AdwsClientPool::Config::limits` 的可选最后参数。所有字段都有安全的默认值,仅在你有特定需求时更改它们: ``` struct ProtocolLimits { uint32_t nmfMaxFrameBytes = 64 * 1024 * 1024; // max NMF frame (default 64 MiB) uint32_t nnsMaxPayloadBytes = 16 * 1024 * 1024; // max NNS packet (default 16 MB) int tcpKeepaliveIdleSec = 60; // idle seconds before first probe int tcpKeepaliveIntervalSec = 10; // seconds between probes int tcpKeepaliveProbeCount = 5; // probes before declaring dead (POSIX only) }; ``` 仅在读取具有异常大二进制属性的对象(例如,包含许多 ACE 的 `nTSecurityDescriptor`,或大型 `thumbnailPhoto`)时,才提高 `nmfMaxFrameBytes` / `nnsMaxPayloadBytes`。在空闲连接会被积极断开的云/NAT 环境中,应降低 keepalive 字段。 命名构造函数辅助工具(内联静态工厂): ``` LdapModification::Replace(name, values) // Replace with text values (default) LdapModification::ReplaceBinary(name, bytes) // Replace with binary values LdapModification::Append(name, values) // Add to multi-valued attribute LdapModification::Remove(name, values={}) // Remove specific values (or all) LdapModification::Clear(name) // Delete the attribute entirely ``` ## 构建 **要求:** CMake 3.25+ 和支持 C++20 的编译器(MSVC 2022+, GCC 12+, Clang 15+)。 ``` # 配置并构建 (仅单元测试,无需实时 AD) cmake -B build -A x64 cmake --build build --config Release # 运行单元测试 ctest --test-dir build -C Release --output-on-failure # 包含集成测试 (需要实时 Domain Controller) cmake -B build -A x64 -DBRIDGEHEAD_INTEGRATION_TESTS=ON cmake --build build --config Release ./build/tests/Release/bridgehead_integration_tests.exe # 使用 CLI 工具 (adws_list,从命令行浏览 AD 对象) cmake -B build -A x64 -DBRIDGEHEAD_BUILD_TOOLS=ON cmake --build build --config Release --target adws_list ./build/tools/Release/adws_list.exe --host --fqdn --domain --user # 生成 API 参考文档 (需要 Doxygen) cmake --build build --target docs # 或者直接: doxygen Doxyfile # 输出:docs/doxygen/html/index.html ``` ### CMake 选项 | 选项 | 默认值 | 描述 | |--------|---------|-------------| | `BRIDGEHEAD_BUILD_TESTS` | `ON` | 构建单元测试套件 | | `BRIDGEHEAD_INTEGRATION_TESTS` | `OFF` | 构建集成测试(需要在线 DC) | | `BRIDGEHEAD_BUILD_TOOLS` | `OFF` | 构建 `adws_list` CLI 工具 | ## 集成 ### 选项 A, CMake 子目录(无需安装) ``` add_subdirectory(bridgehead) target_link_libraries(my_target PRIVATE bridgehead::bridgehead) ``` ### 选项 B, 已安装包(`find_package`) ``` cmake --install build --prefix /usr/local # or any install prefix ``` ``` find_package(bridgehead REQUIRED) target_link_libraries(my_target PRIVATE bridgehead::bridgehead) ``` `pugixml`, `ws2_32`, 和 `secur32` (Windows) / `gssapi_krb5` (Linux/macOS) 均通过传递方式被引入。 ## 平台支持 | 平台 | 认证后端 | 状态 | |----------|-------------|--------| | Windows | SSPI (`secur32.dll`), NTLM 和 Kerberos | **正常工作** | | Linux / macOS | GSSAPI (`libgssapi_krb5`), SPNEGO / Kerberos | 已编译;尚未进行集成测试 | `AuthPackage::Ntlm` 和 `AuthPackage::Kerberos` 都能在 Windows 上工作。Kerberos 在传输过程中隐藏了用户名,在生产环境中是首选;当 KDC(端口 88)不可达时,NTLM 是回退方案。 在 Linux/macOS 上,GSSAPI 后端使用带有 Kerberos 的 SPNEGO。如需 NTLM 支持,请安装 `gss-ntlmssp` 插件: ``` apt install libgss-ntlmssp # Debian / Ubuntu dnf install gssntlmssp # Fedora / RHEL ``` 如果没有该插件,服务器将协商使用 Kerberos。显式的用户/密码凭据使用 `gss_acquire_cred_with_password`(MIT Kerberos 1.9+);传递空字符串以使用默认凭据缓存(`kinit`)。GSSAPI 路径编译无误且设计正确,但尚未针对在线 DC 进行端到端验证,请将 Linux/macOS 支持视为 Beta 版本。 ## 依赖项 在配置时通过 `cmake/Dependencies.cmake` 自动获取,无需手动安装: | 库 | 版本 | 用途 | |---------|---------|---------| | [Catch2](https://github.com/catchorg/Catch2) | v3.5.2 | 单元测试框架(仅限测试目标) | | [pugixml](https://github.com/zeux/pugixml) | v1.14 | XML DOM(仅用于 ADWS 响应解析) | 无 OpenSSL。无 Asio。无 Boost。 ## 已知限制 - **Linux/macOS 上的 NTLM** 需要 `gss-ntlmssp` GSSAPI 插件(参见 [平台支持](#platform-support))。没有它,服务器将协商使用 Kerberos。Windows 上的 NTLM 通过 SSPI 原生工作。 - **pugixml DOM**,每个 SOAP 响应在返回任何结果之前都会被完全解析为内存中的 XML 树。这受到 `maxElements` 页面大小的限制(默认每次 Pull 256 个对象),因此完整的结果集永远不会一次性保存在内存中。只有在使用非常大的页面大小或包含大型二进制属性的对象(例如,具有许多 ACE 的对象上的 `nTSecurityDescriptor`)时,内存才会成为问题。SAX/流式方法可以消除这种情况,但目前尚未计划。 ## 协议参考 - **[MS-ADDM]**, Active Directory Web Services: Data Model and Common Elements - **[MS-NNS]**, .NET NegotiateStream Protocol - **[MC-NMF]**, .NET Message Framing - **[MC-NBFSE]**, .NET Binary Format: SOAP Extension - **[MS-DTYP]**, Windows Data Types (SECURITY_DESCRIPTOR, ACL, ACE, SID, GUID) - **[MS-ADTS]**, Active Directory Technical Specification (LDAP filters / attributes) 所有规范均可从 [Microsoft 开放规范文档](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-winprotlp/) 获取。 ## 特别感谢 特别感谢 IBM X-Force 和 SoaPy 项目提供的灵感、研究以及公开发布的工作,这些都对本项目有所帮助。我还要感谢 Logan Goins,他是 IBM X-Force Red SoaPy 的创建者,正是他的努力和洞察力使那项工作成为可能。 ## 许可证 详情请见 [LICENSE](LICENSE)。
标签:Active Directory, ADWS, Bash脚本, C++20, Conpot, HTTP, LDAP替代, NTLM, Plaso, SOAP, TCP/IP, Windows安全, 协议栈, 原生开发, 域渗透, 并发处理, 无.NET, 横向移动, 电子数据取证, 目录服务, 编程规范, 网络编程, 静态库