Skip to content
Last updated: July 10, 2024

MASTG-DEMO-0009: Detecting Sensitive Data in Network Traffic

Content in BETA

This content is in beta and still under active development, so it is subject to change any time (e.g. structure, IDs, content, URLs, etc.).

Send Feedback

Sample

The snippet below shows sample code that sends sensitive data over the network using the HttpURLConnection class. The data is sent to https://httpbin.org/post which is a dummy endpoint that returns the data it receives.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package org.owasp.mastestapp

import android.content.Context
import android.util.Log
import java.io.BufferedOutputStream
import java.io.BufferedWriter
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import java.util.logging.Logger

class MastgTest (private val context: Context){

    fun mastgTest(): String { 

        val SENSITIVE_DATA = mapOf(
            "precise_location_latitude" to "37.7749",
            "precise_location_longitude" to "-122.4194",
            "name" to "John Doe",
            "email_address" to "[email protected]",
            "phone_number" to "+11234567890",
            "credit_card_number" to "1234 5678 9012 3456"
        )

        var result = ""

        val thread = Thread {
            try {
                val url = URL("https://httpbin.org/post")
                val httpURLConnection = url.openConnection() as HttpURLConnection
                httpURLConnection.requestMethod = "POST"
                httpURLConnection.doOutput = true
                httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")

                // Creating POST data from the SENSITIVE_DATA map
                val postData = SENSITIVE_DATA.map { (key, value) ->
                    "${URLEncoder.encode(key, "UTF-8")}=${URLEncoder.encode(value, "UTF-8")}"
                }.joinToString("&")

                val outputStream = BufferedOutputStream(httpURLConnection.outputStream)
                val bufferedWriter = BufferedWriter(OutputStreamWriter(outputStream, "UTF-8"))
                bufferedWriter.write(postData)
                bufferedWriter.flush()
                bufferedWriter.close()
                outputStream.close()

                val responseCode = httpURLConnection.responseCode
                val responseContent = httpURLConnection.inputStream.bufferedReader().readText()
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    Log.d("HTTP_SUCCESS", "Successfully authenticated.")
                } else {
                    Log.e("HTTP_ERROR", "Failed to authenticate. Response code: $responseCode")
                }
                result = "$responseCode\n\n$responseContent"

            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        thread.start()
        thread.join()

        return result
    }

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package org.owasp.mastestapp;

import android.content.Context;
import android.util.Log;
import androidx.autofill.HintConstants;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import kotlin.Metadata;
import kotlin.TuplesKt;
import kotlin.collections.CollectionsKt;
import kotlin.collections.MapsKt;
import kotlin.jvm.internal.Intrinsics;
import kotlin.jvm.internal.Ref;

/* compiled from: MastgTest.kt */
@Metadata(d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0000\b\u0007\u0018\u00002\u00020\u0001B\r\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004J\u0006\u0010\u0005\u001a\u00020\u0006R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000¨\u0006\u0007"}, d2 = {"Lorg/owasp/mastestapp/MastgTest;", "", "context", "Landroid/content/Context;", "(Landroid/content/Context;)V", "mastgTest", "", "app_debug"}, k = 1, mv = {1, 9, 0}, xi = 48)
/* loaded from: classes4.dex */
public final class MastgTest {
    public static final int $stable = 8;
    private final Context context;

    public MastgTest(Context context) {
        Intrinsics.checkNotNullParameter(context, "context");
        this.context = context;
    }

    /* JADX WARN: Multi-variable type inference failed */
    public final String mastgTest() {
        final Map SENSITIVE_DATA = MapsKt.mapOf(TuplesKt.to("precise_location_latitude", "37.7749"), TuplesKt.to("precise_location_longitude", "-122.4194"), TuplesKt.to(HintConstants.AUTOFILL_HINT_NAME, "John Doe"), TuplesKt.to("email_address", "[email protected]"), TuplesKt.to("phone_number", "+11234567890"), TuplesKt.to("credit_card_number", "1234 5678 9012 3456"));
        final Ref.ObjectRef result = new Ref.ObjectRef();
        result.element = "";
        Thread thread = new Thread(new Runnable() { // from class: org.owasp.mastestapp.MastgTest$$ExternalSyntheticLambda0
            @Override // java.lang.Runnable
            public final void run() {
                MastgTest.mastgTest$lambda$1(SENSITIVE_DATA, result);
            }
        });
        thread.start();
        thread.join();
        return (String) result.element;
    }

    /* JADX INFO: Access modifiers changed from: private */
    /* JADX WARN: Type inference failed for: r7v10, types: [T, java.lang.String] */
    public static final void mastgTest$lambda$1(Map SENSITIVE_DATA, Ref.ObjectRef result) {
        Intrinsics.checkNotNullParameter(SENSITIVE_DATA, "$SENSITIVE_DATA");
        Intrinsics.checkNotNullParameter(result, "$result");
        try {
            URL url = new URL("https://httpbin.org/post");
            URLConnection openConnection = url.openConnection();
            Intrinsics.checkNotNull(openConnection, "null cannot be cast to non-null type java.net.HttpURLConnection");
            HttpURLConnection httpURLConnection = (HttpURLConnection) openConnection;
            httpURLConnection.setRequestMethod("POST");
            httpURLConnection.setDoOutput(true);
            httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            Collection destination$iv$iv = new ArrayList(SENSITIVE_DATA.size());
            for (Map.Entry item$iv$iv : SENSITIVE_DATA.entrySet()) {
                String key = (String) item$iv$iv.getKey();
                String value = (String) item$iv$iv.getValue();
                destination$iv$iv.add(URLEncoder.encode(key, "UTF-8") + '=' + URLEncoder.encode(value, "UTF-8"));
                url = url;
            }
            String postData = CollectionsKt.joinToString$default((List) destination$iv$iv, "&", null, null, 0, null, null, 62, null);
            BufferedOutputStream outputStream = new BufferedOutputStream(httpURLConnection.getOutputStream());
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
            bufferedWriter.write(postData);
            bufferedWriter.flush();
            bufferedWriter.close();
            outputStream.close();
            int responseCode = httpURLConnection.getResponseCode();
            if (responseCode == 200) {
                Log.d("HTTP_SUCCESS", "Successfully authenticated.");
            } else {
                Log.e("HTTP_ERROR", "Failed to authenticate. Response code: " + responseCode);
            }
            result.element = responseCode + "\n\n" + postData;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Steps

Start the device, in this case, the Android emulator:

emulator -avd Pixel_3a_API_33_arm64-v8a -writable-system

Run mitmproxy with the custom script for logging sensitive data and dump the relevant traffic to a file.

Note that the script is preconfigured with data that's already considered sensitive for this application. When running this test in a real-world scenario, you should determine what is considered sensitive data based on the app's privacy policy and relevant privacy regulations. One recommended way to do this is by checking the app's privacy policy and the App Store Privacy declarations.

mitm_sensitive_logger.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from mitmproxy import http

# This data would come from another file and should be defined after identifying the data that is considered sensitive for this application.
# For example by using the Google Play Store Data Safety section.
SENSITIVE_DATA = {
    "precise_location_latitude": "37.7749",
    "precise_location_longitude": "-122.4194",
    "name": "John Doe",
    "email_address": "[email protected]",
    "phone_number": "+11234567890",
    "credit_card_number": "1234 5678 9012 3456"
}

SENSITIVE_STRINGS = SENSITIVE_DATA.values()

def contains_sensitive_data(string):
    return any(sensitive in string for sensitive in SENSITIVE_STRINGS)

def process_flow(flow):
    url = flow.request.pretty_url
    request_headers = flow.request.headers
    request_body = flow.request.text
    response_headers = flow.response.headers if flow.response else "No response"
    response_body = flow.response.text if flow.response else "No response"

    if (contains_sensitive_data(url) or 
        contains_sensitive_data(request_body) or 
        contains_sensitive_data(response_body)):
        with open("sensitive_data.log", "a") as file:
            if flow.response:
                file.write(f"RESPONSE URL: {url}\n")
                file.write(f"Response Headers: {response_headers}\n")
                file.write(f"Response Body: {response_body}\n\n")
            else:
                file.write(f"REQUEST URL: {url}\n")
                file.write(f"Request Headers: {request_headers}\n")
                file.write(f"Request Body: {request_body}\n\n")
def request(flow: http.HTTPFlow):
    process_flow(flow)

def response(flow: http.HTTPFlow):
    process_flow(flow)
run.sh
1
mitmdump -s mitm_sensitive_logger.py

Launch the app from Android Studio and click the button which will send the sensitive data over the network. The script will capture the network traffic and log the sensitive data.

Observation

The script has identified several instances of sensitive data in the network traffic.

  • The first instance is a POST request to https://httpbin.org/post which contains the sensitive data values in the request body.
  • The second instance is a response from https://httpbin.org/post which contains the sensitive data values in the response body.
sensitive_data.log
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
REQUEST URL: https://httpbin.org/post
Request Headers: Headers[(b'Content-Type', b'application/x-www-form-urlencoded'), (b'User-Agent', b'Dalvik/2.1.0 (Linux; U; Android 13; sdk_gphone64_arm64 Build/TE1A.220922.021)'), (b'Host', b'httpbin.org'), (b'Connection', b'Keep-Alive'), (b'Accept-Encoding', b'gzip'), (b'Content-Length', b'188')]
Request Body: precise_location_latitude=37.7749&precise_location_longitude=-122.4194&name=John+Doe&email_address=john.doe%40example.com&phone_number=%2B11234567890&credit_card_number=1234+5678+9012+3456

RESPONSE URL: https://httpbin.org/post
Response Headers: Headers[(b'Date', b'Fri, 19 Jan 2024 10:17:44 GMT'), (b'Content-Type', b'application/json'), (b'Content-Length', b'735'), (b'Connection', b'keep-alive'), (b'Server', b'gunicorn/19.9.0'), (b'Access-Control-Allow-Origin', b'*'), (b'Access-Control-Allow-Credentials', b'true')]
Response Body: {
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "credit_card_number": "1234 5678 9012 3456", 
    "email_address": "[email protected]", 
    "name": "John Doe", 
    "phone_number": "+11234567890", 
    "precise_location_latitude": "37.7749", 
    "precise_location_longitude": "-122.4194"
  }, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Length": "188", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 13; sdk_gphone64_arm64 Build/TE1A.220922.021)", 
    "X-Amzn-Trace-Id": "Root=1-65aa4c48-45514c0e3782665063b14397"
  }, 
  "json": null, 
  "origin": "148.141.65.87", 
  "url": "https://httpbin.org/post"
}

Evaluation

After reviewing the captured network traffic, we can conclude that the test fails because the sensitive data is sent over the network.

This is a dummy example, but in a real-world scenario, you should determine which of the reported instances are privacy-relevant and need to be addressed.

Note that both the request and the response are encrypted using TLS, so they can be considered secure. However, this might represent a privacy issue depending on the relevant privacy regulations and the app's privacy policy. You should now check the privacy policy and the App Store Privacy declarations to see if the app is allowed to send this data to a third-party.