We were informed of a card-verification scripted/automated exploit against a hosted-fields payment gateway, in which the bad actor utilized the gateway's client-side nonce generator in rapid fashion, then submitting the payment method creation form on Drupal Commerce 2. Their goal was apparently to validate a list of stolen or generated card numbers.
The processor did not particularly object to the nonce generation as nefarious (and there is little mitigation for this, since it's all client side) but we were dinged for not rate-limiting or otherwise restricting the submission of these nonces for payment method creation to the gateway API.
I believe this could be mitigated in a basic way on the server side by implementing flood protection with a sensible default that would not be normally triggered by a human, but will thwart scripted attacks such as this.
Per a quick convo with @mglaman in Slack:
on a call w/ coworker, and did a gut check w/ him who deals with a high fraud customer (not on Commerce...). he think this is the right approach, going for 10 fails per minute or whatever
The Form API doesn't prohibit re-submission by default, and we do need to be able to track failed submissions, so this sounds like a natural job for Flood.
As this potentially affects any on-site payment gateway, I think it's reasonable to implement this at the Commerce core level, rather than per-gateway.
Patch forthcoming.
| Comment | File | Size | Author |
|---|---|---|---|
| #41 | 3.diff | 12.5 KB | bradjones1 |
| #31 | 3111144-30-test-only.patch | 5.17 KB | bradjones1 |
| #30 | 3111144-30.patch | 10.21 KB | bradjones1 |
Issue fork commerce-3111144
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
bradjones1Comment #3
bradjones1Oy. Empty test only patch
Comment #4
bradjones1And an updated full patch.
Comment #5
mglamanMakes sense to do base. I was thinking add only. But someone could pull this off if logged in and trying to add cards to their account.
Comment #6
bradjones1Also I figured that's where the constructor is, currently, so as to try and minimize the BC break component of this. You can still get in trouble if you're extending and then, like, calling
parent::on a method that accesses the flood service, but AFAIK that's the kind of thing it sounds like this project puts on site owners to navigate around.Comment #9
bradjones1Tests failing with
Behat\Mink\Exception\ElementNotFoundException: Form field with id|name|label|value "payment_method[billing_information][address][0][address][given_name]" not found.- refactoring a bit for repeat submissions.Comment #10
bradjones1Comment #12
bradjones1Comment #13
bradjones1Forgot to merge in the optional test data for the submission method. Oy...
Comment #16
bradjones1Testbot getting a workout today.
Comment #18
bradjones1Duh... there's a special decline zip code and I was getting too creative.
Comment #20
bradjones1Comment #22
bradjones1So close.
Comment #24
bradjones1Comment #26
bradjones1I'm thinking maybe the issue here is with where/how the error on the form is displayed or not? I'm not super well versed in this aspect of the inline/nested form API. Adding a messenger call here akin to the other strings we test on to see if this is related.
Comment #28
bradjones1Flood's threshold is the number of allowed events - could it be this simple?
Comment #30
bradjones1Closer... actually triggering the flood now. Also updating some error language on the checkout declines; I *believe* the right message is to say "payment", since a decline at that step could also be related to something other than the payment method itself. Also helps to differentiate between these two (currently identical) messages.
Comment #31
bradjones1Test only patch, now that the whole thing is passing.
Comment #32
bradjones1Question on review: Is this sufficient to capture flood on both the one-off payment method add page AND adding a payment method during checkout? I think so, since this is a form that goes inline of the checkout pane?
Comment #34
bradjones1Comment #35
mglamanYeah this should capture all the things.
Any specific reason for changing the messaging here? Doing so in this patch would also break translations.
I'm going to ty and check how many things extend this class in payment gateways. I remember the last time the constructor changed it was a small pain.
However I can't recall if we consider these constructors as BC.
Can we document 9 and 60.
smort.
Comment #36
mglamanInstead of adding an argument to the constructor, can we use a
setFloodmethod, and the create method does something likePreserves BC and headache in payment gateways
Comment #37
rszrama commentedJust to confirm, this applies both to standalone payment method creation and embedded payment method creation, as on the checkout form, right? (If so, might want to retitle the issue to make that clear.)
Also, I think we may need to make the flood parameters configurable - 9 attempts in 60 seconds may be a reasonable default, but I can imagine different payment vendors requiring different settings.
Comment #38
mglamanSo this passes, just needs the nits.
Per #37: this applies anywhere the payment method form is embedded – checkout, user form, any custom form. It's also actually at the base. So anytime a payment method inline form is embedded for
createorupdateit will be utilized.For a setting.. should it just be
Settings::get()or some form UI somewhere? The flood control isn't per payment gateway, it's the system overall. I'm not for a setting because it means building a form for one value and deciding where that form has to live, etc, until there is a demand to expose the setting.Comment #40
bradjones1Updated the nullable parameters I tried to use because core only requires PHP 7.0.8; see #3165544: [POLICY] Adopt a minimum PHP 7 version stricter than core for my humble proposal to require Commerce run on at least PHP 7.2. But in the meantime, I'll use an old construct.
The updated MR does a few items which have become required as this is tested in the wild:
This also probably needs a change record or some sort of opt-in (though I'd rather not go the latter route) since this will increase declines where they are otherwise not, now. Human clients shouldn't have trouble making legitimate payments but this is also IP based and has a 1 day window, so it's worth thinking about.
Comment #41
bradjones1And because GitLab won't let you arbitrarily generate compare diffs between two specific SHAs...
Comment #42
bradjones1Addressed nit re: constructor BC. Let me know if this is an appropriate approach.
Proposed CR: https://www.drupal.org/node/3166751
Comment #43
jsacksick commented@bradjones1: re: constructor BC I still would suggest implementing the approach from Matt instead.
This approach is less intrusive and doesn't force all the gateways to tag a new version to get rid of the deprecation notice...