3

I have a use case where I need to dispose of some data the very moment I don't need it anymore, for security reasons.

I am writing a server in Ruby that deals with logins and passwords. I use BCrypt to store passwords in my database. My server receives the password, makes a bcrypt hash out of it, and then doesn't use the original password anymore.

I know of a kind of cyberattacks that involves stealing data right from RAM, and I am concerned that an attacker might steal a user's password in raw string form in the period of time that the password is still in memory. I am not sure if simply using password_in_string_form = nil would be enough.

I want to nullify the variable that holds the user's password the moment I am done with it. By nullify I mean something akin to using /dev/null to fill something with zeroes. The end goal is irreversible destruction of data.

selamba
  • 384
  • 4
  • 12
  • While you can trigger garbage collection when something goes out of scope, low-level memory management is *not* a feature of Ruby. – Todd A. Jacobs Dec 05 '20 at 19:57
  • I would argue that if someone gets access to your server in a way that allows reading the rare content of the RAM then it would be are simpler and efficient for that person to just listen to incoming traffic on the network device. In that way, they would not only get the passwords of users that just signed up but of all users that sign in while listening. – spickermann Dec 06 '20 at 06:03
  • 2
    @spickermann: That's what people thought for a long time. Then Spectre and Meltdown came along. The scary thing about those is that they show a *fundamental disregard for basic security measures in CPU design across the entire industry*. In particular, those attacks were not actually novel, some of them had been described all the way back in the early 90s already. And they were found in all sorts of CPUs (embedded, mobile, laptop, desktop, workstation, server, midrange) of different architectures (IA-32, AMD64, ARM, POWER, MIPS, Sparc) from different vendors (Intel, AMD, IBM, Oracle, Fujitsu, – Jörg W Mittag Dec 06 '20 at 09:49
  • 2
    … Apple, Qualcomm, Nvidia) of different eras (e.g. all Intel x86 and AMD64 CPUs from 1995 onwards, except Atom until 2013, all IBM PowerPC G5, POWER6, POWER7, POWER8, POWER9, and some PowerPC G4, etc.). And they keep finding new related ones on a regular basis, e.g. SpectreNG, ret2spec, SpectreRSB, SGXPectre, NetSpectre which are expansions of the Spectre attack, or Spoiler, Fallout, ZombieLoad, ZombieLoad 2, etc. that are *completely different* attacks, but based on the same carelessness in design. – Jörg W Mittag Dec 06 '20 at 09:56

2 Answers2

4

I am not sure if simply using password_in_string_form = nil would be enough.

No, it would not be enough. The object might or might not be garbage collected immediately, and even if it was, that does not cause the contents to be erased from memory.

However, unless they have been frozen, Ruby strings are mutable. Thus, as long as you do not freeze the password string, you can replace its contents with zeroes, or random characters, or whatever before you let go of it. In particular, this should work, subject a few provisos, covered later:

(0 ... password_in_string_form.length).each do |i|
    password_in_string_form[i] = ' '
end

But care needs to be exercised, for this approach, which may seem more idomatic, does not work:

# SURPRISE! This does not reliably remove the password from memory!
password_in_string_form.replace(' ' * password_in_string_form.length)

Rather than updating the target string's contents in-place, replace() releases the contents to Ruby's internal allocator (which does not modify them), and chooses a strategy for the new contents based on details of the replacement.

The difference in effect between those two approaches should be a big warning flag for you, however. Ruby is a pretty high-level language. It gives you a lot of leverage, but at the cost of control over fine details, such as whether and how long data are retained in memory.

And that brings me to the provisos. Here are the main ones:

  • As you handle the password string, you must take care to avoid making copies of it or of any part of it, or else to capture all the copies and trash them, too. That will take some discipline and attention to detail, because it is very easy to make such copies.

  • Trashing the password string itself may not be enough to achieve your objective. You also need to trash any other copies of password in memory, such as from upstream of isolating the password string. If yours is a web application, for instance, that would include the contents of the HTTP request in which the password was delivered to your application, and probably more strings derived from it than just the isolated password string. Similar applies to other kinds of applications.

  • passwords may not be the only thing you need to protect. If an adversary is in a position where they can steal passwords from the host machine's memory, then they are also in position to steal the sensitive data that users access after logging in.

For these and other reasons, if the security requirements for your server dictate that in-memory copies of user passwords be destroyed as soon as they are no longer needed, then (pure) Ruby may not be an appropriate implementation language.

On the other hand, if an adversary obtains sufficient access to scrape passwords from memory / swap, then it's probably game over already. At minimum, they will have access to everything your application can access. That doesn't make the passwords altogether moot, but you should take it into consideration in your evaluation of how much effort to devote to this issue.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • `String#replace` discards/frees the original memory buffer and replaces the pointer with the new string buffer (see https://github.com/ruby/ruby/blob/v2_7_2/string.c#L5363). In theory, this means that the original string contents will still be in memory until overwritten with something else. However, when testing, calling replace does appear to be removing the password from memory: `rm -f core; ulimit -c unlimited; echo 'hunter2' | ruby -e 'pass=""; pass=STDIN.sysread(256, pass); pass.replace(" " * pass.length); Process.kill(:ABRT, $$); sleep'; strings core | grep hunter2`. – Phil Ross Dec 05 '20 at 23:18
  • Thanks, @PhilRoss. I have this answer with a different approach and expanded commentary inspired by your observation and test case. In particular, I note that clearing the password string and anything downstream of that is not necessarily sufficient (though it *is* what the OP asks), as there may also be copies of the password upstream of that. – John Bollinger Dec 06 '20 at 15:17
  • have *updated* ... – John Bollinger Dec 06 '20 at 15:44
1

This is not possible in Ruby.

You will have to write some code specific to each implementation (Opal, TruffleRuby, JRuby, Rubinius, MRuby, YARV, etc.) to ensure that. Depending on the implementation, it may not even be possible to do inside of the managed memory of the implementation at all, without having a separate piece of memory that you manage yourself.

I.e. you will probably need to have some tiny piece of native code that manages its own tiny piece of native memory and injects it into your Ruby program.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653