59

Sendy is Insecure: How Not to Implement reCAPTCHA

 4 years ago
source link: https://victorzhou.com/blog/sendy-recaptcha-security/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

A few months ago, I switched from Mailchimp to Sendy , a self-hosted email newsletter alternative. I wrote a whole post about why I switched from Mailchimp to Sendy , but the gist is that Mailchimp got too expensive too fast.

Since then, I’ve been generally satisfied with Sendy. Until one day, this happened:

spam.jpg Spam signups to my Sendy email list

Sendy treats these email addresses as distinct , but they’re actually largely duplicates because of the Gmail period trick . That means these email addresses were getting all of my emails multiple times, which is an easy way for me to get reported for spam.

This incident finally spurred me to invest in protecting my email newsletter ( which I really should’ve done in the first place ). Luckily for me, Sendy comes with reCAPTCHA v2 support built-in! I thought it’d be easy to setup, but for some reason I couldn’t get it to work with my custom subscribe form , so I reached out to Sendy for help:

email1.png

Here’s what he responded with the next day:

email2.png

This line stuck out to me:

There’s no way to implement Google’s reCAPTCHA in an API.

That can’t be right - the reCAPTCHA documentation has a dedicated section on Server Side Validation !

I did some further investigation into Sendy’s standard subscribe form in an attempt to understand what Ben meant. Here’s an abbreviated version of the HTML behind that form:

<form action="https://sendy.victorzhou.com/subscribe" method="POST">
  <div>
    <label for="name">Name</label>
    <input type="text" name="name" id="name">
  </div>
  <div>
    <label for="email">Email</label>
    <input type="email" name="email" id="email">
  </div>
  <input type="hidden" name="list" value="LIST_ID_HERE">
  <input type="hidden" name="subform" value="yes">
  <div class="g-recaptcha" data-sitekey="SITE_KEY_HERE">
    <!-- some more reCAPTCHA stuff here -->
  </div>
  <a>Subscribe to list</a>
</form>

The first thing I noticed was the value of the form’s action attribute: https://sendy.victorzhou.com/subscribe . Interestingly enough, that’s the exact URL where the official Sendy API lives.

Now, we know the standard subscribe form:

  1. Has a working server-side validated reCAPTCHA implementation (I tested it).
  2. Uses the official Sendy Subscribe API.

Those two facts refute this claim:

There’s no way to implement Google’s reCAPTCHA in an API.

Additionally, why would Ben tell me that reCAPTCHA would ” take effect for for the standard subscribe forms that Sendy provides, not the subscribe API “?

<form action="https://sendy.victorzhou.com/subscribe" method="POST">
  <div>
    <label for="name">Name</label>
    <input type="text" name="name" id="name">
  </div>
  <div>
    <label for="email">Email</label>
    <input type="email" name="email" id="email">
  </div>
  <input type="hidden" name="list" value="LIST_ID_HERE">
  <input type="hidden" name="subform" value="yes">  <div class="g-recaptcha" data-sitekey="SITE_KEY_HERE">
    <!-- some more reCAPTCHA stuff here -->
  </div>
  <a>Subscribe to list</a>
</form>

Did you notice that highlighted line before? Why would the official Sendy form include a hardcoded subform field that’s not documented in the Sendy API ?

Yup, you (might have) guessed it. The subform field enables server-side reCAPTCHA verification . If that field isn’t set to yes , server-side reCAPTCHA is completely disabled .

I sent what I’d found to Ben immediately:

email3.png

His response was not what I was hoping for:

email4.png

Oh no. ‍♂️

email5.png

The rest of this email chain doesn’t really go anywhere. That’s why I’m writing this post - please patch this issue, Ben!

Come on, Victor, is this really such a big deal?

Yes. Recall this quote from my most recent email to Ben:

What good is reCAPTCHA if anybody with a computer can write a script in 5 minutes to spam your email list with thousands of fake signups?

Okay, it’s a little exaggerated (you’d obviously need some programming / web dev experience), but I stand by that point. Let me prove it to you.

Here’s a Node.js script that spams a Sendy email list:

spam-sendy.js

// This code is meant ONLY AS AN EXAMPLE.
// DO NOT USE - it's illegal and will get you in trouble.
const request = require('request');
for (let i = 0; i < 100; i++) {
  request.post('https://sendy.victorzhou.com/subscribe').form({
    email: `sendy-vulnerability-${i}@victorzhou.com`,
    list: 'TEST_LIST_ID_HERE',
  });
}
WARNING: DO NOT use this code to attack a real email list without permission. That's super illegal and can get you in serious trouble.

That’s 7 lines of code to send as many spam signups as you want. To test this, I set up a test list on my actual Sendy account, enabled reCAPTCHA for it using my real reCAPTCHA keys , and ran the script.

spam-result.png

It works. If you’re currently using Sendy’s reCAPTCHA implementation, now you know: it’s not doing what you think.

So how do I protect my email list?!

Well, ideally Sendy would release a patch that fixes this issue. Then you’d just have to update your installation and you’d be good to go! If you’re an affected Sendy user, you can help by emailing [email protected] to ask for a patch . Feel free to link this post as a reference - the more support we can get, the better.

In case Sendy doesn’t release a fix soon, here’s how you can modify your Sendy installation yourself to fix this:

subscribe.php
if($recaptcha_secretkey!='')
You can probably just search the file for this line of code.
  1. Move that entire if statement block outside of the $subform check. Here’s what the result looked like for me:

subscribe.php

// This is where the reCAPTCHA logic should end up
if ($recaptcha_secretkey != '') {
  // the rest of the reCAPTCHA code here
}

if ($subform) {
  // Some IP Address logic here
  // ...

  // This is where the reCAPTCHA logic used to be
  // ...

  // Some more stuff afterwards
  // ...
}

You can alsocontact me if you have issues protecting your Sendy subscribe form and I’ll do my best to help.

In Conclusion,

Please don’t implement reCAPTCHA like this.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK