Client-side TLS implementation assessment with qsslcaudit - The WPS Office case

Submitted by pavel on Fri, 02/28/2020 - 16:24

Preface

In this post we demonstrate how to use our tool to assess client-side TLS implementation: qsslcaudit. qsslcaudit helps determine if a TLS client (mobile application, standalone application, web service) properly validates server's certificate and if only secure protocols are supported. Issues in TLS implementation can be abused by attackers to intercept victim's traffic: extract sensitive information, alter client's requests or server's responses and so on.

Basic information on how to use the tool can be found in its README file. However, we believe that the best demo is real-world test scenario against existing and widely used application. For this demo, we chose to target KingSoft WPS Office Android mobile application.

The issues described here are still not fixed at the time of this writing. We reported them to KingSoft via HackerOne, issues were confirmed but not fixed. Then we reported them to Google, got a reply that this is a known problem as it was reported earlier. Given that Kingsoft developers are aware of it but chose not to fix it, and that Google and some bug bounty hunters also know about this issue, we decided to report it publicly. At least WPS Office users (**over 1.3 billion users** per Google Play Store estimates) will be aware of the risks of using it.

Timeline:

  • 27 September 2019. Reported to KingSoft via HackerOne.
  • 30 September 2019. KingSoft triaged the issue with comment: Confirmed to be fixed. SSL implementation.
  • 22 December 2019. Added reminder that the issues are still not fixed.
  • 27 January 2020. Reported to Google via HackerOne Google Play Security Reward Program.
  • 28 January 2020. Google replied that the report is being reviewed.
  • 3 February 2020. Google closed the report as duplicate.

Testing for client-side TLS implementation flaws

Here we describe the approach we follow when testing mobile applications regarding security of the connections they make to upstream servers.

Setup

We installed WPS Office application from the Play Store. The application version was 12.3.5: [geshifilter-]adb shell dumpsys package cn.wps.moffice_eng | grep -B1 versionName versionCode=352 targetSdk=28 versionName=12.3.5[/geshifilter-]

Traffic analysis

We configure our test laptop as WiFi access point for the mobile device running the application. We start capturing the traffic on wireless interface, launch the application and use all available features.

The idea of this exercise is to determine when the application establishes unencrypted connections or emits unusual type of traffic. If unencrypted communications have been found we investigate them separately. This is not discussed in this post.

WPS Office is quite talkative and establishes a large number of connections to various servers. We did not identify non-standard protocols, however, there were plenty of clear-text HTTP connections observed. For instance, the one that sends information about the device and application version:

clear-text traffic

This is a separate issue to investigate, the objective here is to practice our client-side TLS implementation assessment skills. Let's write down the list of HTTPS servers the application connects to:

  • dw-online.ksosoft.com
  • shuc-android.ksord.com
  • plugin-server.wps.com
  • movip.wps.com
  • graph.facebook.com
  • service-api.kingsoft-office-service.com
  • cloudservice14.kingsoft-office-service.com
  • store.wps.com
  • abroad-ad.kingsoft-office-service.com
  • firstpage.kingsoft-office-service.com
  • api-ad-adapter.wps.com
  • activity.wps.com
  • shuc-js.ksord.com
  • web.mo.wpscdn.cn
  • drive.wps.com
  • account.wps.com
  • androidweb.wps.com

As can be seen there are a plenty of hosts in this list and it's not even complete. So in order to keep this blog post short and readable, we will only assess a subset of the application's functionality.

On fresh start WPS Office application displays the privacy policy agreement screen with the only option to agree with it. Once you agreed, the next screen allows you to "Start WPS Office" or "Login in WPS Office" (if you slide right). Login function is interesting. If you select it the application displays various ways of logging in, along with a "Sign up" link. Clicking "Sign up" opens a screen which allows you to create an account in "WPS Services".

Let's focus on the account creation flow.

Client-side TLS implementation

In order to perform the assessment of client-side TLS implementation we need to redirect connections made by the client towards our software. This can be done in several ways which are higly dependent on your setup.

This time we chose to alter the DNS responses. The reason is that the target systems have several alternating IP addresses which could change. By altering the DNS response we can be sure that the application will connect to our listener.

We used create_ap to setup a WiFi access point. Its command line option -d tells DNS server (dnsmasq) to take into account /etc/hosts file. Thus, we can simply add the entries we want into /etc/hosts. For instance, if we want to catch connections made towards dw-online.ksosoft.com we add the following entry:

192.168.12.1 dw-online.ksosoft.com
Where `192.168.12.1` is the default IP address assigned to our wireless interface by `create_ap`.

We then launch qsslcaudit with proper set of options:

sudo qsslcaudit -l 0.0.0.0 -p 443 --user-cert subdomain.gremwell.com+chain.pem --user-key subdomain.gremwell.com.key --selected-tests certs --user-cn dw-online.ksosoft.com

Let's explain each parameter used:

  • -l 0.0.0.0 listen on all interfaces as the connections come from the outside (we can specify the IP of wireless interface here as well).
  • -p 443 listen on 443 port as we used /etc/hosts approach and the connection will be made to the original TCP port. This also requires to run qsslcaudit with superuser privileges.
  • --user-cert subdomain.gremwell.com+chain.pem --user-key subdomain.gremwell.com.key the certificate here is the perfectly valid certificate issued by a trusted certificate authority to subdomain.gremwell.com hostname. In one of the tests the tool will present this certificate. Despite the fact that the certificate is valid the client has to refuse to connect to it as it is issued to another common name. Without providing such certificate the tool will just omit the corresponding test and will present self-signed certificates only.
  • --user-cn dw-online.ksosoft.com the tool will issue self-signed certificates for "dw-online.ksosoft.com" common name. This way we can test if the client trusts any certificate with a valid common name.
  • --selected-tests certs limit to certificate validation tests only. We skip protocols support test just to keep our post short.

A bit more on qsslcaudit usage. Remember that we are testing client-side implementation, each separate test requires a new connection made by the client. Thus, we need to trigger such connections ourselves explicitly by pressing buttons/clicking links or just waiting for the client to retry connection.

We are almost ready to launch the test. In this case there is another caveat: the application uses HTTP service of dw-online.ksosoft.com too. We are not really interested for now in the traffic interception and investigation, but in order to keep the application logic fine, we have to properly handle this connection. The easiest way is to use socat to forward traffic received on port TCP/80 to the legitimate server, like this:

sudo socat TCP4-LISTEN:80,fork,reuseaddr TCP4:18.185.165.181:80

dw-online.ksosoft.com

qsslcaudit produced the following output:

sudo qsslcaudit -l 0.0.0.0 -p 443 --user-cert subdomain.gremwell.com_cert+chain.pem --user-key subdomain.gremwell.com.key --selected-tests certs --user-cn dw-online.ksosoft.com
preparing selected tests...
        skipping test: certificate trust test with user-supplied common name signed by user-supplied CA certificate
        skipping test: certificate trust test with www.example.com common name signed by user-supplied CA certificate
        CVE-2020-0601: no CA certificate provided
        skipping test: test for trusting certificate signed by private key with custom curve

SSL library used: OpenSSL 1.0.2u  20 Dec 2019

running test #1: certificate trust test with user-supplied certificate
listening on 0.0.0.0:443
connection from: 192.168.12.118:33468
SSL connection established
received data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1
accept: */*
connection: Keep-Alive
Accept-Encoding: gzip
Charset: UTF-8
User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36
Host: dw-online.ksosoft.com
Content-Type: application/json
Content-Length: 161

{"appVersion":"12.3.5","channel":"en00001","abTestVersion":0,"sendUrlVersion":0,"transportControlVersion":0,"eventsVersion":0,"abTestName":"","abTestGroupId":""}
disconnected
report:
test failed, client accepted fake certificate, data was intercepted
test finished


running test #2: certificate trust test with self-signed certificate for user-supplied common name
listening on 0.0.0.0:443
connection from: 192.168.12.118:53873
SSL connection established
received data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1
accept: */*
connection: Keep-Alive
Accept-Encoding: gzip
Charset: UTF-8
User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36
Host: dw-online.ksosoft.com
Content-Type: application/json
Content-Length: 161

