Page MenuHomePhabricator

0001-SECURITY-Do-not-allow-keys-named-__proto__-in-Json-o.patch

Authored By
Bawolff
May 16 2016, 5:54 AM
Size
4 KB
Referenced Files
None
Subscribers
None

0001-SECURITY-Do-not-allow-keys-named-__proto__-in-Json-o.patch

From e1804b9921cef22aa55f7d2a6482b7e9d0e6dd79 Mon Sep 17 00:00:00 2001
From: Brian Wolff <bawolff+wn@gmail.com>
Date: Fri, 13 May 2016 04:27:11 -0400
Subject: [PATCH] SECURITY: Do not allow keys named __proto__ in Json output
Browsers interpret that key name specially when you create just
a plain javascript object. In order to prevent unexepcted behaviour
that in certain cases may be exploitable, ban that key name entirely.
Bug: T134719
Change-Id: Ice111d53d9b3ad164fca3a662e37450e7a2b75d6
---
includes/json/FormatJson.php | 22 +++++++++
tests/phpunit/includes/json/FormatJsonTest.php | 62 ++++++++++++++++++++++++++
2 files changed, 84 insertions(+)
diff --git a/includes/json/FormatJson.php b/includes/json/FormatJson.php
index 775ab43..2e59cdd 100644
--- a/includes/json/FormatJson.php
+++ b/includes/json/FormatJson.php
@@ -48,6 +48,8 @@ class FormatJson {
/**
* Skip escaping as many characters as reasonably possible.
*
+ * @note This doesn't include PROTO_OK, since banning __proto__ is a different
+ * sort of escaping from the character escapes.
* @warning When generating inline script blocks, use FormatJson::UTF8_OK instead.
*
* @since 1.22
@@ -55,6 +57,17 @@ class FormatJson {
const ALL_OK = 3;
/**
+ * Allow __proto__ as a key name.
+ *
+ * @warning Do not use this if having the JSON directly interpreted as Javascript.
+ * Web browsers treat __proto__ as containing the object's prototype, which could
+ * potentially be used to trick sanitization filters.
+ *
+ * @since 1.28
+ */
+ const PROTO_OK = 4;
+
+ /**
* If set, treat json objects '{...}' as associative arrays. Without this option,
* json objects will be converted to stdClass.
* The value is set to 1 to be backward compatible with 'true' that was used before.
@@ -168,6 +181,15 @@ class FormatJson {
$json = str_replace( self::$badChars, self::$badCharsEscaped, $json );
}
+ if ( !( $escaping & self::PROTO_OK ) ) {
+ if ( preg_match( '/(?<!\\\\)"__proto__":/', $json ) !== 0 ) {
+ throw new UnexpectedValueException(
+ "__proto__ is not allowed as a key name in JSON. "
+ . "(Use self::PROTO_OK to override)"
+ );
+ }
+ }
+
return $json;
}
diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php
index 01b575c..aa91473 100644
--- a/tests/phpunit/includes/json/FormatJsonTest.php
+++ b/tests/phpunit/includes/json/FormatJsonTest.php
@@ -372,4 +372,66 @@ class FormatJsonTest extends MediaWikiTestCase {
return $cases;
}
+
+ public function testProtoOK() {
+ $res = FormatJson::encode(
+ [ '__proto__' => [ 'foo' => 'bar' ] ],
+ false,
+ FormatJson::PROTO_OK
+ );
+ $this->assertEquals( '{"__proto__":{"foo":"bar"}}', $res );
+ }
+
+ /**
+ * @dataProvider provideProtoNotOkMisses
+ */
+ public function testProtoNotOkMisses( $expectedUgly, $expectedPretty, $data ) {
+ $actualUgly = FormatJson::encode( $data, false );
+ $actualPretty = FormatJson::encode( $data, true );
+
+ $this->assertEquals( $expectedUgly, $actualUgly, "ugly" );
+ $this->assertEquals( $expectedPretty, $actualPretty, "pretty" );
+ }
+
+ public function provideProtoNotOkMisses() {
+ return [
+ [
+ '{"\"__proto__":{}}',
+ "{\n \"\\\"__proto__\": {}\n}",
+ [ '"__proto__' => new stdClass ]
+ ],
+ [
+ '{"__Proto__":{}}',
+ "{\n \"__Proto__\": {}\n}",
+ [ '__Proto__' => new stdClass ]
+ ],
+ [
+ '{"__PROTO__":{}}',
+ "{\n \"__PROTO__\": {}\n}",
+ [ '__PROTO__' => new stdClass ]
+ ],
+ [
+ '{"foo":"__proto__"}',
+ "{\n \"foo\": \"__proto__\"\n}",
+ [ 'foo' => "__proto__" ]
+ ],
+ ];
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testProtoNotOkHitUgly() {
+ $data = [ 'foo' => [ 'bar' => 3, '__proto__' => [ 'baz' => 2 ], 'fred' => 'f' ] ];
+ $res = FormatJson::encode( $data, false );
+ }
+
+ /**
+ * @expectedException UnexpectedValueException
+ */
+ public function testProtoNotOkHitPretty() {
+ $data = [ 'foo' => [ 'bar' => 3, '__proto__' => [ 'baz' => 2 ], 'fred' => 'f' ] ];
+ $res = FormatJson::encode( $data, true );
+ }
+
}
--
2.0.1

File Metadata

Mime Type
text/x-diff
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3730837
Default Alt Text
0001-SECURITY-Do-not-allow-keys-named-__proto__-in-Json-o.patch (4 KB)

Event Timeline