adws

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 2, 2026 License: MIT Imports: 19 Imported by: 0

README

sopa

A practical client for ADWS in Golang.

GitHub Release Go Version Code Size License Build Status Go Report Card GitHub Downloads Twitter Follow

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-Enumeration Enumerate + Pull loop with attribute projection, scope control (Base/OneLevel/Subtree), and pagination
    • get — fetches a single object by DN via WS-Transfer Get
  • Object lifecycle
    • create — creates objects via WS-Transfer ResourceFactory (built-in types: user, computer, group, OU, container; or custom objects from a YAML template via IMDA AddRequest)
    • delete — removes an object by DN via WS-Transfer Delete
  • Attribute editing
    • attr — adds, replaces, or removes individual attribute values on an existing object via WS-Transfer Put
  • Account management
    • set-password — sets an account password via MS-ADCAP SetPassword
    • change-password — changes an account password (requires the old password) via MS-ADCAP ChangePassword
  • ADCAP custom actions
    • translate-name — converts between DN and canonical name formats via TranslateName
    • groups — lists group memberships or authorization groups of a principal via GetADPrincipalGroupMembership / GetADPrincipalAuthorizationGroup
    • members — enumerates group members (optionally recursive) via GetADGroupMember
    • optfeature — toggles optional AD features (e.g. Recycle Bin) via ChangeOptionalFeature
    • info — retrieves topology metadata (version, domain, forest, DC list) via GetVersion, GetADDomain, GetADForest, GetADDomainControllers
  • Service metadata
    • mex — fetches ADWS service endpoint metadata via an unauthenticated WS-MetadataExchange request

Installation

$ go install github.com/Macmod/sopa/cmd/sopa@latest

Usage

# Auth flags (-u, -p, -d, -k, -H, -c, ...) are omitted for brevity — see Authentication section.

# Search objects by LDAP filter
$ sopa query --dc <DC> --filter '(objectClass=*)'

# Fetch a single object by DN
$ sopa get --dc <DC> --dn '<DN>'

# Delete an object by DN
$ sopa delete --dc <DC> --dn '<DN>'

# Edit attribute values
$ sopa attr add     --dc <DC> --dn '<DN>' --attr <ATTR> --value <VALUE>
$ sopa attr replace --dc <DC> --dn '<DN>' --attr <ATTR> --value <VALUE>
$ sopa attr delete  --dc <DC> --dn '<DN>' --attr <ATTR>

# Create objects
$ sopa create user      --dc <DC> --name <CN> --pass <INITIAL_PASS>
$ sopa create computer  --dc <DC> --name <CN>
$ sopa create group     --dc <DC> --name <CN> --type GlobalSecurity
$ sopa create ou        --dc <DC> --name <CN>
$ sopa create container --dc <DC> --name <CN>
$ sopa create custom    --dc <DC> --template <TEMPLATE.yaml>

# Set / change account passwords (MS-ADCAP)
$ sopa set-password    --dc <DC_FQDN> --dn '<DN>' --new <NEW_PASS>
$ sopa change-password --dc <DC_FQDN> --dn '<DN>' --old <OLD_PASS> --new <NEW_PASS>

# Translate DN <-> canonical name (MS-ADCAP)
$ sopa translate-name --dc <DC_FQDN> --offered DistinguishedName --desired CanonicalName '<DN>'

# Principal group memberships (MS-ADCAP)
$ sopa groups --dc <DC_FQDN> --dn '<DN>' --membership --authz

# Group members (MS-ADCAP)
$ sopa members --dc <DC_FQDN> --dn '<GROUP_DN>' --recursive

# Toggle optional AD feature, e.g. Recycle Bin (MS-ADCAP)
$ sopa optfeature --dc <DC_FQDN> --feature-id <FEATURE_GUID> --enable

# Topology info (MS-ADCAP)
$ sopa info version --dc <DC_FQDN>
$ sopa info domain  --dc <DC_FQDN>
$ sopa info forest  --dc <DC_FQDN>
$ sopa info dcs     --dc <DC_FQDN>

# ADWS service endpoint metadata (unauthenticated)
$ sopa mex --dc <DC>

Interactive shell

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]> exit

Use exit, quit, or Ctrl-D to leave the shell.

Custom objects creation

Example template: examples/custom-create.example.yaml

