Hacker News new | past | comments | ask | show | jobs | submit login
Monzo urges 480k customers to change their pin numbers (theguardian.com)
102 points by chaghalibaghali 76 days ago | hide | past | web | favorite | 65 comments



You can read in the announcement the need to update the app, meaning it was the app that logged the PIN and this led to internal logging.

I love Monzo, but one thing that does concern me greatly are banking apps (or any apps that touch highly sensitive pieces of information) that include third party components or make any communication to third parties.

In the case of Monzo: https://reports.exodus-privacy.eu.org/en/reports/88809/

+ Facebook Analytics

+ Facebook Login

+ Google Ads

+ Google CrashLytics

+ Google DoubleClick

+ Google Firebase Analytics

And according to NetGuard locally:

    ws-eu.pusher.com
    graph.facebook.com
    e.crashlytics.com
    app.adjust.com
    graph.accountkit.com
Of those, aside from generally "Why?" I'm most concerned by crashlytics.com . Is this like Sentry? Does it send a stack on a crash? If I'm paying someone and entered my PIN and it crashes, did my PIN go to a third party?

I saw an app recently that gave me the option in the settings to opt out of crashlytics - more of that please!

I'd be much happier seeing nothing third party in apps that deal with sensitive information.

And I'd be happy to memorise a 2nd less important software PIN for app transaction authorisation that wasn't the same as the ATM and hardware PIN.


It wasn't the app logging the PIN, was their AWS ELB setup.

Two APIs were accepting the PIN as URL parameters on GET requests, since in terms of REST principles, the operations were to retrieve information.

They were changed to non-GETs with the PIN sent in the body instead. The apps needed to be updated to switch to the new APIs.


Oh wow.

That's worse than accidental logging.

Engineers should know the GET params get logged fairly routinely and shouldn't be used for anything sensitive.


I thought with https, the ISP (or anyone in between) only sees the base url and not params?


A person in the middle can see the hostname via SNI.

But if you're terminating TLS on behalf of a customer, you can see everything. e.g. https://new.blog.cloudflare.com/terminating-service-for-8cha...

> Among other things, that resulted in us cooperating around monitoring potential hate sites on our network and notifying law enforcement when there was content that contained an indication of potential violence.

That indicates deep inspection of traffic going through CloudFlare.


AWS ELBs typically terminate the SSL connection for you. If you’re using AWS’ certificates management, you won’t have access to the private keys so you have to terminate at the ELBs.


