Quinn Norton recently wrote Everything Is Broken, an article lamenting the sad state of software and internet security in general, concluding that there are “plenty of ways we could regain privacy and make our computers work better by default. It isn’t happening now because we haven’t demanded that it should.” In recent months, we’ve seen ever more reasons why it needs to happen, but if you’re a developer, the task of building secure software can seem to be daunting. Vulnerabilities are a bane of large complex software projects, and companies like Microsoft spend millions to try to address them.
Remote Code Execution (RCE) vulnerabilities are the ones you care about the most. RCE vulnerabilities are virtually all of the vulnerabilities that people like the NSA pay money for and use, since they let attackers take everything you’ve saved on your computer and more. As Quinn Norton said, “they are bugs that let someone take over your whole computer, see everything you type or read and probably watch you pick your nose on your webcam.” Or as the NSA recently boasted, “If we can get the target to visit us in some sort of web browser, we can probably own them.”
This shouldn’t be a surprise, but since it’s popular to claim everything is hackable and nothing can be secure, it’s worth spelling out. Remote code execution vulnerabilities are not hard to prevent if developers follow a few simple, practical rules from the start, since they basically always fall into the below categories:
- Memory corruption flaws
- Generating a command to be executed by another language
- Wrong file access
- Giving away remote control
To categorically avoid this, use only memory-safe languages, like C#, Java, ruby, go, rust, D, or Ada.
Contrary to popular opinion, many of those compile natively and portably, run fast, require minimal dependencies, interface easily with C libraries, and have lots of developer support behind them. If you don’t do this, RCE vulnerabilities can happen virtually anywhere and everywhere in your program; you could accidentally access an array index out of bounds, accidentally use uninitialized memory, or use an object after it was free’d or double-free since keeping track of allocations and deallocations is really hard in complicated programs, and the NSA will undoubtedly own you.
If you do use a memory-safe application, RCE vulnerabilities can only happen at one of the below defined points in your application:
To categorically avoid this, don’t use API’s that parse and execute instructions.
That means don’t craft and execute SQL statements or OS commands or use functions like eval with user-provided data in the command to be executed. Allowing users/untrusted data to instantiate and call methods on arbitrary object types is also often equivalent to eval, so don’t deserialize arbitrary objects either and avoid reflective programming.
For example, you can use prepared statements or a object-based database interface that does not do command parsing.
To categorically avoid this, don’t allow users to specify file paths or any part of file paths to access.
For example, if you accept uploads, generate your own name and path to save the files; don’t use the name and extension given by the user.
If your code reads or writes a file whose path is determined by untrusted input, you can easily fall victim to directory traversal or arbitrary file upload attacks, in which attackers are able to e.g. write executable files or shared libraries to your filesystem that will be executed, or overwrite scripts or configuration data to grant themselves access. If you cannot avoid some arbitrary file access, ensure you whitelist file extensions and filename characters and detect any double-dot sequences. If you do detect any “..” or punctuation like : or ; or a null byte or any other special characters, fail fast! Don’t try to clean the path.
To categorically avoid this, don’t provide remote users the ability to execute arbitrary commands, write arbitrary files, etc.
If you can’t do that since you are writing an auto-updater, ensure the updates you receive are validated by proven public-key authentication methods, such as SSL or digital signatures, and validate that the signature actually was issued by your company or organization. Don’t act on anything that wasn’t signed.
If you can’t do that since you are writing a remote administration application; stop and re-think your life. Are you writing one that will be more secure than one that is out there? For example, are you writing a memory-safe replacement for a non-memory-safe option like Remote Desktop? If not, stop here, but if so, proceed.
There are many things you will need to do to ensure it is secure:
- Above all, isolate as much code as possible from unauthenticated users.
- Don’t include hardcoded credentials.
- Do all validation or authentication server-side.
- If you’re writing a web application, you probably shouldn’t. But if you do anyway, you’ll need to check for CSRF, session fixation, XSS, and many other vulnerabilities.
Other vulnerabilities do exist, and you should take a look at them too, but prevent the RCE vulnerabilities first, or nothing else will matter. Here’s a few examples of where to look next:
Some of the trickiest vulnerabilities deal with crypto; sending any private information over plain text is generally regarded as a Bad Thing. But protocol design is very hard to do without introducing subtle vulnerabilities, so I suggest reading books/taking courses in secure protocol design and definitely getting an independent review from someone who has proven expertise and has previously broken insecure protocols.
Passwords are just generally awful, but if you insist on using them, there are dozens of ways to screw up and be vulnerable to some attack from someone.
Don’t be stupid. Some vulnerabilities can only be categorized as you-gotta-be-kidding-me, such as not requiring authentication before serving customer data.