For more information regarding the security incident at F5, the actions we are taking to address it, and our ongoing efforts to protect our customers, click here.

Server Name Indication (SNI) based RDP Proxy

Problem this snippet solves:

The outlined iRule can be used to access multiple TLS enabled Remote Desktop Hosts behind a single Virtual Server.

The code below, will emulate the initial RDP connection handshake to the client, till the client sends the TLS based SNI record (via Start-TLS message).

It would then use a data-group to compare the SNI information with valid FQDNs. If a valid FQDN is found, it would used the data-group value to set up the backend RDP connection. It would then emulate the initial RDP connection handshake to the backend server and finally forward the original Start-TLS message to the selected backend server.

If a valid FQDN couldn't be found in the Start-TLS message it would reject the RDP connection...

Cheers, Kai

How to use this snippet:

  • Setup a standard Virtual Server for TCP:3389.
  • Apply SNAT and TCP profiles as needed.
  • You don't need to add a default_pool.
  • Create the DG_RDP_SERVER data-group to resolves the valid FQDNs to their Nodes.

Code :

ltm data-group internal DG_RDP_SERVER {
    records {
        server1.itacs.de {
            data "192.168.1.1 3389"
        }
        server2.itacs.de {
            data "192.168.1.2 3389"
        }
    }
    type string
}

when CLIENT_ACCEPTED {
# Init packet counter
set rdp_packet 0
# Collect client side RDP data
TCP::collect
}
when CLIENT_DATA {
if { [incr rdp_packet] == 1 } then {
# Directly respond to the intial RDP protocol negotiation (using fixed payload)
TCP::respond [b64decode AwAAEw7QAAASNAACDwgACAAAAA==]
# Drop the received client payload and collect addtional payload
TCP::payload replace 0 [TCP::payload length] ""
TCP::collect
} elseif { $rdp_packet == 2 } then {
# Match the second RDP packet (aka. Start-TLS) for known SNI records using a datagroup. 
if { [set node [class match -value [string tolower [TCP::payload]] contains DG_RDP_SERVER]] ne "" } then {
# The Start-TLS packet contains a known SNI value. Using the datagroup result for node selection.
node $node
# Store the Start-TLS packet for later use.
set tls_start [TCP::payload]
# Replace the payload to negotiate the server side RDP connection. (using fixed payload)
TCP::payload replace 0 [TCP::payload length] [b64decode AwAAEw7gAAAAAAABAAgACwAAAA==]
# Release the request
TCP::release
} else {
# Start-TLS packet didn't contain a known FQDN. Rejecting the connection...
TCP::release
reject
}
}
}

when SERVER_CONNECTED {
# Collect server side RDP data
TCP::collect
}
when SERVER_DATA {
if { [info exist tls_start] } then { 
# Server Side RDP / TLS negotiation is in progress. Drop the initial RDP connection handshake, since the client has already established the connection.
TCP::payload replace 0 [TCP::payload length] ""
# Replay the stored Start-TLS playload to the server side.
TCP::respond $tls_start
unset -nocomplain tls_start
} else {
# Server Side RDP / TLS negotiation has completed. 
# Release the request
TCP::release 
}
}

Tested this on version:

12.0
Published Jan 25, 2016
Version 1.0
No CommentsBe the first to comment