Sendy Is Insecure: How Not to Implement ReCAPTCHA
victorzhou.com
victorzhou.com
There are a lot of miscommunication between me and the author of this post. The selected snippets of messages posted on the article seem to put me in a bad light, however it's only one side of the story. The selected messages posted on the article are ones that are favorable to his argument. For example the author did not post a screenshot of me saying that I will be looking into this but went ahead to write an elaborate blog post immediately to put Sendy in an unfavourable light.
Bugs and issues with security has been and always will be the top priority with Sendy over the years. I agree the client side parameter 'subform' bypasses the reCAPTCHA and should be fixed. It is an oversight. And it will be fixed.
There are concrete things you can do to improve.
1) When someone reports a security issue to you, always always always thank them, even if you think that they're wrong, and are about to tell them. They're taking time that they don't need to take, with the goal to help you deal with a problem that is so much more yours than theirs. In other words they are demonstrating generosity, so thank them for that. If it turns out that they were wrong and that there is indeed no issue, well, no skin off your back; if it turns out that they are right, you'll be glad to still have them on your side rather than writing posts.
2) Be more curious. Instead of declaring "This is not a vulnerability or a security issue", as if the issue was closed, you can simply ask: "I'm not understanding why this is a vulnerability or a security issue. Can you explain and demonstrate a proof-of-concept attack?"
3) Consider getting advice from a security-minded person on such issues. It doesn't really take credentials. It just takes a certain kind of mindset that, in my experience, is not held by the majority of even very talented software developers.
I have to say, reading your response paragraph that starts with "if a human opens up his browser console to remove the subform parameter...", I recognized a very common feeling in me. Oh no, this person is just not getting security. Same facepalm reaction as the author. Having an API parameter that lets an (untrusted) client override a security measure isn't an "oversight", and more like a big design flaw. Kind of like if someone had a login API with a parameter called "pretend_password_is_correct" that let you sign in as anyone when set to true. If you're not seeing the issue when pointed out to you so clearly, it is really in your best interest to not make security decisions by yourself.
You shouldn't feel alone in this. Most developers that I've worked with, even top talent, cannot manage to put themselves in an attacker's shoes. Usually it's hard enough to put yourself in the normal user's shoes and get the thing to work for them. Following advice from people who have the skill of thinking like an attacker is the most valuable thing you can do to protect yourself.
I've been asking this hundreds of times but never got an answer, why doesn't Sendy add a visual e-mail builder?
Customers are going crazy over the issues with Wysiwyg and tables to make simple two-column designs, while drag/drop builders are straightforward and simple while keeping the code fully compatible with mailers.
Basically all competitors have drag/drop builders now, even the simpler one-time-pay scripts on Envato.
Sendy's pricing model simply doesn't allow such 3rd party product licensing and I imagine maintaining a heavy front-end application like a drag-and-drop template builder across the variety of clients is too costly (dev and support time) as well.
Background: I write PHP for fun, I don't compete with Sendy, I don't have any stake in any service that competes with Sendy. I also don't expect PHP to be written in the most OOP-everything Symfony-esque way either. But this is a bit too much for me even.
It doesn't follow any modern standards or guidelines; it doesn't use any templating system, just PHP hardcoded inline with HTML in hundreds of files (no modern framework, lightweight or not), concatenated variables into SQL queries and HTML. The author seems to hate using { } for if/else statements, which has the potential of introducing fun bugs if not extremely careful. 1 and 2 letter variable names are common (and I don't mean in the way of $i or $j); parts of it border on insane or bizarre; there are 500 character lines where instead of an error generation feature they just die() with a full HTML document as a string for error pages; functions are written inline all over the place and they all use the keyword global; there are no parameterised queries - it's covered in repetitive mysqli_real_escape_string and query concatenation. There is a massive amount of copy-pasted code: instead of some kind of single database management layer, there's a re-definition of a function that connects to a database with the same name in dozens of files, all of which try to read global variables (doesn't take any parameters)
Our MySQL env is a pxc/galera cluster, and connections require tls. When I asked the developer about adding tls support (about 4 lines of code if you’re using mysqli as he is) I just got a flat “nope, not doing that”.
At which point I lost all reservations about deobfuscating the “protected” files and simply replacing the db setup call in every single god damn file with a single setup that accepts tls options.
I’ve worked on some garbage code projects, but sendy is almost deliberately bad. In one place it: disabled all error reporting; tries to connect to the database; fails silently if the connection doesn’t succeed.
There is zero logging (besides an ungodly number of notices and warnings about undeclared variables, etc) and the author has apparently zero interest in actually fixing or improving any part of it at a technical level.
For those who work in php and want a comparison: it makes Wordpress in general look like a well architected application.
Still, the big selling point is: Inexpensive. Easy to install (simple PHP). Looks relatively polished.
All the "competitors" are either much more expensive, or you need to be an enthusiast in the tech stack they have chosen.
We've been using it for years without any issues.
A lot of popular (but expensive) services like ConvertKit have moved to a tagging approach many many years ago.
It's so much easier to manage your list of emails when it's a single list and then you add tags to specific users like "purchased X". This way the email address only exists in 1 spot and you can segment on the tags.
The Sendy approach (and from the looks of it your app too) becomes very unwieldy with having to manage, parse and merge multiple lists on a regular basis.
Performance is extra helpful for making real-time segment size calculations. We use elastic search and it's costly.
The multi-list approach has several other benefits. When manager / sender (and other) permissions get introduced, it will be straight forward to restrict users to managing certain lists. In addition, multiple lists allow subscribers to selectively subscribe / unsubscribe from lists.
Internally, the structure is simple. There is only one subscribers table and subscriber data is not duplicated anywhere. List (foreign key) relationships are in a separate table.
So in theory could you have 1 list and each subscriber has many tags, and then you can segment on those tags?
Also, do you have any public success stories beyond your own Zerodha campaigns? Have you compared delivery / bounce / etc. rates vs Sendy and other tools using the same email providers? Also how fast can you send emails out through SES?
Like GrapesJS: https://github.com/artf/grapesjs-mjml
I feel like part of the problem here is just miscommunication. Victor and Ben seem to be talking past each other.
This line stood 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 assume what Ben meant was that it's impossible to implement reCAPTCHA entirely in an API. The nature of reCAPTCHA requires you to have a UI element, which would be impossible in an API. Victor interpreted the claim as if Ben said that an API can't support reCAPTCHA, which would be incorrect, but may not be what Ben meant.
I feel like the lead sort of got buried in Victor's report. The headline to me is that abusers are actively automating phony signups with this vulnerability. I don't see that stated explicitly anywhere. The closest is this line in Victor's third email, after the conversation has gotten somewhat heated:
>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.
The fact that attackers are exploiting this in the wild seems to be the most salient point, but I'm not sure that Ben knew that from the correspondence shown.
That’s exactly what I meant. Thanks for picking up on this.
> The fact that attackers are exploiting this in the wild seems to be the most salient point, but I'm not sure that Ben knew that from the correspondence shown.
I know that and hence was working on a fix for the next update.
Even though some of my comments may not be in agreement with the author, but I did mentioned in my email conversation that I am looking into it. But of course that was being left out of the post, no screenshots of that comment was found in the author’s post.
If I had released the next update without addressing this issue then yes feel free to write a post with these accusations. But I wasn’t given the benefit of the doubt.
But they are distinct! Why should anyone have to special-case some provider's hack? Do you expect Sendy to special-case plus addresses? For which hosts?
Sure, it's nice if Sendy handles special cases, but it's strictly a bonus feature, and not doing it is also correct.
(The main issue with the hidden field is a valid complaint, of course, and it really doesn't look good)
I've mentioned it at least a dozen times here on HN because it's an underrated piece of software that works and is actively maintained, unfortunately their marketing is very weak.
Bonus: the code is not obfuscated, it's built on PHP using a proper framework and the author is very active on the support forums. I'm hosting it on Webfaction (currently migrating to Opalstack) for cheap and almost no maintenance.
I think it's hard to get taken seriously by companies when you sell a "script" on Invato too and I'm not sure why they do this? Why not just sell it yourself and receive more of the profits?
Nothing prevents him from doing it in the future though, when he already have enough customers to make a recurring cost profitable.
EDIT: forgot to mention I'm the author of this post
EDIT: docs are here: https://developers.google.com/recaptcha/docs/display#javascr... though the lifecycle of how this all works is confusing. I’ll have to do a write-up on this at some point.
I have not found any self hosted solutions (paid or free / open source) and I've looked a lot in the past. I've seen services like Drip and ConvertKit offer this but both of them are at the high end of hosting costs. To put things into perspective it's $80 / month for a list of 3,000 users with ConvertKit even if you send nothing.
One of the more polished tools I've seen is Freecodecamp's https://github.com/freeCodeCamp/mail-for-good project but it doesn't support auto responders or tagging. I've opened issues on this 2+ years ago at https://github.com/freeCodeCamp/mail-for-good/issues/196 and https://github.com/freeCodeCamp/mail-for-good/issues/197 but I don't think they are going to implement it.
ConvertKits is expensive - all plans fall under BigMailer's free plan :-o
I was hoping to find a 1 time purchase solution that meets those requirements. Sorry for not being more clear in my description.
It might not be that easy, because there might be a bunch of users currently depending on the current behavior, and as soon as a real ReCAPTCHA token is required, they will break. They might need to introduce a 3rd ReCAPTCHA option. So they would have 3 options "ReCAPTCHA off", "ReCAPTCHA legacy weak", and "ReCAPTCHA on".
this change requires verifying secret api key in the subform=no case and restricts opt_in bypass to this subscribe api usage (since captcha is not good enough to stop all bots)
Shameless plug - I run a hosted alternative to Sendy https://www.mailblast.io/
>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.
"Super illegal"?
Sendy is open source (albeit not free) and running on your own server. Ben's response says because you are using a customized webform instead of sendy's provided forms, that you need to handle the captcha code on the form and siteverify on the backend on your own.
Before sendy had recaptcha support you would have to modify the subscribe code to include a call to captcha siteverify. Here's some PHP code for how you call siteverify before proceeding with something: https://www.adam-bray.com/2018/04/02/adding-recaptcha-with-p...
You can avoid running your own backend altogether now anyway since SES supports bulk template emails. You can store all your form submissions using a lambda/cloudfunction/webworker to verify the captcha and store the list. When you want to send email you can pull your list into something running on your laptop and then invoke the SES bulk templated email from there. You can use lambdas for pixels and custom links that update records for those users. I wrote my own angular-firebase version of this
if subform:
if captcha fails:
feedback = "Failed recaptcha test"
...
if feedback!='Failed recaptcha test' (&& other stuff) do subscribe
edit: misread the code and formatting on HN didn't even show my intent, but the subform check doesn't contain the subscribe logic. The bug is clearly that it doesn't check if the captcha has passed.if you need API support, you could use captcha_passed = verify_api_key($api_key) and include the api_key in the post request
According to what or who? It ships with laughably obfuscated files, it makes no mention of open source in any documentation.
How does that work? What's the license exactly?
> This Agreement grants a non-exclusive, non-transferable license to install and use the Software on a single Website. Additional Software licenses must be purchased in order to install and use the Software on additional Websites. The Author reserves the right to determine whether use of the Software qualifies under this Agreement. The Author owns all rights, title and interest to the Software (including all intellectual property rights) and reserves all rights to the Software that are not expressly granted in this Agreement.
> [...] You may not:
> Distribute derivative works based on the Software;
> Reproduce the Software except as described in this Agreement;
OP might mean source accessible (rather than compiled or being a hosted service).