5

The general question is: Is it possible to remove value of the String variable from physical memory after that variable being unset in PHP?

The problem arised from certain requirements of security standards (there should be no way to dump data from memory to disk while processing certain vital data). According to "Is memory encrypted?" topic there is no good way to encrypt data in-memory.

So, when unsetting String variable in PHP you cannot say for sure that data in memory was overwritten. Same story about setting new value to variable.

Thus I'm interested if it is possible to wipe out variable value from memory without changing core code of unset method?

Community
  • 1
  • 1
WASD42
  • 2,352
  • 6
  • 27
  • 41
  • @jprofitt Re-assigning would probably only change the reference stored in the variable and not overwrite the string. But I'm no expert on php internals... – mensi Apr 10 '12 at 12:14
  • @mensi Yeah that's the same boat I'm in! Was about to edit my comment to include that statement. – jprofitt Apr 10 '12 at 12:15
  • @jprofitt mensi is right, I mentioned it in question - PHP will create new reference. – WASD42 Apr 10 '12 at 12:15
  • @WASD42 What kind of information do you need to destroy? Depending on the nature of it, you might be able to process it with something like homomorphic encryption to still get your results without actually exposing data – mensi Apr 10 '12 at 12:20
  • I'm betting you can write a PHP extension that does what you need to after calling `unset`. This way, there's no modification to the actual PHP source, and you accomplish what you need. – nickb Apr 10 '12 at 12:30
  • @mensi We are talking about low-level credit cards processing :) – WASD42 Apr 10 '12 at 13:46
  • @nickb Well, pros about rewriting `unset` function is that we can just call PHP GC every time `unset` is called. In case of special extension we should implement it ourself (or just call GC). In PHP 5.3 new function has been introduced - `gc_collect_cycles()`. Maybe that would do too... – WASD42 Apr 10 '12 at 13:50

3 Answers3

5

First of all, I'm not sure that wiping out the string will meet the security requirement you described, as one could still theoretically dump memory before the string is wiped anyway. But that's impossible to meet anyway, as you can't process data without having it in memory.

Anyway, if you want to ensure the string is wiped, I think the only way to do so in PHP is to loop through the string and modify each character: remember, the contents of memory do not go away until overwritten, even if you have no references to the variable and PHP GC has run.

I believe this will work:

for( $i=0; $i < strlen($str); $i++ )
    $str[$i] = 'x';
kitti
  • 14,663
  • 31
  • 49
  • +1: I suspect this is indeed the best you can do in PHP. Of course, there's no guarantee that there might not be some other copies of the string in memory that will be left unwiped. – Ilmari Karonen Apr 10 '12 at 13:19
  • @IlmariKaronen I think as long as the strings are only read and not manipulated it should work. String manipulation functions likely create copies and return new strings rather than modifying in-place; hence I avoided those and used character access. – kitti Apr 10 '12 at 13:28
  • The problem is that we don't know what the OP may have done with the string _before_ wiping it. PHP just loves to make copies of strings, and unless _all_ of those copies are wiped (which may not be possible, if you call a function that makes an internal copy and frees it without wiping it first) some pieces of the string may be left in memory. – Ilmari Karonen Apr 10 '12 at 13:43
  • Well, we can assume that no such function will be executed on that string. So by now it's a best way to archive the goal I think :) Thanks. – WASD42 Apr 10 '12 at 13:45
0

tl;dr: After testing thoroughly many combinations, it seems that the for loop overwriting each character clears/changes the string contents in memory. Just be careful where the initial contents come from, it might still be present in those variables! See the update bellow.

Did some tests with PHP 5.6:

<?php
     $cc = 'AAAABBBBCCCCDDDD';
     #v1:  unset($cc);
     #v2:  $cc=null;
     #v3:  for( $i=0; $i < strlen($cc); $i++ ) $cc[$i] = 'x';
     sleep(500); // enough time for taking script pid and run the memory dump
?>

Tried each version of the above script (#v1 enabled, #v2 enabled, #v3 enabled); Dump the memory of the process (see https://serverfault.com/a/408929/374467) and the 'AAAABBBBCCCCDDDD' string always shows up in the binary files generated (which contain the process memory). Also tested when $cc value is sent as a command line argument ($argv). Same result.

To date, I found NO reliable way to do this in PHP.

UPDATE

As suggested in the comments, using a string literal to initialize the variable might affect the results, as the literal might be held in memory. Thus I changed the script so that it takes an argument from cli and initializes the variable by applying str_rot13() on it, making sure the contents of the variable are new. I also added gc_collect_cycles() to force the garbage collection. So the test script looks like this:

<?php
     $cc = str_rot13($argv[1]);
     #v1:  unset($cc);
     #v2:  $cc=null;
     #v3:  for( $i=0; $i < strlen($cc); $i++ ) $cc[$i] = 'x';
     gc_collect_cycles();
     sleep(120); // enough time for taking script pid and run the memory dump
     echo "done \n";
?>

It seems that the first two methods (unset/null) won't clear the new value of $cc from memory (even with gc_collect_cycles on) BUT the for loop actually changes it! even if gc_collect_cycles is used or not.

Tested with PHP 7.2.20 (cli).

CAUTION You can still find the initial value of the argument ($argv) in memory!

Adi Fatol
  • 956
  • 15
  • 24
  • 1
    The problem here might be that the secure string is stored in the plain text as a literal. A better test would be to construct a string in memory (e.g. rot13) and see if you can recover that too. – Georg Schölly Jul 10 '19 at 14:48
  • That could be a solution. I will test it and edit the answer soon. – Adi Fatol Jul 11 '19 at 10:48
-2

So as far as I understood the problem is not in using unset() or PHP's garbage collector, but making sure that used memory is actually on physical level cleared?

Simple way - just set it to null before unsetting (or any other value for that matter) See What's better at freeing memory with PHP: unset() or $var = null

The hard and supid way would be to allocate all php memory with garbage, to make sure that its overwritten, but I'm not sure if virtual memory is fixed on predefined diapason, or changes based on process needs.

Community
  • 1
  • 1
Artjom Kurapov
  • 6,115
  • 4
  • 32
  • 42
  • 1
    That does not make sense, since `$a = 'hello'; $b = $a; $b = null;` would have undesirable side-effects. I highly doubt php will actually zero out memory. (It would be a stupid thing to do) – mensi Apr 10 '12 at 12:46
  • well this way $b **is** becoming empty. $a will still have its value, since assignment was done by reference – Artjom Kurapov Apr 10 '12 at 12:49
  • 1
    I'm far from an php internals expert but I rather doubt that setting $a = null; will actually make the 'hello' go away. It won't be referenced by $a anymore but the string itself will still be floating around until the memory is reused. – Cerad Apr 10 '12 at 14:18
  • @Cerad if php would just use new memory each time you assign data to a variable, it would be much more bloated that it is right now.. obviously assigning data overwrites existing value. The only problem with left-over data after `unset()` is with cyclic references, but thats what garbage collector is for. – Artjom Kurapov Apr 10 '12 at 14:31
  • Nope nope nope. For example, if it kept the same address then 'hello' might become NULL'ello'. PHP would have zero motivation to always clear the complete string even if it reused the same physical memory. For security reasons, only clearing the first letter of a string would not be enough. In any event, I do know that PHP does handle nulls and unsets in it's own unique fashion. – Cerad Apr 10 '12 at 16:49
  • My mistake. i thought it was `$b = &$a`. Anyhow, everyone recommends setting everything to null. How did you see that it sets only first byte? I can't seem to find source of unset or assignment constructs on php github source – Artjom Kurapov Apr 10 '12 at 18:09