You have been pwned
Security today plays a major role in our lives. Password policies, encryption, multi factor authentication, you name it.
But there’s much more to it. When we code, there are many additional considerations to have: 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 Zero Day Initiative can give us good insights to current exploits and how to prevent them. Here I will share some considerations and while I will use Java in the examples, they are mostly applicable to any other coding language.
It’s all about encryption
When managing secrets for our applications, databases or token generation, we should try to use well-known and tested solutions such as SHA-512, BCrypt or SCrypt.
The former may be a bit dated but still stands strong and fast when it comes to managing sensitive data. Something else to take into consideration is the fact that longer passwords, even if they are fully fledged phrases in natural language, can be more resilient to hacking than a very complex, but short, password.
Take 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 one would need much more time to be cracked than the second, and also less likely to be “guessed” by dictionary attacks. Refer to this great XKCD comic for a brief explanation on this.
In any case, remember that adding some salt to your encryption hash will make it 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 done this hundreds of times: ram this library you need in your maven or gradle, wait for it to compile and there it will live forevermore. It’s our obligation to keep the list of dependencies down and functional, but we also need to check if this library is introducing security risks in our application.
Outdated libraries usually are vulnerable to new exploits that appear every day, and leaking all of our company’s sensitive information because of an insecure library that adds sprinkles to our logs is something really bad.
If we take a quick look at the following list, we’ll see that many libraries at the maven repository are vulnerable and we should take quick action to eradicate the issue since ultimately, we are responsible for any software security hole we may introduce. Luckily there are some tools that can help us with this task, i.e.: OSSIndex and Dependency check
Handle (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>
Could compromise your jsp (I know it’s old, but it exists). That’s why you should sanitize all user input. Stripping, escaping and replacing are the most usual techniques when 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 provided by OWASP against log forging is to use ESAPI which will take care of encoding user input data to be easily identifiable in the logs. Obviously, refrain from logging any sensitive data such as passwords, tokens (even expired ones, to avoid CSRF) and very verbose errors which 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.
Billion Laughs Attack
It is an XML based attack and can be exploited 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 union or any other dynamic query which will lead you to the plain old string query format. There are some other ways of achieving this and protect ourselves from unwanted injection.
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)
Conclusion: secure coding techniques and best practices
We have but scratched the surface of a very big and important topic. Security is not only a matter of infrastructure and we must take it very seriously when coding. Always question yourself when using new libraries or whether you should print that info in your log or not. Audit your dependencies from time to time and try to use tools that crawl your code to find OWASP vulnerabilities like Sonar.
And most importantly, never underestimate what a 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!