Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,17 @@ An example usage with Nette Framework (can be used with other frameworks or stan
```php
$this->template->phpinfo = Html::el()->setHtml($this->phpInfo->getHtml());
```

## Sanitization
By default, session id (as returned by `session_id()`) will be sanitized and replaced by `[***]` in the output.
This is to prevent some session hijacking attacks that would read the session id from the cookie value reflected in the `phpinfo()` output.
You can disable that by calling `doNotSanitizeSessionId()` but it's totally not recommended. Do not disable that. Please.

You can add own strings to be sanitized in the output with
```php
addSanitization(string $sanitize, ?string $with = null): self
```
If found, the string in `$sanitize` will be replaced with the string `$with`, if `$with` is null then the default `[***]` will be used instead.

Some of the values in `phpinfo()` output are printed URL-encoded, so the `$sanitize` value will also be searched URL-encoded automatically.
This means that both `foo,bar` and `foo%2Cbar` would be replaced.
44 changes: 43 additions & 1 deletion src/PhpInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,57 @@
class PhpInfo
{

private bool $sanitizeSessionId = true;

private string $sanitizeWith = '[***]';

/** @var array<string, string> */
private array $sanitize = [];


public function getHtml(): string
{
$error = 'Cannot get phpinfo() output';
ob_start();
phpinfo();
$info = preg_replace('~^.*?(<table[^>]*>.*</table>).*$~s', '$1', ob_get_clean() ?: $error) ?? $error;
// Convert inline styles to classes defined in admin/info.css so we can drop CSP style-src 'unsafe-inline'
$info = str_replace('style="color: #', 'class="color-', $info);
$replacements['style="color: #'] = 'class="color-';
$sanitize = [];
if ($this->sanitizeSessionId && $this->getSessionId() !== null) {
$sanitize[$this->getSessionId()] = $this->sanitizeWith;
}
$sanitize = $this->sanitize + $sanitize;
foreach ($sanitize as $search => $replace) {
$replacements[$search] = $replace;
$replacements[urlencode($search)] = $replace;
}
$info = strtr($info, $replacements);
return sprintf('<div id="phpinfo">%s</div>', $info);
}


private function getSessionId(): ?string
{
return session_id() ?: null;
}


/**
* WARNING: Not recommended, disabling session id sanitization may allow
* session stealing attacks that read the cookie from the output of phpinfo().
*/
public function doNotSanitizeSessionId(): self
{
$this->sanitizeSessionId = false;
return $this;
}


public function addSanitization(string $sanitize, ?string $with = null): self
{
$this->sanitize[$sanitize] = $with ?? $this->sanitizeWith;
return $this;
}

}
61 changes: 59 additions & 2 deletions tests/PhpInfoTest.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,71 @@ require __DIR__ . '/bootstrap.php';
class PhpInfoTest extends TestCase
{

private const SESSION_ID = 'foobar,baz';
private const WALDO_1337 = 'waldo-fred-1337';
private const WALDO_1338 = 'waldo-quux-1338';


protected function setUp(): void
{
$_SERVER['HTTP_WALDO_FRED'] = self::WALDO_1337;
$_SERVER['HTTP_COOKIE'] = 'PHPSESSID=' . urlencode(self::SESSION_ID);
$_COOKIE['PHPSESSID'] = self::SESSION_ID;

session_set_save_handler(new TestSessionHandler(self::SESSION_ID));
session_start();
}


protected function tearDown(): void
{
session_destroy();
}


public function testGetHtml(): void
{
$phpInfo = new PhpInfo();
$html = $phpInfo->getHtml();
$html = (new PhpInfo())->getHtml();
Assert::contains('<div id="phpinfo">', $html);
Assert::contains('disable_functions', $html);
}


public function testGetHtmlSessionIdSanitization(): void
{
$html = (new PhpInfo())->getHtml();
Assert::notContains(self::SESSION_ID, $html);
Assert::notContains(urlencode(self::SESSION_ID), $html);
Assert::contains('[***]', $html);
}


public function testGetHtmlSessionIdSanitizationCustomReplacement(): void
{
$phpInfo = new PhpInfo();
$phpInfo->addSanitization(self::SESSION_ID, 'yeah, sure');
Assert::contains('yeah, sure', $phpInfo->getHtml());
}


public function testGetHtmlDoNotSanitizeSessionIdButWhy(): void
{
$phpInfo = new PhpInfo();
$html = $phpInfo->doNotSanitizeSessionId()->getHtml();
Assert::contains(self::SESSION_ID, $html);
Assert::contains(urlencode(self::SESSION_ID), $html);
}


public function testGetHtmlAddSanitization(): void
{
$phpInfo = new PhpInfo();
Assert::contains(self::WALDO_1337, $phpInfo->getHtml());
$html = $phpInfo->addSanitization(self::WALDO_1337, self::WALDO_1338)->getHtml();
Assert::notContains(self::WALDO_1337, $html);
Assert::contains(self::WALDO_1338, $html);
}

}

(new PhpInfoTest())->run();
61 changes: 61 additions & 0 deletions tests/TestSessionHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
declare(strict_types = 1);

namespace Spaze\PhpInfo;

use SessionHandlerInterface;
use SessionIdInterface;

class TestSessionHandler implements SessionHandlerInterface, SessionIdInterface
{

public function __construct(
private string $sessionId,
private array $data = [],
) {
}


public function create_sid(): string
{
return $this->sessionId;
}


public function open(string $path, string $name): bool
{
return true;
}


public function close(): bool
{
return true;
}


public function destroy(string $id): bool
{
return true;
}


public function gc(int $max_lifetime): int|false
{
return 0;
}


public function read(string $id): string|false
{
return $this->data[$id] ?? '';
}


public function write(string $id, string $data): bool
{
$this->data[$id] = $data;
return true;
}

}
5 changes: 4 additions & 1 deletion tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<?php
declare(strict_types = 1);

use Composer\Autoload\ClassLoader;
use Tester\Environment;

require __DIR__ . '/../vendor/autoload.php';
/** @var ClassLoader $loader */
$loader = require __DIR__ . '/../vendor/autoload.php';
$loader->addPsr4('Spaze\\PhpInfo\\', __DIR__);

Environment::setup();