Template schema (YAML):

  • parentDN (string, required): container DN
  • rdn (string, required): relative DN for the new object (e.g. CN=Foo)
  • attributes (list, required): each item has:
    • name (string, required): attribute name (cn or addata:cn)
    • type (string, optional): string|int|bool|base64|hex (or explicit xsd:*), default string
    • either value (string) or values (list of strings)

Notes:

  • Do not include ad:relativeDistinguishedName or ad:container-hierarchy-parent in the template (they are injected automatically).
  • hex values are converted to xsd:base64Binary.
  • To set an empty string explicitly, use value: "".

Authentication

sopa supports the following credential modes:

# Password
$ sopa <action> --dc <DC> -u <USER> -p <PASS> -d <DOMAIN> ...

# NT hash
$ sopa <action> --dc <DC> -u <USER> -H <NT_HASH> -d <DOMAIN> ...

# AES session key (DC must be FQDN; Kerberos is implied)
# 32 hex chars = AES-128, 64 hex chars = AES-256
$ sopa <action> --dc <DC_FQDN> -u <USER> --aes-key <HEX_KEY> -d <DOMAIN> ...

# Kerberos ccache (DC must be FQDN; Kerberos is implied)
$ sopa <action> --dc <DC_FQDN> -u <USER> -c <CCACHE_PATH> -d <DOMAIN> ...

# PFX certificate
$ sopa <action> --dc <DC_FQDN> -u <USER> --pfx <CERT.pfx> --pfx-password <PFX_PASS> -d <DOMAIN> ...

# PEM certificate
$ sopa <action> --dc <DC_FQDN> -u <USER> --cert <CERT.pem> --key <KEY.pem> -d <DOMAIN> ...

Contributing

Contributions are welcome by opening an issue or submitting a pull request.

Other ADWS tools

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:

Acknowledgements

  • Big thanks to oiweiwei for go-msrpc, as his ssp package implemented the authentication flow with GSSAPI seamlessly.

References

  • 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

License

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.

Documentation

Overview

Package adws - Type conversion between ADWS and LDAP formats

Package adws - High-level ADWS client for Active Directory query and transfer operations

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConvertGUIDBytes

func ConvertGUIDBytes(guidBytes []byte) (string, error)

ConvertGUIDBytes converts a binary GUID to standard GUID format.

GUID format (mixed-endian):

[0:4]   Data1 (little-endian)
[4:6]   Data2 (little-endian)
[6:8]   Data3 (little-endian)
[8:16]  Data4 (big-endian, 8 bytes)

Output format: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

func ConvertSIDBytes

func ConvertSIDBytes(sidBytes []byte) (string, error)

ConvertSIDBytes converts a binary SID to S-1-5-... format.

SID format:

[0]    Revision (always 1)
[1]    SubAuthorityCount (number of SubAuthorities)
[2:8]  IdentifierAuthority (6 bytes, big-endian)
[8:]   SubAuthorities (4 bytes each, little-endian)

func DetectAttributeType

func DetectAttributeType(attrName string) string

DetectAttributeType attempts to detect the binary attribute type by name.

This is needed because OctetString is ambiguous - it can be SID, GUID, or other binary data.

Types

type ADCAPActiveDirectoryDomain

type ADCAPActiveDirectoryDomain = soap.ADCAPActiveDirectoryDomain

ADCAPActiveDirectoryDomain is the public alias for an MS-ADCAP ActiveDirectoryDomain.

type ADCAPActiveDirectoryDomainController

type ADCAPActiveDirectoryDomainController = soap.ADCAPActiveDirectoryDomainController

ADCAPActiveDirectoryDomainController is the public alias for an MS-ADCAP ActiveDirectoryDomainController.

type ADCAPActiveDirectoryForest

type ADCAPActiveDirectoryForest = soap.ADCAPActiveDirectoryForest

ADCAPActiveDirectoryForest is the public alias for an MS-ADCAP ActiveDirectoryForest.

type ADCAPActiveDirectoryGroup

type ADCAPActiveDirectoryGroup = soap.ADCAPActiveDirectoryGroup

ADCAPActiveDirectoryGroup is the public alias for an MS-ADCAP ActiveDirectoryGroup.

type ADCAPActiveDirectoryObject

type ADCAPActiveDirectoryObject = soap.ADCAPActiveDirectoryObject

ADCAPActiveDirectoryObject is the public alias for an MS-ADCAP ActiveDirectoryObject.

type ADCAPActiveDirectoryPrincipal

type ADCAPActiveDirectoryPrincipal = soap.ADCAPActiveDirectoryPrincipal

ADCAPActiveDirectoryPrincipal is the public alias for an MS-ADCAP ActiveDirectoryPrincipal.

type ADCAPVersionInfo

type ADCAPVersionInfo = soap.ADCAPVersionInfo

ADCAPVersionInfo is the public alias for an MS-ADCAP GetVersion result.

type ADWSItem

type ADWSItem = soap.ADWSItem

ADWSItem is the public alias for an ADWS object item.

type ADWSValue

type ADWSValue = soap.ADWSValue

ADWSValue is the public alias for an ADWS attribute value.

type Config

type Config struct {
	DCFQDN      string        // DC fully-qualified domain name (required)
	Port        int           // ADWS port (default 9389)
	LDAPPort    int           // LDAP port used in SOAP headers for the target directory service (default 389; use 3268 for GC)
	Username    string        // Domain\username or username@domain (required)
	Password    string        // Password (optional if NTHash/CCachePath/PFX/Cert provided)
	NTHash      string        // NT hash auth (optional)
	AESKey      string        // Kerberos AES-128 or AES-256 session key, hex-encoded (optional, implies Kerberos)
	CCachePath  string        // Kerberos ccache path (optional, implies Kerberos)
	PFXFile     string        // PKCS#12 (.pfx/.p12) certificate file for PKINIT (optional)
	PFXPassword string        // Password for PFX file (optional, default empty)
	CertFile    string        // PEM certificate file for PKINIT (use with KeyFile)
	KeyFile     string        // PEM RSA private key file for PKINIT (use with CertFile)
	UseKerberos bool          // Use SPNEGO/Kerberos negotiation
	Domain      string        // Domain name (required)
	Timeout     time.Duration // Connection timeout (default 30s)
	UseTLS      bool          // Use TLS (future - currently not supported by ADWS)
	DebugXML    bool          // Print raw SOAP XML when true (or via ADWS_DEBUG_XML=1)
}

Config contains ADWS client configuration.

type IMDAAttribute

type IMDAAttribute struct {
	Name    string
	XSIType string
	Values  []string
}

IMDAAttribute describes an attribute for an IMDA AddRequest.

Name should be a fully qualified attribute type (e.g. "addata:cn", "addata:objectClass"). XSIType should be an xsd:* type (e.g. "xsd:string", "xsd:int", "xsd:base64Binary"). Values contains 1+ values for the attribute.

type NameTranslateResult

type NameTranslateResult = soap.NameTranslateResult

NameTranslateResult is the public alias for an MS-ADCAP TranslateName result.

type WSClient

type WSClient struct {
	// contains filtered or unexported fields
}

WSClient represents an ADWS client for querying and transfer operations in Active Directory.

ADWS provides an alternative to traditional LDAP (ports 389/3268) by using port 9389 with SOAP/XML over an authenticated and encrypted channel.

Protocol stack (bottom to top):

  1. TCP connection to dc.domain.com:9389
  2. NNS (.NET NegotiateStream) - NTLM/Kerberos authentication with signing/sealing
  3. NMF (.NET Message Framing) - Record boundaries and encoding negotiation
  4. SOAP/XML - WS-Enumeration/WS-Transfer protocol operations

func NewWSClient

func NewWSClient(cfg Config) (*WSClient, error)

NewWSClient creates a new ADWS client with the given configuration. Credential fields (Username, Password, etc.) are validated at Connect() time, so callers that only intend to call GetMetadata() may omit them.

func (*WSClient) ADCAPChangeOptionalFeature

func (c *WSClient) ADCAPChangeOptionalFeature(distinguishedName string, enable bool, featureID string) error

ADCAPChangeOptionalFeature enables or disables an optional feature in a naming context.

func (*WSClient) ADCAPChangePassword

func (c *WSClient) ADCAPChangePassword(accountDN, partitionDN, oldPassword, newPassword string) error

ADCAPChangePassword changes the password for the specified account DN in the specified partition.

func (*WSClient) ADCAPGetADDomain

func (c *WSClient) ADCAPGetADDomain() (*ADCAPActiveDirectoryDomain, error)

ADCAPGetADDomain returns information about the domain containing the directory service.

func (*WSClient) ADCAPGetADDomainControllers

func (c *WSClient) ADCAPGetADDomainControllers(ntdsSettingsDNs []string) ([]ADCAPActiveDirectoryDomainController, error)

ADCAPGetADDomainControllers returns info about domain controllers for the given nTDSDSA settings DNs.

func (*WSClient) ADCAPGetADForest

func (c *WSClient) ADCAPGetADForest() (*ADCAPActiveDirectoryForest, error)

ADCAPGetADForest returns information about the forest containing the directory service.

func (*WSClient) ADCAPGetADGroupMember

func (c *WSClient) ADCAPGetADGroupMember(groupDN, partitionDN string, recursive bool) ([]ADCAPActiveDirectoryPrincipal, error)

ADCAPGetADGroupMember returns the members of the specified group.

func (*WSClient) ADCAPGetADPrincipalAuthorizationGroup

func (c *WSClient) ADCAPGetADPrincipalAuthorizationGroup(principalDN, partitionDN string) ([]ADCAPActiveDirectoryGroup, error)

ADCAPGetADPrincipalAuthorizationGroup returns the security-enabled groups used for authorization for a principal.

func (*WSClient) ADCAPGetADPrincipalGroupMembership

func (c *WSClient) ADCAPGetADPrincipalGroupMembership(principalDN, partitionDN, resourceContextPartition, resourceContextServer string) ([]ADCAPActiveDirectoryGroup, error)

ADCAPGetADPrincipalGroupMembership returns a set of groups associated with the specified principal.

Note: per MS-ADCAP, this returns direct group membership only (no transitive expansion).

func (*WSClient) ADCAPGetVersion

func (c *WSClient) ADCAPGetVersion() (*ADCAPVersionInfo, error)

ADCAPGetVersion returns ADWS Custom Action Protocol version information.

func (*WSClient) ADCAPSetPassword

func (c *WSClient) ADCAPSetPassword(accountDN, partitionDN, newPassword string) error

ADCAPSetPassword sets the password for the specified account DN in the specified partition.

func (*WSClient) ADCAPTranslateName

func (c *WSClient) ADCAPTranslateName(formatOffered, formatDesired string, names []string) ([]NameTranslateResult, error)

ADCAPTranslateName translates an array of names from one format to another. Valid formats: DistinguishedName, CanonicalName.

func (*WSClient) Close

func (c *WSClient) Close() error

Close closes the ADWS connection.

func (*WSClient) Connect

func (c *WSClient) Connect() error

Connect establishes a connection to the ADWS server.

func (*WSClient) Get

func (c *WSClient) Get(dn string, attrs []string) (*ADWSItem, error)

Get retrieves a single AD object by distinguished name.

func (*WSClient) GetDCFQDN

func (c *WSClient) GetDCFQDN() string

GetDCFQDN returns the DC FQDN this client is connected to.

func (*WSClient) GetMetadata

func (c *WSClient) GetMetadata() (*wsmex.ADWSMetadata, error)

GetMetadata fetches and parses the WS-MetadataExchange document from the unauthenticated ADWS MEX endpoint. No credentials are required.

func (*WSClient) IsConnected

func (c *WSClient) IsConnected() bool

IsConnected returns true if the client is connected.

func (*WSClient) PrincipalAuthorizationGroups

func (c *WSClient) PrincipalAuthorizationGroups(principalDN string) ([]ADWSItem, error)

PrincipalAuthorizationGroups returns the security-enabled groups used for authorization decisions for the specified principal, using the MS-ADCAP GetADPrincipalAuthorizationGroup custom action.

func (*WSClient) PrincipalGroupMembership

func (c *WSClient) PrincipalGroupMembership(principalDN string) ([]ADWSItem, error)

PrincipalGroupMembership returns a set of groups associated with the specified principal, using the MS-ADCAP GetADPrincipalGroupMembership custom action.

Note: per MS-ADCAP, no transitive group membership evaluation is performed.

func (*WSClient) Query

func (c *WSClient) Query(baseDN, filter string, attrs []string, scope int) ([]ADWSItem, error)

Query performs an LDAP query via ADWS and returns all results.

func (*WSClient) QueryWithBatchChannel

