Firsthand do’s and don’ts, from design to testing
“At least, have some dignity.”
M, Exercises in Futility VI -Mgła, Exercises in Futility LP (No Solace)
One thing that you notice once you embark on the arcane ways of offensive InfoSec (a.k.a penetration testing/red team engagement) is that your success is the direct consequence of someone else’s error. Most of the time, it’s the developers or the ops people whose mistake you will exploit to your mischievous intentions (simulated, of course – after all, we’re still the good guys). Also, most of the time, you’ll aim at the easiest way in – something that would be easily avoidable should those previously mentioned individuals took some basic security advice and applied it properly.
And it should be applied from the very beginning.
Include security guys in the design phase
Yes, everything starts even before the first line of code is written, or the first server spawn. If you don’t have a properly trained security professionals in your team, try to form a ‘red team’ – a small team of people who will NOT be engaged in application development but who possess some development knowledge. Their role will be to oppose your design solutions, and try to find a way to sabotage them, from a standpoint of a malicious user.
It is paramount not to use future app developers in the red team. Red team should be able to take a fresh look at the app, unobstructed by functional requests and development solutions.
Keep it simple!
Try to come up with some standard and simple solutions for your functional requirements. If your function, method, or procedure takes more than two A4 pages of code and is a tangled mess of if statements and complex loops, know that there HAS to be a bug hidden somewhere. Overly complex code is a sure sign of a failed design, and it is almost always better to go back to the design table then to proceed with such a buggy solution.
Assume nothing, check everything, trust no-one
Most of the vulnerabilities in the code come from assuming if something (data or action) will always have the presumed shape, then the value of will be carried as intended. This is especially the case when you do not have complete control over such data or action. But if you absolutely need to assume, always assume the worst.
Assume, for instance, a large class of vulnerabilities called ‘SQL injections. These types of vulnerabilities especially hit web apps but can occur whenever your app must connect to a SQL database, and SQL queries contain user-supplied data. You may assume that the ‘Username:’ field in the web login page will always be filled with proper usernames, but your hackers do not think so. Instead, they’ll try to insert something that can alter your SQL statement to return completely different results from those intended.
Therefore, check your input. Check it always. Even if it comes from some other part of your app. If you don’t control completely data that is being inputted in your piece of code, you don’t trust it. Check it and check it on multiple places. Check it when it’s inputted in your code. Check it again when you want to use that data.
Many new applications are based on service architecture. However, even if the data that you need comes from different part of your app or a different service, don’t trust it. Yes, all those checks may slow down your app, but are you ready to sacrifice the leak of data on your system over some speed gain? If you cannot fully control the creation and transport of data from some external service to your app, how do you know if that data was not tampered? Trust no-one.
Even if it’s not a requirement, try to implement data encryption in transit as well at rest. Often is the case that your adversary aims specifically at the data you possess. It would be stupid to implement all those checks only that your attacker sniffs your network and picks up everything they need from an unencrypted data stream.
Don’t allow more than you need
You should try to define what are the minimum-security permissions that are needed to perform a specific task. If, however, there are some parts of your app that need more permissions to do what it needs to do, DO NOT elevate privileges to your entire app. Instead, try to isolate that specific part and only give elevated privileges to that part. This is especially the case with web apps: bear in mind that web apps are collections of independent web pages which are, by definition, isolated mini-apps. Therefore, if one of those pages, for instance, needs to modify data in your database, create a separate database user that has privileges to modify data. Use another user that does not have those modifying privileges for pages that only querying data from the database.
Watch carefully what you put in your code repositories
Always double-check what you put in your code repositories. Don’t ever put sensitive data in them, like passwords. Never put config files in your repositories, unless they are just templates. This also applies even if your code repositories are private. If you’re using external code repositories not fully managed by you, remember – trust no-one, not even those big players like GitHub or BitBucket.
Fully isolate your development and test systems
If you don’t need general public to access your development and test systems, cut them from the outside network. Set up a separate VPN service for your developers and testers to be able to control access to your systems.
Always – and I cannot stress this enough – always separate your development and test systems from the rest of your network, especially the production system and live data.
Don’t be overly creative
Yes, this is a tough one. Everyone, especially young developers, wants to express their creativity by creating elegant and stunning code and implement novel algorithms. Don’t do that. Or try to be moderate. If you come up with some super new algorithm or implementation, ask yourself first: how come no one did that before I did? If you still come up with a satisfactory answer, then do the following: find someone from your team who hates you really much and give them your new, bright code to try to find vulnerabilities and bugs. Only then you can be somewhat sure that what you just come up with is safe to use.
And don’t use recursion. No, really, don’t!
Yes, they are out to get you!
You may say to yourselves that, sooner or later, everyone will be hacked, so why bother? Well, you don’t need to be the one whose mistake hacker exploited. Or, if you have to be that guy, have some dignity to give a good fight – let your hacker opponent sweat blood and tears to beat your app/server/system. Don’t let some know-nothing script kiddie beat your defences. If you have to fall, let it be by the hand of some seasoned hacker professional who dons a uniform every morning when going to work and who knows how to properly salute a senior officer.