{"appVersion":"12.3.5","channel":"en00001","abTestVersion":0,"sendUrlVersion":0,"transportControlVersion":0,"eventsVersion":0,"abTestName":"","abTestGroupId":""}
disconnected
report:
test failed, client accepted fake certificate, data was intercepted
test finished


running test #3: certificate trust test with self-signed certificate for www.example.com
listening on 0.0.0.0:443
connection from: 192.168.12.118:52806
SSL connection established
received data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1
accept: */*
connection: Keep-Alive
Accept-Encoding: gzip
Charset: UTF-8
User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36
Host: dw-online.ksosoft.com
Content-Type: application/json
Content-Length: 161

{"appVersion":"12.3.5","channel":"en00001","abTestVersion":0,"sendUrlVersion":0,"transportControlVersion":0,"eventsVersion":0,"abTestName":"","abTestGroupId":""}
disconnected
report:
test failed, client accepted fake certificate, data was intercepted
test finished


running test #4: certificate trust test with user-supplied common name signed by user-supplied certificate
listening on 0.0.0.0:443
connection from: 192.168.12.118:36898
SSL connection established
received data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1
accept: */*
connection: Keep-Alive
Accept-Encoding: gzip
Charset: UTF-8
User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36
Host: dw-online.ksosoft.com
Content-Type: application/json
Content-Length: 161

{"appVersion":"12.3.5","channel":"en00001","abTestVersion":0,"sendUrlVersion":0,"transportControlVersion":0,"eventsVersion":0,"abTestName":"","abTestGroupId":""}
disconnected
report:
test failed, client accepted fake certificate, data was intercepted
test finished


running test #5: certificate trust test with www.example.com common name signed by user-supplied certificate
listening on 0.0.0.0:443
connection from: 192.168.12.118:48969
SSL connection established
received data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1
accept: */*
connection: Keep-Alive
Accept-Encoding: gzip
Charset: UTF-8
User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36
Host: dw-online.ksosoft.com
Content-Type: application/json
Content-Length: 161

{"appVersion":"12.3.5","channel":"en00001","abTestVersion":0,"sendUrlVersion":0,"transportControlVersion":0,"eventsVersion":0,"abTestName":"","abTestGroupId":""}
disconnected
report:
test failed, client accepted fake certificate, data was intercepted
test finished

tests results summary table:
+----|------------------------------------|------------|-----------------------------+
| ## |             Test Name              |   Result   |           Comment           |
+----|------------------------------------|------------|-----------------------------+
|  1 | custom certificate trust           | FAILED !!! | mitm possible               |
|  2 | self-signed certificate for target | FAILED !!! | -//-                        |
|    |  domain trust                      |            |                             |
|  3 | self-signed certificate for invali | FAILED !!! | -//-                        |
|    | d domain trust                     |            |                             |
|  4 | custom certificate for target doma | FAILED !!! | -//-                        |
|    | in trust                           |            |                             |
|  5 | custom certificate for invalid dom | FAILED !!! | -//-                        |
|    | ain trust                          |            |                             |
+----|------------------------------------|------------|-----------------------------+
most likely all connections were established by the same client
the first connection details:
source host: 192.168.12.118
dtls?: false
ssl errors:
ssl conn established?: true
intercepted data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1
accept: */*
connection: Keep-Alive
Accept-Encoding: gzip
Charset: UTF-8
User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36
Host: dw-online.ksosoft.com
Content-Type: application/json
Content-Length: 161