func (c *WSClient) QueryWithBatchChannel(baseDN, filter string, attrs []string, scope, maxElementsPerPull int, batchChannel chan<- []ADWSItem) error

QueryWithBatchChannel performs an LDAP query and streams each Pull batch to batchChannel.

func (*WSClient) QueryWithCallback

func (c *WSClient) QueryWithCallback(baseDN, filter string, attrs []string, scope int, callback func(items []ADWSItem) error) error

QueryWithCallback performs an LDAP query and calls a callback for each batch of results.

func (*WSClient) SetDebugXML

func (c *WSClient) SetDebugXML(enabled bool)

SetDebugXML enables/disables raw SOAP response logging.

func (*WSClient) SetTimeout

func (c *WSClient) SetTimeout(timeout time.Duration)

SetTimeout sets the connection timeout.

func (*WSClient) WSTransferAddComputer

func (c *WSClient) WSTransferAddComputer(parentDN, computerName, computerPass string) (string, error)

WSTransferAddComputer executes a WS-Transfer Create (IMDA AddRequest) against the ResourceFactory endpoint to create a computer account under parentDN.

This mirrors SharpADWS' AddComputer method and sets unicodePwd, dNSHostName, userAccountControl, and servicePrincipalName.

func (*WSClient) WSTransferAddContainer

func (c *WSClient) WSTransferAddContainer(parentDN, cn string) (string, error)

WSTransferAddContainer creates a container object under parentDN via ResourceFactory.

func (*WSClient) WSTransferAddGroup

func (c *WSClient) WSTransferAddGroup(parentDN, groupName, groupType string) (string, error)

WSTransferAddGroup creates a group object under parentDN via ResourceFactory.

func (*WSClient) WSTransferAddOU

func (c *WSClient) WSTransferAddOU(parentDN, ouName string) (string, error)

WSTransferAddOU creates an organizationalUnit object under parentDN via ResourceFactory.

func (*WSClient) WSTransferAddUser

func (c *WSClient) WSTransferAddUser(parentDN, userName, userPass string, enabled bool) (string, error)

WSTransferAddUser creates a user object under parentDN via ResourceFactory.

func (*WSClient) WSTransferCreate

func (c *WSClient) WSTransferCreate(instanceXML string) (string, error)

WSTransferCreate executes a WS-Transfer Create against the ResourceFactory endpoint.

The returned address is best-effort and may be empty if the server response does not include a parsable ResourceCreated/Address or objectReferenceProperty.

func (*WSClient) WSTransferCreateComputer

func (c *WSClient) WSTransferCreateComputer(parentDN, computerName string) (string, error)

WSTransferCreateComputer executes a WS-Transfer Create (IMDA AddRequest) against the ResourceFactory endpoint to create a simple computer object under parentDN.

This is a state-changing operation.

func (*WSClient) WSTransferCreateCustom

func (c *WSClient) WSTransferCreateCustom(parentDN, rdn string, attrs []IMDAAttribute) (string, error)

WSTransferCreateCustom creates a custom object via WS-Transfer ResourceFactory using an IMDA AddRequest.

parentDN is the container DN; rdn is the relative distinguished name (e.g. "CN=MyObject"). The required IMDA attributes ad:relativeDistinguishedName and ad:container-hierarchy-parent are always injected.

func (*WSClient) WSTransferDelete

func (c *WSClient) WSTransferDelete(dn string) error

WSTransferDelete executes a WS-Transfer Delete against the Resource endpoint.

func (*WSClient) WSTransferGet

func (c *WSClient) WSTransferGet(dn string, attrs []string) (*ADWSItem, error)

WSTransferGet executes a WS-Transfer Get against the Resource endpoint.

func (*WSClient) WSTransferModifyAttribute

func (c *WSClient) WSTransferModifyAttribute(dn, operation, attr string, values []string) error

WSTransferModifyAttribute performs a WS-Transfer Put using an IMDA ModifyRequest.

operation must be one of: add, replace, delete. attr may be either a local attribute name (e.g. "description") or a fully-qualified type (e.g. "addata:description"). Values are treated as xsd:string.

func (*WSClient) WSTransferPut

func (c *WSClient) WSTransferPut(dn, instanceXML string) error

WSTransferPut executes a WS-Transfer Put against the Resource endpoint.

Directories

Path Synopsis
cmd
sopa command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL