Skip to content

Commit be627cc

Browse files
authored
Can configure the sanitizer as a standalone object to sanitize arbitrary strings too (#12)
For example: ```php $sanitizer = new SensitiveValueSanitizer(); $sanitizer->addSanitization('🍍', '🍌'); $string = $sanitizer->sanitize('🍍🍕'); // [...] $phpInfo = new PhpInfo($sanitizer); $html = $phpInfo->getHtml(); ```
2 parents 4e424c0 + 94dc46e commit be627cc

5 files changed

Lines changed: 177 additions & 37 deletions

File tree

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,18 @@ If found, the string in `$sanitize` will be replaced with the string `$with`, if
3030

3131
Some of the values in `phpinfo()` output are printed URL-encoded, so the `$sanitize` value will also be searched URL-encoded automatically.
3232
This means that both `foo,bar` and `foo%2Cbar` would be replaced.
33+
34+
## Sanitizing arbitrary strings
35+
If you have your `phpinfo()` output (or anything really) in a string, you can use the sanitizer standalone, for example:
36+
```php
37+
$sanitizer = new SensitiveValueSanitizer();
38+
$string = $sanitizer->addSanitization('🍍', '🍌')->sanitize('🍍🍕');
39+
```
40+
41+
The sanitizer will sanitize session id automatically, you can (but shouldn't) disable it with `doNotSanitizeSessionId()`.
42+
43+
You can then pass the configured sanitizer to `PhpInfo` class which will then use your configuration for sanitizing the `phpinfo()` output too:
44+
```php
45+
$phpInfo = new PhpInfo($sanitizer);
46+
$html = $phpInfo->getHtml();
47+
```

src/PhpInfo.php

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
class PhpInfo
77
{
88

9-
private bool $sanitizeSessionId = true;
9+
private SensitiveValueSanitizer $sanitizer;
1010

11-
private string $sanitizeWith = '[***]';
1211

13-
/** @var array<string, string> */
14-
private array $sanitize = [];
12+
public function __construct(?SensitiveValueSanitizer $sanitizer = null)
13+
{
14+
$this->sanitizer = $sanitizer ?? new SensitiveValueSanitizer();
15+
}
1516

1617

1718
public function getHtml(): string
@@ -21,42 +22,26 @@ public function getHtml(): string
2122
phpinfo();
2223
$info = preg_replace('~^.*?(<table[^>]*>.*</table>).*$~s', '$1', ob_get_clean() ?: $error) ?? $error;
2324
// Convert inline styles to classes defined in admin/info.css so we can drop CSP style-src 'unsafe-inline'
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-
$search = (string)$search;
32-
$replacements[$search] = $replace;
33-
$replacements[urlencode($search)] = $replace;
34-
}
35-
$info = strtr($info, $replacements);
25+
$info = str_replace('style="color: #', 'class="color-', $info);
26+
$info = $this->sanitizer->sanitize($info);
3627
return sprintf('<div id="phpinfo">%s</div>', $info);
3728
}
3829

3930

40-
private function getSessionId(): ?string
41-
{
42-
return session_id() ?: null;
43-
}
44-
45-
4631
/**
4732
* WARNING: Not recommended, disabling session id sanitization may allow
4833
* session stealing attacks that read the cookie from the output of phpinfo().
4934
*/
5035
public function doNotSanitizeSessionId(): self
5136
{
52-
$this->sanitizeSessionId = false;
37+
$this->sanitizer->doNotSanitizeSessionId();
5338
return $this;
5439
}
5540

5641

5742
public function addSanitization(string $sanitize, ?string $with = null): self
5843
{
59-
$this->sanitize[$sanitize] = $with ?? $this->sanitizeWith;
44+
$this->sanitizer->addSanitization($sanitize, $with);
6045
return $this;
6146
}
6247

src/SensitiveValueSanitizer.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Spaze\PhpInfo;
5+
6+
class SensitiveValueSanitizer
7+
{
8+
9+
private bool $sanitizeSessionId = true;
10+
11+
private string $sanitizeWith = '[***]';
12+
13+
/** @var array<string, string> */
14+
private array $sanitize = [];
15+
16+
17+
public function sanitize(string $info): string
18+
{
19+
$sanitize = [];
20+
if ($this->sanitizeSessionId && $this->getSessionId() !== null) {
21+
$sanitize[$this->getSessionId()] = $this->sanitizeWith;
22+
$sanitize[urlencode($this->getSessionId())] = $this->sanitizeWith;
23+
}
24+
return strtr($info, $this->sanitize + $sanitize);
25+
}
26+
27+
28+
private function getSessionId(): ?string
29+
{
30+
return session_id() ?: null;
31+
}
32+
33+
34+
/**
35+
* WARNING: Not recommended, disabling session id sanitization may allow
36+
* session stealing attacks that read the cookie from the output of phpinfo().
37+
*/
38+
public function doNotSanitizeSessionId(): self
39+
{
40+
$this->sanitizeSessionId = false;
41+
return $this;
42+
}
43+
44+
45+
public function addSanitization(string $sanitize, ?string $with = null): self
46+
{
47+
$this->sanitize[$sanitize] = $this->sanitize[urlencode($sanitize)] = $with ?? $this->sanitizeWith;
48+
return $this;
49+
}
50+
51+
}

tests/PhpInfoTest.phpt

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -79,20 +79,14 @@ class PhpInfoTest extends TestCase
7979
}
8080

