Signal-Android leaks Signal's version and Android's SDK version through user-agent to external services

Hello.

In my free time, I usually like to explore Signal-Android’s code-base. I’m not a software engineer. I’m no expert, just curious. I would love it if an expert commented on the issue, maybe @valldrac would like to shed some light?

And yes, I did send an email to Signal to security @ signal (dot) org on 2024.09.20, but there was no response as of this moment.

Here’s the original email I sent to Signal.

Dear Signal,

Signal-Android makes use of OkHttpClients in many places around the app, while I understand the reasoning behind the use of the StandardUserAgentInterceptor class when communicating with Signal’s services, I’m not entirely sure that it was intentional to use it with external services, such as Stripe and Giphy.

In many places around the app, AppDependencies.okHttpClient is reused, which has the interceptor StandardUserAgentInterceptor applied to it.

E.g.

Users of AppDependencies.okHttpClient
Stripe related:
components/settings/app/subscription/StripeRepository.kt
jobs/ExternalLaunchDonationJob.kt

jobs/InAppPaymentAuthCheckJob.kt

Giphy related :
giph/mp4/GiphyMp4PagedDataSource.java
video/exo/SimpleExoPlayerPool.kt

And just as important, users that create their own okHttpClients and apply StandardUserAgentInterceptor directly:
giph/mp4/GiphyMp4Repository.java
glide/ChunkedImageUrlLoader.java
glide/OkHttpUrlLoader.java

Another concern that I have is with the usage of WebViews, which is fine of course, but what User-Agent do they use? Do they use some sort of combo of my WebView version/Android version? If so, is it possible to change the User-Agent to something that’s really generic/popular so we can just blend in when connecting to external services? I’m also not really concerned with the WebView that connects to Signal’s services for a registration captcha for example (although I would still prefer to give out as little information as possible, even to Signal), but I am concerned with leaking a possibly identifying User-Agent to external services, such as Stripe, since I see that WebViews are being used for Stripe related stuff in the app.

P.s. I’m not an iOS user so I can’t confirm whether all these things apply to iOS as well.

Best regards,

5 Likes

Can you simply issues for non technicals.

1 Like

Of course. I’ll give you a general example first then explain what’s happening with Signal.

General example: Each web browser has a user-agent that gets sent whenever you visit a webpage. For example, I’m currently using Firefox, so if I visit Google for example, they see the following text: “Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131”

Now that’s not really ‘unique’ to my own person, because you (or many others) could be running Windows 10 x64 with Firefox 131, right?

Signal: While Signal-Android is not a web browser, it does make http calls similar to web browsers and websites. For example, when you send a message to another person on Signal, it makes a request to the Signal servers, similar to the original example of your browser making a request to Google. And in doing so, Signal sends out a user-agent in that request, similar to what your browser does, that user-agent is, for example: “Signal-Android/7.18.2 Android/35”.

Now in the context of Signal servers, many users could be running Signal v7.18.2 on Android 15. No big deal for Signal servers to know that information anyway. (it actually helps them understand which Android versions are not in use or for them to ban specific Signal versions in case they have something severely wrong with them)

But if you start handing out that user-agent to external non-Signal services, then that’s a problem in my opinion. That currently happens for Stripe and Giphy (possibly others?) on Signal, Whenever you make a donation that goes through Stripe, Stripe gets that unique user-agent, which in my opinion, is very identifiable. Or whenever you search for a Gif in Signal gif picker, you also leak out your Signal version along with your Android version to Giphy, again, no reason for them to know that.

So that’s pretty much it in simple terms.

It’s also very important to note that Signal is usually careful with that stuff, for example, when you enable link previews, Signal needs to connect to the url you paste in your chat window in order to generate a link preview for you, Signal uses the user-agent “WhatsApp/2” so now servers see your user-agent as one of the billions of WhatsApp users. Pretty neat. While officially, A dev said that they use that user-agent because websites give out more consistent link previews. I’ll still take the side effect of the extra privacy.

2 Likes

Hey, great find!

