Skip to content

CheckRDPEncryption function #6204

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

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions cmd/nuclei/rdp.yaml
Original file line number Diff line number Diff line change
@@ -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
10 changes: 6 additions & 4 deletions pkg/js/generated/go/librdp/rdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
46 changes: 38 additions & 8 deletions pkg/js/generated/ts/rdp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


/**
* CheckRDPAuth checks if the given host and port are running rdp server
* with authentication and returns their metadata.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -71,8 +103,6 @@ export interface IsRDPResponse {
OS?: string,
}



/**
* ServiceRDP Interface
*/
Expand Down
16 changes: 16 additions & 0 deletions pkg/js/libs/rdp/memo.rdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
186 changes: 186 additions & 0 deletions pkg/js/libs/rdp/rdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rdp
import (
"context"
"fmt"
"net"
"time"

"github.com/praetorian-inc/fingerprintx/pkg/plugins"
Expand Down Expand Up @@ -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
Comment on lines +160 to +176
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

defer conn.Close() inside a tight loop leaks sockets until function exit

Every iteration defers a close that is only executed when checkRDPEncryption returns.
For a single call that’s ~9 open TCP fds; across many goroutines this quickly exhausts file descriptors and slows scanning.

-   conn, err := protocolstate.Dialer.Dial(...)
-   ...
-   defer conn.Close()
+   conn, err := protocolstate.Dialer.Dial(...)
+   ...
+   // close immediately after the test to free the fd
+   isRDP, err := testRDPProtocol(conn, timeout, value)
+   _ = conn.Close()

Do the same in the cipher loop below (lines 202-208).

Committable suggestion skipped: line range outside the PR's diff.

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
}
Loading