Hacker News new | ask | show | jobs
by foooobaba 1245 days ago
This has nothing to do with ASLR and stack canaries.. Log4jshell wasn’t a buffer overflow exploit, it was the result of yet another dumb idea, adding remote jndi loading capability into the logging framework.

You can assume any input to your program will be manipulated by an attacker. This implies if you use a non memory safe language you’ll need to make sure there is no way the user can input enough data to overflow your buffer, which will corrupt your memory, and get it right 100% of the time. If you’re building a logging framework it’s extremely likely people will be logging some sort of information from the outside, so not a great idea to execute it as code. Similarly for sql injections, if you simply use prepared statements you remove a whole class of problems. Knowing an attacker will probably inject some garbage into the input of your program, and assuming user input is malicious, is a basic principle you can use to design better systems. I believe this is what the author meant by his statement.

2 comments

What do we do in practice?

- Personally audit all the 392 library dependencies in our project to make sure they don't do anything dumb?

- Ask the intern to write a non-dumb logger (and the other 392 deps) from scratch?

- Don't use dependencies and write bare metal assembler? (JDK, libc, OS kernels are dependencies and do introduce a steady stream of CVEs)

- Give up on doing anything complicated and congratulate myself at being able to write a simple echo service by implementing the whole TCP/IP stack on bare metal?

Well, it isn’t easy and there is no silver bullet. In practice must use your engineering judgment and THINK about these tradeoffs for every problem you encounter.

That being said, there are some principles you can think about to help you get the tradeoffs right when you encounter a problem.

The main principal the author discussed is the idea of enumerating the good, rather than enumerating the bad. Deny everything except the good by default, and do it at every level of your system. This a good idea to consider, but may not apply to everything.

If there is something you don’t control, you are taking a risk, so understand it, and limit it’s potential impact. In some cases it might be better to use a tried and true library or roll your own vs using some fancy new dependency - or maybe not, that’s for you to think about, but it is worth considering carefully.

Try to keep things as simple as possible and use tech that are easier to understand, well documented, well maintained, hard to shoot yourself in the foot with over things that are fancy and cool.

For example say you’re building a distributed system.

At the network level, only allow the types of traffic you need, so don’t allow incoming traffic you don’t need, and don’t allow outbound traffic you don’t need. This means it’s going to be much harder for an attacker to get in, or exfiltrate data out. Use secure channels, for example mTLS where both sides authenticate each other.

At the application level, think about what data the user has control of and treat it carefully. Is there a way you can authenticate the user is valid, and authorize them to only perform certain actions that are allowed - can you use something like signatures to ensure that every subsystem can verify the data isn’t tampered with.

At the technology level, yes think about your dependencies, and keep things as simple as possible. This makes it easier to secure but also easier to maintain, reduces risk of vendor lock in ect. Depending on what your are dealing with, yes, you might actually want to have someone audit all your dependencies, or if that’s too expensive maybe you can isolate parts of the system that deal with sensitive information so those subsystems don’t use many dependencies. Your value proposition as an engineer is not to just string together code, but to build useful, reliable, secure, flexible software and juggle the tradeoffs. The dependencies you choose, and the way you choose to use them DO matter. Just like someone designing a bridge must use materials manufactured by a 3rd party, and assembled by another 3rd party they must be careful with who they select, and perform their own testing to ensure things will work. But those tradeoffs are going to be completely different than if you make cheap toy drones for example.

So basically in practice think carefully about the risks, costs, benefits and what tradeoffs are worth making. Keep relevant principles in mind like: favor only allowing the good, rather than trying to enumerate the bad, assume threats at every level, avoid foot-guns, favor simplicity, favor tested/trustworthy dependencies.

Yes, perfectly sound and meaningless advice.

In practice, for example, I import openssl libraries to get mTLS, even knowing the history of CVEs they had over the years, because I know I'm definitely going to do a worse job at implementing it, and not implementing it is also worse.

So now, I knowingly included a bad-but-less-bad thing to avoid the bad-bad things. Now I have to keep myself aware of the bad things from the less-bad library that comes up from time to time in the form of CVEs. Those CVEs are "enumerating the bad". In theory I should be able to write a bulletproof mTLS library myself (or convince somebody else to), but apparently this thing doesn't exist, and the only real alternative is to wait for other people to enumerate CVEs from time to time and keep patches up to date.

>So basically in practice think carefully about the risks, costs, benefits and what tradeoffs are worth making.

And then be told by management to scratch that, we need the app out yesterday or the competitor is going to eat us.

So? The article doesn't mention "security against specific memory attacks". It's meant to be generic security, and I was pointing out that your comment was restricting the field too much.