You have been pwned
Security plays a significant role in our lives today. Password policies, encryption, multi-factor authentication – you name it.
But there’s more to say about security. When coding, you have to answer basic questions like
- Are your libraries up-to-date?
- Is that super-duper encryption algorithm secure enough?
- Is my application prepared for the Billion Laughs Attack?
Sites like OWASP, Sans, and the Zero Day Initiative can give us helpful insights into current exploits and how to prevent them. Here, I will share some considerations, and while I will use Java in the examples, they apply to other secure coding languages.
It’s all about encryption
When managing secrets for our applications, databases, or token generation, we use well-known and tested solutions such as SHA-512, BCrypt, or SCrypt.
SHA-512 may be outdated, but it’s still reliable for managing sensitive data. Something else to consider is that longer passwords, even if they are fully fledged phrases in natural language, are more easily hackable than a very complex but short password.
For example, the phrase “There is a lady who is sure all that glitters is gold” and the typical password “1337Pa$w0rD!1one!”. The first password would take longer to crack than the second. However, the second password is also less likely to be “guessed” by dictionary attacks. Refer to this XKCD comic for a brief explanation.
One secure development practice to consider is adding salt to your encryption hash. This security measure can make your passwords harder to break
SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; random.nextBytes(salt); MessageDigest md = MessageDigest.getInstance("SHA-512"); md.update(salt); byte[] hashedPassword = md.digest( passwordToHash.getBytes(StandardCharsets.UTF_8));
Thanks to this; we will get different hashes even if the plain text password is the same, thus improving the overall security should our hash database leak.
Dependency is bad
You’ve probably practiced dependency hundreds of times: ram this library you need in your maven or gradle, wait for it to compile, and there it will live forever. However, you must keep the list of dependencies down and functional, but you also need to ensure the library isn’t introducing any software or web application security risks.
Outdated libraries are usually vulnerable to new exploits that appear daily. Plus, leaking a company’s sensitive information because of an insecure library that adds sprinkles to your logs can be detrimental.
Let’s take a quick look at the following list. We’ll see that many libraries at the Maven repository are vulnerable, and we should take rapid action to eradicate the issue. It’s imperative to respond swiftly since, ultimately, we are responsible for any software security holes we may introduce. Luckily, some tools can help us with this task, i.e., OSSIndex and Dependency Check.
Handle the data with care
Never underestimate a user’s ability to enter data that could jeopardize your software security. Something so trivial as this:
<script> alert("Show me the pass!!"+$pazz); </script>
It could compromise your JSP (I know it’s old, but it exists). As a secure software development practice, always sanitize all user input. Stripping, escaping, and replacing are the most common techniques for sanitizing user input.
-
JVM Log Forging
The log forging technique consists of a malicious user introducing forged log lines that can interfere with log analysis. Take this excerpt:
String val = request.getParameter("val"); try { int value = Integer.parseInt(val); } catch (NumberFormatException) { log.info("Failed to parse val = " + val); }
The console will produce something like this:
INFO: Failed to parse val=twenty-one
But should the user input something like this “twenty-one%0a%0aINFO:+User+logged+out%3dbadguy”, then we’ll get the following:
INFO: Failed to parse val=twenty-one INFO: User logged out=badguy
One solution OWASP provides against log forging is using ESAPI, which will take care of encoding user input data to be easily identifiable in the logs. Refrain from logging sensitive data such as passwords and tokens (even expired ones) to avoid CSRF and very verbose errors that could lead to potential exploitation.
-
Deserialization of external data
This topic is easily overlooked and we’ve all been there: we get an InputStream from an external user, and with a sleight of hand it’s converted into one of our fancy POJOs. And then the fun begins.
Denial of service, Billion Laughs attack, server data compromise, you name it. We will review some of them below but the problem is so extensive, that I’d rather point you to a source that explain how deserialization can lead you to total havoc much better than I can:
Setting up some defenses
I’d like to finish with a non-comprehensive list of attacks and some mitigations that we can use to avoid them.
-
Man in the middle
A malicious attacker will take over an insecure (non SSL) connection. This is easily mitigated in production environments with the use of a trusted certificate, something that usually happens.
But there’s another point of entry: Developers.
As of today, there are many maven dependencies that are still downloaded via insecure HTTP connections, which could lead to an attack on the dev’s computer with a potentially sensitive data leak.
Find more information here and here.
-
Billion Laughs Attack
It is an XML-based attack hackers can exploit with misused XML parsers in our code. It comes from the following XML file:
<?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ELEMENT lolz (#PCDATA)> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz>
When a Java XML parser tries to resolve this file, it will end up with a billion “lolz” strings potentially causing a DoS. To mitigate this problem, disallowing XXE in your XML parser can do the trick.
SAXParserFactory factory = SAXParserFactory. newInstance(); SAXParser saxParser = factory.newSAXParser(); factory.setFeature("http://xml.org/sax/features/ external-general-entities", false); saxParser.getXMLReader().setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature("http://apache.org/xml/ features/disallow-doctype-decl", true);
-
SQL Injection
This one may seem easy, and you probably got it covered; just use parameterized queries, and we’re all set.
But there is more to it: you may need to create complex queries with unions or any other dynamic query, which may lead you to the plain old string query format. There are more ways of achieving this and protecting ourselves from unwanted injections.
We can use Criteria API to build complex and secure queries in Java notation
CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Account> cq = cb.createQuery(Account.class); Root<Account> root = cq.from(Account.class); cq.select(root).where(cb.equal(root.get(Account_.customerId), customerId)); TypedQuery<Account> q = em.createQuery(cq); // Execute query and return mapped results (omitted)
Another way of dealing with injection is sanitizing the data. We can tailor the list of valid user input to achieve this:
// Map of valid JPA columns for sorting final Map<String,SingularAttribute<Account,?>> VALID_JPA_COLUMNS_FOR_ORDER_BY = Stream.of( new AbstractMap.SimpleEntry<>(Account_.ACC_NUMBER, Account_.accNumber), new AbstractMap.SimpleEntry<>(Account_.BRANCH_ID, Account_.branchId), new AbstractMap.SimpleEntry<>(Account_.BALANCE, Account_.balance)) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); SingularAttribute<Account,?> orderByAttribute = VALID_JPA_COLUMNS_FOR_ORDER_BY.get(orderBy); if (orderByAttribute == null) { throw new IllegalArgumentException("Nice try!"); } CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Account> cq = cb.createQuery(Account.class); Root<Account> root = cq.from(Account.class); cq.select(root) .where(cb.equal(root.get(Account_.customerId), customerId)) .orderBy(cb.asc(root.get(orderByAttribute))); TypedQuery<Account> q = em.createQuery(cq); // Execute query and return mapped results (omitted)
Best Practices for Secure Coding
Ready to improve your code security? Here are some of the best secure development practices to apply.
1. Error handling and logging
Error handling relates to how the software deals with unexpected issues arising during its operation. The goal is to handle errors in a way that doesn’t expose sensitive system or user information.
Logging, on the other hand, is the process of recording system activities. These activities can include information about errors or warnings. Secure logging allows organizations to track the actions performed within their system and identify irregular patterns or suspicious behavior. This can range from multiple failed login attempts to unauthorized access attempts to sensitive data.
2. Routine security training for developers
Organizing regular training programs for developers is necessary to help them stay updated on the latest secure software development practices. Developers must understand how to code securely, as they are the first line of defense against software security vulnerabilities.
Let the program consist of workshops, webinars, and hands-on projects so everyone can learn in their way. Encourage collaboration through discussions, Q&A sessions, and group activities.
3. Automated testing and security
Another secure development practice to adopt is using automated testing tools to check code for common security vulnerabilities. These tools can help you identify issues like code injection or data leakage that manual testing might miss. Additionally, developers can use them repeatedly, allowing for continuous security testing.
Some of these tools include selenium, cypress, burpsite, and Nmap. Integrate them into your CI/CD pipeline and keep them up-to-date to benefit from the latest features and security patches.
4. Grounding deployment security
Grounding deployment security involves several practices to protect software deployment in a live environment. One secure development practice in this concept is secure configuration, which may involve disabling unnecessary services or setting up secure communication protocols before deployment.
Adopting the principle of least privilege is another practice that can tighten up your software or web application security. This means that every module (such as a user, program, or process) must be able to access only the information and resources necessary for its legitimate purpose.
5. Collaborative security culture
A collaborative security culture is one where all members of an organization work together to protect information assets. This culture goes beyond the IT department and involves everyone, from top management to the newest recruit. It starts at the top. Leadership must set the tone by emphasizing the importance of security and setting clear expectations.
They should also invest in security infrastructure and training. Regular security awareness training keeps everyone updated on the latest threats and best practices.
6. Incident response plan
No matter how well you build up your security systems and protocols, there’s no guarantee they will always hold. That’s why you need to develop a plan to minimize the damage and downtime from such incidents.
Document various incident types, including their indicators and potential impact. Then, use this documentation to tailor response procedures to specific incident scenarios. Finally, conduct simulated drills to test the plan’s effectiveness and identify improvement areas.
Conclusion: secure coding techniques and best practices
We have scratched the surface of a significant topic. Code security is not only a matter of infrastructure. So, we must take security very seriously when coding.
Always question yourself when using new libraries if you should print that information in your log or not. Audit your dependencies frequently, and try to use tools that crawl your code to find OWASP vulnerabilities, like Sonar.
And most importantly, don’t underestimate what a malicious user can do with the appropriate tools.
Bibliography and sources
Header image: Liam Tucker on Unsplash
Hash with salt code snippet example: https://www.baeldung.com/java-password-hashing
JVM Log Forging snippet: https://owasp.org/www-community/attacks/Log_Injection
Criteria API and sanitize snippets: https://www.baeldung.com/sql-injection
XML Vulnerabilities and Attacks cheatsheet: https://gist.github.com/mgeeky/4f726d3b374f0a34267d4f19c9004870
Security guidelines: https://www.oracle.com/technetwork/java/seccodeguide-139067.html
Security cheat sheet: https://res.cloudinary.com/snyk/raw/upload/v1568658651/Cheat_Sheet-_10_Java_Security_Best_Practices.pdf
OWASP Top Ten: https://owasp.org/www-project-top-ten/
Feel free to reach out to us if you would like to get more information or collaborate on security initiatives!