2023-09-16 16:02:17 +05:30
|
|
|
package rdp
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
2025-05-01 18:20:02 +05:30
|
|
|
"net"
|
2025-09-25 22:46:40 +02:00
|
|
|
"strconv"
|
2023-09-16 16:02:17 +05:30
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
|
|
|
|
|
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/rdp"
|
2023-10-17 17:44:13 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
2023-09-16 16:02:17 +05:30
|
|
|
)
|
|
|
|
|
|
2024-02-07 21:45:40 +05:30
|
|
|
type (
|
|
|
|
|
// IsRDPResponse is the response from the IsRDP function.
|
|
|
|
|
// this is returned by IsRDP function.
|
|
|
|
|
// @example
|
|
|
|
|
// ```javascript
|
|
|
|
|
// const rdp = require('nuclei/rdp');
|
|
|
|
|
// const isRDP = rdp.IsRDP('acme.com', 3389);
|
|
|
|
|
// log(toJSON(isRDP));
|
|
|
|
|
// ```
|
|
|
|
|
IsRDPResponse struct {
|
|
|
|
|
IsRDP bool
|
|
|
|
|
OS string
|
|
|
|
|
}
|
|
|
|
|
)
|
2023-09-16 16:02:17 +05:30
|
|
|
|
|
|
|
|
// IsRDP checks if the given host and port are running rdp server.
|
|
|
|
|
// If connection is successful, it returns true.
|
|
|
|
|
// If connection is unsuccessful, it returns false and error.
|
|
|
|
|
// The Name of the OS is also returned if the connection is successful.
|
2024-02-07 21:45:40 +05:30
|
|
|
// @example
|
|
|
|
|
// ```javascript
|
|
|
|
|
// const rdp = require('nuclei/rdp');
|
|
|
|
|
// const isRDP = rdp.IsRDP('acme.com', 3389);
|
|
|
|
|
// log(toJSON(isRDP));
|
|
|
|
|
// ```
|
2025-07-09 14:47:26 -05:00
|
|
|
func IsRDP(ctx context.Context, host string, port int) (IsRDPResponse, error) {
|
|
|
|
|
executionId := ctx.Value("executionId").(string)
|
|
|
|
|
return memoizedisRDP(executionId, host, port)
|
2024-03-01 16:10:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @memo
|
2025-07-09 14:47:26 -05:00
|
|
|
func isRDP(executionId string, host string, port int) (IsRDPResponse, error) {
|
2023-09-16 16:02:17 +05:30
|
|
|
resp := IsRDPResponse{}
|
|
|
|
|
|
2025-07-09 14:47:26 -05:00
|
|
|
dialer := protocolstate.GetDialersWithId(executionId)
|
|
|
|
|
if dialer == nil {
|
|
|
|
|
return IsRDPResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-16 16:02:17 +05:30
|
|
|
timeout := 5 * time.Second
|
2025-07-09 14:47:26 -05:00
|
|
|
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port))
|
2023-09-16 16:02:17 +05:30
|
|
|
if err != nil {
|
|
|
|
|
return resp, err
|
|
|
|
|
}
|
2025-07-01 00:40:44 +07:00
|
|
|
defer func() {
|
2025-07-09 14:47:26 -05:00
|
|
|
_ = conn.Close()
|
|
|
|
|
}()
|
2023-09-16 16:02:17 +05:30
|
|
|
|
|
|
|
|
server, isRDP, err := rdp.DetectRDP(conn, timeout)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return resp, err
|
|
|
|
|
}
|
|
|
|
|
if !isRDP {
|
|
|
|
|
return resp, nil
|
|
|
|
|
}
|
|
|
|
|
resp.IsRDP = true
|
|
|
|
|
resp.OS = server
|
|
|
|
|
return resp, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-07 21:45:40 +05:30
|
|
|
type (
|
|
|
|
|
// CheckRDPAuthResponse is the response from the CheckRDPAuth function.
|
|
|
|
|
// this is returned by CheckRDPAuth function.
|
|
|
|
|
// @example
|
|
|
|
|
// ```javascript
|
|
|
|
|
// const rdp = require('nuclei/rdp');
|
|
|
|
|
// const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);
|
|
|
|
|
// log(toJSON(checkRDPAuth));
|
|
|
|
|
// ```
|
|
|
|
|
CheckRDPAuthResponse struct {
|
|
|
|
|
PluginInfo *plugins.ServiceRDP
|
|
|
|
|
Auth bool
|
|
|
|
|
}
|
|
|
|
|
)
|
2023-09-16 16:02:17 +05:30
|
|
|
|
|
|
|
|
// CheckRDPAuth checks if the given host and port are running rdp server
|
|
|
|
|
// with authentication and returns their metadata.
|
2024-02-07 21:45:40 +05:30
|
|
|
// If connection is successful, it returns true.
|
|
|
|
|
// @example
|
|
|
|
|
// ```javascript
|
|
|
|
|
// const rdp = require('nuclei/rdp');
|
|
|
|
|
// const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);
|
|
|
|
|
// log(toJSON(checkRDPAuth));
|
|
|
|
|
// ```
|
2025-07-09 14:47:26 -05:00
|
|
|
func CheckRDPAuth(ctx context.Context, host string, port int) (CheckRDPAuthResponse, error) {
|
|
|
|
|
executionId := ctx.Value("executionId").(string)
|
|
|
|
|
return memoizedcheckRDPAuth(executionId, host, port)
|
2024-03-01 16:10:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @memo
|
2025-07-09 14:47:26 -05:00
|
|
|
func checkRDPAuth(executionId string, host string, port int) (CheckRDPAuthResponse, error) {
|
2023-09-16 16:02:17 +05:30
|
|
|
resp := CheckRDPAuthResponse{}
|
|
|
|
|
|
2025-07-09 14:47:26 -05:00
|
|
|
dialer := protocolstate.GetDialersWithId(executionId)
|
|
|
|
|
if dialer == nil {
|
|
|
|
|
return CheckRDPAuthResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
|
|
|
|
|
}
|
2023-09-16 16:02:17 +05:30
|
|
|
timeout := 5 * time.Second
|
2025-07-09 14:47:26 -05:00
|
|
|
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port))
|
2023-09-16 16:02:17 +05:30
|
|
|
if err != nil {
|
|
|
|
|
return resp, err
|
|
|
|
|
}
|
2025-07-01 00:40:44 +07:00
|
|
|
defer func() {
|
2025-07-09 14:47:26 -05:00
|
|
|
_ = conn.Close()
|
|
|
|
|
}()
|
2023-09-16 16:02:17 +05:30
|
|
|
|
|
|
|
|
pluginInfo, auth, err := rdp.DetectRDPAuth(conn, timeout)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return resp, err
|
|
|
|
|
}
|
|
|
|
|
if !auth {
|
|
|
|
|
return resp, nil
|
|
|
|
|
}
|
|
|
|
|
resp.Auth = true
|
|
|
|
|
resp.PluginInfo = pluginInfo
|
|
|
|
|
return resp, nil
|
|
|
|
|
}
|
2025-05-01 18:20:02 +05:30
|
|
|
|
2025-09-25 22:46:40 +02:00
|
|
|
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"
|
|
|
|
|
)
|
|
|
|
|
|
2025-05-01 18:20:02 +05:30
|
|
|
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 {
|
2025-09-25 22:46:40 +02:00
|
|
|
// Protocols
|
|
|
|
|
NativeRDP bool
|
|
|
|
|
SSL bool
|
|
|
|
|
CredSSP bool
|
|
|
|
|
RDSTLS bool
|
|
|
|
|
CredSSPWithEarlyUserAuth bool
|
|
|
|
|
|
|
|
|
|
// EncryptionLevels
|
|
|
|
|
RC4_40bit bool
|
|
|
|
|
RC4_56bit bool
|
|
|
|
|
RC4_128bit bool
|
|
|
|
|
FIPS140_1 bool
|
2025-05-01 18:20:02 +05:30
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 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));
|
|
|
|
|
// ```
|
2025-09-25 22:46:40 +02:00
|
|
|
func CheckRDPEncryption(ctx context.Context, host string, port int) (RDPEncryptionResponse, error) {
|
|
|
|
|
executionId := ctx.Value("executionId").(string)
|
|
|
|
|
return memoizedcheckRDPEncryption(executionId, host, port)
|
2025-05-01 18:20:02 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @memo
|
2025-09-25 22:46:40 +02:00
|
|
|
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)
|
|
|
|
|
}
|
2025-05-01 18:20:02 +05:30
|
|
|
resp := RDPEncryptionResponse{}
|
2025-09-25 22:46:40 +02:00
|
|
|
defaultTimeout := 5 * time.Second
|
2025-05-01 18:20:02 +05:30
|
|
|
|
|
|
|
|
// Test different security protocols
|
2025-09-25 22:46:40 +02:00
|
|
|
protocols := map[SecurityLayer]int{
|
|
|
|
|
SecurityLayerNativeRDP: 0,
|
|
|
|
|
SecurityLayerSSL: 1,
|
|
|
|
|
SecurityLayerCredSSP: 3,
|
|
|
|
|
SecurityLayerRDSTLS: 4,
|
|
|
|
|
SecurityLayerCredSSPWithEarlyUserAuth: 8,
|
2025-05-01 18:20:02 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for name, value := range protocols {
|
2025-09-25 22:46:40 +02:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
|
|
|
|
defer cancel()
|
|
|
|
|
conn, err := dialer.Fastdialer.Dial(ctx, "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
|
2025-05-01 18:20:02 +05:30
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2025-09-25 22:46:40 +02:00
|
|
|
defer func() {
|
|
|
|
|
_ = conn.Close()
|
|
|
|
|
}()
|
2025-05-01 18:20:02 +05:30
|
|
|
|
|
|
|
|
// Test protocol
|
2025-09-25 22:46:40 +02:00
|
|
|
isRDP, err := testRDPProtocol(conn, value)
|
2025-05-01 18:20:02 +05:30
|
|
|
if err == nil && isRDP {
|
2025-09-25 22:46:40 +02:00
|
|
|
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
|
2025-05-01 18:20:02 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Test different encryption levels
|
2025-09-25 22:46:40 +02:00
|
|
|
ciphers := map[EncryptionLevel]int{
|
|
|
|
|
EncryptionLevelRC4_40bit: 1,
|
|
|
|
|
EncryptionLevelRC4_56bit: 8,
|
|
|
|
|
EncryptionLevelRC4_128bit: 2,
|
|
|
|
|
EncryptionLevelFIPS140_1: 16,
|
2025-05-01 18:20:02 +05:30
|
|
|
}
|
|
|
|
|
|
2025-09-25 22:46:40 +02:00
|
|
|
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)))
|
2025-05-01 18:20:02 +05:30
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2025-09-25 22:46:40 +02:00
|
|
|
defer func() {
|
|
|
|
|
_ = conn.Close()
|
|
|
|
|
}()
|
2025-05-01 18:20:02 +05:30
|
|
|
|
|
|
|
|
// Test cipher
|
2025-09-25 22:46:40 +02:00
|
|
|
isRDP, err := testRDPCipher(conn, value)
|
2025-05-01 18:20:02 +05:30
|
|
|
if err == nil && isRDP {
|
2025-09-25 22:46:40 +02:00
|
|
|
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
|
2025-05-01 18:20:02 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// testRDPProtocol tests RDP with a specific security protocol
|
2025-09-25 22:46:40 +02:00
|
|
|
func testRDPProtocol(conn net.Conn, protocol int) (bool, error) {
|
2025-05-01 18:20:02 +05:30
|
|
|
// 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
|
2025-09-25 22:46:40 +02:00
|
|
|
func testRDPCipher(conn net.Conn, cipher int) (bool, error) {
|
2025-05-01 18:20:02 +05:30
|
|
|
// 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
|
|
|
|
|
}
|