Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ldap protocol enhancements #4667

Merged
merged 18 commits into from
Feb 5, 2024

Conversation

5amu
Copy link
Contributor

@5amu 5amu commented Jan 20, 2024

Closes #4645

I'm implementing the following features:

  • Connection checker
  • Authentication method for AD and OpenLdap (implemented but tested only on unencrypted AD Ldap)
  • Generic search function
  • Better metadata collection
  • Better Kerberoastable users search method
  • Method to find accounts trusted for delegation
  • Method to find accounts not requiring a password
  • Method to find accounts with never expiring password
  • Method to find accounts that are administrators
  • Method to find all users or all active users
  • Method to find all groups
  • Method to find all domain controllers
  • Method to retrieve the domain SID

I'm still working on it, I'm starting the PR just to track progress 😄

@ehsandeep ehsandeep marked this pull request as draft January 20, 2024 20:21
@5amu
Copy link
Contributor Author

5amu commented Jan 20, 2024

As commit bd1238d is possible to connect, authenticate and query the Ldap server with custom filters chosing which attribute to get.

Example:

id: ldap-test-auth-search

info:
  name: Test Authentication and Search method
  author: 5amu
  severity: info

javascript:
  - args:
      DomainController: "{{Host}}"
    code: |
      ldap = require("nuclei/ldap");
      var client = ldap.LdapClient();
      var connected = client.Connect(DomainController, Port, false, false);
      var authenticated = client.Authenticate(template.Domain, template.Username, template.Password);
      var filter = "(&(!(userAccountControl:1.2.840.113556.1.4.803:=2))(objectCategory=person)(servicePrincipalName=*))"
      var res = client.Search(filter, "sAMAccountName", "servicePrincipalName")
      to_json(res)
    extractors:
      - type: json
        json:
          - '.[] | "\(.sAMAccountName[0]) => \(.servicePrincipalName.[])"'
go run .\cmd\nuclei\main.go -u dc01.lab.local:389 -t .\ldap-test-auth-search.yaml -var Username=victim -var Password=Trust_Me_Br0 -var Domain=LAB.LOCAL

                     __     _
   ____  __  _______/ /__  (_)
  / __ \/ / / / ___/ / _ \/ /
 / / / / /_/ / /__/ /  __/ /
/_/ /_/\__,_/\___/_/\___/_/   v3.1.5

                projectdiscovery.io

[WRN] Found 12 template[s] loaded with deprecated paths, update before v3 for continued support.
[INF] Current nuclei version: v3.1.5 (outdated)
[INF] Current nuclei-templates version: v9.7.4 (latest)
[WRN] Scan results upload to cloud is disabled.
[INF] New templates added in latest release: 6
[INF] Templates loaded for current scan: 1
[WRN] Executing 1 unsigned templates. Use with caution.
[INF] Targets loaded for current scan: 1
[ldap-test-auth-search] [javascript] [info] dc01.lab.local:389 ["pdtest2 => DC01/cifs","roastme => DC01/ldap"]

pkg/js/libs/ldap/ldap.go Fixed Show fixed Hide fixed
pkg/js/libs/ldap/ldap.go Fixed Show fixed Hide fixed
@5amu
Copy link
Contributor Author

5amu commented Jan 21, 2024

Features from Nmap

I saw the list of feateres from nmap that @tarunKoyalwar linked in the issue: https://nmap.org/nsedoc/lib/ldap.html

Extra Features

  • GetADUsers()
  • GetADActiveUsers()
  • GetADUserWithNeverExpiringPasswords()
  • GetADUserTrustedForDelegation()
  • GetADUserWithPasswordNotRequired()
  • GetADGroups()
  • GetADDCList()
  • GetADAdmins()
  • GetADUserKerberoastable()
  • GetADDomainSID()

Caveats

  • I could not use the fastdialer net.Conn as underlying connection for the ldap client, so I used the vanilla net.Conn, created with ldap.Dial and ldap.DialTLS.
  • I didn't test the connection over LDAPS or LDAP with the upgrade to TLS, but I can't see why it wouldn't work in the project as it's implemented in my project and I tested that in real environments.
  • I don't know why the Github actions checks are failing, but it seems that the causes are unrelated.

@5amu 5amu marked this pull request as ready for review January 21, 2024 19:18
@olearycrew
Copy link
Contributor

Thanks for this contribution @5amu !

serverName = c.cfg.ServerName
}
conn, err = protocolstate.Dialer.DialTLSWithConfig(context.TODO(), "tcp", net.JoinHostPort(host, port),
&tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10, ServerName: serverName})

Check failure

Code scanning / CodeQL

Disabled TLS certificate check High

InsecureSkipVerify should not be used in production code.
if c.cfg.ServerName != "" {
serverName = c.cfg.ServerName
}
if err := c.conn.StartTLS(&tls.Config{InsecureSkipVerify: true, ServerName: serverName}); err != nil {

Check failure

Code scanning / CodeQL

Disabled TLS certificate check High

InsecureSkipVerify should not be used in production code.
serverName = c.cfg.ServerName
}
conn, err = protocolstate.Dialer.DialTLSWithConfig(context.TODO(), "tcp", net.JoinHostPort(host, port),
&tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10, ServerName: serverName})

Check failure

Code scanning / CodeQL

Insecure TLS configuration High

Using insecure TLS version VersionTLS10 for MinVersion.
Copy link
Member

@tarunKoyalwar tarunKoyalwar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm !

$  ./nuclei -u 192.168.122.100 -t b.yaml -V Realm=mydomain.com -debug

                     __     _
   ____  __  _______/ /__  (_)
  / __ \/ / / / ___/ / _ \/ /
 / / / / /_/ / /__/ /  __/ /
/_/ /_/\__,_/\___/_/\___/_/   v3.1.10

                projectdiscovery.io

[INF] Current nuclei version: v3.1.10 (latest)
[INF] Current nuclei-templates version: v9.7.5 (latest)
[WRN] Scan results upload to cloud is disabled.
[INF] New templates added in latest release: 106
[INF] Templates loaded for current scan: 1
[WRN] Executing 1 unsigned templates. Use with caution.
[INF] Targets loaded for current scan: 1
[DBG] [ldap-collect-metadata] Dumped Javascript request for 192.168.122.100:
Variables:
        1. DC => ldap://192.168.122.100
        2. Pass => Pa$$w0rd
        3. Port => 
        4. User => tarun-admin address=192.168.122.100
[DBG]  [ldap-collect-metadata] Javascript Code:

        const ldap = require("nuclei/ldap");
        const client = new ldap.Client(DC, template.Realm)
        client.Authenticate(User, Pass)
        to_json(client.CollectMetadata())

[DBG] [ldap-collect-metadata] Dumped Javascript response for 192.168.122.100:
        1. response => {   "BaseDN": "dc=mydomai .... ame": "DC.mydomain.com" }
        2. success => true address=192.168.122.100
[ldap-collect-metadata] [javascript] [info] 192.168.122.100 ["{\"BaseDN\":\"dc=mydomain,dc=com\",\"DefaultNamingContext\":\"DC=mydomain,DC=com\",\"DnsHostName\":\"DC.mydomain.com\",\"Domain\":\"\",\"DomainControllerFunctionality\":\"7\",\"DomainFunctionality\":\"7\",\"ForestFunctionality\":\"7\"}"]
$ ./nuclei -u 192.168.122.100 -t a.yaml -V Realm=mydomain.com -debug

                     __     _
   ____  __  _______/ /__  (_)
  / __ \/ / / / ___/ / _ \/ /
 / / / / /_/ / /__/ /  __/ /
/_/ /_/\__,_/\___/_/\___/_/   v3.1.10

                projectdiscovery.io

[INF] Current nuclei version: v3.1.10 (latest)
[INF] Current nuclei-templates version: v9.7.5 (latest)
[WRN] Scan results upload to cloud is disabled.
[INF] New templates added in latest release: 106
[INF] Templates loaded for current scan: 1
[WRN] Executing 1 unsigned templates. Use with caution.
[INF] Targets loaded for current scan: 1
[DBG] [ldap-get-spn] Dumped Javascript request for 192.168.122.100:
Variables:
        1. DC => ldap://192.168.122.100
        2. Pass => Pa$$w0rd
        3. Port => 
        4. User => tarun-admin address=192.168.122.100
[DBG]  [ldap-get-spn] Javascript Code:

        const ldap = require("nuclei/ldap");
        const client = new ldap.Client(DC, template.Realm)
        client.Authenticate(User, Pass)
        var res = client.Search(ldap.FilterHasServicePrincipalName, "servicePrincipalName")
        to_json(res)

[DBG] [ldap-get-spn] Dumped Javascript response for 192.168.122.100:
        1. response => [   {     "servicePrincip .... min/changepw"     ]   } ]
        2. success => true address=192.168.122.100
[ldap-get-spn] [javascript] [info] 192.168.122.100 ["null => ldap/DC.mydomain.com/ForestDnsZones.mydomain.com","null => GC/DC.mydomain.com/mydomain.com","null => HOST/DC.mydomain.com","null => ldap/e8526d24-8019-4ab8-b5be-df51fb892a01._msdcs.mydomain.com","null => kadmin/changepw","null => Dfsr-12F9A27C-BF97-4787-9364-D31B6C55EB04/DC.mydomain.com","null => ldap/DC.mydomain.com/MYDOMAIN","null => HOST/CLIENT1","null => HOST/DC","null => DNS/DC.mydomain.com","null => WSMAN/CLIENT1.mydomain.com","null => HOST/CLIENT1.mydomain.com","null => ldap/DC","null => ldap/DC.mydomain.com/DomainDnsZones.mydomain.com","null => RestrictedKrbHost/DC.mydomain.com","null => RPC/e8526d24-8019-4ab8-b5be-df51fb892a01._msdcs.mydomain.com","null => HOST/DC.mydomain.com/mydomain.com","null => E3514235-4B06-11D1-AB04-00C04FC2DCD2/e8526d24-8019-4ab8-b5be-df51fb892a01/mydomain.com","null => ldap/DC/MYDOMAIN","null => HOST/DC/MYDOMAIN","null => ldap/DC.mydomain.com","null => ldap/DC.mydomain.com/mydomain.com","null => WSMAN/CLIENT1","null => RestrictedKrbHost/CLIENT1","null => HOST/DC.mydomain.com/MYDOMAIN","null => RestrictedKrbHost/DC","null => RestrictedKrbHost/CLIENT1.mydomain.com"]

Great Work @5amu , i especially appreciate hardcoded OID's and flexible search and filter methods 🔥

I could not use the fastdialer net.Conn as underlying connection for the ldap client, so I used the vanilla net.Conn, created with ldap.Dial and ldap.DialTLS.

I used ldap.NewConn and passed conn created by fastdialer that took care of default dialer issue

@tarunKoyalwar tarunKoyalwar merged commit 7647af1 into projectdiscovery:dev Feb 5, 2024
11 of 12 checks passed
@5amu
Copy link
Contributor Author

5amu commented Feb 6, 2024

Hi @tarunKoyalwar! Thank you for the improvements! It is really easier to use in a template.
I was testing it using the merged features and realized that the realm is never stored in the client when the object is created.

https://github.com/projectdiscovery/nuclei/blob/7647af1722e4ec87d3a00468e74a85fe1dbcf1f0/pkg/js/libs/ldap/ldap.go#L48C1-L124C2

It should be stored in the object as the BaseDN is built from the Realm (and it is necessary to query active directory). For example:

func NewClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
	// omitted for brevity

	// validate arguments
	c.nj.Require(ldapUrl != "", "ldap url cannot be empty")
	c.nj.Require(realm != "", "realm cannot be empty")
	
        c.Realm = realm // this is the line to add

        // omitted for brevity
}

I don't know what's the etiquette in this situation, but please add this before release.
Thank you again for your great work!

@tarunKoyalwar
Copy link
Member

tarunKoyalwar commented Feb 6, 2024

@5amu Ah ! looks like i missed that one . Thanks for sharing . i am currently working on pr to generate typescript .d.ts files for intellisense and documentation using code generation i will update it in this pr shortly #4487

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ldap protocol enhancements
3 participants