An Obfuscated String Implementation for Go
2014-02-25Recently, I purchased the domain rit.singles, and I was possessed with the urge of making an actual dating website. I thought it would be cool to write it in Go, since that's one of the hip new languages of the now, and also because it does a lot of things that I really like.
I started by writing the account management and registration code. This is surprisingly tricky, because handling sensitive data with any semblance of security is a pain. Luckily, Go exposes a number of very useful syscalls for us to work with.
Since I'm not offering enough click-bait on my block, I feel like it's only appropriate to turn the rest of this post into a listicle.
4 Weird Tricks One Florida Man Used to Protect Sensitive Data in Memory!!! (Hackers hate him!)
1. mlock
your pages
Allocate as many pages of memory as you need to hold your string. Then
mlock
them immediately.
mlock
is a portable function that strongly
encourages your kernel not to swap out a page of memory.
Why is this useful?
Swap doesn't get zeroed out, and there's no way to guarantee when it will get overwritten, so you don't know how long any persisted data will last. While encrypting your swap is a good idea, you can't expect everyone to encrypt their swap paritions.
Any caveats?
Hibernating will still persist passwords to swap. Sorry.
Solaris and Solaris-based operating systems require that you grant the
proc_lock_memory privilege before a user can run anything that calls
mlock
.
Memory that you mlock
must be page-aligned. I mmap
my sensitive memory to
ensure this.
2. mprotect
your pages
mprotect
assigns permissions to your pages. I typically write my sensitive
data, and then immediately mprotect
the page such that it's read-only. If
any process tries to write or execute that memory, that triggers a segfault,
which is much better than tampering with sensitive memory.
Why is this useful?
Instead of a rogue buffer overrun wiping your sensitive data, you instead trigger a segfault. This helps prevent tampering, either malicious or accidental, of your sensitive data.
Any caveats?
Again, the memory that you pass into mprotect
must be page-aligned.
3. Encrypt your sensitive strings
It's really just obfuscation rather than offering real security, but it's a worthwhile tactic. Passwords become less trivially identifiable if they're encrypted in memory.
Why is this important?
If an attacker gets a memory dump, or if a machine hibernated and an attacker has your swap device, it makes it far harder to identify a password.
Any caveats?
Hell yes.
The key needs to be stored in memory. If the attacker knows where in memory your key is, then it's trivial to decrypt your sensitive string. It really just ends up being obfuscation more than anything.
4. memset_s
your pages before you munmap
them
memset_s is a
new C11 function that will memset
with the guarantee that it won't be
optimized out.
If the value that you memset
isn't used after the memset
, smart compilers
will optimize it away. If you're clearing sensitive memory after you're done
using it, this is definitely not what you want.
Why is this important?
When you free
or munmap
memory, it isn't wiped before being given back to
the OS. If an attacker gets a memory dump after you have freed a sensitive
block of information, it's very possible that the value is still in memory.
memset
isn't sufficient for the reason I outlined above.
Any caveats?
memset_s
isn't implemented in many libc's. If this is a case for your
targets, then check out this implementation from
CERT.
Now where the hell does Go come into all of this?
Since I'm working with passwords for rit.singles in Go, I felt the need to
write a secure password implementation in Go. In it, I did all of these
things, save for clearing my data with memset_s
. As far as I'm aware, Go's
compiler won't optimize away a normal memset
, so I'm safe there.
The code for the secstring
package is on
GitHub. The documentation is on
GoDoc.
I welcome any bugs/criticisms/questions/audits.