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

nla redirection: use certificate of original server #424

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

spameier
Copy link
Contributor

fixes #423

@obilodeau obilodeau added this to the v1.2.0 milestone Dec 1, 2022
@obilodeau
Copy link
Member

At first, this looks good. I'm wondering about certificate caching though, I take a deeper look later.

Scheduling for our next release.

@obilodeau
Copy link
Member

The patch crashes for me:

[2022-12-15 14:43:26,023] - INFO - June698880 - pyrdp.mitm.connections - Fetching certificate of the original host 127.0.0.1:13389 because of NLA redirection
Unhandled Error
Traceback (most recent call last):
  File "/usr/lib/python3.10/asyncio/base_events.py", line 603, in run_forever
    self._run_once()
  File "/usr/lib/python3.10/asyncio/base_events.py", line 1899, in _run_once
    handle._run()
  File "/usr/lib/python3.10/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/home/olivier/Documents/gosecure/src/pyrdp/venv/lib/python3.10/site-packages/twisted/internet/asyncioreactor.py", line 271, in _onTimer
    self.runUntilCurrent()
--- <exception caught here> ---
  File "/home/olivier/Documents/gosecure/src/pyrdp/venv/lib/python3.10/site-packages/twisted/internet/base.py", line 994, in runUntilCurrent
    call.func(*call.args, **call.kw)
  File "/home/olivier/Documents/gosecure/src/pyrdp/pyrdp/mitm/RDPMITM.py", line 231, in doClientTls
    pem = ssl.get_server_certificate(
  File "/usr/lib/python3.10/ssl.py", line 1524, in get_server_certificate
    with context.wrap_socket(sock, server_hostname=host) as sslsock:
  File "/usr/lib/python3.10/ssl.py", line 513, in wrap_socket
    return self.sslsocket_class._create(
  File "/usr/lib/python3.10/ssl.py", line 1071, in _create
    self.do_handshake()
  File "/usr/lib/python3.10/ssl.py", line 1342, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLZeroReturnError: TLS/SSL connection has been closed (EOF) (_ssl.c:997)

However if I connect directly to the non-NLA host it works:

[2022-12-15 14:52:04,859] - INFO - David903492 - pyrdp.mitm.connections.tcp - New client connected from 127.0.0.1:54064
[2022-12-15 14:52:04,859] - INFO - David903492 - pyrdp.mitm.connections.x224 - Cookie: mstshash=Administrator
[2022-12-15 14:52:04,860] - INFO - David903492 - pyrdp.mitm.connections.tcp - Server connected
[2022-12-15 14:52:06,258] - INFO - David903492 - pyrdp.mitm.connections.cert - Cloned server certificate to pyrdp_output/certs/Win11EvalRDPClient.crt

@obilodeau obilodeau added the investigate Needs more thought / experience label Dec 15, 2022
@obilodeau
Copy link
Member

Spent the last 3 hours on this and couldn't manage a fix. This is happening deep in Twisted land.

Something from the X224MITM layer side must notify the RDPMITM side before we kill the original connection and we go grab the cert. However, this doesn't work synchronously and I couldn't manage to introduce a new async state that wouldn't continue the X224MITM code and thus kill the connection and start a new one.

I should probably refactor and design/document a state machine. I'm not going to do this now. I want to release next week so I'll unfortunately defer this patch unless you manage to fix it.

@obilodeau obilodeau modified the milestones: v1.2.0, v1.3.0 Dec 15, 2022
@obilodeau
Copy link
Member

I played with this a little bit more this morning and I don't understand how could the synchronous code you proposed worked.

The high-level ssl.get_server_certificate() call will fail on regular RDP servers because some client-side nego stuff needs to be sent before RDP puts itself into TLS mode.

This simple test:

import ssl

hostname = "127.0.0.1"
port = 13389

pem = ssl.get_server_certificate((hostname, port))

Will yield a:

SSLZeroReturnError: TLS/SSL connection has been closed (EOF) (_ssl.c:997)

And at first I thought it was due to automatic cert validation but then I modified it to:

import socket
import ssl

hostname = "127.0.0.1"
port = 13389

context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
context.verify_mode = ssl.CERT_NONE
context.check_hostname = False

with socket.create_connection((hostname, port)) as sock:
    with context.wrap_socket(sock) as sslsock:

        der_cert = sslsock.getpeercert(True)

        # from binary DER format to PEM
        pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)
        print(pem_cert)

and still get the same exception. However, an openssl s_client connection does work and return a certificate.

If your initial patch worked in your setup, I wonder what could be different?...

@spameier
Copy link
Contributor Author

That's really weird, in my setup your code snippet to retrieve the PEM works without issues:

$ python3     
Python 3.10.8 (main, Nov  4 2022, 09:21:25) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ssl
>>> pem = ssl.get_server_certificate(("192.168.251.12",3389))
>>> print(pem)
-----BEGIN CERTIFICATE-----
MIIC9DCCAdygAwIBAgIQa7p8SPWng49GWHgsA+5SVzANBgkqhkiG9w0BAQsFADAj
[...]

The code works on my machine(:tm:) (as seen in the line Fetching certificate of the original host...):

$ ./bin/pyrdp-mitm.py --nla-redirection-host pyrdp-server-no-nla.pyrdp.local --nla-redirection-port 3389 pyrdp-server.pyrdp.local
[2022-12-16 23:41:48,224] - INFO - GLOBAL - pyrdp.mitm - Target: pyrdp-server.pyrdp.local:3389
[2022-12-16 23:41:48,224] - INFO - GLOBAL - pyrdp.mitm - Output directory: /home/user/pyrdp-original/pyrdp_output
[2022-12-16 23:41:48,225] - INFO - GLOBAL - pyrdp - MITM Server listening on 0.0.0.0:3389
[2022-12-16 23:41:51,563] - INFO - Easter923017 - pyrdp.mitm.connections.tcp - New client connected from 192.168.254.107:57354
[2022-12-16 23:41:51,563] - INFO - Easter923017 - pyrdp.mitm.connections.x224 - Cookie: mstshash=user@pyrd
[2022-12-16 23:41:51,567] - INFO - Easter923017 - pyrdp.mitm.connections.tcp - Server connected
[2022-12-16 23:41:51,572] - INFO - Easter923017 - pyrdp.mitm.connections.x224 - The server forces the use of NLA. Using redirection host: pyrdp-server-no-nla.pyrdp.local:3389
[2022-12-16 23:41:51,572] - INFO - Easter923017 - pyrdp.mitm.connections.x224 - Cookie: mstshash=user@pyrd
[2022-12-16 23:41:51,574] - INFO - Easter923017 - pyrdp.mitm.connections.tcp - Server connected
[2022-12-16 23:41:52,580] - INFO - Easter923017 - pyrdp.mitm.connections - Fetching certificate of the original host pyrdp-server.pyrdp.local:3389 because of NLA redirection
[2022-12-16 23:41:52,623] - INFO - Easter923017 - pyrdp.mitm.connections.cert - Cloned server certificate to pyrdp_output/certs/pyrdp-server.pyrdp.local.crt
[2022-12-16 23:41:52,633] - INFO - Easter923017 - pyrdp.mitm.connections.tcp - Client connection closed. Connection to the other side was lost in a non-clean fashion: Connection lost.
[2022-12-16 23:41:52,633] - INFO - Easter923017 - pyrdp.mitm.connections.tcp - Connection report: report: 1.0, connectionTime: 1.0700116157531738, totalInput: 0, totalOutput: 0, replayFilename: rdp_replay_20221216_23-41-51_562_Easter923017.pyrdp
[2022-12-16 23:42:11,000] - INFO - Sandra817743 - pyrdp.mitm.connections.tcp - New client connected from 192.168.254.107:57355
[2022-12-16 23:42:11,000] - INFO - Sandra817743 - pyrdp.mitm.connections.x224 - Cookie: mstshash=user@pyrd
[2022-12-16 23:42:11,002] - INFO - Sandra817743 - pyrdp.mitm.connections.tcp - Server connected
[2022-12-16 23:42:11,006] - INFO - Sandra817743 - pyrdp.mitm.connections.x224 - The server forces the use of NLA. Using redirection host: pyrdp-server-no-nla.pyrdp.local:3389
[2022-12-16 23:42:11,007] - INFO - Sandra817743 - pyrdp.mitm.connections.x224 - Cookie: mstshash=user@pyrd
[2022-12-16 23:42:11,008] - INFO - Sandra817743 - pyrdp.mitm.connections.tcp - Server connected
[2022-12-16 23:42:12,015] - INFO - Sandra817743 - pyrdp.mitm.connections - Fetching certificate of the original host pyrdp-server.pyrdp.local:3389 because of NLA redirection
[2022-12-16 23:42:12,037] - INFO - Sandra817743 - pyrdp.mitm.connections.cert - Using cached certificate for pyrdp-server.pyrdp.local
CLIENT_RANDOM 639cf443c959cc9b858438d09e355b6e6a86832b28776e824f0c82a317384993 1d7b4ef5a7b244b6d466183dcb3dc0ec2fcb3a6ac30433ef6710886889bc7b00f2dd291348926d7a4571f7ccce49bc66
[2022-12-16 23:42:12,048] - INFO - Sandra817743 - pyrdp.mitm.connections.mcs - Client hostname DESKTOP-1K4UP57
CLIENT_RANDOM cd6048a1a70f8935b3e27e54f3afbe455589d1c2a5c9e0e4c9d7b74381655d3f 8a7f22c3be335ab1ca358b2057550ed64a722106ce962da762e18a1819b64e6686870f189527c697248c95c085e74f81
[2022-12-16 23:42:12,067] - INFO - Sandra817743 - pyrdp.mitm.connections.mcs - rdpdr <---> Channel #1004
[2022-12-16 23:42:12,067] - INFO - Sandra817743 - pyrdp.mitm.connections.mcs - rdpsnd <---> Channel #1005
[2022-12-16 23:42:12,067] - INFO - Sandra817743 - pyrdp.mitm.connections.mcs - cliprdr <---> Channel #1006
[2022-12-16 23:42:12,068] - INFO - Sandra817743 - pyrdp.mitm.connections.mcs - drdynvc <---> Channel #1007

Also, while testing I had no problems at all. In fact, the reason for using the synchronous ssl.get_server_certificate was to bypass the whole complexity of twisted.

My virtual setup is as follows:

  • Windows 10 22H2 build 19045.2364
  • Windows Server 2022 21H2 build 19045.2364 (three times: one DC, one NLA enforcing and one not)
  • Kali 2022.3
  • VyOS Router

The clients (Win 10 and Kali) and the servers are in separate IPv4 subnets (servers: 192.168.251.0/24, clients: 192.168.254.0/24). The Windows 10 client is then ARP spoofed by Kali using bettercap.

@obilodeau
Copy link
Member

I just doublechecked and still get the EOF problem. What is your OpenSSL version? I believe python's ssl module is influenced by it.

Here is mine:

$ python
Python 3.10.8 (main, Nov  1 2022, 14:18:21) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ssl
>>> ssl.OPENSSL_VERSION
'OpenSSL 3.0.7 1 Nov 2022'

@spameier
Copy link
Contributor Author

I have the same version:

$ python                    
Python 3.10.8 (main, Nov  4 2022, 09:21:25) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ssl
>>> ssl.OPENSSL_VERSION
'OpenSSL 3.0.7 1 Nov 2022

I looked up a NLA enforcing RDP server with a hopefully static IP on shodan. Can you also check against that?

I have the following output:

$ python3 -c 'import ssl;print(ssl.get_server_certificate(("168.119.201.150",3389)))'
-----BEGIN CERTIFICATE-----
MIIC5DCCAcygAwIBAgIQdJgbWE24nalAN/h3dCDwyzANBgkqhkiG9w0BAQsFADAb
MRkwFwYDVQQDHhAAVgBXAFMgFABFAEIAQQBZMB4XDTIyMDkwOTAyNDY1N1oXDTIz
MDMxMTAyNDY1N1owGzEZMBcGA1UEAx4QAFYAVwBTIBQARQBCAEEAWTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALViGFlK5ZwR9t7D170JKOo4U3mZd8OG
iRZU0aPLc7ltwipxx2vLhe4aBRxvdI/qsZxJxVQKDewglQX7xsC0W8aSEtsdEfgG
VZwCqTazTqSOhC8qG3QlXODCD/g1yUQrD2tf/OfYGpzkECYU5WkUDLnXd9rhOOl8
zgHItsw9Wtj2TJKCKj0HTD+7UFpqLiST1+BwAArDbFtSCBI49HWY9J2x4uxQCIZC
WDxSe+VLLDdtAAADBOXiDarKEKcxF8t4xrTw3W5chxPKmFEKKZZ9QlxS6NQe6pTO
C5v883obx7RngE9IaJQBovpuci706oIIj8u3sqS7hS7wTdnQTYpJlbECAwEAAaMk
MCIwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCwYDVR0PBAQDAgQwMA0GCSqGSIb3DQEB
CwUAA4IBAQBejLyTYiXQzrO9l2+oNJCrMU+pypukvs3KDXe70aYwgQkn4mDTWsl8
zCvIc8pWSI9KfU2Cd83oIYSW9adqm1D5VVMEYdZo76k7mbbjiPm4LF2TANeU8P1R
j6yNLm1g8cI8kjOOHqP5KNXlcGBWgyaTGTFq7jXztv8D+y3e5ulUQ/LdobGxO9fj
KjiBr+IyY1Uhvv5za8ucUFAnGZgcrgF708C/nJ+ANea25huOPl+3r7yIyAkE4UNM
qMZN6eleAJlJYke+QqiLm9ohYPhj3OUUz67ogjn0Q4HXPQJBhBEjEF5BCr/AZr2E
oJCXhhcIzwZHJwDAtoaqSJGBnw4/4XFL
-----END CERTIFICATE-----

xfreerdp shows that NLA is required (Error: HYBRID_REQUIRED_BY_SERVER):

$ xfreerdp /v:168.119.201.150 -sec-nla                         
[20:02:58:712] [130217:130218] [INFO][com.freerdp.client.x11] - No user name set. - Using login name: user
[20:02:59:173] [130217:130218] [WARN][com.freerdp.core.nego] - Error: HYBRID_REQUIRED_BY_SERVER
[20:02:59:245] [130217:130218] [ERROR][com.freerdp.core.transport] - BIO_read returned a system error 104: Connection reset by peer
[20:02:59:245] [130217:130218] [ERROR][com.freerdp.core] - transport_read_layer:freerdp_set_last_error_ex ERRCONNECT_CONNECT_TRANSPORT_FAILED [0x0002000D]
[20:02:59:454] [130217:130218] [WARN][com.freerdp.core.nego] - Error: HYBRID_REQUIRED_BY_SERVER
[20:02:59:522] [130217:130218] [ERROR][com.freerdp.core.transport] - BIO_read returned a system error 104: Connection reset by peer
[20:02:59:522] [130217:130218] [ERROR][com.freerdp.core] - transport_read_layer:freerdp_set_last_error_ex ERRCONNECT_CONNECT_TRANSPORT_FAILED [0x0002000D]
[20:02:59:522] [130217:130218] [ERROR][com.freerdp.core] - freerdp_post_connect failed
$ nmap -n -Pn -p3389 -sC --script="rdp*" 168.119.201.150
Starting Nmap 7.93 ( https://nmap.org ) at 2022-12-19 20:02 CET
Nmap scan report for 168.119.201.150
Host is up (0.027s latency).

PORT     STATE SERVICE
3389/tcp open  ms-wbt-server
| rdp-ntlm-info: 
|   Target_Name: VWS-EBAY
|   NetBIOS_Domain_Name: VWS-EBAY
|   NetBIOS_Computer_Name: VWS-EBAY
|   DNS_Domain_Name: VWS\xE2\x80\x94EBAY
|   DNS_Computer_Name: VWS\xE2\x80\x94EBAY
|   Product_Version: 10.0.14393
|_  System_Time: 2022-12-19T19:02:41+00:00
| rdp-enum-encryption: 
|   Security layer
|     CredSSP (NLA): SUCCESS
|     CredSSP with Early User Auth: SUCCESS
|_    RDSTLS: SUCCESS

Nmap done: 1 IP address (1 host up) scanned in 2.31 seconds

@spameier
Copy link
Contributor Author

spameier commented Dec 20, 2022

I tried the same commands on an older CentOS system:

$ grep PRETTY_NAME /etc/os-release
PRETTY_NAME="CentOS Linux 7 (Core)"
$ python3 -c 'import ssl; print(ssl.OPENSSL_VERSION)'
OpenSSL 1.0.2k-fips  26 Jan 2017
$ python3 -c 'import ssl;print(ssl.get_server_certificate(("168.119.201.150",3389)))'
-----BEGIN CERTIFICATE-----
MIIC5DCCAcygAwIBAgIQdJgbWE24nalAN/h3dCDwyzANBgkqhkiG9w0BAQsFADAb
MRkwFwYDVQQDHhAAVgBXAFMgFABFAEIAQQBZMB4XDTIyMDkwOTAyNDY1N1oXDTIz
MDMxMTAyNDY1N1owGzEZMBcGA1UEAx4QAFYAVwBTIBQARQBCAEEAWTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALViGFlK5ZwR9t7D170JKOo4U3mZd8OG
iRZU0aPLc7ltwipxx2vLhe4aBRxvdI/qsZxJxVQKDewglQX7xsC0W8aSEtsdEfgG
VZwCqTazTqSOhC8qG3QlXODCD/g1yUQrD2tf/OfYGpzkECYU5WkUDLnXd9rhOOl8
zgHItsw9Wtj2TJKCKj0HTD+7UFpqLiST1+BwAArDbFtSCBI49HWY9J2x4uxQCIZC
WDxSe+VLLDdtAAADBOXiDarKEKcxF8t4xrTw3W5chxPKmFEKKZZ9QlxS6NQe6pTO
C5v883obx7RngE9IaJQBovpuci706oIIj8u3sqS7hS7wTdnQTYpJlbECAwEAAaMk
MCIwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCwYDVR0PBAQDAgQwMA0GCSqGSIb3DQEB
CwUAA4IBAQBejLyTYiXQzrO9l2+oNJCrMU+pypukvs3KDXe70aYwgQkn4mDTWsl8
zCvIc8pWSI9KfU2Cd83oIYSW9adqm1D5VVMEYdZo76k7mbbjiPm4LF2TANeU8P1R
j6yNLm1g8cI8kjOOHqP5KNXlcGBWgyaTGTFq7jXztv8D+y3e5ulUQ/LdobGxO9fj
KjiBr+IyY1Uhvv5za8ucUFAnGZgcrgF708C/nJ+ANea25huOPl+3r7yIyAkE4UNM
qMZN6eleAJlJYke+QqiLm9ohYPhj3OUUz67ogjn0Q4HXPQJBhBEjEF5BCr/AZr2E
oJCXhhcIzwZHJwDAtoaqSJGBnw4/4XFL
-----END CERTIFICATE-----

Edit: it also works on segfault.net's disposable root servers:

┌──(root💀sf-MangoMaterial)-[~]
└─# grep PRETTY_NAME /etc/os-release
PRETTY_NAME="Kali GNU/Linux Rolling"
                                                                                                                                                                                                                                            
┌──(root💀sf-MangoMaterial)-[~]
└─# uname -a
Linux sf-MangoMaterial 5.15.0-1022-aws #26-Ubuntu SMP Thu Oct 13 12:59:25 UTC 2022 x86_64 GNU/Linux

┌──(root💀sf-MangoMaterial)-[~]
└─# python3 -c 'import ssl; print(ssl.OPENSSL_VERSION)'
OpenSSL 3.0.7 1 Nov 2022
                                                                                                                                                                                                                                            
┌──(root💀sf-MangoMaterial)-[~]
└─# python3 -c 'import ssl;print(ssl.get_server_certificate(("168.119.201.150",3389)))'
-----BEGIN CERTIFICATE-----
MIIC5DCCAcygAwIBAgIQdJgbWE24nalAN/h3dCDwyzANBgkqhkiG9w0BAQsFADAb
MRkwFwYDVQQDHhAAVgBXAFMgFABFAEIAQQBZMB4XDTIyMDkwOTAyNDY1N1oXDTIz
MDMxMTAyNDY1N1owGzEZMBcGA1UEAx4QAFYAVwBTIBQARQBCAEEAWTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALViGFlK5ZwR9t7D170JKOo4U3mZd8OG
iRZU0aPLc7ltwipxx2vLhe4aBRxvdI/qsZxJxVQKDewglQX7xsC0W8aSEtsdEfgG
VZwCqTazTqSOhC8qG3QlXODCD/g1yUQrD2tf/OfYGpzkECYU5WkUDLnXd9rhOOl8
zgHItsw9Wtj2TJKCKj0HTD+7UFpqLiST1+BwAArDbFtSCBI49HWY9J2x4uxQCIZC
WDxSe+VLLDdtAAADBOXiDarKEKcxF8t4xrTw3W5chxPKmFEKKZZ9QlxS6NQe6pTO
C5v883obx7RngE9IaJQBovpuci706oIIj8u3sqS7hS7wTdnQTYpJlbECAwEAAaMk
MCIwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCwYDVR0PBAQDAgQwMA0GCSqGSIb3DQEB
CwUAA4IBAQBejLyTYiXQzrO9l2+oNJCrMU+pypukvs3KDXe70aYwgQkn4mDTWsl8
zCvIc8pWSI9KfU2Cd83oIYSW9adqm1D5VVMEYdZo76k7mbbjiPm4LF2TANeU8P1R
j6yNLm1g8cI8kjOOHqP5KNXlcGBWgyaTGTFq7jXztv8D+y3e5ulUQ/LdobGxO9fj
KjiBr+IyY1Uhvv5za8ucUFAnGZgcrgF708C/nJ+ANea25huOPl+3r7yIyAkE4UNM
qMZN6eleAJlJYke+QqiLm9ohYPhj3OUUz67ogjn0Q4HXPQJBhBEjEF5BCr/AZr2E
oJCXhhcIzwZHJwDAtoaqSJGBnw4/4XFL
-----END CERTIFICATE-----

@obilodeau
Copy link
Member

Yes, it seems to work but why wouldn't it work on a Windows 10 environment when pyrdp/xfreerdp/mstsc.exe does? It is not reliable enough to be integrated.

Here are results from two connection attempts on the same machine from the same terminal just a couple of seconds apart:

Python ssl:

In [3]: print(ssl.get_server_certificate(("127.0.0.1",13389)))
---------------------------------------------------------------------------
SSLZeroReturnError                        Traceback (most recent call last)

xfreerdp:

$ xfreerdp /v:127.0.0.1:13389
[16:59:43:111] [692814:692815] [INFO][com.freerdp.client.x11] - No user name set. - Using login name: olivier
[16:59:43:495] [692814:692815] [WARN][com.freerdp.crypto] - Certificate verification failure 'self-signed certificate (18)' at stack position 0
[16:59:43:495] [692814:692815] [WARN][com.freerdp.crypto] - CN = Win11EvalRDPServer
[16:59:43:497] [692814:692815] [ERROR][com.freerdp.crypto] - The host key for 127.0.0.1:13389 has changed

@spameier
Copy link
Contributor Author

Just for clarification and so I can replicate your setup: what are you running on port 13389? I mean is it Windows listening on that port or some kind of port forwarding?

@obilodeau
Copy link
Member

Just for clarification and so I can replicate your setup: what are you running on port 13389? I mean is it Windows listening on that port or some kind of port forwarding?

It's a VirtualBox host port forward to 3389 on the guest:

image

Ok, I was mistaken about Windows 10. It works on Windows 10. It's on Windows 11 that it doesn't work.

@spameier
Copy link
Contributor Author

spameier commented Jan 2, 2023

I'm sorry but I cannot reproduce your error message (SSLZeroReturnError). I tried to reproduce your setup with a VirtualBox guest and port forwarding but never encountered the error message you shared.

$ python3 -c 'import ssl;print(ssl.get_server_certificate(("127.0.0.1",13389)))'
-----BEGIN CERTIFICATE-----
MIIC2DCCAcCgAwIBAgIQWDEQ6cTaZLtFTH/HNL1JeDANBgkqhkiG9w0BAQsFADAV
MRMwEQYDVQQDEwp3aW4xMS12Ym94MB4XDTIzMDEwMTE0MjUxMFoXDTIzMDcwMzE0
MjUxMFowFTETMBEGA1UEAxMKd2luMTEtdmJveDCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANq7vvcvGmRBocdSgIlDPdKwaIz2zNilqm5yctOgELE3I0hK
b2x/s3/2SH2mZ2O964ww2AVzEPpF/pM19MXz1YmDFB+LxVQm4jhnMiyev1Eg3mfA
34sKTrz1G8WU9FyMLuJ3j5kdasNog/3E3ApARKnyLhy8VXAsu+kUXu9KLV/bGgUj
djTCngRKv7YdSuXE9ezBj0NdYS+i/1wtOhGGJOAe/8+DJxyiRmXGGF2MrkbZH2nX
GnDlnKJZ5uL83vctcpyvmksnIs5gWK9tUDsx95A5svmdc6G8Dg48DyVIn06+YdD5
neeqTai6NjwIEh+iUA1Dzv1WUksVOE6Nw6RYtvUCAwEAAaMkMCIwEwYDVR0lBAww
CgYIKwYBBQUHAwEwCwYDVR0PBAQDAgQwMA0GCSqGSIb3DQEBCwUAA4IBAQBhAbgO
ivjlE5TxURm8sCm/U+mGotFsRU5HKMtqp2v23tP26kcYNNw3BmHQPIuI61/osix/
5mOcQRBWpGGEyZbLo9sDBtNe/2OaW8qz/ub3mw63hn63wYijKIy2NMtNcv+k6lr0
4rrt+a6lOBX++NfpI1wKL/bV/VC2/htxTu9HxKA/ayL6XtAbENDQFV6lvLYpvwLF
iP3sv2ffWOuYob9euL0yyvmot/8cOsGXqzeEinuvj1ZaZiG3UY9N5K1r+VMG6qyR
jKy/lAWCUWBU+EO6fYBAy9CNVGnxwDOYxAPTJgyT81Zk15T12BzDh+7cXbY8mHsG
92YynWYD4hmG/wFO
-----END CERTIFICATE-----

@obilodeau
Copy link
Member

Connecting to a Windows 10 server with the longer form of the test code (available here) it works:

$ python test_ssl.py 
/home/olivier/Documents/gosecure/src/pyrdp/test_ssl.py:7: DeprecationWarning: ssl.PROTOCOL_TLS is deprecated
  context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
-----BEGIN CERTIFICATE-----
[...]

Connecting to Windows 11 stops working:

$ python test_ssl.py 
/home/olivier/Documents/gosecure/src/pyrdp/test_ssl.py:7: DeprecationWarning: ssl.PROTOCOL_TLS is deprecated
  context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
Traceback (most recent call last):
  File "/home/olivier/Documents/gosecure/src/pyrdp/test_ssl.py", line 12, in <module>
    with context.wrap_socket(sock) as sslsock:
  File "/usr/lib/python3.10/ssl.py", line 513, in wrap_socket
    return self.sslsocket_class._create(
  File "/usr/lib/python3.10/ssl.py", line 1071, in _create
    self.do_handshake()
  File "/usr/lib/python3.10/ssl.py", line 1342, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLZeroReturnError: TLS/SSL connection has been closed (EOF) (_ssl.c:997)

I'm not sure what changed with Windows 11 but clearly the default way of fetching the certificate no longer works with it. However, since PyRDP can intercept Windows 11 connections, I started digging into how we do connect and found out we are using OpenSSL's API instead of Python's ssl module.

I adapted the example code from above and now it works!

import socket

import OpenSSL
from OpenSSL import SSL


hostname = "127.0.0.1"
port = 13389

# taken from: https://gist.github.com/shanemhansen/3853468
def verify_cb(conn, cert, errnum, depth, ok):
    # This obviously has to be updated
    print('Got certificate: %s' % cert.get_subject())
    print(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert).decode('ascii'))
    #don't ever do this in production.
    #this force verifies all certs.
    return 1

# Taken from: pyrdp/core/ssl.py's ClientTLSContext
context = SSL.Context(SSL.SSLv23_METHOD)
context.set_options(SSL.OP_DONT_INSERT_EMPTY_FRAGMENTS)
context.set_options(SSL.OP_TLS_BLOCK_PADDING_BUG)
context.set_options(SSL.OP_NO_TLSv1_3)

# install callback
context.set_verify(SSL.VERIFY_NONE, verify_cb)

# Set up client
sock = SSL.Connection(context, socket.socket(socket.AF_INET, socket.SOCK_STREAM))
sock.connect((hostname, port))

while True:
    try:
        buf = sock.recv(4096)
    except SSL.SysCallError:
        break
    if not buf:
        break

Here's the output:

$ python test_ssl.py 
Got certificate: <X509Name object '/CN=Win11EvalRDPServer'>
-----BEGIN CERTIFICATE-----
MIIC6DCCAdCgAwIBAgIQRYKZ5qKik79OvnoCOAcxCDANBgkqhkiG9w0BAQsFADAd
[...]
-----END CERTIFICATE-----

Got certificate: <X509Name object '/CN=Win11EvalRDPServer'>
-----BEGIN CERTIFICATE-----
MIIC6DCCAdCgAwIBAgIQRYKZ5qKik79OvnoCOAcxCDANBgkqhkiG9w0BAQsFADAd
[...]
-----END CERTIFICATE-----

If your patch could be adapted to use pyrdp/core/ssl.py's ClientTLSContext it would be closer to a merge. Unfortunately, I don't have time right now to do it myself.

@spameier
Copy link
Contributor Author

I tried to implement it with the handshake done in OpenSSL and it works for me. Can you please test?

@spameier
Copy link
Contributor Author

spameier commented Mar 5, 2023

Hi @obilodeau, what do you think about the latest change?

@obilodeau obilodeau modified the milestones: v1.3.0, v2.0.1 Dec 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
investigate Needs more thought / experience
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Wrong certificate when using NLA redirection.
2 participants