Skip to content

Commit e1b2218

Browse files
authored
Merge pull request #8 from spaze/spaze/sanitize
Sanitize session id by default, can add more strings
2 parents ef9cbbc + fbd6ebe commit e1b2218

File tree

5 files changed

+181
-4
lines changed

5 files changed

+181
-4
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,17 @@ An example usage with Nette Framework (can be used with other frameworks or stan
1616
```php
1717
$this->template->phpinfo = Html::el()->setHtml($this->phpInfo->getHtml());
1818
```
19+
20+
## Sanitization
21+
By default, session id (as returned by `session_id()`) will be sanitized and replaced by `[***]` in the output.
22+
This is to prevent some session hijacking attacks that would read the session id from the cookie value reflected in the `phpinfo()` output.
23+
You can disable that by calling `doNotSanitizeSessionId()` but it's totally not recommended. Do not disable that. Please.
24+
25+
You can add own strings to be sanitized in the output with
26+
```php
27+
addSanitization(string $sanitize, ?string $with = null): self
28+
```
29+
If found, the string in `$sanitize` will be replaced with the string `$with`, if `$with` is null then the default `[***]` will be used instead.
30+
31+
Some of the values in `phpinfo()` output are printed URL-encoded, so the `$sanitize` value will also be searched URL-encoded automatically.
32+
This means that both `foo,bar` and `foo%2Cbar` would be replaced.

src/PhpInfo.php

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,57 @@
66
class PhpInfo
77
{
88

9+
private bool $sanitizeSessionId = true;
10+
11+
private string $sanitizeWith = '[***]';
12+
13+
/** @var array<string, string> */
14+
private array $sanitize = [];
15+
16+
917
public function getHtml(): string
1018
{
1119
$error = 'Cannot get phpinfo() output';
1220
ob_start();
1321
phpinfo();
1422
$info = preg_replace('~^.*?(<table[^>]*>.*</table>).*$~s', '$1', ob_get_clean() ?: $error) ?? $error;
1523
// Convert inline styles to classes defined in admin/info.css so we can drop CSP style-src 'unsafe-inline'
16-
$info = str_replace('style="color: #', 'class="color-', $info);
24+
$replacements['style="color: #'] = 'class="color-';
25+
$sanitize = [];
26+
if ($this->sanitizeSessionId && $this->getSessionId() !== null) {
27+
$sanitize[$this->getSessionId()] = $this->sanitizeWith;
28+
}
29+
$sanitize = $this->sanitize + $sanitize;
30+
foreach ($sanitize as $search => $replace) {
31+
$replacements[$search] = $replace;
32+
$replacements[urlencode($search)] = $replace;
33+
}
34+
$info = strtr($info, $replacements);
1735
return sprintf('<div id="phpinfo">%s</div>', $info);
1836
}
1937

38+
39+
private function getSessionId(): ?string
40+
{
41+
return session_id() ?: null;
42+
}
43+
44+
45+
/**
46+
* WARNING: Not recommended, disabling session id sanitization may allow
47+
* session stealing attacks that read the cookie from the output of phpinfo().
48+
*/
49+
public function doNotSanitizeSessionId(): self
50+
{
51+
$this->sanitizeSessionId = false;
52+
return $this;
53+
}
54+
55+
56+
public function addSanitization(string $sanitize, ?string $with = null): self
57+
{
58+
$this->sanitize[$sanitize] = $with ?? $this->sanitizeWith;
59+
return $this;
60+
}
61+
2062
}

tests/PhpInfoTest.phpt

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,71 @@ require __DIR__ . '/bootstrap.php';
1111
class PhpInfoTest extends TestCase
1212
{
1313

14+
private const SESSION_ID = 'foobar,baz';
15+
private const WALDO_1337 = 'waldo-fred-1337';
16+
private const WALDO_1338 = 'waldo-quux-1338';
17+
18+
19+
protected function setUp(): void
20+
{
21+
$_SERVER['HTTP_WALDO_FRED'] = self::WALDO_1337;
22+
$_SERVER['HTTP_COOKIE'] = 'PHPSESSID=' . urlencode(self::SESSION_ID);
23+
$_COOKIE['PHPSESSID'] = self::SESSION_ID;
24+
25+
session_set_save_handler(new TestSessionHandler(self::SESSION_ID));
26+
session_start();
27+
}
28+
29+
30+
protected function tearDown(): void
31+
{
32+
session_destroy();
33+
}
34+
35+
1436
public function testGetHtml(): void
1537
{
16-
$phpInfo = new PhpInfo();
17-
$html = $phpInfo->getHtml();
38+
$html = (new PhpInfo())->getHtml();
1839
Assert::contains('<div id="phpinfo">', $html);
1940
Assert::contains('disable_functions', $html);
2041
}
2142

43+
44+
public function testGetHtmlSessionIdSanitization(): void
45+
{
46+
$html = (new PhpInfo())->getHtml();
47+
Assert::notContains(self::SESSION_ID, $html);
48+
Assert::notContains(urlencode(self::SESSION_ID), $html);
49+
Assert::contains('[***]', $html);
50+
}
51+
52+
53+
public function testGetHtmlSessionIdSanitizationCustomReplacement(): void
54+
{
55+
$phpInfo = new PhpInfo();
56+
$phpInfo->addSanitization(self::SESSION_ID, 'yeah, sure');
57+
Assert::contains('yeah, sure', $phpInfo->getHtml());
58+
}
59+
60+
61+
public function testGetHtmlDoNotSanitizeSessionIdButWhy(): void
62+
{
63+
$phpInfo = new PhpInfo();
64+
$html = $phpInfo->doNotSanitizeSessionId()->getHtml();
65+
Assert::contains(self::SESSION_ID, $html);
66+
Assert::contains(urlencode(self::SESSION_ID), $html);
67+
}
68+
69+
70+
public function testGetHtmlAddSanitization(): void
71+
{
72+
$phpInfo = new PhpInfo();
73+
Assert::contains(self::WALDO_1337, $phpInfo->getHtml());
74+
$html = $phpInfo->addSanitization(self::WALDO_1337, self::WALDO_1338)->getHtml();
75+
Assert::notContains(self::WALDO_1337, $html);
76+
Assert::contains(self::WALDO_1338, $html);
77+
}
78+
2279
}
2380

2481
(new PhpInfoTest())->run();

tests/TestSessionHandler.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Spaze\PhpInfo;
5+
6+
use SessionHandlerInterface;
7+
use SessionIdInterface;
8+
9+
class TestSessionHandler implements SessionHandlerInterface, SessionIdInterface
10+
{
11+
12+
public function __construct(
13+
private string $sessionId,
14+
private array $data = [],
15+
) {
16+
}
17+
18+
19+
public function create_sid(): string
20+
{
21+
return $this->sessionId;
22+
}
23+
24+
25+
public function open(string $path, string $name): bool
26+
{
27+
return true;
28+
}
29+
30+
31+
public function close(): bool
32+
{
33+
return true;
34+
}
35+
36+
37+
public function destroy(string $id): bool
38+
{
39+
return true;
40+
}
41+
42+
43+
public function gc(int $max_lifetime): int|false
44+
{
45+
return 0;
46+
}
47+
48+
49+
public function read(string $id): string|false
50+
{
51+
return $this->data[$id] ?? '';
52+
}
53+
54+
55+
public function write(string $id, string $data): bool
56+
{
57+
$this->data[$id] = $data;
58+
return true;
59+
}
60+
61+
}

tests/bootstrap.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
<?php
22
declare(strict_types = 1);
33

4+
use Composer\Autoload\ClassLoader;
45
use Tester\Environment;
56

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

811
Environment::setup();

0 commit comments

Comments
 (0)