Netzwerktraffic von Apps analysieren: Ein Leitfaden zur API-Inspektion und Dokumentation

Dominic Böttger

January 21, 2025

 • 

5

 min read

Blog

>

Netzwerktraffic von Apps analysieren: Ein Leitfaden zur API-Inspektion und Dokumentation

Die Analyse des Netzwerkverkehrs bietet tiefere Einblicke in die API-Kommunikation und hilft, Sicherheitslücken aufzudecken. Dabei wird oft die SSL-Verschlüsselung aufgebrochen, um Datenpakete und deren Inhalte sichtbar zu machen. Zusätzlich kann eine OpenAPI-Spezifikation generiert werden, um die API besser zu dokumentieren.

Warum Netzwerktraffic analysieren?

Das Analysieren von Netzwerktraffic ist besonders nützlich, um:

  • Sicherheitslücken in Apps zu identifizieren.
  • Zu überprüfen, welche Daten die App mit externen Servern austauscht.
  • Den Aufbau und die Funktionsweise von APIs zu verstehen.
  • API-Dokumentationen für Debugging und Entwicklung zu erstellen.

Voraussetzungen

Hardware

  • Gerootetes Android-Gerät:
    • Erforderlich für RProxid, da es das globale Proxy-Setting erzwingt und somit auch Apps, die normalerweise den Proxy umgehen würden, über mitmproxy leitet. Für Flutter - basierte Apps ist dies leider nicht ausreichend, da diese auch nicht auf diese Einstellungen reagieren. Hierzu ist dann zwingend das Frida-Skript notwendig.
    • Erlaubt das Deaktivieren von SSL-Pinning und den Zugriff auf Systemdateien.

Software

  1. ADB (Android Debug Bridge): Lade die Android Platform Tools von der offiziellen Website herunter: Android Platform Tools.
  2. Host-System:
    • Installiere mitmproxy oder mitmweb.
    • Installiere Python 3.6+ sowie die notwendigen Bibliotheken:
    pip install mitmproxy frida-tools
  
  • Optionale Tools:
    • disable-flutter-tls.js: Ein Frida-Skript, das TLS-Verifizierungen in Flutter-Apps deaktiviert. Dieses Skript ist notwendig, da Flutter-Apps oft TLS-Pinning verwenden. Du kannst es hier herunterladen: NVISOsecurity. Es manipuliert die App zur Laufzeit, um verschlüsselte Kommunikation sichtbar zu machen.

Mitmproxy-Zertifikat installieren

Für die meisten Android-Apps ohne TLS-Pinning kannst du das mitmproxy-Zertifikat einfach direkt über die Webschnittstelle installieren:

  • Mitmproxy starten:
    mitmweb
  


  1. Mitmproxy-Zertifikat installieren:
    • Öffne die Seite http://mitm.it/ im Browser des Android-Geräts.
    • Wähle das Betriebssystem Android und folge den Anweisungen zur Zertifikatsinstallation.
  2. Proxy auf dem Gerät einrichten:
    • Gehe in die WLAN-Einstellungen deines Android-Geräts.
    • Wähle das aktuelle Netzwerk aus und konfiguriere manuell einen Proxy mit der IP-Adresse deines Host-Systems und Port 8080.

Diese Methode ist schnell und reicht aus, um den Netzwerktraffic von Apps ohne TLS-Pinning zu analysieren.

Analyse von Flutter-Apps mit TLS-Pinning

Für Flutter-Apps, die TLS-Pinning verwenden, sind zusätzliche Schritte erforderlich:

  • Frida-Server auf Android-Gerät installieren:
    adb root
adb push /pfad/zu/frida-server /data/local/tmp/
adb shell "<span style="" class="hljs-string">"chmod 755 /data/local/tmp/frida-server"</span>"
adb shell "<span style="" class="hljs-string">"/data/local/tmp/frida-server &"</span>"


  


  • App zur Laufzeit anpassen: Starte die Zielanwendung und führe das TLS-Bypass-Skript mit Frida aus:
    frida -U MeineApp -l ./disable-flutter-tls.js


  

Dieses Skript deaktiviert die SSL-Zertifikatsprüfung der App, sodass mitmproxy den verschlüsselten Traffic einsehen kann.

Schritt 1: RProxid konfigurieren

  1. Installiere RProxid über den Google Play Store auf deinem Android-Gerät.
  2. Konfiguriere die App:
    • Zielanwendung: Wähle die App aus, deren Traffic analysiert werden soll.
    • Proxy-Host: IP-Adresse des Hosts, auf dem mitmproxy läuft (z. B. 192.168.1.100).
    • Proxy-Port: Standardport von mitmproxy (8080).
  3. Aktiviere RProxid.

Schritt 2: Netzwerktraffic mit mitmproxy erfassen

  1. mitmproxy exportieren:Die mitmproxy-Instanz ist bereits gestartet. Öffne die Weboberfläche unter http://127.0.0.1:8081 und navigiere zu den gewünschten Daten. Exportiere die gefilterten Anfragen im gewünschten Format, z. B. als .dump-Datei für die spätere Analyse.
  2. Traffic generieren: Interagiere mit der App, um API-Anfragen zu erzeugen.
  3. Daten speichern:
    • Exportiere gefilterte Anfragen in der Weboberfläche als meineapp-flow.dump.

Schritt 3: Konvertierung in OpenAPI-Spezifikation

  • Dump-Datei in JSON konvertieren: Führe convertToJson.py aus:
    python convertToJson.py


  

Code der convertToJson.py:

     <span style="" class="hljs-keyword">from</span> mitmproxy.io <span style="" class="hljs-keyword">import</span> FlowReader
<span style="" class="hljs-keyword">import</span> json

<span style="font-style: italic;" class="hljs-comment"># Input and output file paths</span>
input_file = <span style="" class="hljs-string">"myapp-flow.dump"</span>  <span style="font-style: italic;" class="hljs-comment"># Your .dump file</span>
output_file = <span style="" class="hljs-string">"decoded_flows.json"</span>  <span style="font-style: italic;" class="hljs-comment"># Desired output JSON file</span>

<span style="font-style: italic;" class="hljs-comment"># Read the dump file and decode the flows</span>
flows_list = []
<span style="" class="hljs-keyword">with</span> <span style="" class="hljs-built_in">open</span>(input_file, <span style="" class="hljs-string">"rb"</span>) <span style="" class="hljs-keyword">as</span> f:
    reader = FlowReader(f)
    <span style="" class="hljs-keyword">for</span> flow <span style="" class="hljs-keyword">in</span> reader.stream():
        <span style="" class="hljs-keyword">try</span>:
            <span style="font-style: italic;" class="hljs-comment"># Extract request and response data</span>
            request = {
                <span style="" class="hljs-string">"method"</span>: flow.request.method,
                <span style="" class="hljs-string">"url"</span>: flow.request.url,
                <span style="" class="hljs-string">"headers"</span>: <span style="" class="hljs-built_in">dict</span>(flow.request.headers),
                <span style="" class="hljs-string">"body"</span>: flow.request.text
            }
            response = <span style="" class="hljs-literal">None</span>
            <span style="" class="hljs-keyword">if</span> flow.response:
                response = {
                    <span style="" class="hljs-string">"status_code"</span>: flow.response.status_code,
                    <span style="" class="hljs-string">"headers"</span>: <span style="" class="hljs-built_in">dict</span>(flow.response.headers),
                    <span style="" class="hljs-string">"body"</span>: flow.response.text
                }

            flows_list.append({
                <span style="" class="hljs-string">"request"</span>: request,
                <span style="" class="hljs-string">"response"</span>: response
            })
        <span style="" class="hljs-keyword">except</span> Exception <span style="" class="hljs-keyword">as</span> e:
            <span style="" class="hljs-built_in">print</span>(<span style="" class="hljs-string">f"Error processing flow: <span style="" class="hljs-subst">{e}</span>"</span>)

<span style="font-style: italic;" class="hljs-comment"># Save decoded data to JSON file</span>
<span style="" class="hljs-keyword">with</span> <span style="" class="hljs-built_in">open</span>(output_file, <span style="" class="hljs-string">"w"</span>) <span style="" class="hljs-keyword">as</span> f:
    json.dump(flows_list, f, indent=<span style="" class="hljs-number">2</span>)

<span style="" class="hljs-built_in">print</span>(<span style="" class="hljs-string">f"Decoded flows saved to <span style="" class="hljs-subst">{output_file}</span>"</span>)



  


Ergebnis: decoded_flows.jsonEs bietet sich an, die Datei vor der Konvertierung auf personenbezogene oder sensible Daten zu überprüfen und diese gegebenenfalls zu anonymisieren. Dies schützt sensible Informationen.

  • JSON in OpenAPI-Spezifikation umwandeln: Führe convertToOpenAPI.py aus:
    python convertToOpenAPI.py


  

Code der convertToOpenAPI.py:

    <span style="" class="hljs-keyword">import</span> json
<span style="" class="hljs-keyword">import</span> re
<span style="" class="hljs-keyword">from</span> urllib.parse <span style="" class="hljs-keyword">import</span> urlparse, parse_qs

<span style="font-style: italic;" class="hljs-comment"># Input and output file paths</span>
input_file = <span style="" class="hljs-string">"decoded_flows.json"</span>  <span style="font-style: italic;" class="hljs-comment"># Decoded JSON from mitmproxy flows</span>
output_file = <span style="" class="hljs-string">"openapi_spec.json"</span>  <span style="font-style: italic;" class="hljs-comment"># OpenAPI output file</span>

<span style="font-style: italic;" class="hljs-comment"># Template for OpenAPI spec</span>
openapi_spec = {
    <span style="" class="hljs-string">"openapi"</span>: <span style="" class="hljs-string">"3.0.0"</span>,
    <span style="" class="hljs-string">"info"</span>: {
        <span style="" class="hljs-string">"title"</span>: <span style="" class="hljs-string">"Speediance API"</span>,
        <span style="" class="hljs-string">"description"</span>: <span style="" class="hljs-string">"Generated from mitmproxy flows against https://euapi.speediance.com"</span>,
        <span style="" class="hljs-string">"version"</span>: <span style="" class="hljs-string">"1.0.0"</span>
    },
    <span style="" class="hljs-string">"paths"</span>: {}
}

<span style="font-style: italic;" class="hljs-comment"># Function to safely decode JSON strings</span>
<span style="" class="hljs-keyword">def</span> <span style="" class="hljs-title function_">robust_decode_json</span>(<span style="" class="hljs-params">data</span>):
    <span style="" class="hljs-string">"""
    Decodes JSON strings, assuming they are Unicode-compliant.
    """</span>
    <span style="" class="hljs-keyword">if</span> <span style="" class="hljs-keyword">not</span> data:  <span style="font-style: italic;" class="hljs-comment"># Handle empty or None data</span>
        <span style="" class="hljs-keyword">return</span> {}

    <span style="" class="hljs-keyword">if</span> <span style="" class="hljs-built_in">isinstance</span>(data, <span style="" class="hljs-built_in">str</span>):
        <span style="" class="hljs-keyword">try</span>:
            <span style="font-style: italic;" class="hljs-comment"># Direct JSON decoding, assuming valid Unicode</span>
            <span style="" class="hljs-keyword">return</span> json.loads(data)
        <span style="" class="hljs-keyword">except</span> json.JSONDecodeError <span style="" class="hljs-keyword">as</span> e:
            <span style="" class="hljs-built_in">print</span>(<span style="" class="hljs-string">f"JSON decode error: <span style="" class="hljs-subst">{e}</span> | Data: <span style="" class="hljs-subst">{data[:<span style="" class="hljs-number">100</span>]}</span>..."</span>)
            <span style="font-style: italic;" class="hljs-comment"># If decoding fails, return the raw string</span>
            <span style="" class="hljs-keyword">return</span> data
    <span style="" class="hljs-keyword">return</span> data

<span style="font-style: italic;" class="hljs-comment"># Load the decoded JSON</span>
<span style="" class="hljs-keyword">with</span> <span style="" class="hljs-built_in">open</span>(input_file, <span style="" class="hljs-string">"r"</span>) <span style="" class="hljs-keyword">as</span> f:
    flows = json.load(f)

<span style="font-style: italic;" class="hljs-comment"># Process each flow to build the OpenAPI spec</span>
<span style="" class="hljs-keyword">for</span> flow <span style="" class="hljs-keyword">in</span> flows:
    <span style="" class="hljs-keyword">try</span>:
        request = flow[<span style="" class="hljs-string">"request"</span>]
        response = flow.get(<span style="" class="hljs-string">"response"</span>, {})
        method = request[<span style="" class="hljs-string">"method"</span>].lower()

        <span style="font-style: italic;" class="hljs-comment"># Parse path and query parameters</span>
        parsed_url = urlparse(request[<span style="" class="hljs-string">"url"</span>])
        path = parsed_url.path  <span style="font-style: italic;" class="hljs-comment"># Ensure it starts with `/`</span>
        query_parameters = parse_qs(parsed_url.query)  <span style="font-style: italic;" class="hljs-comment"># Parse query parameters safely</span>
        headers = request.get(<span style="" class="hljs-string">"headers"</span>, {})

        <span style="font-style: italic;" class="hljs-comment"># Decode request body</span>
        request_body = robust_decode_json(request.get(<span style="" class="hljs-string">"body"</span>))

        <span style="font-style: italic;" class="hljs-comment"># Decode response body</span>
        response_body = robust_decode_json(response.get(<span style="" class="hljs-string">"body"</span>))

        <span style="font-style: italic;" class="hljs-comment"># Add the path and method to OpenAPI spec</span>
        <span style="" class="hljs-keyword">if</span> path <span style="" class="hljs-keyword">not</span> <span style="" class="hljs-keyword">in</span> openapi_spec[<span style="" class="hljs-string">"paths"</span>]:
            openapi_spec[<span style="" class="hljs-string">"paths"</span>][path] = {}

        <span style="font-style: italic;" class="hljs-comment"># Build the schema for this endpoint and method</span>
        openapi_spec[<span style="" class="hljs-string">"paths"</span>][path][method] = {
            <span style="" class="hljs-string">"summary"</span>: <span style="" class="hljs-string">f"<span style="" class="hljs-subst">{method.upper()}</span> <span style="" class="hljs-subst">{path}</span>"</span>,
            <span style="" class="hljs-string">"parameters"</span>: [
                <span style="font-style: italic;" class="hljs-comment"># Query parameters</span>
                *[
                    {
                        <span style="" class="hljs-string">"name"</span>: key,
                        <span style="" class="hljs-string">"in"</span>: <span style="" class="hljs-string">"query"</span>,
                        <span style="" class="hljs-string">"required"</span>: <span style="" class="hljs-literal">False</span>,
                        <span style="" class="hljs-string">"schema"</span>: {<span style="" class="hljs-string">"type"</span>: <span style="" class="hljs-string">"string"</span>}
                    }
                    <span style="" class="hljs-keyword">for</span> key <span style="" class="hljs-keyword">in</span> query_parameters
                ],
                <span style="font-style: italic;" class="hljs-comment"># Header parameters</span>
                *[
                    {
                        <span style="" class="hljs-string">"name"</span>: key,
                        <span style="" class="hljs-string">"in"</span>: <span style="" class="hljs-string">"header"</span>,
                        <span style="" class="hljs-string">"required"</span>: <span style="" class="hljs-literal">False</span>,
                        <span style="" class="hljs-string">"schema"</span>: {<span style="" class="hljs-string">"type"</span>: <span style="" class="hljs-string">"string"</span>}
                    }
                    <span style="" class="hljs-keyword">for</span> key <span style="" class="hljs-keyword">in</span> headers
                ],
            ],
            <span style="" class="hljs-string">"responses"</span>: {
                <span style="" class="hljs-built_in">str</span>(response.get(<span style="" class="hljs-string">"status_code"</span>, <span style="" class="hljs-number">200</span>)): {
                    <span style="" class="hljs-string">"description"</span>: <span style="" class="hljs-string">"Response"</span>,
                    <span style="" class="hljs-string">"content"</span>: {
                        <span style="" class="hljs-string">"application/json"</span>: {
                            <span style="" class="hljs-string">"example"</span>: response_body  <span style="font-style: italic;" class="hljs-comment"># Properly decoded JSON object</span>
                        }
                    }
                }
            } <span style="" class="hljs-keyword">if</span> response_body <span style="" class="hljs-keyword">else</span> {}
        }

        <span style="font-style: italic;" class="hljs-comment"># Remove requestBody for GET requests</span>
        <span style="" class="hljs-keyword">if</span> method == <span style="" class="hljs-string">"get"</span>:
            openapi_spec[<span style="" class="hljs-string">"paths"</span>][path][method].pop(<span style="" class="hljs-string">"requestBody"</span>, <span style="" class="hljs-literal">None</span>)
        <span style="" class="hljs-keyword">elif</span> request_body:  <span style="font-style: italic;" class="hljs-comment"># Add requestBody for other methods</span>
            openapi_spec[<span style="" class="hljs-string">"paths"</span>][path][method][<span style="" class="hljs-string">"requestBody"</span>] = {
                <span style="" class="hljs-string">"required"</span>: <span style="" class="hljs-literal">True</span>,
                <span style="" class="hljs-string">"content"</span>: {
                    <span style="" class="hljs-string">"application/json"</span>: {
                        <span style="" class="hljs-string">"example"</span>: request_body  <span style="font-style: italic;" class="hljs-comment"># Properly decoded JSON object</span>
                    }
                }
            }

    <span style="" class="hljs-keyword">except</span> Exception <span style="" class="hljs-keyword">as</span> e:
        <span style="" class="hljs-built_in">print</span>(<span style="" class="hljs-string">f"Error processing flow: <span style="" class="hljs-subst">{e}</span>"</span>)

<span style="font-style: italic;" class="hljs-comment"># Save the OpenAPI specification to a file</span>
<span style="" class="hljs-keyword">with</span> <span style="" class="hljs-built_in">open</span>(output_file, <span style="" class="hljs-string">"w"</span>) <span style="" class="hljs-keyword">as</span> f:
    json.dump(openapi_spec, f, indent=<span style="" class="hljs-number">2</span>)

<span style="" class="hljs-built_in">print</span>(<span style="" class="hljs-string">f"OpenAPI specification saved to <span style="" class="hljs-subst">{output_file}</span>"</span>)



  



Ergebnis: openapi_spec.json

Dokumentation der API-Endpunkte

Die generierte OpenAPI-Spezifikation ermöglicht es, die API-Endpunkte der App lesbar und nachvollziehbar zu dokumentieren. Die Spezifikation kann in Tools wie dem Swagger Editor importiert werden, um die API-Struktur zu visualisieren und für weitere Analysen bereitzustellen. Dies hilft, die Funktionsweise der App besser zu verstehen und die Kommunikation zwischen App und Server detailliert zu untersuchen.

Dominic Böttger

Author

INSPIRATIONLABS

Technologie inspiriert durch Euer Potenzial!
Wir gestalten IT so, dass Teams endlich ihr ganzes Potenzial nutzen können.

Jetzt oder nie!

Unsere Kund:innen

Kontaktiere uns

Du hast eine Frage oder Interesse an einem unverbindlichen Erstgespräch? Schreib uns!

Vielen Dank, wir haben Deine Anfrage erhalten und melden uns bald!
Oops! Something went wrong while submitting the form.