Eavesdropping HTTPS connections - ackoch/ssl-eavesdropping GitHub Wiki

How to debug in Rest-API based development

Background

Modern software architecture more and more relies on connecting web-services together. This adds a bit more complexity to the undertaking. And if a service call fails, you never know, what side of the communication failed: Is the called service not available or does the caller have a bug or is the service result not what the caller expected. The issues can be manifold.

Wouldn’t it be nice to eavesdrop into the communication to see what’s happening to help the to endpoints to come to terms? Well – we can. Let us explore, how.

IP sniffing

Here, we basically wiretap into the protocol stack with a tool like wire shark and read the raw packages and see what the to endpoints are talking about. This would be so convenient, as it doesn’t require any change to the running application. But it has severe limitations: You just see that the two endpoints are communicating, not what they are sending: You cannot read encrypted connections – that’s what encryption is meant for. And you will not be able to use that in a client environment for debugging: Most hosting partners do not allow network tools on their machines for security reasons. And also – this class of tools are very low-level and hard to understand for a mortal developer. We will not pursue this approach here.

HTTP Proxy

The other potential solution is to use an HTTP proxy between the requesting client C and the service S. Generally we have to alternatives for this setup:

  1. Transparent proxy
  2. Reverse proxy

Both solutions place an http Proxy "P" between C and S.

The difference is how you configure C – and more importantly - what configuration options C offers:

Transparent Proxy

You tell the client 'C' to connect to 'S' but use the Proxy as a “hop” in-between. This requires, that 'C' is capable of setting up proxy connections. But: Not all clients are capable to use a proxy.

Reverse Proxy

In case the client does not have proxy capabilities you still can use a proxy in the following topology:

Here you tell the client to use the proxy as if it was the actual service. The proxy then would behave exactly like the service would. To do so, the proxy would require a bit more information, though. To answer the requests, it needs to ask the actual service for the answer. And to be able to do so, it needs to know where the actual service is located. So, a reverse proxy needs to be configured with a range of ports it listens on and a port and a server URL where to forward the requests to.

Not all clients allow you to change the IP address of the service. If that is not configurable, you can spoof the remote IP address, by redirecting the remote services DNS name to your localhost (127.0.0.1) and tell the reverse proxy to use the original IP address as a target host. (The proxy forwards the original service host name in a header if configured properly).

Decrypting the Message

That’s it? Not quite yet! What we said about eavesdropping encrypted messages still is true. The service S is still encrypting the message unreadable for the proxy P.

The P could decrypt the messages coming from S but C would recognize that P is a 'man in the middle', who had decrypted the message.

For the sake of better understanding, you can imagine an encrypted message m being but into an envelope and sealed with a seal only S has. This encrypted message would be denoted as encrypted(S, msg). This "seal" in the digital world is a the digital certificate cert-s only S can use.

What P can do is, it can decrypt the message on behalf of C. Metaphorically speaking, P would break the seals and open the envelope to get the message m.

But C expects an encrypted connection signed or "sealed" by cert-s. Mind, that C is calling the proxy with “https://”. If the seal is broken and C gets the bare msg, it knows the connection was tampered and refuses to accept the connection.

So, P needs to re-encrypt the msg, to put it into a new envelope for C and re-seal it. But it cannot use the service's certificate cert-s. But P can try to use it’s own certificate cert-p.

The transmitted message now looks like this: encrypted(P, msg). P swapped out the S certificate for its own.

But this will fail: S uses a certificate that has been signed by a known Root CA and C considers cert-s to be trustworthy. The proxy only uses a hand-crafted certificate cert-p, that is not trustworthy to the client. C notices that the connection was manipulated and refuses to accept - again.

But that can be fixed, too – all you need to do is import the proxy’s certificate cert-p into the client C so that it is considered trustworthy as well.

WARNING: This is only a development setup. The proxy’s certificate is not as trustworthy as one that is signed by a Certificate Authority. You should only apply this technique in a local environment. If you need to apply that on a production machine, make sure to uninstall the certificate from the after the debugging session.

Example

Let’s demonstrate that procedure with a concrete example.

Client:

As client we us AEM - the Adobe Experience Manager.

Proxy:

As proxy we use the Software “Charles Proxy”

Service:

The service in this example is Adobe Target. But you can apply the same pattern to similar setups.

Let’s find where the client defines its endpoint. In our case it’s in an OSGi configuration. Usually you would look up the services URL in either the cloud configuration in the repository or in the OSGi configuration:

In this special case, it seems, like the setting was ignored and some hardcoded value was used. Luckily, the API is well documented, so we pick up the real endpoint from there:

Unfortunately, the Target endpoint in AEM does not use the global proxy setting in the AEM configuration. So we need to use the reverse proxy setting.

We resolve the hostname to an IP-address by pinging the name:

$ ping mc.adobe.io
PING mc.adobe.io (52.48.87.10): 56 data bytes

We copy the IP address from the result and create a new “Reverse Proxy” in Charles:

The local port must be 443 - as the local proxy is going to mimic the remote service.

Make also sure that Charles is actually decrypting the SSL connection for that specific host:

To make the client connect with the local procy instead of the real service we need to re-route the services name to the localhost address. This technique is called "DNS spoofing":

$ cat  /etc/hosts

# DEBUG TARGET CLOUD SERVICE

127.0.0.1 mc.adobe.io

Note: Charles also offers an option "DNS Spoofing" - but we couldn't figure out how to use it in this case... Other cases might work, though.

At this point, Charles will intercept and re-encrypt the transmissions. But as we said before the client very likely will recognize the interception and refuse to work.

So we will import the "fake" Root certificate of Charles into the Java Virtual Machine to make the JVM accept it. So we download it from the proxy:

We save the certificate to the desktop under ~/Desktop/charles-ssl-proxying-certificate.pem

and import it into the

$ cd /Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home/jre/lib/security

$ sudo keytool -import -alias charles -file ~/Desktop/charles-ssl-proxying-certificate.pem -keystore cacerts -storepass changeit

Note: The location of your virtual machine will probably differ.

Note: "changeit" is the default passwword of your the trustsore in a JVM. You probably havn't changed it on your local development machine - or else you would know.

The command

$ keytool -list -keystore cacerts -alias charles -storepass changeit

charles, Jul 4, 2019, trustedCertEntry, 
Certificate fingerprint (SHA1): F2:F5:8C:91:96:F7:65:26:6D:DB:DB:D6:63:F7:BB:50:A6:E9:BF:05

validates the successful import.

Voila! Now we can eavesdrop into an SSL connection.

If we - for example - click Update experience fragment in AEM:

We can see the transmission in plain in the proxy:

See also:

https://www.charlesproxy.com/documentation/using-charles/ssl-certificates/ https://docs.oracle.com/javase/tutorial/security/toolfilex/rstep1.html

http://developers.adobetarget.com/api/

https://connect2id.com/blog/importing-ca-root-cert-into-jvm-trust-store