A practical client for ADWS in Golang.
Sopa implements the ADWS protocol stack (MS-NNS + MC-NMF + SOAP), exposing the following command-line features:
- Object search & retrieval
query— runs LDAP-filter searches via WS-EnumerationEnumerate+Pullloop with attribute projection, scope control (Base/OneLevel/Subtree), and paginationget— fetches a single object by DN via WS-TransferGet
- Object lifecycle
create— creates objects via WS-TransferResourceFactory(built-in types: user, computer, group, OU, container; or custom objects from a YAML template via IMDAAddRequest)delete— removes an object by DN via WS-TransferDelete
- Attribute editing
attr— adds, replaces, or removes individual attribute values on an existing object via WS-TransferPut
- Account management
set-password— sets an account password via MS-ADCAPSetPasswordchange-password— changes an account password (requires the old password) via MS-ADCAPChangePassword
- ADCAP custom actions
translate-name— converts between DN and canonical name formats viaTranslateNamegroups— lists group memberships or authorization groups of a principal viaGetADPrincipalGroupMembership/GetADPrincipalAuthorizationGroupmembers— enumerates group members (optionally recursive) viaGetADGroupMemberoptfeature— toggles optional AD features (e.g. Recycle Bin) viaChangeOptionalFeatureinfo— retrieves topology metadata (version, domain, forest, DC list) viaGetVersion,GetADDomain,GetADForest,GetADDomainControllers
- Service metadata
mex— fetches ADWS service endpoint metadata via an unauthenticated WS-MetadataExchange request
$ go install github.com/Macmod/sopa/cmd/sopa@latest# Auth flags (-u, -p, -d, -k, -H, -c, ...) are omitted for brevity — see Authentication section.
# Search objects by LDAP filter
$ sopa [auth_flags] query --dc <DC> --filter '(objectClass=*)'
# Fetch a single object by DN
$ sopa [auth_flags] get --dc <DC> --dn '<DN>'
# Delete an object by DN
$ sopa [auth_flags] delete --dc <DC> --dn '<DN>'
# Edit attribute values
$ sopa [auth_flags] attr add --dc <DC> --dn '<DN>' --attr <ATTR> --value <VALUE>
$ sopa [auth_flags] attr replace --dc <DC> --dn '<DN>' --attr <ATTR> --value <VALUE>
$ sopa [auth_flags] attr delete --dc <DC> --dn '<DN>' --attr <ATTR>
# Create objects
$ sopa [auth_flags] create user --dc <DC> --name <CN> --pass <INITIAL_PASS>
$ sopa [auth_flags] create computer --dc <DC> --name <CN>
$ sopa [auth_flags] create group --dc <DC> --name <CN> --type GlobalSecurity
$ sopa [auth_flags] create ou --dc <DC> --name <CN>
$ sopa [auth_flags] create container --dc <DC> --name <CN>
$ sopa [auth_flags] create custom --dc <DC> --template <TEMPLATE.yaml>
# Set / change account passwords (MS-ADCAP)
$ sopa [auth_flags] set-password --dc <DC> --dn '<DN>' --new <NEW_PASS>
$ sopa [auth_flags] change-password --dc <DC> --dn '<DN>' --old <OLD_PASS> --new <NEW_PASS>
# Translate DN <-> canonical name (MS-ADCAP)
# (this call is mostly useless but kept for completeness 😄)
$ sopa [auth_flags] translate-name --dc <DC> --offered DistinguishedName --desired CanonicalName '<DN>'
# Principal group memberships (MS-ADCAP)
$ sopa [auth_flags] groups --dc <DC> --dn '<DN>' --membership --authz
# Group members (MS-ADCAP)
$ sopa [auth_flags] members --dc <DC> --dn '<GROUP_DN>' --recursive
# Toggle optional AD feature, e.g. Recycle Bin (MS-ADCAP)
$ sopa [auth_flags] optfeature --dc <DC> --feature-id <FEATURE_GUID> --enable
# Topology info (MS-ADCAP)
$ sopa [auth_flags] info version --dc <DC>
$ sopa [auth_flags] info domain --dc <DC>
$ sopa [auth_flags] info forest --dc <DC>
$ sopa [auth_flags] info dcs --dc <DC>
# ADWS service endpoint metadata (unauthenticated - auth flags not needed)
$ sopa mex --dc <DC>Run sopa without a subcommand to open an interactive shell. It reuses a single connection for all commands and provides tab-completion.
$ sopa --dc <DC> -u <USER> -p <PASS> -d <DOMAIN>
sopa v1.1.0
Connected dc.corp.local domain=corp.local user=Administrator
Type 'help' for commands or 'exit' to quit.
[corp.local]> query --filter '(objectClass=user)' --attrs sAMAccountName
[corp.local]> get --dn 'CN=Administrator,CN=Users,DC=corp,DC=local'
[corp.local]> exitUse exit, quit, or Ctrl-D to leave the shell.
Example template: examples/custom-create.example.yaml
Template schema (YAML):
parentDN(string, required): container DNrdn(string, required): relative DN for the new object (e.g.CN=Foo)attributes(list, required): each item has:name(string, required): attribute name (cnoraddata:cn)type(string, optional):string|int|bool|base64|hex(or explicitxsd:*), defaultstring- either
value(string) orvalues(list of strings)
Notes:
- Do not include
ad:relativeDistinguishedNameorad:container-hierarchy-parentin the template (they are injected automatically). hexvalues are converted toxsd:base64Binary.- To set an empty string explicitly, use
value: "".
--dc accepts a FQDN, an IP address, or can be omitted.
Because the DC's hostname is sometimes not available from the network's default DNS, it is strongly recommended to always pass --dns <DC-IP> so that sopa uses the DC's own DNS server for all lookups:
# Option 1: let sopa resolve everything through the DC's DNS
$ sopa query --dns 192.168.1.10 -d corp.local -u user -p pass --filter '(objectClass=user)'When --dc is omitted and --domain is provided, sopa discovers a DC
automatically by querying SRV records:
_ldap._tcp.<domain> (tried first)
_kerberos._tcp.<domain> (fallback)
The target of the highest-priority record is used. This requires that the
DNS server pointed to by --dns can answer those SRV queries — the DC's own
integrated DNS server (when present) should be capable of that.
# Option 2: provide DC explicitly without Kerberos
$ sopa info version --dc 192.168.1.10 --domain corp.local -u user -p passWhen an IP is provided for --dc and Kerberos is in use, the IP is resolved to an FQDN via a reverse PTR lookup so that the Kerberos SPN / KDC address are correct.
This PTR lookup also goes through --dns, so a correctly configured reverse
zone on the DC is required:
# Option 3: IP input + Kerberos: PTR lookup resolves 192.168.1.10 -> dc.corp.local
$ sopa info version --dc 192.168.1.10 --dns 192.168.1.10 -k --domain corp.local -u user -p passWhen Kerberos is not in use, there is no PTR lookup — the raw IP is used throughout.
All DNS operations in the stack - DC discovery, PTR resolution, ADWS TCP dial,
and Kerberos KDC connections - use the same resolver built from --dns /
--dns-tcp.
| Flag | Description |
|---|---|
--dns <host[:port]> |
Custom DNS server for all lookups (SRV, PTR, forward). Defaults to port 53. |
--dns-tcp |
Force DNS queries over TCP instead of UDP. Useful when UDP is blocked or SRV responses are large. |
--dns-timeout <duration> |
Timeout for DNS operations (default 10s). |
--tcp-timeout <duration> |
Timeout for TCP dial and ADWS protocol operations (default 30s). |
sopa supports the following credential modes:
# Password
$ sopa --dc <DC> -d <DOMAIN> -u <USER> -p <PASS> <subcommand> [...]
# NT hash
$ sopa --dc <DC> -d <DOMAIN> -u <USER> -H <NT_HASH> <subcommand> [...]
# AES session key (Kerberos is implied)
# 32 hex chars = AES-128, 64 hex chars = AES-256
$ sopa --dc <DC> -d <DOMAIN> -u <USER> --aes-key <HEX_KEY> <subcommand> [...]
# Kerberos ccache (Kerberos is implied)
$ sopa --dc <DC> -d <DOMAIN> -u <USER> -c <CCACHE_PATH> <subcommand> [...]
# PFX certificate (Kerberos is implied / via PKINIT)
$ sopa --dc <DC> -d <DOMAIN> -u <USER> --pfx <CERT.pfx> --pfx-password <PFX_PASS> <subcommand> [...]
# PEM certificate (Kerberos is implied / via PKINIT)
$ sopa --dc <DC> -d <DOMAIN> -u <USER> --cert <CERT.pem> --key <KEY.pem> <subcommand> [...]Contributions are welcome by opening an issue or submitting a pull request.
The idea to write this tool came from a wave of ADWS-focused tools, mainly for evasion purposes. In theory all that can be done with these can also be done with sopa, but if you want to perform more complex/specific actions, check them out too:
SOCKS is currently not implemented. Use a solution like OkamiW/proxy-ns if needed for your use case.
- Big thanks to oiweiwei for go-msrpc, as his
ssppackage implemented the authentication flow with GSSAPI seamlessly.
- MS-NNS - .NET NegotiateStream Protocol
- MS-NMF - .NET Message Framing Protocol
- MS-ADDM - Active Directory Web Services: Data Model and Common Elements
- MS-WSDS - WS-Enumeration: Directory Services Protocol Extensions
- MS-WSTIM - WS-Transfer: Identity Management Operations for Directory Access Extensions
- MS-ADCAP - Active Directory Web Services Custom Action Protocol
- MS-ADTS - Active Directory Technical Specification
The MIT License (MIT)
Copyright (c) 2023 Artur Henrique Marzano Gonzaga
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.