Couple of questions:

  1. Are you sure the user agent being presented is customized to end user device or is it just a standard string shared with every call, irrespective of the end device and app version? Because as far as I know, no user app actually communicates with Giphy, and instead the request are routed through Signal servers (Source)
  2. If the user agent is being sent as it is, is it sufficient bits of information for actually accessing end user identity, or is it just data like Android version and App version that has a large crowd to mix in with (especially since Signal enforces min app version, and have target SDKs to prevent too old android versions)

Thanks in advance!

Here’s the standard Signal-Android user-agent format:
“Signal-Android/” + BuildConfig.VERSION_NAME + " Android/" + Build.VERSION.SDK_INT

So if you’re running Signal v7.18.2 on Android 15, your user-agent would be “Signal-Android/7.18.2 Android/35”
Source:

Signal hosts a Giphy proxy server, your user-agent would still be sent to the actual host, which is Giphy. Signal’s intention when they created the proxy is that they didn’t want to know any data that you sent to Giphy, they just wanted to forward your requests, a typical proxy job. So if the Signal app sends some header (in this case, user-agent) it would still be delivered to the host sitting behind the proxy.
You can see from Signal-Android source that it’s just an http proxy:

See above for a sample of the user-agent. And no matter how big the Signal user base is, why would I tell Stripe or Giphy “Hey I’m using Signal with the following version and following Android version” when I can just pretend I’m one of the millions of Chrome/Firefox users? The question is, why give out such information? Does it, for some reason, make the service better for the end user? I don’t believe so since both Stripe and Giphy are two apis that don’t differentiate between incoming user-agents. It’s not like we’re talking about a website that needs to execute a specific piece of code if it finds you’re using a specefic browser version so the webpage displays correctly.

1 Like

Thanks for the additional information!

So I asked around a bit, and it seems the Stripe one is mandatory for compliance. Signal uses stripe as a payment processor, and part of their PCI compliance is a tool called Radar, which includes machine learning for fraud detection. Sending valid useragents seems to be a basic requirement. (Original Source) . They are also working on adding other payment handlers after community feedback (Example)

The Giphy one seems interesting. I again pinged a few members, but they say the users object when they use other UA, and they also object when they use correct UA. Currently they use correct UA to ensure gifs display well.

Tbh, I’m not satisfied with this explanation. Their documentation also seems lacking in places (Example where they are actually sending iOS UA for link previews)

So I have also copied part of your mail and sent it to the security team. Do update here if you receive a response.

I mean, using “Signal” as their user-agent to Stripe would indeed be a valid UA, why include both Signal version and Android OS version in the UA?

I think you mixed up link previews and gifs, because you linked to a github issue about Signal using WhatsApp UA for link previews (which is fine imo). Which is not the same when they use Giphy.

Again, I think you mean they use WhatsApp UA for link previews to display well. Not gifs from Giphy.

I didn’t know iOS does that, thanks for looking into it.

Will do.

Was giving an example of how users objected in the past when they used Whatsapp for link previews (unrelated to Giphy, was just an example)

Thanks!

Response from Signal:

Thank you for reaching out to us at this email. When Signal clients communicate with a service like Giphy or Stripe, they are already doing so with API keys that are associated with Signal — so there’s no “leak” in that regard, where somehow Stripe learns that a transaction is associated with Signal via the User-Agent alone.

The version number also doesn’t seem particularly relevant or sensitive either, given the 90-day expiration window that is hardcoded into Signal clients.

We appreciate your curiosity, but don’t believe this is a vulnerability in Signal or that it warrants coordinated disclosure.

Security @ Signal

They didn’t mention why having the Android OS version in the user-agent is necessary. Which in my opinion has no reason to be in the UA at all.

Typically I would respond suggesting removing the Android OS version and maybe even removing Signal version itself from the UA and keeping a simple “Signal” UA. But, I won’t be doing that.

They also didn’t respond to my concerns around WebViews in my original email.

Anyway, I hope I didn’t waste people’s time with this thread.

Cheers.

2 Likes