It's almost impossible to have not heard of the massive Equifax data breach that was announced last week, but in short: the credit reporting agency was breached by hackers, who walked away with as many as 143 million Social Security numbers, names, and addresses. For Americans, this is nothing short of catastrophic.
First, off, it seems like there are a few things you can do. The New York Times piece has a few recommendations, as does a comprehensive Reddit thread—check out the sticked first post for some things you can do straightaway.
Maybe, some point in the future, we'll get a full postmortem of what allowed this intrusion to happen. Until then, let's think about how we can better protect our own applications from becoming victim to this increasingly-familiar story. It's important to note that I'm focusing primarily on single developers, or small teams, who aren't dedicated security experts—large corporations and those dedicated to systems security have wildly different, and more complex, things to worry about.
1. Store only what you need to
Do you really need to store customer credit card data from their one-off purchase? Do you really need to ask for their mother's maiden name during signup and keep that in your database? Do you really need their home address and the last 4 digits of their Social Security number?
The answer, in most cases, is probably not. Or, definitely not.
The absolute best way to protect yourself against an embarrassing breach of highly sensitive information is to simply refuse to store that information in the first place. In most cases, particularly if you're running something like a simple SaaS application, you shouldn't need much more than their username and their password—maybe a first name to help personalize their experience.
Recurring billing models require a bit more information, of course, but more on that in a minute.
I genuinely hope that this incident, for all its potential impact for pretty much every American with a credit card, finally resets our expectations of what information an application needs to know about its users, and what users are expected to cough up in exchange for a functioning account.
Choice is critical here—some applications require more data than others, but the less required fields, the better the experience for everyone. Any application we build should emphasize minimalist data collection. That reduces risk, and, if databases are breached, at least minimizes the loss.
2. Outsource what you can't adequately protect
Businesses rely on taking payments, and doing so online is no joke. Do you really want to dive into the 12 primary requirements and 220 sub-requirements to become compliant with PCI-DSS? That's the barrier you need to hit in order to protect yourself against massive fines and other civil action in the event your payment information database is breached. Chances are you don't have the time, effort, or expertise to take credit cards online properly.
Square has a nice round-up of potential consequences for not complying with PCI standards: up to $100,000/month in fines, costly forensic audits, and more.
This is why payment companies exist. Whether it's retail point of sale (POS) or online credit card processing, just about every business ends up relying on some credit card processing company to take on the bulk of the security work and ensure customer data is used and stored in a safe manner.
For online payments, the leading brands are Stripe and Square, and it seems hard to go wrong with either. We use Stripe for our customer payments, and it works beautifully, allowing us to take payments from people all over the world, and even in BitCoin.
Outsourcing work isn't admitting defeat. For credit cards, it's the only way to go.
3. Encourage strong security practices from your users
Users have a tendency to be lazy, particularly when it comes to passwords. If you're building a webapp, you should encourage your users to use strong, unique passwords when signing up. Sometimes, simply reminding them what "strong" means is a big help—simply adding an exclamation point to "mypassword" doesn't do a whole lot of good.
At the same time, don't restrict your users into very specific password forms. It can be frustrating to users to require at least one lower case letter, one uppercase letter, one of the following symbols (!@#$) but not some others (%^&*), and with a length between 10 and 24 characters. Those requirements, while well-intentioned, actually encourage users to come up with quick workarounds rather than adhering to a strong password scheme that works for them.
Same goes for requiring users to change passwords at any given frequency—that's a recipe for password fatigue.
Encourage users to enable two-factor authentication (2FA) whenever possible as well. If you can create an incentive for them to do so, even better. (Note: add this to the SSD Nodes to-do list.) 2FA isn't a perfect system, but it certainly raises the bar for potential attackers who are looking for low-hanging fruit.
4. Don't try to reinvent security
Now that we're getting a little bit deeper into actual development, here's an important one to remember: Do not try to write your own cryptography code!
And if you need to hear it again, from a classic article on salted password hashing:
If you are thinking of writing your own password hashing code, please don't! It's too easy to screw up. No, that cryptography course you took in university doesn't make you exempt from this warning. This applies to everyone: DO NOT WRITE YOUR OWN CRYPTO!
Instead of relying on one of the proven cryptography systems out there (bcrypt, usually), a lot of newer users will try cobbling something together of their own. Luckily, popular cryptography libraries/functions have implementations available in most popular programming languages for web development, such as NodeJS, Ruby, Python, Java, and others.
Those who don't heed warnings well enough, or don't read the documentation properly, often end up posting well-meaning questions on Stack Overflow like this one on Node.js encryption of passwords:
I am currently using the following for encrypting passwords:
var pass_shasum = crypto.createHash('sha256').update(req.body.password).digest('hex');
Could you please suggest improvements to make the project safer?
So: do some research about proper, modern cryptography, and find the most popular and actively-developed binding in your language of choice. Follow the documentation. Don't go it alone.
It's important to remember that properly securing user passwords does not protect you from intrusions. Proper password hashing and storage is all about protecting your users, not you—you're still responsible for securing your infrastructure.
But, in the event your infrastructure is compromised, you can at least assure your users that their passwords were encrypted properly, which means that they won't end up on some list of known passwords for hackers to pass around and try out elsewhere.
The bcrypt binding for Ruby project reiterates this point about developer responsibility well:
It's your responsibility as a web developer to make your web application secure -- blaming your users for not being security experts is not a professional response to risk.
5. Use HTTPS everywhere
This is pretty self-explanatory—if you're accepting user credentials, it should be transmitted over HTTPS. This used to be a far more complex process, but Let's Encrypt has changed the game.
Certbot, developed by the Electronic Frontier FOundation, is the de facto Let's Encrypt client, and makes this process remarkably easy. Just input the system you're working on, whether you're using Nginx, Apache, or something else, and Certbot will give you details instructions on how to get your certificate and automatically renew it in perpetuity.
6. Eavesdrop on the experts
It's impossible for any one developer—particularly those who aren't devoted to security—to stay on top of the industry's latest movements.
One way to dive deeper into what the experts are most concerned about is through the Open Web Application Security Project, which is a collaborative effort to identify how and why certain vulnerabilities are more or less critical, depending on how widespread they are, how easy they are to exploit, and the potential ramifications. The current Top 10 2017, in a release candidate for, has been rejected to make some revisions, but given that the last Top 10 was released in 2014, it still comes with some key insights.
It pays to follow these discussions—only informed developers can start to make the right choices about how they structure and protect their applications. Time to get reading!
7. Make sure you know something actually went wrong... with logging!
Digging into the 2017 Verizon Data Breach Investigations Report could have been part of the previous item, but there's one big takeaway from Verizon's intensive breach analysis that's important to point out as a leadup into logging: typically, it only takes hackers minutes to compromise a system, but for most businesses, discovering that anything went wrong usually takes weeks or months.
That seems to be part of what happened with Equifax—although details are light, there appears to be a wide gap between when the intrusion occurred, when the company discovered said intrusion, and when they told everyone about it.
Logging is an essential tool in the effort to understand exactly what's happening on your server at any given time. You'll only know about anomalous behavior if you actually understand what anomalous means.
The ELK (Elasticsearch, Logstash, and Kibana) stack is a popular, proven system for pulling log data from disparate places into a central repository, enabling real-time analysis and search, and creating valuable visuals from said data. Check out this great rundown from Qbox on the three components of the ELK stack and why they matter.
Luckily for you, there's an actively-maintained Docker image that should set you up with a functioning ELK stack in a matter of minutes. Now, how you actually use that ELK stack to detect that something has gone wrong... well, that's for another article, and you might also want to look into a dedicated intrusion detection system.
And now, the tl;dr:
- Store a minimal amount of information about your users
- Never process or store a credit card yourself
- Encourage strong passwords
- Encourage (and incentivize) two-factor authentication
- Don't write your own cryptography
- Use an existing cryptography library + binding for your language, and follow the documentation
- Use HTTPS everywhere
- Figure out what the experts think
- Use logging to find intrusions in the first place
- Invest in intrusion detection systems(?)
I would love to put together a second (or third) part to this piece with more specific ideas for developers who want to improve the security of their applications. If you have ideas, drop them in the comments. Until then, for those in the U.S. who care about their financial future, stay vigilant of your credit score...