From 32845bccf251c569d49750b9766a108d076c6e96 Mon Sep 17 00:00:00 2001 From: pussycat0x <65701233+pussycat0x@users.noreply.github.com> Date: Thu, 1 May 2025 18:20:02 +0530 Subject: [PATCH 1/2] CheckRDPEncryption --- cmd/nuclei/rdp.yaml | 26 +++++ pkg/js/generated/go/librdp/rdp.go | 10 +- pkg/js/generated/ts/rdp.ts | 46 ++++++-- pkg/js/libs/rdp/memo.rdp.go | 16 +++ pkg/js/libs/rdp/rdp.go | 186 ++++++++++++++++++++++++++++++ 5 files changed, 272 insertions(+), 12 deletions(-) create mode 100644 cmd/nuclei/rdp.yaml diff --git a/cmd/nuclei/rdp.yaml b/cmd/nuclei/rdp.yaml new file mode 100644 index 000000000..285e459ad --- /dev/null +++ b/cmd/nuclei/rdp.yaml @@ -0,0 +1,26 @@ +id: rdp-enc-check + +info: + name: RDP Enc - Detection + author: pussycat0x + severity: info + metadata: + verified: true + max-request: 1 + shodan-query: port:"3389" + tags: js,network,rdp,info,enum + +javascript: + - code: | + let m = require('nuclei/rdp'); + let response = m.CheckRDPEncryption(Host,Port); + Export(response); + + args: + Host: "{{Host}}" + Port: "3389" + + extractors: + - type: dsl + dsl: + - response \ No newline at end of file diff --git a/pkg/js/generated/go/librdp/rdp.go b/pkg/js/generated/go/librdp/rdp.go index f3129ef21..6395b1a73 100644 --- a/pkg/js/generated/go/librdp/rdp.go +++ b/pkg/js/generated/go/librdp/rdp.go @@ -15,14 +15,16 @@ func init() { module.Set( gojs.Objects{ // Functions - "CheckRDPAuth": lib_rdp.CheckRDPAuth, - "IsRDP": lib_rdp.IsRDP, + "CheckRDPAuth": lib_rdp.CheckRDPAuth, + "CheckRDPEncryption": lib_rdp.CheckRDPEncryption, + "IsRDP": lib_rdp.IsRDP, // Var and consts // Objects / Classes - "CheckRDPAuthResponse": gojs.GetClassConstructor[lib_rdp.CheckRDPAuthResponse](&lib_rdp.CheckRDPAuthResponse{}), - "IsRDPResponse": gojs.GetClassConstructor[lib_rdp.IsRDPResponse](&lib_rdp.IsRDPResponse{}), + "CheckRDPAuthResponse": gojs.GetClassConstructor[lib_rdp.CheckRDPAuthResponse](&lib_rdp.CheckRDPAuthResponse{}), + "CheckRDPEncryptionResponse": gojs.GetClassConstructor[lib_rdp.RDPEncryptionResponse](&lib_rdp.RDPEncryptionResponse{}), + "IsRDPResponse": gojs.GetClassConstructor[lib_rdp.IsRDPResponse](&lib_rdp.IsRDPResponse{}), }, ).Register() } diff --git a/pkg/js/generated/ts/rdp.ts b/pkg/js/generated/ts/rdp.ts index 7858b7831..12ebf68f2 100755 --- a/pkg/js/generated/ts/rdp.ts +++ b/pkg/js/generated/ts/rdp.ts @@ -1,5 +1,3 @@ - - /** * CheckRDPAuth checks if the given host and port are running rdp server * with authentication and returns their metadata. @@ -15,7 +13,19 @@ export function CheckRDPAuth(host: string, port: number): CheckRDPAuthResponse | return null; } - +/** + * CheckRDPEncryption checks the RDP server's supported security layers and encryption levels. + * It tests different protocols and ciphers to determine what is supported. + * @example + * ```javascript + * const rdp = require('nuclei/rdp'); + * const encryption = rdp.CheckRDPEncryption('acme.com', 3389); + * log(toJSON(encryption)); + * ``` + */ +export function CheckRDPEncryption(host: string, port: number): RDPEncryptionResponse | null { + return null; +} /** * IsRDP checks if the given host and port are running rdp server. @@ -33,8 +43,6 @@ export function IsRDP(host: string, port: number): IsRDPResponse | null { return null; } - - /** * CheckRDPAuthResponse is the response from the CheckRDPAuth function. * this is returned by CheckRDPAuth function. @@ -52,7 +60,31 @@ export interface CheckRDPAuthResponse { Auth?: boolean, } - +/** + * RDPEncryptionResponse is the response from the CheckRDPEncryption function. + * This is returned by CheckRDPEncryption function. + * @example + * ```javascript + * const rdp = require('nuclei/rdp'); + * const encryption = rdp.CheckRDPEncryption('acme.com', 3389); + * log(toJSON(encryption)); + * ``` + */ +export interface RDPEncryptionResponse { + SecurityLayer: { + NativeRDP: boolean; + SSL: boolean; + CredSSP: boolean; + RDSTLS: boolean; + CredSSPWithEarlyUserAuth: boolean; + }; + EncryptionLevel: { + RC4_40bit: boolean; + RC4_56bit: boolean; + RC4_128bit: boolean; + FIPS140_1: boolean; + }; +} /** * IsRDPResponse is the response from the IsRDP function. @@ -71,8 +103,6 @@ export interface IsRDPResponse { OS?: string, } - - /** * ServiceRDP Interface */ diff --git a/pkg/js/libs/rdp/memo.rdp.go b/pkg/js/libs/rdp/memo.rdp.go index c592e20e1..1639a4a14 100755 --- a/pkg/js/libs/rdp/memo.rdp.go +++ b/pkg/js/libs/rdp/memo.rdp.go @@ -39,3 +39,19 @@ func memoizedcheckRDPAuth(host string, port int) (CheckRDPAuthResponse, error) { return CheckRDPAuthResponse{}, errors.New("could not convert cached result") } + +func memoizedcheckRDPEncryption(host string, port int) (RDPEncryptionResponse, error) { + hash := "checkRDPEncryption" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + + v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { + return checkRDPEncryption(host, port) + }) + if err != nil { + return RDPEncryptionResponse{}, err + } + if value, ok := v.(RDPEncryptionResponse); ok { + return value, nil + } + + return RDPEncryptionResponse{}, errors.New("could not convert cached result") +} diff --git a/pkg/js/libs/rdp/rdp.go b/pkg/js/libs/rdp/rdp.go index 7a985ab25..249fbf5ee 100644 --- a/pkg/js/libs/rdp/rdp.go +++ b/pkg/js/libs/rdp/rdp.go @@ -3,6 +3,7 @@ package rdp import ( "context" "fmt" + "net" "time" "github.com/praetorian-inc/fingerprintx/pkg/plugins" @@ -112,3 +113,188 @@ func checkRDPAuth(host string, port int) (CheckRDPAuthResponse, error) { resp.PluginInfo = pluginInfo return resp, nil } + +type ( + // RDPEncryptionResponse is the response from the CheckRDPEncryption function. + // This is returned by CheckRDPEncryption function. + // @example + // ```javascript + // const rdp = require('nuclei/rdp'); + // const encryption = rdp.CheckRDPEncryption('acme.com', 3389); + // log(toJSON(encryption)); + // ``` + RDPEncryptionResponse struct { + SecurityLayer struct { + NativeRDP bool + SSL bool + CredSSP bool + RDSTLS bool + CredSSPWithEarlyUserAuth bool + } + EncryptionLevel struct { + RC4_40bit bool + RC4_56bit bool + RC4_128bit bool + FIPS140_1 bool + } + } +) + +// CheckRDPEncryption checks the RDP server's supported security layers and encryption levels. +// It tests different protocols and ciphers to determine what is supported. +// @example +// ```javascript +// const rdp = require('nuclei/rdp'); +// const encryption = rdp.CheckRDPEncryption('acme.com', 3389); +// log(toJSON(encryption)); +// ``` +func CheckRDPEncryption(host string, port int) (RDPEncryptionResponse, error) { + return memoizedcheckRDPEncryption(host, port) +} + +// @memo +func checkRDPEncryption(host string, port int) (RDPEncryptionResponse, error) { + resp := RDPEncryptionResponse{} + timeout := 5 * time.Second + + // Test different security protocols + protocols := map[string]int{ + "NativeRDP": 0, + "SSL": 1, + "CredSSP": 3, + "RDSTLS": 4, + "CredSSPWithEarlyUserAuth": 8, + } + + for name, value := range protocols { + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + continue + } + defer conn.Close() + + // Test protocol + isRDP, err := testRDPProtocol(conn, timeout, value) + if err == nil && isRDP { + switch name { + case "NativeRDP": + resp.SecurityLayer.NativeRDP = true + case "SSL": + resp.SecurityLayer.SSL = true + case "CredSSP": + resp.SecurityLayer.CredSSP = true + case "RDSTLS": + resp.SecurityLayer.RDSTLS = true + case "CredSSPWithEarlyUserAuth": + resp.SecurityLayer.CredSSPWithEarlyUserAuth = true + } + } + } + + // Test different encryption levels + ciphers := map[string]int{ + "RC4_40bit": 1, + "RC4_56bit": 8, + "RC4_128bit": 2, + "FIPS140_1": 16, + } + + for name, value := range ciphers { + conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + if err != nil { + continue + } + defer conn.Close() + + // Test cipher + isRDP, err := testRDPCipher(conn, timeout, value) + if err == nil && isRDP { + switch name { + case "RC4_40bit": + resp.EncryptionLevel.RC4_40bit = true + case "RC4_56bit": + resp.EncryptionLevel.RC4_56bit = true + case "RC4_128bit": + resp.EncryptionLevel.RC4_128bit = true + case "FIPS140_1": + resp.EncryptionLevel.FIPS140_1 = true + } + } + } + + return resp, nil +} + +// testRDPProtocol tests RDP with a specific security protocol +func testRDPProtocol(conn net.Conn, timeout time.Duration, protocol int) (bool, error) { + // Set connection timeout + _ = conn.SetDeadline(time.Now().Add(timeout)) + defer func() { + _ = conn.SetDeadline(time.Time{}) + }() + + // Send RDP connection request with specific protocol + // This is a simplified version - in reality you'd need to implement the full RDP protocol + // including the negotiation phase with the specified protocol + _, err := conn.Write([]byte{0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, byte(protocol), 0x00, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00}) + if err != nil { + return false, err + } + + // Read response + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + return false, err + } + + // Check if response indicates RDP + if n >= 19 && buf[0] == 0x03 && buf[1] == 0x00 && buf[2] == 0x00 { + // For CredSSP and CredSSP with Early User Auth, we need to check for NLA support + if protocol == 3 || protocol == 8 { + // Check for NLA support in the response + if n >= 19 && buf[18]&0x01 != 0 { + return true, nil + } + return false, nil + } + return true, nil + } + + return false, nil +} + +// testRDPCipher tests RDP with a specific encryption level +func testRDPCipher(conn net.Conn, timeout time.Duration, cipher int) (bool, error) { + // Set connection timeout + _ = conn.SetDeadline(time.Now().Add(timeout)) + defer func() { + _ = conn.SetDeadline(time.Time{}) + }() + + // Send RDP connection request with specific cipher + // This is a simplified version - in reality you'd need to implement the full RDP protocol + // including the negotiation phase with the specified cipher + _, err := conn.Write([]byte{0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, byte(cipher), 0x03, 0x00, 0x00, 0x00}) + if err != nil { + return false, err + } + + // Read response + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + return false, err + } + + // Check if response indicates RDP + if n >= 19 && buf[0] == 0x03 && buf[1] == 0x00 && buf[2] == 0x00 { + // Check for encryption level support in the response + if n >= 19 && buf[18]&byte(cipher) != 0 { + return true, nil + } + return false, nil + } + + return false, nil +} From cb2d93174ac332afef699ebbdfac849603893ae8 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Thu, 25 Sep 2025 22:46:40 +0200 Subject: [PATCH 2/2] fixing logic --- cmd/nuclei/rdp.yaml | 26 ------ pkg/js/generated/ts/rdp.ts | 25 +++--- pkg/js/libs/rdp/memo.rdp.go | 4 +- pkg/js/libs/rdp/rdp.go | 162 +++++++++++++++++++++--------------- 4 files changed, 107 insertions(+), 110 deletions(-) delete mode 100644 cmd/nuclei/rdp.yaml diff --git a/cmd/nuclei/rdp.yaml b/cmd/nuclei/rdp.yaml deleted file mode 100644 index 285e459ad..000000000 --- a/cmd/nuclei/rdp.yaml +++ /dev/null @@ -1,26 +0,0 @@ -id: rdp-enc-check - -info: - name: RDP Enc - Detection - author: pussycat0x - severity: info - metadata: - verified: true - max-request: 1 - shodan-query: port:"3389" - tags: js,network,rdp,info,enum - -javascript: - - code: | - let m = require('nuclei/rdp'); - let response = m.CheckRDPEncryption(Host,Port); - Export(response); - - args: - Host: "{{Host}}" - Port: "3389" - - extractors: - - type: dsl - dsl: - - response \ No newline at end of file diff --git a/pkg/js/generated/ts/rdp.ts b/pkg/js/generated/ts/rdp.ts index 12ebf68f2..28a28468a 100755 --- a/pkg/js/generated/ts/rdp.ts +++ b/pkg/js/generated/ts/rdp.ts @@ -71,19 +71,18 @@ export interface CheckRDPAuthResponse { * ``` */ export interface RDPEncryptionResponse { - SecurityLayer: { - NativeRDP: boolean; - SSL: boolean; - CredSSP: boolean; - RDSTLS: boolean; - CredSSPWithEarlyUserAuth: boolean; - }; - EncryptionLevel: { - RC4_40bit: boolean; - RC4_56bit: boolean; - RC4_128bit: boolean; - FIPS140_1: boolean; - }; + // Security Layer Protocols + NativeRDP: boolean; + SSL: boolean; + CredSSP: boolean; + RDSTLS: boolean; + CredSSPWithEarlyUserAuth: boolean; + + // Encryption Levels + RC4_40bit: boolean; + RC4_56bit: boolean; + RC4_128bit: boolean; + FIPS140_1: boolean; } /** diff --git a/pkg/js/libs/rdp/memo.rdp.go b/pkg/js/libs/rdp/memo.rdp.go index f14e10caa..f295e97b7 100755 --- a/pkg/js/libs/rdp/memo.rdp.go +++ b/pkg/js/libs/rdp/memo.rdp.go @@ -40,11 +40,11 @@ func memoizedcheckRDPAuth(executionId string, host string, port int) (CheckRDPAu return CheckRDPAuthResponse{}, errors.New("could not convert cached result") } -func memoizedcheckRDPEncryption(host string, port int) (RDPEncryptionResponse, error) { +func memoizedcheckRDPEncryption(executionId string, host string, port int) (RDPEncryptionResponse, error) { hash := "checkRDPEncryption" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) { - return checkRDPEncryption(host, port) + return checkRDPEncryption(executionId, host, port) }) if err != nil { return RDPEncryptionResponse{}, err diff --git a/pkg/js/libs/rdp/rdp.go b/pkg/js/libs/rdp/rdp.go index 28027c402..b53cf17ac 100644 --- a/pkg/js/libs/rdp/rdp.go +++ b/pkg/js/libs/rdp/rdp.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "strconv" "time" "github.com/praetorian-inc/fingerprintx/pkg/plugins" @@ -129,6 +130,29 @@ func checkRDPAuth(executionId string, host string, port int) (CheckRDPAuthRespon return resp, nil } +type ( + SecurityLayer string +) + +const ( + SecurityLayerNativeRDP = "NativeRDP" + SecurityLayerSSL = "SSL" + SecurityLayerCredSSP = "CredSSP" + SecurityLayerRDSTLS = "RDSTLS" + SecurityLayerCredSSPWithEarlyUserAuth = "CredSSPWithEarlyUserAuth" +) + +type ( + EncryptionLevel string +) + +const ( + EncryptionLevelRC4_40bit = "RC4_40bit" + EncryptionLevelRC4_56bit = "RC4_56bit" + EncryptionLevelRC4_128bit = "RC4_128bit" + EncryptionLevelFIPS140_1 = "FIPS140_1" +) + type ( // RDPEncryptionResponse is the response from the CheckRDPEncryption function. // This is returned by CheckRDPEncryption function. @@ -139,19 +163,18 @@ type ( // log(toJSON(encryption)); // ``` RDPEncryptionResponse struct { - SecurityLayer struct { - NativeRDP bool - SSL bool - CredSSP bool - RDSTLS bool - CredSSPWithEarlyUserAuth bool - } - EncryptionLevel struct { - RC4_40bit bool - RC4_56bit bool - RC4_128bit bool - FIPS140_1 bool - } + // Protocols + NativeRDP bool + SSL bool + CredSSP bool + RDSTLS bool + CredSSPWithEarlyUserAuth bool + + // EncryptionLevels + RC4_40bit bool + RC4_56bit bool + RC4_128bit bool + FIPS140_1 bool } ) @@ -163,76 +186,89 @@ type ( // const encryption = rdp.CheckRDPEncryption('acme.com', 3389); // log(toJSON(encryption)); // ``` -func CheckRDPEncryption(host string, port int) (RDPEncryptionResponse, error) { - return memoizedcheckRDPEncryption(host, port) +func CheckRDPEncryption(ctx context.Context, host string, port int) (RDPEncryptionResponse, error) { + executionId := ctx.Value("executionId").(string) + return memoizedcheckRDPEncryption(executionId, host, port) } // @memo -func checkRDPEncryption(host string, port int) (RDPEncryptionResponse, error) { +func checkRDPEncryption(executionId string, host string, port int) (RDPEncryptionResponse, error) { + dialer := protocolstate.GetDialersWithId(executionId) + if dialer == nil { + return RDPEncryptionResponse{}, fmt.Errorf("dialers not initialized for %s", executionId) + } resp := RDPEncryptionResponse{} - timeout := 5 * time.Second + defaultTimeout := 5 * time.Second // Test different security protocols - protocols := map[string]int{ - "NativeRDP": 0, - "SSL": 1, - "CredSSP": 3, - "RDSTLS": 4, - "CredSSPWithEarlyUserAuth": 8, + protocols := map[SecurityLayer]int{ + SecurityLayerNativeRDP: 0, + SecurityLayerSSL: 1, + SecurityLayerCredSSP: 3, + SecurityLayerRDSTLS: 4, + SecurityLayerCredSSPWithEarlyUserAuth: 8, } for name, value := range protocols { - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + conn, err := dialer.Fastdialer.Dial(ctx, "tcp", net.JoinHostPort(host, strconv.Itoa(port))) if err != nil { continue } - defer conn.Close() + defer func() { + _ = conn.Close() + }() // Test protocol - isRDP, err := testRDPProtocol(conn, timeout, value) + isRDP, err := testRDPProtocol(conn, value) if err == nil && isRDP { - switch name { - case "NativeRDP": - resp.SecurityLayer.NativeRDP = true - case "SSL": - resp.SecurityLayer.SSL = true - case "CredSSP": - resp.SecurityLayer.CredSSP = true - case "RDSTLS": - resp.SecurityLayer.RDSTLS = true - case "CredSSPWithEarlyUserAuth": - resp.SecurityLayer.CredSSPWithEarlyUserAuth = true + switch SecurityLayer(name) { + case SecurityLayerNativeRDP: + resp.NativeRDP = true + case SecurityLayerSSL: + resp.SSL = true + case SecurityLayerCredSSP: + resp.CredSSP = true + case SecurityLayerRDSTLS: + resp.RDSTLS = true + case SecurityLayerCredSSPWithEarlyUserAuth: + resp.CredSSPWithEarlyUserAuth = true } } } // Test different encryption levels - ciphers := map[string]int{ - "RC4_40bit": 1, - "RC4_56bit": 8, - "RC4_128bit": 2, - "FIPS140_1": 16, + ciphers := map[EncryptionLevel]int{ + EncryptionLevelRC4_40bit: 1, + EncryptionLevelRC4_56bit: 8, + EncryptionLevelRC4_128bit: 2, + EncryptionLevelFIPS140_1: 16, } - for name, value := range ciphers { - conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port)) + for encryptionLevel, value := range ciphers { + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + conn, err := dialer.Fastdialer.Dial(ctx, "tcp", net.JoinHostPort(host, strconv.Itoa(port))) if err != nil { continue } - defer conn.Close() + defer func() { + _ = conn.Close() + }() // Test cipher - isRDP, err := testRDPCipher(conn, timeout, value) + isRDP, err := testRDPCipher(conn, value) if err == nil && isRDP { - switch name { - case "RC4_40bit": - resp.EncryptionLevel.RC4_40bit = true - case "RC4_56bit": - resp.EncryptionLevel.RC4_56bit = true - case "RC4_128bit": - resp.EncryptionLevel.RC4_128bit = true - case "FIPS140_1": - resp.EncryptionLevel.FIPS140_1 = true + switch encryptionLevel { + case EncryptionLevelRC4_40bit: + resp.RC4_40bit = true + case EncryptionLevelRC4_56bit: + resp.RC4_56bit = true + case EncryptionLevelRC4_128bit: + resp.RC4_128bit = true + case EncryptionLevelFIPS140_1: + resp.FIPS140_1 = true } } } @@ -241,13 +277,7 @@ func checkRDPEncryption(host string, port int) (RDPEncryptionResponse, error) { } // testRDPProtocol tests RDP with a specific security protocol -func testRDPProtocol(conn net.Conn, timeout time.Duration, protocol int) (bool, error) { - // Set connection timeout - _ = conn.SetDeadline(time.Now().Add(timeout)) - defer func() { - _ = conn.SetDeadline(time.Time{}) - }() - +func testRDPProtocol(conn net.Conn, protocol int) (bool, error) { // Send RDP connection request with specific protocol // This is a simplified version - in reality you'd need to implement the full RDP protocol // including the negotiation phase with the specified protocol @@ -280,13 +310,7 @@ func testRDPProtocol(conn net.Conn, timeout time.Duration, protocol int) (bool, } // testRDPCipher tests RDP with a specific encryption level -func testRDPCipher(conn net.Conn, timeout time.Duration, cipher int) (bool, error) { - // Set connection timeout - _ = conn.SetDeadline(time.Now().Add(timeout)) - defer func() { - _ = conn.SetDeadline(time.Time{}) - }() - +func testRDPCipher(conn net.Conn, cipher int) (bool, error) { // Send RDP connection request with specific cipher // This is a simplified version - in reality you'd need to implement the full RDP protocol // including the negotiation phase with the specified cipher