Es ist wieder mal ein neuer Container in meinem Docker-Zoo eingezogen
Nachdem ich bereits andere Docker-Container zur Unterstützung bei der Softwareentwicklung in meinen Zoo aufgenommen habe, war nun endlich MITMProxy, ein in Python geschriebenes Werkzeug zum Aufbrechen von TLS-verschlüsselten Verbindungen an der Reihe.
Warum würde man solche sicheren Verbindungen aufbrechen wollen? Nun, zum einen kommen da Smartphones in Betracht. Das allein erklärt jemandem, der sich noch nie aktiv mit Datensparsamkeits- und Datenschutzaspekten solcher und ähnlicher "smart" Devices beschäftigt hat, noch nicht viel. Solche Geräte - auch alle smartHome-Geräte oder Sprachassistenz-Systeme - nehmen gerne mal mit dem Mutterschiff Kontakt auf, um Daten dorthin zu übertragen.
Falls man paranoid genug ist, diesen Geräten nicht zu vertrauen, kann man - und muss man, da diese Verbindungen immer mehr oder weniger gut verschlüsselt sind - mit einem solchen MITM-Proxy diese Verschlüsselung einfach aufbrechen und im Klartext mitlesen, worüber sich das Gerät und sein Mutterschiff denn so unterhalten.
Für Entwicklungszwecke kann es hin und wieder ebenfalls ganz nützlich sein, die verschlüsselten Kommunikationsinhalte einer neuen Anwendung im Klartext vor sich zu sehen.
Und natürlich setzen Unternehmen solche Systeme gerne mal ein, um zum Beispiel ihr Firmennetz mittels einer Deep-Packet-Inspection vor Unbill zu schützen.
Technisch funktioniert das so, dass das Betriebssystem oder die Client-Anwendung so konfiguriert wird, dass sie nicht direkt mit dem Server kontakt aufnimmt, sondern dies über einen Proxy tut. Proxies waren früher Systeme, die beispielsweise bei Volumenverträgen oder Verbindungen mit geringer Bandbreite zum Internet dafür sorgten, dass die Menge der übertragenen Daten möglichst gering bleib, indem sie einen intelligenten Cache an der Außenkante des lokalen Netzwerks implementierten.
Diese Funktionalität könnte auch für TLS-Verbindugnen immer noch genutzt werden - in diesem Fall wird die Verschlüsselung nicht gebrochen. Es ist aber auch möglich, dass der Proxy als echter "Man-in-the-Middle" - nichts anderes bedeutet MITM - agiert: der Client sagt dem Proxy in diesem Fall, mit wem er Verbindung aufnehmen möchte. Der Proxy baut dann die TLS-Verbindung zum eigentlichen Kommunikationsziel auf und baut einen zweiten verschlüsselten Kanal zum Client auf, so dass dieser weiterhin eine mittels TLS verschlüsselte Verbindung sieht.
Der Proxy kann sich aber gegenüber dem Client nicht als der eigentliche Server ausgeben - dazu bräuchte er Zugriff auf den privaten Schlüssel des Servers und den hat er nicht. Er erzeugt vielmehr on-the-fly entsprechende Zertifikate für den Server um sie dem Client zu präsentieren - diese Zertifikate sind aber von seiner eigenen CA signiert (und damit eigentlich gefälscht).
Hier würde man sich wünschen, dass mehr Menschen sich den jeweiligen Schlüssel des Zertifikats und am besten aller Zertifikate der Kette anzusehen und mit einer Vorlage, die über einen sicheren Kanal erhalten wurde zu vergleichen...
Und damit haben wir schon die erste Aufgabe: Damit dies alles nämlich funktioniert, muss der Proxy den Client dazu bringen, Zertifikaten zu vertrauen, die dieser ausgestellt hat. MITMProxy benutzt dazu seine eigene on-the-fly generierte Root-CA. Deren Zertifikat kann man kopieren und als vertrauenswürdige CA im eigenen Truststore - global oder spezifisch für den jeweiligen Client - hinterlegen.
Es ist aber auch möglich - fall man sowieso schon eine eigene PKI betreibt - eine spezielle digitale Identität für MTIMProxy zu erstellen, die dieser dann benutzt, um die "gefälschten" Zertifikate auszustellen. Ist das Zertifikat der Root-CA der eigenen PKI bereits im System als vertrauenswürdig eingestuft, muss man also am Client zur Nutzung von MITMProxy nichts mehr ändern.
Ich wollte - wie bereits oben geschrieben - die Lösung in meinem Docker-Zoo einsetzen und daher suchte ich nach einem bereits vorhandenen Dockerfile und einem darauf aufbauenden docker-compose.yml.
Ich fand etwas, auf dem ich aufbauen konnte, musste dies jedoch stark anpassen. Ich musste mich recht früh davon verabschieden, die Weboberfläche über den reverse-Proxy Traefik anzubieten, da MITMProxy derzeit den Zugriff darauf nur per IP-Adresse und nicht per (DNS-)Namen gestattet.
Das Unterschieben einer von mir erstellten digitalen Identität zum Signieren der gefälschten Server-Zertifikate war einfach. Schwieriger war es, das Root-CA-Zertifikat meiner eigenen PKI MITMProxy selbst als vertrauenswürdig zu definieren. Ich musste dazu ein Dockerfile erstellen, das zunächst meine eigenen vertrauenswürdigen Zertifikate zum systemweiten Truststore hinzufügte. Anschließend - da es sich hier um eine Python-Anwendung handelte und Python per Default den systemweiten Truststore ignoriert - musste ich den modifizierten Truststore MITMProxy noch als Truststore unterschieben.
Dadurch sind die folgenden beiden Dateien entstanden:
FROM mitmproxy/mitmproxy
ADD root-ca-cert.pem /usr/local/share/ca-certificates/foo.crt
RUN chmod 644 /usr/local/share/ca-certificates/foo.crt && update-ca-certificates
version: '3'
services:
mitmweb:
build: .
#image: mitmproxy/mitmproxy
tty: true
ports:
#HTTP-PROXY
- 6080:8080
#SOCKS-PROXY
- 4080:1080
#WEB-FRONTEND
- 6081:8081
#HTTP-PROXY
command: mitmweb --web-host 0.0.0.0 --cert-passphrase $CERT_PASSPHRASE --set ssl_verify_upstream_trusted_ca=/etc/ssl/certs/ca-certificates.crt
#SOCKS-PROXY
# command: mitmweb --web-host 0.0.0.0 --mode socks5 --cert-passphrase $CERT_PASSPHRASE --set ssl_verify_upstream_trusted_ca=/etc/ssl/certs/ca-certificates.crt
volumes:
- ./mitmproxy:/home/mitmproxy/.mitmproxy
Ich benutzte zum Testen eine stark angepasste Version des hier zu findenden Grundgerüsts:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ManagedHttpClientConnection;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.*;
/**
* How to send a HTTP or HTTPS request via SOCKS proxy.
* https://stackoverflow.com/a/32370018
*/
public class ClientExecuteSOCKS {
private final static java.lang.String SOCKSPROXYADDRESS="SOCKSPROXYADDRESS";
private final static java.lang.String SOCKSPROXYHOST="192.168.10.3";
private final static short SOCKSPROXYPORT=4080;
private final static java.lang.String HTTPPROXYADDRESS="HTTPPROXYADDRESS";
private final static java.lang.String HTTPPROXYHOST="192.168.10.3";
private final static short HTTPPROXYPORT=6080;
private static final String PEER_CERTIFICATES = "PEER_CERTIFICATES";
public static void main(String[] args) throws Exception {
java.io.File pemFile=new java.io.File("/home/elbosso/tests/tls/Dama11_Root_CA-ca.crt");
KeyStore trustStore= de.elbosso.util.security.Utilities.initializeTruststoreWithPemCertificates(pemFile,true);
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
sslContext.init(null, tmf.getTrustManagers() , null);
Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", new MyHTTPConnectionSocketFactory())
.register("https", new MyHTTPSConnectionSocketFactory(
//sslContext
SSLContexts.createSystemDefault()
))
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
//https://memorynotfound.com/apache-httpclient-get-server-certificates/
// create http response certificate interceptor
HttpResponseInterceptor certificateInterceptor = (httpResponse, context) -> {
ManagedHttpClientConnection routedConnection = (ManagedHttpClientConnection)context.getAttribute(HttpCoreContext.HTTP_CONNECTION);
SSLSession sslSession = routedConnection.getSSLSession();
if (sslSession != null) {
// get the server certificates from the {@Link SSLSession}
Certificate[] certificates = sslSession.getPeerCertificates();
// add the certificates to the context, where we can later grab it from
context.setAttribute(PEER_CERTIFICATES, certificates);
}
};
try (CloseableHttpClient httpclient = HttpClients.custom()
.setConnectionManager(cm)
.addInterceptorLast(certificateInterceptor)
.build()) {
InetSocketAddress socksaddr = new InetSocketAddress(SOCKSPROXYHOST, SOCKSPROXYPORT);
InetSocketAddress httpaddr = new InetSocketAddress(HTTPPROXYHOST, HTTPPROXYPORT);
HttpClientContext context = HttpClientContext.create();
// context.setAttribute(SOCKSPROXYADDRESS, socksaddr);
context.setAttribute(HTTPPROXYADDRESS, httpaddr);
HttpHost target = new HttpHost("google.com", 443, "https");
HttpGet request = new HttpGet("/");
System.out.println("Executing request " + request + " to " + target + " via SOCKS " +
"proxy " + socksaddr);
try (CloseableHttpResponse response = httpclient.execute(target, request, context)) {
//https://memorynotfound.com/apache-httpclient-get-server-certificates/
// obtain the server certificates from the context
Certificate[] peerCertificates = (Certificate[])context.getAttribute(PEER_CERTIFICATES);
if(peerCertificates!=null)
{
System.out.println("----------------------------------------\nCertificate(s):");
for(Certificate peerCertificate:peerCertificates)
{
System.out.println(((X509Certificate) peerCertificate).getSubjectX500Principal());
}
}
else
System.out.println("no certificates found!");
System.out.println("----------------------------------------\nHeaders:");
Header[] headers= response.getAllHeaders();
for(Header header:headers)
{
System.out.println(header);
}
System.out.println("----------------------------------------\nStatus:");
System.out.println(response.getStatusLine());
System.out.println("----------------------------------------\nResponse:");
System.out.println(EntityUtils.toString(response.getEntity(), StandardCharsets
.UTF_8));
}
}
}
static class MyHTTPConnectionSocketFactory extends PlainConnectionSocketFactory {
@Override
public Socket createSocket(final HttpContext context) throws IOException {
InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute(SOCKSPROXYADDRESS);
if(socksaddr!=null)
{
Socket rv = null;
if (socksaddr.isUnresolved())
{
rv = super.createSocket(context);
}
else
{
Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
rv = new Socket(proxy);
}
return rv;
}
else
{
socksaddr = (InetSocketAddress) context.getAttribute(HTTPPROXYADDRESS);
if (socksaddr != null)
{
Socket rv = null;
if (socksaddr.isUnresolved())
{
rv = super.createSocket(context);
}
else
{
Proxy proxy = new Proxy(Proxy.Type.HTTP, socksaddr);
rv = new Socket(proxy);
}
return rv;
}
else
throw new java.io.IOException("no "+SOCKSPROXYADDRESS + " nor "+HTTPPROXYADDRESS+" found in context!");
}
}
}
static class MyHTTPSConnectionSocketFactory extends SSLConnectionSocketFactory {
public MyHTTPSConnectionSocketFactory(final SSLContext sslContext) {
super(sslContext);
}
@Override
public Socket createSocket(final HttpContext context) throws IOException {
InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute(SOCKSPROXYADDRESS);
if(socksaddr!=null)
{
Socket rv = null;
if (socksaddr.isUnresolved())
{
rv = super.createSocket(context);
}
else
{
Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
rv = new Socket(proxy);
}
return rv;
}
else
{
socksaddr = (InetSocketAddress) context.getAttribute(HTTPPROXYADDRESS);
if (socksaddr != null)
{
Socket rv = null;
if (socksaddr.isUnresolved())
{
rv = super.createSocket(context);
}
else
{
Proxy proxy = new Proxy(Proxy.Type.HTTP, socksaddr);
rv = new Socket(proxy);
}
return rv;
}
else
throw new java.io.IOException("no "+SOCKSPROXYADDRESS + " nor "+HTTPPROXYADDRESS+" found in context!");
}
}
}
}
Multi-User-WebDAV, Docker, GitHub
17.11.2019
Nachdem ich mich in letzter Zeit verstärkt mit Docker und dem zugehörigen Ökosystem beschäftige, habe ich begonnen, verschiedenste Dienste in Containern zu testen um zu sehen, ob in manchen Fällen LXC oder KVM nicht doch die bessere Wahl wäre...
Weiterlesen...Android Basteln C und C++ Chaos Datenbanken Docker dWb+ ESP Wifi Garten Geo Go GUI Gui Hardware Java Jupyter Komponenten Links Linux Markdown Markup Music Numerik OpenSource PKI-X.509-CA Python QBrowser Rants Raspi Revisited Security Software-Test sQLshell TeleGrafana Verschiedenes Video Virtualisierung Windows Upcoming...
In eigener Sache...
Weiterlesen...Ich habe eine neue Java Swing Komponente erstellt: Es handelt sich um einen Wrapper für von JToolBar abgeleitete Klassen, die die Werkzeugleiste minimieren und sie nur dann einblenden, wenn der Mauszeiger über ihnen schwebt.
Weiterlesen...Ich habe bereits in einem früheren Artikel über meine ersten Erfolge berichtet, der sQLshell auf Basis des bestehenden Codes aus dem Projekt EBMap4D eine bessere Integration für Geo-Daten zu spendieren und entsprechende Abfragen, bzw. deren Ergebnisse auf einer Kartenansicht zu visualisieren.
Weiterlesen...Manche nennen es Blog, manche Web-Seite - ich schreibe hier hin und wieder über meine Erlebnisse, Rückschläge und Erleuchtungen bei meinen Hobbies.
Wer daran teilhaben und eventuell sogar davon profitieren möchte, muss damit leben, daß ich hin und wieder kleine Ausflüge in Bereiche mache, die nichts mit IT, Administration oder Softwareentwicklung zu tun haben.
Ich wünsche allen Lesern viel Spaß und hin und wieder einen kleinen AHA!-Effekt...
PS: Meine öffentlichen Codeberg-Repositories findet man hier.