5 surefire ways developers can secure their code from the get-go
With security moving closer to the beginning of the software development lifecycle, developers are being asked to make their code more secure as they write it. Not only will that better protect organizations from cyberattacks, but it can save time and money by getting apps online faster.
Developers, however, are not security experts. That's why compilations such as the OWASP Top Ten list of critical web application security risks contain the same vulnerabilities, such as SQL injection, year after year.
Here is a roundup of best practices from leading security experts that should help you as a developer get up to speed on thinking app sec-first.
1. No SQL injection for you
An SQL injection, which is aimed at databases, can be used by an attacker to bypass authentication or impersonate users; steal, alter or delete data; or, depending on the configuration of the database server, leverage the SQL injection to mount an attack on a network behind a firewall.
Such attacks can be avoided by writing code that never trusts input to SQL statements. You should always validate data used in the SQL statement as correctly formed. In addition, never use concatenation or string replacement to build SQL statements. Use prepared queries, also known as placeholders, or binding.
2. Never trust your client
Trust is also an issue for apps being written by server-side programmers. Typically, they allow their apps to trust everything from a client, said Ksenia Dmitrieva-Peguero, an associate principal consultant with Cigital, a provider of application and software security services.
"Hackers know that when data is being sent from a browser to a server is one of the easiest times to intercept it."
Coders may feel they've covered their bases if they have SSL enabled. That's not the case because the data can be intercepted before it makes it to the network layer, where it's encrypted. So programmers should always validate the data coming from the client, regardless of its format. One way to do that is to create a white list of the expected characters from the client. "If we receive something we're not expecting, then the character should be removed or an error message should be shown to the client," Dmitrieva-Peguero said.
Defining acceptable input may seem like a chore. A developer may be tempted to define unacceptable input, believing that the list will be shorter than one for acceptable input. That's not the case. Besides, if you omit something you categorize as unacceptable input, you're opening the door for an attacker to compromise your app. If you omit a character from a white list, you may irritate a user, but your app remains secure.
Of course, defining expectations isn't always possible. In those cases, the data stream needs to be purged of dangerous characters. For example, when data is being sent to the browser, angle brackets are dangerous characters because they can contain scripts.
3. Check defaults in libraries
Dmitrieva-Peguero said another common error programmers make is to use third-party libraries without checking key defaults. "They may use a third-party encryption library, for example, and call the functions as they are and not check what algorithm is being used," she explained.
"Oftentimes when we examine these libraries, we find them not secure."
Vulnerabilities are continually being discovered in the open source libraries that developers use for their apps. Since many libraries are included with the operating systems coders use, they typically use those libraries without checking to see if they work with the latest version.
"You should always check with the community for a library to catch up on the latest vulnerabilities," said Chenxi Wang, chief strategy officer for the container security provider Twistlock. She warns, however, that checking individual libraries isn't enough to identify all the potential vulnerabilities connected to those libraries.
"Libraries can be dependent on other libraries that are dependent on even other libraries. That's why developers should use tools to discover all the dependencies in the libraries they're using."
Such tools include Node Security Project (NSP), RetireJS, OSSIndex, Dependency-check, Bundler-audit, Hakiri, Snyk, Gemnasium and SRC:CLR.
4. Authorized APIs only
Authorization is another area where loose coding can result in an attacker gaining privileges that could be used to compromise data on a system. For example, when creating APIs, programmers will cache authorization information locally and reuse it when making API calls. For a coder, the technique provides an efficient way to use the API. For an attacker, it's a way to escalate or hijack privileges of users on the system.
When a user logs into a system, for instance, their account ID is issued a token. The system uses the token to authenticate the account in order to allow whoever is using it to exercise the privileges associated with the account. When a program makes an API call, the server checks to see if the account and token are valid. But it doesn't check if the account is the same one initially associated with a token.
"I can just change my account ID and make a call," said Avi Cavale, CEO of Shippable, a software delivery platform vendor. "If you don't check if the account number was issued to the token, then you can get information from someone else's account. You could ask to see all the sales orders associated with that other account or all of its credit cards. What you really need to check is whether the token belongs to the account."
To allow a developer to do that, the API architecture needs to be structured in a way that allows authorization to be performed centrally, Cavale said.
"Every single API call goes through the central component to make sure the call is validated, as opposed to making a call, caching it as part of the client and then subsequently repeating the call to the API with cached information."
Cavale says that handling calls centrally can affect system performance, but he maintains the performance hit can be rendered negligible. For example, even on the fastest network, latency ranges from 30 to 50 milliseconds. "If your authentication and authorization stores are super well-written and you can keep latency below 50 milliseconds, you can hide the call checking behind the network latency," Canavale said.
5. The most powerful pillar: privilege
While writing secure code is complex, there is one governing principle that towers above all others, sand that is the principle of least privilege, say Michael Howard and David LeBlanc in their book, Writing Secure Code. An application that runs with minimal privileges, they explain, can do very more than what it's supposed to do. Writing such applications isn't easy, they acknowledge, but the payoff is worth it.
"When you do take the plunge and run with reduced privileges, chances are good that you will break some older capability that will prevent users from getting their jobs done," they write.
But the authors urge developers to take the difficult least privilege path anyway. "Don't fall into the bad habit of simply running services as SYSTEM and requiring that users be admins to use your application," they write. "If you do, not only are you leaving your clients open to serious consequences if they are compromised, but also as time passes by and you add more code to the system, it will become harder to run the application with reduced, and safer, privileges."
"Get it right from the start: design, build, and test for least privilege, and document the privilege requirements for your applications."
Code like you mean it (security)
OK, so you're not a security pro. But the pressure will intensify to code like one. Follow these best practices to code like you mean security from the start. Share your best practices for others to learn from in the comments below.