That is correct (assuming https isn't compromised). The issue is that once the requests hits your systems, nothing by default treats url paths as sensitive. The end up in you your elb/Apache/nginx/stack trace logs. Making them really accessible to most employees.


This is correct, I've seen the diff of the two Android versions.


Do you have a source for this you could share?


Monzo will post a technical write-up soon. But, in the meantime:

Before:

  .method public abstract pan(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/reactivex/Single;
      .param p1    # Ljava/lang/String;
    .annotation runtime Lretrofit2/http/Query;
        value = "account_id"
    .end annotation
      .end param
      .param p2    # Ljava/lang/String;
    .annotation runtime Lretrofit2/http/Query;
        value = "card_id"
    .end annotation
      .end param
      .param p3    # Ljava/lang/String;
    .annotation runtime Lretrofit2/http/Query;
        value = "challenge_type"
    .end annotation
      .end param
      .param p4    # Ljava/lang/String;
    .annotation runtime Lretrofit2/http/Query;
        value = "challenge"
    .end annotation
      .end param
      .param p5    # Ljava/lang/String;
    .annotation runtime Lretrofit2/http/Query;
        value = "idempotency_key"
    .end annotation
      .end param
      .annotation system Ldalvik/annotation/Signature;
    value = {
        "(",
        "Ljava/lang/String;",
        "Ljava/lang/String;",
        "Ljava/lang/String;",
        "Ljava/lang/String;",
        "Ljava/lang/String;",
        ")",
        "Lio/reactivex/Single<",
        "Lcom/monzo/card/data/api/PanResponse;",
        ">;"
    }
      .end annotation

      .annotation runtime Lretrofit2/http/GET;
    value = "card/pan"
      .end annotation
  .end method
After (v2.59.1 has the fix):

  .method public abstract pan(Lcom/monzo/card/data/api/RetrievePanRequest;)Lio/reactivex/Single;
    .param p1  # Lcom/monzo/card/data/api/RetrievePanRequest;
    .annotation runtime Lretrofit2/http/Body;
    .end annotation
    .end param
    .annotation system Ldalvik/annotation/Signature;
    value = {
      "(",
      "Lcom/monzo/card/data/api/RetrievePanRequest;",
      ")",
      "Lio/reactivex/Single<",
      "Lcom/monzo/card/data/api/PanResponse;",
      ">;"
    }
    .end annotation

    .annotation runtime Lretrofit2/http/PUT;
    value = "card/retrieve-pan"
    .end annotation
  .end method


Can't you send a body with a GET request? Why change the request to POST/something else if GET was a better semantic match?


It should have been a POST.

Idempotency and implications for which operations can be cached is why.

GETs are cacheable, but if a PIN can change so can the answer. This is an authentication action, and it should have been POST.


imo this is exactly the sort of situation where if you can't implement perfectly "clean" REST, you shouldn't try at all and just use a simplified RPC mechanism only instead.

For my company, it's all POST-only, no URL params allowed, all inputs as JSON in the body only, no resource IDs in the path - just the method & version only.


You can, but it's unusual. Probably best to implement the most obvious fix, particularly because a lot of libraries (and engineers) will associate data with the query string in the context of a GET request.

Principle of least surprise and all that.


Out of interest, where did you hear this from?


Uh. I checked my bank (Boursorama, France) and I got 17 trackers. WTF? How can you just add trackers left and right in a _banking_ app?

    AdColony
    Adincube
    AppLovin
    ATInternet
    Facebook Ads
    Google Ads
    Google CrashLytics
    Google DoubleClick
    Google Firebase Analytics
    Inmobi
    MAdvertise
    Millennial Media
    Ogury Presage
    Smart
    Tapjoy
    Twitter MoPub
    Unity3d Ads


Report them to the French information commissioner, for GDPR violation.


Embedding a particular SDK doesn't necessarily mean that there's a GDPR violation?


That's true, but as n_trackers increases, so does the chance of of some kind of GDPR violation (assuming independence of violations amongst trackers)


>Is this like Sentry? Does it send a stack on a crash?

It's just like Sentry and does send back stack traces on a crash or any error the developer chooses to send back.

Crashlytics part of fabric.io, which was bought by Google and is actively being integrated into Google's Firebase.


One of my concerns about Monzo, that I have mentioned to them on Twitter some time ago, is the fact that they entirely rely on CloudFlare for customer facing services. Although I trust CloudFlare and understand how useful their services are, I am a bit uneasy that my banking information is transiting in clear through any third party.


    dig internal-api.monzo.com
    
    ;; QUESTION SECTION:
    ;internal-api.monzo.com.  IN A
    
    ;; ANSWER SECTION:
    internal-api.monzo.com. 288 IN CNAME k8s-worker-external-alb-prod-1306866561.eu-west-1.elb.amazonaws.com.
    k8s-worker-external-alb-prod-1306866561.eu-west-1.elb.amazonaws.com. 8 IN A 34.254.57.66
    k8s-worker-external-alb-prod-1306866561.eu-west-1.elb.amazonaws.com. 8 IN A 52.212.7.167
    k8s-worker-external-alb-prod-1306866561.eu-west-1.elb.amazonaws.com. 8 IN A 34.255.246.8
    
Monzo the bank, runs from Amazon and not via Cloudflare.

    dig www.monzo.com
    
    ;; QUESTION SECTION:
    ;www.monzo.com.   IN A
    
    ;; ANSWER SECTION:
    www.monzo.com.  59 IN A 104.25.212.99
    www.monzo.com.  59 IN A 104.25.211.99
    
Monzo the marketing website for the bank, uses Cloudflare.

If you go through the DNS logs for the Monzo app, you'll see everything else goes direct... Google, Facebook, Status Page, AWS.

With the only exception being some image assets:

    dig monzo-prod-user-images.imgix.net
    
    ;; QUESTION SECTION:
    ;monzo-prod-user-images.imgix.net. IN A
    
    ;; ANSWER SECTION:
    monzo-prod-user-images.imgix.net. 2280 IN CNAME dualstack.com.imgix.map.fastly.net.
    dualstack.com.imgix.map.fastly.net. 4 IN A 151.101.18.208


There are still plenty of customer-data-carrying things which do go over cloudflare - e.g. the internet banking, the public API, in-app webviews, etc


The Monzo web interface uses the public API too.


"through any third party"

I would argue that using a third party that specialises in security is better than remaking the wheel yourself.

Cloudflare invest a significant amount in the security of their platform and have a lot of talented engineers that focus solely on that. They have a lot more data to play around with and I would expect they can do a better job at security than Monzo alone, with their own infrastructure, with their own engineers. Note that Cloudflare is PCI compliant (https://support.cloudflare.com/hc/en-us/articles/202249734-C...)

Not sure I understand the hype of remaking the wheel, when specialist services exist that probably do the work better, more safely and cheaper.


In clear? Could you show me any example supporting this claim?


Cloudflare basically acts as a reverse proxy adding all kinds of features (mostly caching, origin protection against ddos, waf, etc). I'm pretty sure they're pci compliant


They are. Relevant support page for those who are curious: https://support.cloudflare.com/hc/en-us/articles/202249734-C...


Many banks use a CDN, primarily for DDOS protection. This includes the actual online banking endpoints. E.g. Lloyds and Barclays (major UK banks) both use Akamai, as does American Express.


The Google and Facebook stalking concerns me way more than any PIN leakage to be honest (and I'm wondering why they need both Google and Facebook Analytics?).

The leaked PINs were "leaked" to employees (and even then there's no evidence they were misused) and I expect them to be able to do damage even without those PINs if they wanted to, so to me it's not that big of a deal. The risk here could be if you reused the PIN on other cards.


Because they are optimising for both Google and Facebook ads. You may not like that answer, but it's true.


On you crashlytics concern. We use sentry which allows you to sanitize data being sent for reporting. We consider the sentry app to be "unprotected" from a data security standpoint and therefore assume anything sent to it is breached. I wouldn't be surprised if crashlytics had similar systems


I like what the challenger banks are doing. A customer of several, including Monzo, there are some features that I think Monzo and similar could add or more broadly adopt.

Different PIN for app and ATM.

'Always on' app not being always on. By this I mean opening the app and instantly being logged - no password - in with balance, transaction history, access to funds, etc. Material concern if phone lost. N26 by comparison always request password.

Again, comparing to N26, no self-imposed limit on ATM or online transactions. In N26 this is set in the app/web interface. Monzo doesn't have this self-set limit feature.

No web interface. Using a phone for everything is annoying.

Monzo do have very responsive and knowledgable customer service. And a very distinctively coloured card.


Seems like credentials being stored in logs is something that happens at pretty much every tech company - see e.g. Facebook[1] and Google[2]. Perhaps client and serverside hashing should be standard - at least then the actual credentials wouldn't be leaked, and the salt could be rolled the next time the user inputted it

[1] https://krebsonsecurity.com/2019/03/facebook-stored-hundreds...

[2] https://www.theverge.com/2019/5/21/18634842/google-passwords...


Rather than this workaround, don't use secrets this way at all. Do public key crypto. You don't need a PKI because you have an existing registration + authentication flow you can re-use to figure out which keys belong to which users.

This design is safe because your systems don't have any secrets to mistakenly log or otherwise lose. Make things we don't want to happen _impossible_ and they'll stop happening. Merely saying "Don't do that" doesn't make it stop happening.

Your site can literally post all the WebAuthn registration data for users on the front page, and it won't make it any easier for bad guys to sign in as those users, there aren't any secrets in there. That's a correct design.


PIN's are 4 digits in the UK. Hashing them (even with a per user salt) would only produce 10k possible hashes. To have anything that was difficult to brute force would also just not practical on CPU constrained mobile devices.


When traditional banks do "1st, 2nd and 4th character" checks (i.e. via their apps), some go to pretty good lengths to protect these, even given the limited entropy.

These systems are typically backed by HSMs, and the HSM generates a high-entropy "challenge". The challenge is sent to the client, which combines the challenge with the characters the user entered. The resulting hash is sent back to the HSM, which can verify internally that the correct characters were selected, by computing the expected hash for the correct response (hopefully in constant-time).

It seems there is a bigger issue here -- Monzo used card PINs as a generic security credential in their app, and was storing them in a recoverable form (albeit only accessible to a very small number of staff). That's not standard practice elsewhere that I'm aware of - given use of the PIN is the "proof" you approved the transaction, it's usually used only in environments with dedicated PIN entry devices (ATM, card readers), rather than commodity devices (phones), unless those go through PCI-style verification and approval.


That's why you don't hash it, you encrypt it. What they want to do here can be solved using something called channel binding.

The problem is not that we can't do this securely, it's two separate but related problems:

1. The web stack was never designed for RPC and attempts to use it that way are often unsafe.

2. Poor or missing libraries to make common patterns safe. Why was the logging framework happy to log the PIN, well, because the type system didn't know the PIN is sensitive. Why was the PIN even sent in the clear at all, well, because libraries to do this securely aren't well known and the web doesn't help.


PINS for the ATM are but my online banking has a number more


I assume they're all double-checking after it was reported by Facebook, and they're realising they've been doing the same thing for a long time too.


As someone who is fully drunk on Monzo kool-aid, well done to them on (a) identifying the problem and (b) immediately telling customers what to do.

Imagine how long this would have been an issue if it had happened at Barclays or TSB.


To use this news to belittle their competition about a hypothetical event seems like a dangerous form of kool-aid fanboyism.


I think we can safely applaud Monzo for their transparency in stark contrast to the opaque nature of "traditional" banks.



I find it bizarre how people aren't giving Monzo more of a hard time over this. This is a security blunder of the highest order. Saying sorry in a cutesy email after the fact doesn't in any way make up for it.


Probably because it's not a "security blunder of the highest order".

Equifax was a "security blunder of the highest order". This is an it-happened-to-Facebook-and-Google "shit, some sensitive info ended up in internal logs" blunder where there's no reason to suspect the PINs actually leaked out of the company. And even if they did, a PIN on its own is not sensitive information - unlike a password, it's just a 4 digit number, there's only 10000 of them. And unlike SSNs, they can be changed. I can tell you my PIN is 1077 and what on earth are you going to do with that information?


> This is a security blunder of the highest order.

No information was leaked... it's really kind of a non-issue. Monzo's approach is radical transparency. Any other bank probably would have never said anything.


> Monzo's approach is radical transparency.

How can you be sure of that?


Barclays have surprised me with how good their tech seems to be


Really? They can't join a Barclaycard with a checking account because the former was opened through the internet and not from the store (Their words.) Plus they don't allow installing the Barclay's mobile app if you are running Lineage OS and openGapps despite passing safetyNet. Last but not least the web log in process for Barclaycard is security theatre. Split into 2+ steps but all knowledge based, ie still only 1 factor but annoying to use.


> Lineage OS and openGapps despite passing safetyNet

I don’t know what any of these words mean, and I’m relatively technical

The sign-in I use requires a password (something you know) and a OTP generated by an irretrievable key, eg, something I have.


I believe LineageOS used to be called Cyanogenmod, and it's a fully free/open source implementation of Android. openGapps is Google Apps (like Maps) that you can install without having the Play Store.

Using LineageOS lets you do many more awesome things with your device, but financial apps (and even some apps like bus ticket apps) often don't work on this setup because it's viewed as insecure.


I remember we worked for a well established (no startup) EU bank on a completely new mobile banking (which later won several awards) and I always kind of wondered why they didn't want any 3rd party services like Google Analytics or Fabric. Well now I completely understand. Also, the PIN (which was a "password" to enter into the app) never left the app and the bank didn't know the PIN. A SRP (Secure Remote Password) protocol was used so that the passwords never left the device and actually even the communication could be done over HTTP (instead of SSL) and the attack would not gain the passwords/keys. I became a customer after working onsite for them and seeing the code and working with the devs at the bank :-).


Would that be my personal pin number, or some other pin number?


> personal pin number

It's your personal personal identification number number


The PIN you use to prove it's your card, when you use it in an ATM or in a chip and PIN reader.


I'm going to hazard a guess that @ggambetta is making a veiled reference to "RAS Syndrome".

https://en.wikipedia.org/wiki/RAS_syndrome


Off topic, but is there a name for names that are diagnostic or demonstrative of the thing. Eg you can't say 'lisp' with a lisp, have trouble spelling dyslexia with dyslexia. 'hippopotomonstrosesquippedaliophobia' is the fear of long words, TLA (three letter acronym) and the above obviously.


I think you are looking for: https://en.wikipedia.org/wiki/Autological_word


Perhaps? The examples given seem much more limited to the qualities of the word itself, I'm not sure the lisp example would fit under that banner.

That page does link to 'Ostensive definition' though:

"An ostensive definition conveys the meaning of a term by pointing out examples"

So perhaps 'Auto-ostensive'?

https://en.m.wikipedia.org/wiki/Ostensive_definition


Yep :) Didn't know it was called that.


It's refreshing to see the correct capitalisation and no redundant words in your sentence :)


It's the one you use in the ATM Machine :)


I only use automatic ATM machines.


It's your personal identification pin number




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: