Proposal: Add an object.obliterate or nullify function to objects to overwrite the content in memory

It was discovered during a PEN test of an Electron application that password values remain in memory as clear text after the variables are no longer in scope. The clear-text passwords can be found by performing a memory dump of the process. Upon further testing, we found that we could recover credit numbers in memory after placing orders in websites using Chrome. It is suspected that the same behavior will be found in other browsers and Node applications. This is obviously a serious security flaw and anyone that can access a shared computer that can access memory dumps have a treasure trove of private data.

Variables are immutable in Javascript. As such, overwriting a variable is impossible. When code reassigns the value of a variable or the variable goes out of scope, the original value is not overwritten. Instead the previous allocated memory is handled over to the Garbage Collector (GC) and will sit there indefinitely until the memory is reallocated. It is unclear as to when the memory space is zeroed out but I assume it is when the new memory is being allocated. It is believed that even forcing garbage collection, which is not allowed, that clear-text string variables would remain in the memory heap indefinitely.

This was reported as a bug to the Chromium team but they closed it out as a feature request.

The proposal is to add a function to the language or objects similar to the delete(object.property) that will zero out the memory that was allocated by the variable or object.

As a side note, we also discovered that all network traffic that is performed via Chromium is stored in memory as clear text, even though the communications was via HTTPS. The network traffic is retained in memory to use in the Web Dev Tools - Network inspector. Our solution to that was to encrypt the private data with a public key that the site provided as part of the login page. This obscured the password so that only the web site server could decode it. However the input variable that the web form discarded remained in memory indefinitely. In our research, there is no way of disabling or clearing the network traffic recording. It appears that there is a cache that flushes the oldest request based on hitting some memory limit.

From an ECMA-262 Specification perspective there is no notion of the allocation, memory location or deallocation of String values.

For example even JavaScript being garbage collected is not technically part of the spec. Garbage collection is only mentioned in 3 places as side-notes in the entire spec ECMAScript® 2020 Language Specification

Maybe someone who knows this area better would be able to correct me.

This seems like something utterly out of scope of the JavaScript language itself, which makes this not the appropriate venue for it.

That said, unless there's a way using javascript to get a memory dump, it doesn't seem like a vulnerability to me at all, and if there were, that would be a bug in a specific implementation.

1 Like

I don't know if simply adding an obliterate function would really solve this issue, and it would add a lot of oddities to the language.

async function main() {
  // The secret value was pieced together from string fragments inside of the fetch-API.
  // we never see those intermediat values here, so we don't really have a way to tell fetch
  // to obliterate these intermediat values.
  // ... unless you get around the issue by encrypting it yourself too (like you're doing), so we'll run with that.
  const encryptedSecret = await fetch('example.com/secret').then(res => res.text())

  // We run into the same problem again here. This third party library is probably creating intermediate values
  // as it unencrypts the secret, which we never see from out here. To solve this, we would also
  // have to unencrypt the secret ourself, and not use third-party libraries.
  const rawSecret = await someThirdPartyLibrary.unencrypt(encryptedSecret)

  // ok, now we have a secret, lets use it.
  useSecret(rawSecret).then(doSomethingElse)
  useSecret2(rawSecret)

  obliterate(rawSecret) // Destroy this string from memory

  // What happens when you try to use the value after obliteration?
  doYetAnothingThing(rawSecret)
}
main()

async function useSecret(x) {
  await forSomething() // obliteration will happen during this await
  return x // What does this promise resolve to?
}

const data = {}
function useSecret2(x) {
  data.x = x // What will data.x be after obliteration?
}

Edit: I guess a lot of these oddities could be sidestepped if "obliteration" happened during garbage collection, and not immediately - but there's still the issue that many third-party and built-in libraries will become unusable because they won't obliterate any intermediate value they make. You would have to hand-craft every function you want to use with these values if you really want to ensure they're obliterated properly, and if you're going that far, you might as well just store the secret in a UInt32Buffer or something, and zero-out the result when you're done - I think this would be the only way to really guarantee memory cleanup. Though, even then, any time you want to access something from the array, etc, you're going to be making little temporary copies that won't get zeroed-out.

I would beg to disagree on both points. Let's start with security first.

Security has to be a holistic approach and needs to be multi-layered. The fact that private information like social security numbers, passwords, and credit card numbers can be easily obtainable is a concern for every person that uses a browser.

Just because you can't access these variables from within JavaScript doesn't mean that it isn't a security vulnerability. Have you ever used a public computer? Did you know that the next person could gain access to your passwords when you logged in to read your mail? It IS a vulnerability that has a simple fix.

This enhancement idea is just a small part in a big problem. Providing developers to harden their web applications with a simple function call would be a prudent step towards improving security for all.

utterly out of scope of the JavaScript language

JavaScript was implemented such that variables are immutable. The fact that variables are immutable is the cause of this vulnerability. It is in JavaScript that this issue needs to be tackled.

All that I am asking for is a function that would overwrite the bytes contained in a string such that the variable afterwards appears as undef and the actual bytes are zeroed out.

With this feature, it is up to the developers to caretake for obliterating sensitive variables before they go out of scope.

What I am requesting has nothing to do with Garbage Collection. This request is to have an override to the immutable behavior of variables. Currently, there is no means of overwriting a variable's content. The enhancement would only replace the bytes allocated to a String variable. If the String contains "YouWillNeverGuessMyPassword", after calling obliterate(passwordVar) then the memory space would consist of 27 bytes set to zero (0) and the variable would appear as undef.

IMO, this is a reasonable request to further the advances of security.

I honestly don't feel that this would bring up any oddities. Remember that this is ONLY overwriting the value stored in the memory space with zeros/nulls. The variable would continue to exist as if you had done rawSecret=null; and would from that point forward be recognized as undef. In your code example where you continue using the variable (e.g. doYetAnothingThing(rawSecret)), obviously, as a good programmer you shouldn't be expecting the variable to be useful and as such, the example is just conflating this discussion.

Waiting until GC occurs is unnecessary and would require some sort of flagging that this variable should be obliterated/nullified. Just overwriting the value immediately is simple and required no extra plumbing.

You would have to hand-craft every function

I do not believe that to be entirely so. In most cases, when passing a variable around, it is passed by reference and as such no duplicate Strings are created.

In the case that a String is duplicated then, yes, the developer will need to be aware and code appropriately. That is no different than being diligent so as to avoid writing code that creates memory leaks. That said, just because it raises the responsibility of developers to care take for sensitive data, that is no excuse for not allowing the ability to develop code that is more secure.

Further more, think of how often in code, that nullifying a variable would really need to be addressed. In my case of dealing with the password, there is very little code that needs to be cared for. The user enters the characters into an input field that which is copied into a JavaScript String. We pass that string to a RSA encryption function and then discard the variable. I would venture to guess this would hold true for code that handles credit cards and social security codes. This would not IMO be all that onerous on developers. It would be far less effort and training than it takes to write code that doesn't cause memory leaks. The best practice would be, if you use a variable that contains sensitive data, nullify(variable); when your code is done with it, unless it was a passed in by reference.

well just store the secret in a UInt32Buffer or something, and zero-out the result when you're done

I have already thought of this solultion and this is NOT as easy as it sounds. As mentioned in my original post, we need to encrypt the password so that it doesn't appear in the browser network traffic logs. We use the RSA encryption library. As far as I know the library does not support UInt32Buffer or UInt32Array. As such we would have to write our own encryption library.

Secondly, how can we access the input variable content other than as a String? We use Angular and the implementation is such that the variable is inserted into the model object as a String. Extracting values out of the DOM is done as a String. Using JQuery to access the text from the input is returned as a String. I am unaware of anyway of extracting content from a Form or the DOM that wouldn't result in a String being created.

I even considered performing keyboard intercepts in a password input field that converts each keystroke into the ascii value and appending to a UInt32Array. While this would work, imaging all of the complexity it would require to address all of the keyboard ways of manipulating inputted text. Consider left/right arrows, was the shift key held such that text is being selected and any character would delete previous content, Ctrl-A Backspace, Ctrl-Left Arrow then typing that inserts at the beginning, the mouse click changing the location of the cursor. This would be a VERY complex input control to develop and we will still need to content with the encryption issue afterwards.

All I am asking for is a function that would give the developer the same capability as they have with UInt32Array/UInt32Buffer to overwrite the values contained in a variable. This would be simple and addresses the majority of this security vulnerability.

I believe that the function name nullify() is less confusing since that is more precisely what it is doing.

I really don’t know what you mean about variables being immutable - do you mean you can’t un-define a variable? You can assign null or undefined to it, for example, and it effectively takes up zero memory.

Security has to be a holistic approach - which means that security requires pursuing changes in multiple venues. In this case, the language isn’t the right one.

Separately, anything that requires developers to take action to harden their applications doesn’t increase security very much.

I really don’t know what you mean about variables being immutable

The first answer to this question on StackOverflow clearly articulates what I mean by immutable. In this case, the values stored in memory that a variable points to, is non-modifiable.

You can assign null or undefined to it, for example, and it effectively takes up zero memory.

That is not actually true. When your code (e.g. rawSecret=null;) assigns a new value the original value remains in the memory space indefinitely until garbage collection overwrites the value or a new variable allocation is performed and just so happens to overwrite the memory space. Depending on the activity of the memory after a private variable is released, it could remain in the heap for an extended period of time.

In the scenario of the password entry, I would suspect that the variable would be allocated and remain in the young or new generation space memory area since it only exists for a moment. This is typically an 8MB block memory that Minor GCs perform a * Scavenger* where it defrags the memory space periodically. There is no guaranty that this will overwrite the memory but will eventually if the application is busy allocating/deallocating memory.

Think of the memory as a large whiteboard and the private variable a sticky note (with the password text clearly visible) placed somewhere on the board with a string attached to it. Memory management may move that sticky note anywhere on the board but can not place it over any other sticky note that has a string attached. When the variable is dereferenced, then the string is removed and other sticky notes can be placed over it. Until that happens you can still read what is on that sticky note.

The posting Visualizing memory management in V8 Engine (JavaScript, NodeJS, Deno, WebAssembly) (see section Minor GC (Scavenger) ), which does a great job of describing how the GCs are performed.

Security has to be a holistic approach - which means that security requires pursuing changes in multiple venues. In this case, the language isn’t the right one.

I honestly can not understand your reasoning that it isn't the right one. How else can we resolve this security vulnerability?

Using my sticky note example above, think of the nullify() function like a big indelible marker that redacts the text that appears on the sticky. Plain and simple. This way that un-referenced string can remain in the heap space indefinitely without any concerns since it has been redacted.

Perhaps the method name could be redact(rawSecret); :-)

anything that requires developers to take action to harden their applications doesn’t increase security very much.

I respectfully disagree with your opinion. If that were the case, then storing passwords in a database in clear-text or creating SQL statements without using parameters (SQL Injections) would be acceptable but obviously it isn't. Developers ARE responsible for care taking for writing secure software. There is no denying that responsibilty.

Have you ever read OWASP's Top Ten Web Application Security Risks? Each one of these requires developers to adhere to best practices. In fact, #3 on the list, A3:2017-Sensitive Data Exposure, is precisely why I am making this request. The ability to dump the memory allows a hacker to write private data to disk that can then be discovered. This enhancement would negate this vunerability but it does require that a developer be diligent, as with every other security concern.

I think you missed my point a bit.

For whatever encryption library you're using to work, it's got to be slicing up the string into smaller pieces, and operating on those pieces. Depending on how this library is implemented (out of your control), and how the Javascript engine is implemented (out of your control), those pieces may all end up in separate places in the heap. Even if you obliterated the master string, Someone knowing how the heap is organized and how this library works would be able to rope together these pieces and reconstruct the original password.

Lets take a trivial encryption example.

function encrypt(plainText, key) {
  if (plainText.length !== key.length) throw new Error()
  const result = []
  for (let i = 0; i < plainText.length; ++i) {-
    result.push(plainText.charCodeAt(i) ^ key.charCodeAt(i))
  }
  return result.join(',')
}

This encryption function is probably safe by normal standards (though I'm no cryptography expert). But - just look at all of those temporary intermediate values we're creating in the middle of that function. Even if plainText was zeroed-out afterwards, what about the temporaries that were allocated on the stack, when plainText.charCodeAt() was called? What about that result array? Heck, just checking the length of the key could leave that length value on the stack (depending on optimizations, etc), which could be gleaned from the memory later and used as a clue to reconstruct the key. And, how can we be certain that built-in functions such as charCodeAt() isn't leaving its own temporary values in the heap and stack.

I'm not against finding a solution to this problem, but I do think that a simple .obliterate()/.nullify() function won't be sufficient.

1 Like

If you want an example of a potential programming oddity that could crop up with this .nullify() change, that isn't necessarily bad code, here's one:

You use a third-party encryption library to encrypt your plain text. This particular encryption algorithm was implemented with heavy reliance on functional techniques, and to speed up the algorithm, they employ the common practice of memoizing intermediate values, which may include your plaintext. Let's say the memoization works by storing an argument (like the plaintext) as a map key, with the function result as a value (they may be using a normal javascript Map instance, or their own custom map implementation).

You use this encryption function twice, and after each time obliterate the plaintext. All of a sudden, two keys with different values in their internal memoization mapping have been set to null. What does this even mean? What kinds of issues might this cause? You just reached through into their internal data structure and modified it, potentially putting it into an invalid state. This kind of thing isn't really comparable to just reassigning a local variable to undefined - this is messing with the internal state of tools you're using. And boy, this issue would not be fun to debug.

Maybe you have things to pick at with this example where people should have done things differently, but scenarios like this will still certainly happen in the real world, so this isn't just "conflating this discussion". This is certainly something that needs to be considered. Another similar thing to consider is the security concerns of allowing users to mess with internal data structures that were previously out of their reach - I wouldn't be surprised if a change like this opened up a handful of security exploits, because scripts that were supposed to be running in a sandboxed environment were now able to mess with their environment in ways that weren't considered during the design of the sandbox, because of this new .nullify() function.

1 Like

Hey Scotty,

Thanks for the feedback. I enjoy discussing these concerns and it is good that we consider all angles of the problem. Your clarifications helped me to understand your concern better.

Your argument of the intermediate memory variables while potentially a problem, it is a hypothetically and could happen but memory management may also allocate each variable in various segments of memory.

In you example specifically, the memory allocations would be the ASCII codes as numbers that will take up memory in an array. As such, there wouldn't be anything in a memory dump that would closely resemble a String that could be simply scanned for without a significant amount of effort.

if plainText was zeroed-out afterwards, what about the temporaries that were allocated on the stack

The temporaries would not be an issue. The temporaries were three integer values that were computed as part of the method invocation and are not a pointer back to the original value. Separately, the logic that would nullify the original value does not happen until after the function call has finished. As such, those temporaries would have been long gone.

What about that result array?

In your example the array would be a bunch of Integers that were encrypted (with the power function) - it would be meaningless data in a memory dump...

gleaned from the memory later and used as a clue to reconstruct the key

The problem that this enhancement is trying to address is for sensitive data appears in "clear text" in a memory dump. We are NOT trying to solve for someone scouring data and reverse engineering it to potentially discover what the data actually means from some random segment of memory filled with gibberish bytes of data.

I'm not against finding a solution to this problem, but I do think that a simple .obliterate()/.nullify() function won't be sufficient.

I totally agree. This is NOT totally sufficient. The only way to make a web application completely non-vulnerable is to never turn on your computer. Security is layers upon layers of due diligence. There is no one thing that solves all problems. As such, the nullify() method still has merit and will help reduce but perhaps not entirely eliminate the concern

1 Like

I tend to think that any security library that is legitimate would NEVER create a memoized (Map) of all plaintext strings passed to it. That would be insane and also a memory suck.

I can see how if a String was used in a Map and we change the content of the key under the covers, it could cause issues. One enhancement to this function might be to determine the reference counts at the time that the function is made and only do so if there is one (1), otherwise throw a runtime error. That would make troubleshooting pretty straight forward.

That said, I don't know for certain that strings used as keys in maps are actually references to the original string or if a clone is created. That would be a consideration that needs to be considered.

the security concerns of allowing users to mess with internal data structures that were previously out of their reach

The delete() function that ACTUALLY does mess with the internal data structure. The nullify() would not be messing with data structures (aside your potential map concern). It would be replacing character values of strings. There is a big difference.

Perhaps instead of it replacing the string with nulls characters, it replaces it with blank characters (e.g. blankOut(clearTextVar) ) thereby leaving the data as a valid string without oddities that nulls may introduce. This would not allow users anyway of performing stack overflows by overwriting specific data with new code/data, which is predominately how these sorts of exploits are done.

The nullify() or blankOut() function is something that is to be used in limited circumstances. Like a utility knife, it should be used with care and only used when you know it is safe.

Alright. If we're simply trying to add a stumbling block for attackers, and aren't trying to completely stop them, then let me propose an alternative solution. This one has the benefit that it'll work in Javascript today and doesn't require the pitfalls of making strings mutable. It'll even protect the passwords while they're still being used in memory - a zero-out solution can not protect the password while the password is still being used by the program.

Make a hacker honeypot like this (or some variation of this):

const seed = getSeed()
const passwords = []
for (let i = 0; i < 10000; ++i) {
  passwords.push(generateFakePassword(seed))
}

const pswIndex = Math.floor(Math.random() * passwords.length)

passwords[pswIndex] = getRealPasswordFromUi()

doWhateverWithPasswords(passwords, pswIndex)

The idea is to have a bunch of fake passwords in memory. Whatever seed is used to generate the fake passwords is also known by the server. If the server ever receives one of these fake passwords, it'll immediately lock the users account and inform them that someone is trying to break into it. It'll of course blacklist the IP that sent the fake password too.

This can still be bypassed, but not without a bunch of extra work understanding the internals of how this application works, and maybe the memory layout too.

Don't know if it's a good idea or not, but it's an idea.

The Honeypot idea is interesting however for things like credit cards, social security numbers, etc that wouldn't be applicable because that data will be abused out of band of the application.

Separately, this would not address the concern that provoked my request for this enhancement in the first place. One of our company's applications had just gone through a client/partner PEN test. The test exposed this security hole.

Think of a rogue admin employee that can take a memory dump of a Node credit card processing application. Without this sort of function, they could steal countless credit cards numbers in the matter of minutes. While this change isn't fool proof, it is a step in the right direction.

Such a rogue admin employee could also modify the javascript code to do whatever they want. There's not really a defense against that.

1 Like

Strings in the popular JavaScript implementations tend to be more complex than single objects. Spider monkey for example has around 8 different internal string types

Strings can be formed of ropes where multiple smaller strings are stitched together to form longer strings.

But all of this is an implementation detail. The ecmascript spec does not mention anywhere how and where strings are stored in memory, so a new API in the spec would have a hard time describing how to zero out something it has no notion of.

The spec is an abstract language.

Someone is free to build a JS compliant VM that additionally exposes an API that guarantees to zero out all memory for unreachable objects.

An attacker could easily circumvent that one - the real password is always allocated first.

I'm still at a loss of why this is even a serious security issue in the first place. I'm not hearing any reason cited other than paranoia, and paranoia doesn't in of itself give you hardly anything. Like what's the actual underlying concern here?

2 Likes

Ask the pentesters (or some other security expert) what their recommended fix is. They should know that you can't truly defend information against a malicious actor who's already gained read access to your program's internal memory.

If you really want to protect against rogue admin employees, that's an IT problem, almost never a JS client problem.

1 Like

For passwords there is already a far better option, use Webauthn. That way, a private key will be generated directly on the device and will be stored in a way that it can't be read out that easily (e.g. in a "trusted platform module"). For data that has to still be in RAM, I'm not sure wether an object.obliterate would really help, as an attacker might also be able to modify the code that gets executed.