php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #81711 Private properties data leak by casting object to array
Submitted: 2022-02-23 11:09 UTC Modified: 2022-02-24 09:25 UTC
From: black at scene-si dot org Assigned:
Status: Suspended Package: Class/Object related
PHP Version: 8.1.3 OS: Linux, web
Private report: No CVE-ID: None
 [2022-02-23 11:09 UTC] black at scene-si dot org
Description:
------------
In order to access private variables, one needs to reach the variable either with the scope (an instance of the class and it's descendants, bound variables, etc.), or specific functions like var_dump, reflection, zvar. These things can be disabled with the `disable_function` options for the paranoid among us, but the document behaviour of `(array)` casting cannot.

[1] I started this with the wish to expand an object with each property corresponding to a named argument. My idea was that `...$obj` or `...(array)$obj` should work. Apparently, objects are not Traversable

[2] Casting an object to (array) exposes private variables, prefixed with the class name. This is actually documented [here](https://www.php.net/manual/en/language.types.array.php#language.types.array.casting)

[3] Case 3 is a successful output - `...get_object_vars($obj)` behaves exactly as I would like `...$obj` or `...(array)$obj` to behave.

[4] Just demonstrating that $a->b is unreachable from the current scope.

I am forced to conclude:

- casting objects to arrays with (array) is a data exfiltration vector, which can't be disabled with disable_functions or other php.ini settings,
- it's a valid step in a digital hack: after gaining RCE, even with the strictest disable_function settings, it's possible to escalate the hack by reading values which would otherwise be protected by program scope, possibly exposing database connection details and other credentials for private and public services.

My recommendation:

1. Support `...$obj` for argument unpacking, don't expose private vars,
2. Modify behavior of `(array)$obj`, don't expose private vars,
3. Provide a std class for objects to implement Traversable over their properties (slightly related to 1. as a way to implement).

Test script:
---------------
<?php

// Repro code: https://www.tehplayground.com/WL0AFQHHybPQI1St

class A {
    public string $a = "hello world";
    private string $b = "from hell";
}

function PrintGreeting($a = "", $b = "from croatia") {
    echo "$a $b\n";
}

$a = new A;

foreach ($a as $k => $v) {
    echo "k=$k v=$v\n";
}
echo "====\n";

var_dump((array)$a);

try {
    PrintGreeting(...$a);
} catch (\TypeError $e) {
    echo "[1] Caught TypeError: " . $e->getMessage() . "\n";
}

try {
    PrintGreeting(...(array)$a);
} catch (\Error $e) {
    echo "[2] Caught Error: " . $e->getMessage() . "\n";
}

$a->a = "[3] hello world";
PrintGreeting(...get_object_vars($a));

try {
    echo $a->b;
} catch (\Error $e) {
    echo "[4] " . $e->getMessage() . "\n";
}

Expected result:
----------------
k=a v=hello world
====
array(2) {
  ["a"]=>
  string(11) "hello world"
}
[1] Caught TypeError: Only arrays and Traversables can be unpacked
[2] Caught Error: Unknown named parameter $
hello world from croatia
[4] Cannot access private property A::$b

- (array) cast should be equal to get_object_vars()


Actual result:
--------------
k=a v=hello world
====
array(2) {
  ["a"]=>
  string(11) "hello world"
  ["Ab"]=>
  string(9) "from hell"
}
[1] Caught TypeError: Only arrays and Traversables can be unpacked
[2] Caught Error: Unknown named parameter $
[3] hello world from croatia
[4] Cannot access private property A::$b

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2022-02-23 18:15 UTC] stas@php.net
-Type: Security +Type: Bug
 [2022-02-23 18:15 UTC] stas@php.net
Visibility is not a security feature.
 [2022-02-24 09:25 UTC] cmb@php.net
-Status: Open +Status: Suspended
 [2022-02-24 09:25 UTC] cmb@php.net
The suggested changes would introduce a serious BC break (some
code is relying on the current behavior), so that would at the
very least require the RFC process[1].  Anybody is welcome to
pursue this process!  For the time being, I suspend this ticket.

[1] <https://wiki.php.net/rfc/howto>
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Dec 21 15:01:29 2024 UTC