{"appVersion":"12.3.5","channel":"en00001","abTestVersion":0,"sendUrlVersion":0,"transportControlVersion":0,"eventsVersion":0,"abTestName":"","abTestGroupId":""}
received data, bytes: 854
transmitted data, bytes: 5567
protocol: TLSv1.2
accepted ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_DHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:TLS_ECDHE_RSA_WITH_RC4_128_SHA:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_AES_256_CBC_SHA:TLS_RSA_WITH_RC4_128_SHA:TLS_EMPTY_RENEGOTIATION_INFO_SCSV
ALPN: http/1.1

qsslcaudit version: 0.8.1

You can already see from this output that we spotted an issue. But let's explain this output.

At first after launch the tool prepares all the selected tests. It feeds each test with user-supplied parameters and the test decides if it has all necessary data to be launched. For those tests that can not be run with the provided settings the tool prints the corresponding message:

preparing selected tests...
        skipping test: certificate trust test with user-supplied common name signed by user-supplied CA certificate
        skipping test: certificate trust test with www.example.com common name signed by user-supplied CA certificate
        CVE-2020-0601: no CA certificate provided
        skipping test: test for trusting certificate signed by private key with custom curve

Then it prints the OpenSSL library version used. It is important to verify it if some tests are unable to launch. This should be "1.0.2" version, like this one:

SSL library used: OpenSSL 1.0.2u  20 Dec 2019

After that the tool executes each test one by one. For each test a TLS listener is launched on the provided socket. The listener is configured with settings and certificates according to the current test. When the client connects to the listener and the TLS handshake is completed, the test is considered as done and the following output is printed:

Running test #1: certificate trust test with user-supplied certificate
listening on 0.0.0.0:443
connection from: 192.168.12.118:33468
SSL connection established
received data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1
accept: */*
connection: Keep-Alive
Accept-Encoding: gzip
Charset: UTF-8
User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36
Host: dw-online.ksosoft.com
Content-Type: application/json
Content-Length: 161

{"appVersion":"12.3.5","channel":"en00001","abTestVersion":0,"sendUrlVersion":0,"transportControlVersion":0,"eventsVersion":0,"abTestName":"","abTestGroupId":""}
disconnected
report:
test failed, client accepted fake certificate, data was intercepted
test finished

In this case the client connected from 192.168.12.118 IP, agreed to establish the TLS connection and sent some data (HTTP POST request). The tool remembers the results and runs the next test.

Once all tests are completed the tool outputs a summary table like the one shown above. In fact, it is coloured, so one can easily identify if something was spotted:

summary mitm

Then the tool attempts to compare all incoming connections and decide if they were made by the same TLS client. This becomes useful if it is not possible to isolate network and some unexpected connections can be received. Lastly qsslcaudit prints a fingerprint of the first connection (and others, if they are different). Such fingerprint can be used to double-check that the expected client was caught. The most useful field in this case is server name indication (SNI). However, WPS Office did not use such:

most likely all connections were established by the same client
the first connection details:
source host: 192.168.12.118
dtls?: false
ssl errors:
ssl conn established?: true
intercepted data: POST /api/dynamicParam/v1/app/bf6f6bab78a07c6b HTTP/1.1
accept: */*
connection: Keep-Alive
Accept-Encoding: gzip
Charset: UTF-8
User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36
Host: dw-online.ksosoft.com
Content-Type: application/json
Content-Length: 161

{"appVersion":"12.3.5","channel":"en00001","abTestVersion":0,"sendUrlVersion":0,"transportControlVersion":0,"eventsVersion":0,"abTestName":"","abTestGroupId":""}
received data, bytes: 854
transmitted data, bytes: 5567
protocol: TLSv1.2
accepted ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_DHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:TLS_ECDHE_RSA_WITH_RC4_128_SHA:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_AES_256_CBC_SHA:TLS_RSA_WITH_RC4_128_SHA:TLS_EMPTY_RENEGOTIATION_INFO_SCSV
ALPN: http/1.1

From the obtained output we can conclude that WPS Office application does not properly verify TLS certificate when connecting to https://dw-online.ksosoft.com/. This is certainly a finding as suitably positioned attacker can intercept the connection, steal and alter data in transit. However, its severity depends on what type of traffic is transmitted and how the responses are interpreted by the application. During an assessment this has to be evaluated, but right now we are just demonstrating how to use qsslcaudit.

shuc-android.ksord.com

qsslcaudit produced the following output:

$ sudo qsslcaudit -l 0.0.0.0 -p 443 --user-cert subdomain.gremwell.com_cert+chain.pem --user-key subdomain.gremwell.com.key --selected-tests certs --user-cn shuc-android.ksord.com
preparing selected tests...
        skipping test: certificate trust test with user-supplied common name signed by user-supplied CA certificate
        skipping test: certificate trust test with www.example.com common name signed by user-supplied CA certificate
        CVE-2020-0601: no CA certificate provided
        skipping test: test for trusting certificate signed by private key with custom curve

SSL library used: OpenSSL 1.0.2u  20 Dec 2019
...skipping for brevity...
tests results summary table:
+----|------------------------------------|------------|-----------------------------+
| ## |             Test Name              |   Result   |           Comment           |
+----|------------------------------------|------------|-----------------------------+
|  1 | custom certificate trust           | FAILED !!! | mitm possible               |
|  2 | self-signed certificate for target | FAILED !!! | -//-                        |
|    |  domain trust                      |            |                             |
|  3 | self-signed certificate for invali | FAILED !!! | -//-                        |
|    | d domain trust                     |            |                             |
|  4 | custom certificate for target doma | FAILED !!! | -//-                        |
|    | in trust                           |            |                             |
|  5 | custom certificate for invalid dom | FAILED !!! | -//-                        |
|    | ain trust                          |            |                             |
+----|------------------------------------|------------|-----------------------------+
most likely all connections were established by the same client
the first connection details:
source host: 192.168.12.118
dtls?: false
ssl errors:
ssl conn established?: true
intercepted data: POST / HTTP/1.1
Content-Type: application/x-www-form-urlencoded
accept: */*
connection: Keep-Alive
Accept-Encoding: gzip
Charset: UTF-8
User-Agent: Mozilla/5.0 (Linux; Android 6.0.1; MI 4W Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/51.0.2704.81 Mobile Safari/537.36
Host: shuc-android.ksord.com
Content-Length: 3500

eyJfcCI6 ...skipping for brevity...
received data, bytes: 4172
transmitted data, bytes: 5567
protocol: TLSv1.2
accepted ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_DHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:TLS_ECDHE_RSA_WITH_RC4_128_SHA:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_AES_256_CBC_SHA:TLS_RSA_WITH_RC4_128_SHA:TLS_EMPTY_RENEGOTIATION_INFO_SCSV
ALPN: http/1.1

qsslcaudit version: 0.8.1

As can be seen, again, the certificates were not validated. The data posted contained even more information about the device used.

plugin-server.wps.com

qsslcaudit produced the following output:

$ sudo qsslcaudit -l 0.0.0.0 -p 443 --user-cert subdomain.gremwell.com_cert+chain.pem --user-key subdomain.gremwell.com.key --selected-tests certs --user-cn plugin-server.wps.com
preparing selected tests...
        skipping test: certificate trust test with user-supplied common name signed by user-supplied CA certificate
        skipping test: certificate trust test with www.example.com common name signed by user-supplied CA certificate
        CVE-2020-0601: no CA certificate provided
        skipping test: test for trusting certificate signed by private key with custom curve

SSL library used: OpenSSL 1.0.2u  20 Dec 2019

running test #1: certificate trust test with user-supplied certificate
listening on 0.0.0.0:443
connection from: 192.168.12.118:45530
SSL connection established
socket error: The TLS/SSL connection has been closed (#1)
socket error: The remote host closed the connection (#1)
no unencrypted data received (The remote host closed the connection)
disconnected
report:
HTTPS client did not accept fake certificate without explicit error message
test finished


running test #2: certificate trust test with self-signed certificate for user-supplied common name
listening on 0.0.0.0:443
connection from: 192.168.12.118:49821
SSL connection established
received data: GET /client/com.wps.ovs.docer/version?appVersion=12.3.5&hostVersion=1005011&pluginVersion=1&deviceId=aaa66681948902838109382338857706 HTTP/1.1
Connection: Keep-Alive
timeStamp: 1582799122
client: com.wps.ovs.docer
sign: be9c6715fef4320438462222ca1ab20f
User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1; SM-A310F Build/MMB29K)
Host: plugin-server.wps.com
Accept-Encoding: gzip


disconnected
report:
test failed, client accepted fake certificate, data was intercepted
test finished


running test #3: certificate trust test with self-signed certificate for www.example.com
listening on 0.0.0.0:443
connection from: 192.168.12.118:35038
SSL connection established
socket error: The TLS/SSL connection has been closed (#1)
socket error: The remote host closed the connection (#1)
no unencrypted data received (The remote host closed the connection)
disconnected
report:
HTTPS client did not accept fake certificate without explicit error message
test finished


running test #4: certificate trust test with user-supplied common name signed by user-supplied certificate
listening on 0.0.0.0:443
connection from: 192.168.12.118:56326
SSL connection established
received data: GET /client/com.wps.ovs.docer/version?appVersion=12.3.5&hostVersion=1005011&pluginVersion=1&deviceId=aaa02935789133279734867793581560 HTTP/1.1
Connection: Keep-Alive
timeStamp: 1582799215
client: com.wps.ovs.docer
sign: 08db428254c47e2fd5603fb928fe2d23
User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1; SM-A310F Build/MMB29K)
Host: plugin-server.wps.com
Accept-Encoding: gzip


disconnected
report:
test failed, client accepted fake certificate, data was intercepted
test finished


running test #5: certificate trust test with www.example.com common name signed by user-supplied certificate
listening on 0.0.0.0:443
connection from: 192.168.12.118:57738
SSL connection established
socket error: The TLS/SSL connection has been closed (#1)
socket error: The remote host closed the connection (#1)
no unencrypted data received (The remote host closed the connection)
disconnected
report:
HTTPS client did not accept fake certificate without explicit error message
test finished

tests results summary table:
+----|------------------------------------|------------|-----------------------------+
| ## |             Test Name              |   Result   |           Comment           |
+----|------------------------------------|------------|-----------------------------+
|  1 | custom certificate trust           |   PASSED   |                             |
|  2 | self-signed certificate for target | FAILED !!! | mitm possible               |
|    |  domain trust                      |            |                             |
|  3 | self-signed certificate for invali |   PASSED   |                             |
|    | d domain trust                     |            |                             |
|  4 | custom certificate for target doma | FAILED !!! | mitm possible               |
|    | in trust                           |            |                             |
|  5 | custom certificate for invalid dom |   PASSED   |                             |
|    | ain trust                          |            |                             |
+----|------------------------------------|------------|-----------------------------+
most likely all connections were established by the same client
the first connection details:
source host: 192.168.12.118
dtls?: false
ssl errors: The TLS/SSL connection has been closed The remote host closed the connection
ssl conn established?: true
socket errors ids: 1 1
received data, bytes: 344
transmitted data, bytes: 5583
protocol: TLSv1.2
accepted ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_DHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:TLS_ECDHE_RSA_WITH_RC4_128_SHA:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_AES_256_CBC_SHA:TLS_RSA_WITH_RC4_128_SHA:TLS_EMPTY_RENEGOTIATION_INFO_SCSV
SNI: plugin-server.wps.com
ALPN: http/1.1

qsslcaudit version: 0.8.1

This result is interesting! Usually the applications we test either trust all certificates or properly verify them. In this case the TLS client trusts all the certificates which are issued to the expected common name.

account.wps.com

That is all interesting and already can be (and actually was) reported. But let's intercept more interesting data. During traffic analysis we spotted account.wps.com domain. This is the host that the application uses during the login process. In this case we have to login with WPS account (not Google, Facebook or Dropbox).

qsslcaudit produced the following output:

$ sudo qsslcaudit -l 0.0.0.0 -p 443 --user-cert subdomain.gremwell.com_cert+chain.pem --user-key subdomain.gremwell.com.key --selected-tests certs --user-cn account.wps.com
preparing selected tests...
        skipping test: certificate trust test with user-supplied common name signed by user-supplied CA certificate
        skipping test: certificate trust test with www.example.com common name signed by user-supplied CA certificate
        CVE-2020-0601: no CA certificate provided
        skipping test: test for trusting certificate signed by private key with custom curve

SSL library used: OpenSSL 1.0.2u  20 Dec 2019

...skipped for brevity...

tests results summary table:
+----|------------------------------------|------------|-----------------------------+
| ## |             Test Name              |   Result   |           Comment           |
+----|------------------------------------|------------|-----------------------------+
|  1 | custom certificate trust           | FAILED !!! | mitm possible               |
|  2 | self-signed certificate for target | FAILED !!! | -//-                        |
|    |  domain trust                      |            |                             |
|  3 | self-signed certificate for invali | FAILED !!! | -//-                        |
|    | d domain trust                     |            |                             |
|  4 | custom certificate for target doma | FAILED !!! | -//-                        |
|    | in trust                           |            |                             |
|  5 | custom certificate for invalid dom | FAILED !!! | -//-                        |
|    | ain trust                          |            |                             |
+----|------------------------------------|------------|-----------------------------+
most likely all connections were established by the same client

Nice! But in this case we intercepted only the first connection which does not contain any sensitive information. Let's use qsslcaudit's functionality to forward intercepted data. Do note that it forwards the traffic **inside** TLS channel. Thus, we additionally have to forward clear-text HTTP towards HTTPS, we used socat for that:

socat TCP4-LISTEN:6666,fork,reuseaddr openssl:90.84.192.191:443,verify=0

We also have to select the particular test that allows man-in-the-middle:

$ sudo qsslcaudit -l 0.0.0.0 -p 443 --user-cert subdomain.gremwell.com_cert+chain.pem --user-key subdomain.gremwell.com.key --selected-tests 1 --forward 127.0.0.1:6666 --user-cn account.wps.com
preparing selected tests...

SSL library used: OpenSSL 1.0.2u  20 Dec 2019

running test #1: certificate trust test with user-supplied certificate
listening on 0.0.0.0:443
connection from: 192.168.12.118:49466
forwarding incoming data to the provided proxy
to get test results, relauch this app without 'forward' option
report:
no data received with socket in incorrect state
test finished

tests results summary table:
+----|------------------------------------|------------|-----------------------------+
| ## |             Test Name              |   Result   |           Comment           |
+----|------------------------------------|------------|-----------------------------+
|  1 | custom certificate trust           | UNDEF ???  | report this case to develop |
|    |                                    |            | ers                         |
+----|------------------------------------|------------|-----------------------------+
most likely all connections were established by the same client
the first connection details:
source host: 192.168.12.118
dtls?: false
ssl errors:
ssl conn established?: false
intercepted data: GET /api/server_ok HTTP/1.1
Content-MD5: ce1f01765892367a5ab8a74fb89b1773
Device-Name: samsung SM-A310F
host: account.wps.com
X-Platform-Language: en-GB
X-Platform: Android-6.0.1
X-App-Version: 12.3.5
X-Client-Ver: Android-WPS Office-12.3.5
X-App-Name: WPS Office
Device-Type: android
X-App-Channel: en00001
User-Agent: qing/2.2.17 (Android-6.0.1;U;en-GB) Version/12.3.5 App/android-office
Accept-Encoding: gzip
Cookie: wpsua=V1BTVUEvMS4wIChhbmRyb2lkLW9mZmljZToxMi4zLjU7YW5kcm9pZDo2LjAuMTs0ZjA1OTFmYTJkMjU5NDcxZGQ2ZDU2OGU4M2Q5OGQwMTpjMkZ0YzNWdVp5QlRUUzFCTXpFd1JnPT0pc2Ftc3VuZy9TTS1BMzEwRg==
Content-Type: application/json; charset=utf-8
Device-Id: 4f0591fa2d259471dd6d568e83d98d01
Date: Thu, 27 Feb 2020 10:53:01 GMT
X-Sdk-Ver: Android-2.2.17
Accept-Language: en-GB
Authorization: WPS-2:AqY7ik9XQ92tvO7+NlCRvA==:e25c94fd1c2e1d5c8b0658008cf7e210c1499215
Connection: Keep-Alive

POST /api/signin HTTP/1.1
Content-MD5: 721f38d1c450e936388850c136e8fe2c
Device-Name: samsung SM-A310F
host: account.wps.com
X-Platform-Language: en-GB
X-Platform: Android-6.0.1
X-App-Version: 12.3.5
X-Client-Ver: Android-WPS Office-12.3.5
X-App-Name: WPS Office
Device-Type: android
X-App-Channel: en00001
User-Agent: qing/2.2.17 (Android-6.0.1;U;en-GB) Version/12.3.5 App/android-office
Accept-Encoding: gzip
Cookie: wpsua=V1BTVUEvMS4wIChhbmRyb2lkLW9mZmljZToxMi4zLjU7YW5kcm9pZDo2LjAuMTs0ZjA1OTFmYTJkMjU5NDcxZGQ2ZDU2OGU4M2Q5OGQwMTpjMkZ0YzNWdVp5QlRUUzFCTXpFd1JnPT0pc2Ftc3VuZy9TTS1BMzEwRg==
Content-Type: application/json; charset=utf-8
Device-Id: 4f0591fa2d259471dd6d568e83d98d01
Date: Thu, 27 Feb 2020 10:53:34 GMT
X-Sdk-Ver: Android-2.2.17
Accept-Language: en-GB
Authorization: WPS-2:AqY7ik9XQ92tvO7+NlCRvA==:ad7340f9fa2aeb06fd32f89fcafea42e62d09eeb
Content-Length: 90
Connection: Keep-Alive

{"account":"gremwell@gremwell.com","password":"OV-UOIt-L5kzKC2-Pr4j7g==\n","encrypt":true}
received data, bytes: 0
transmitted data, bytes: 0
not a valid TLS/SSL client, 0 byte(s) of raw data received, i.e.:

qsslcaudit version: 0.8.1

We were able to intercept the credentials, shown in the last of two intercepted requests. Yes, they are "encrypted", but as we can intercept all communications, we have all information to decrypt them.

{"account":"gremwell@gremwell.com","password":"OV-UOIt-L5kzKC2-Pr4j7g==\n","encrypt":true}

Conclusion

In this document we described a typical mobile application test scenario we follow when assessing SSL/TLS communications security. We always verify client-side TLS implementation for all kinds of applications we test: mobile application, web services and other types of software. As we demonstrated in this example it has to be done in all cases: even widely used applications suffer from such vulnerabilities.

qsslcaudit tool helps pentesters in such assessments as it prepares all kind of certificates. As we demonstrated some clients may not trust self-signed certificates issued for invalid common names but trust others, which have the expected common name field. Manual tests with openssl s_server are doable, but prone to errors and do not have coloured output. :-)

Happy intercepting!

Contacts

+32 (0) 2 215 53 58

Gremwell BVBA
Sint-Katherinastraat 24
1742 Ternat
Belgium
VAT: BE 0821.897.133.