8181

82-
public function testGetHtmlNumericSessionId(): void
82+
public function testGetHtmlSessionIdSanitizationCustomSanitizer(): void
8383
{
84-
$sessionId = '31337';
85-
$_COOKIE['PHPSESSID'] = $sessionId;
86-
87-
// Set a new session id
88-
session_destroy();
89-
session_set_save_handler(new TestSessionHandler($sessionId));
90-
session_start();
91-
92-
Assert::noError(function () use ($sessionId, &$html): void {
93-
$html = (new PhpInfo())->getHtml();
94-
});
95-
Assert::notContains($sessionId, $html);
84+
$sanitizer = new SensitiveValueSanitizer();
85+
$sanitizer->addSanitization(self::SESSION_ID, '🍕');
86+
$html = (new PhpInfo($sanitizer))->getHtml();
87+
Assert::notContains(self::SESSION_ID, $html);
88+
Assert::notContains(urlencode(self::SESSION_ID), $html);
89+
Assert::contains('🍕', $html);
9690
}
9791

9892
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Spaze\PhpInfo;
5+
6+
use Tester\Assert;
7+
use Tester\TestCase;
8+
9+
require __DIR__ . '/bootstrap.php';
10+
11+
class SensitiveValueSanitizerTest extends TestCase
12+
{
13+
14+
private const SESSION_ID = 'foobar,baz';
15+
16+
private string $string;
17+
18+
19+
public function __construct()
20+
{
21+
$this->string = sprintf('waldo %s fred %s quux', self::SESSION_ID, urlencode(self::SESSION_ID));
22+
}
23+
24+
25+
protected function setUp(): void
26+
{
27+
session_set_save_handler(new TestSessionHandler(self::SESSION_ID));
28+
session_start();
29+
}
30+
31+
32+
protected function tearDown(): void
33+
{
34+
session_destroy();
35+
}
36+
37+
38+
public function testSanitize(): void
39+
{
40+
$string = (new SensitiveValueSanitizer())->sanitize($this->string);
41+
Assert::contains('waldo', $string);
42+
Assert::notContains(self::SESSION_ID, $string);
43+
Assert::notContains(urlencode(self::SESSION_ID), $string);
44+
Assert::contains('[***]', $string);
45+
}
46+
47+
48+
public function testSanitizeSessionIdCustomReplacement(): void
49+
{
50+
$sanitizer = new SensitiveValueSanitizer();
51+
$sanitizer->addSanitization(self::SESSION_ID, 'yeah, sure');
52+
$string = $sanitizer->sanitize($this->string);
53+
Assert::contains('waldo', $string);
54+
Assert::notContains(self::SESSION_ID, $string);
55+
Assert::notContains(urlencode(self::SESSION_ID), $string);
56+
Assert::contains('yeah, sure', $string);
57+
}
58+
59+
60+
public function testSanitizeDoNotSanitizeSessionIdButWhy(): void
61+
{
62+
$string = (new SensitiveValueSanitizer())->doNotSanitizeSessionId()->sanitize($this->string);
63+
Assert::contains('waldo', $string);
64+
Assert::contains(self::SESSION_ID, $string);
65+
Assert::contains(urlencode(self::SESSION_ID), $string);
66+
Assert::notContains('[***]', $string);
67+
}
68+
69+
70+
public function testSanitizeAddSanitization(): void
71+
{
72+
$string = (new SensitiveValueSanitizer())->addSanitization('setec', '🍌')->sanitize('setec astronomy');
73+
Assert::notContains('setec', $string);
74+
Assert::contains('🍌', $string);
75+
}
76+
77+
78+
public function testGetHtmlNumericSessionId(): void
79+
{
80+
$sessionId = '31337';
81+
82+
// Set a new session id
83+
session_destroy();
84+
session_set_save_handler(new TestSessionHandler($sessionId));
85+
session_start();
86+
87+
Assert::noError(function () use ($sessionId, &$html): void {
88+
$html = (new SensitiveValueSanitizer())->sanitize("31336 + 1 = {$sessionId}");
89+
});
90+
Assert::notContains($sessionId, $html);
91+
}
92+
93+
}
94+
95+
(new SensitiveValueSanitizerTest())->run();

0 commit comments

Comments
 (0)