projects/aports·blob master · refs

221 lines · 8.2 KiB

1Patch-Source: https://github.com/php/php-src/commit/ab048bd83b578119cf81b456526d50498421d617
2From c5ef5f06a0f823f6324a19e544abf647b5d89aaa Mon Sep 17 00:00:00 2001
3From: Ilija Tovilo <[email protected]>
4Date: Mon, 27 Jul 2026 16:49:38 +0200
5Subject: [PATCH 2/4] Fix SQL injection in ext-pgsql via E'...' backslash
6 breakout
7
8php_pgsql_add_quotes() quotes the string with E'...', but PQescapeStringConn()
9does not escape \ unless standard_conforming_strings is off.
10PQescapeStringConn() is meant to be used with '', so do that instead.
11
12Fixes GHSA-7qpv-r5mr-78m4
13
14(cherry picked from commit ab048bd83b578119cf81b456526d50498421d617)
15---
16 ext/pgsql/pgsql.c | 3 +-
17 ext/pgsql/tests/10pg_convert_9.phpt | 18 +++++-
18 ext/pgsql/tests/10pg_convert_json_array.phpt | 4 +-
19 ext/pgsql/tests/12pg_insert_9.phpt | 2 +-
20 ext/pgsql/tests/14pg_update_9.phpt | 2 +-
21 ext/pgsql/tests/GHSA-7qpv-r5mr-78m4.phpt | 58 ++++++++++++++++++++
22 ext/pgsql/tests/bug64609.phpt | 2 +-
23 ext/pgsql/tests/bug68638.phpt | 2 +-
24 8 files changed, 81 insertions(+), 10 deletions(-)
25 create mode 100644 ext/pgsql/tests/GHSA-7qpv-r5mr-78m4.phpt
26
27diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c
28index 01fb9dde3ba..de90b0b21d7 100644
29--- a/ext/pgsql/pgsql.c
30+++ b/ext/pgsql/pgsql.c
31@@ -4563,7 +4563,7 @@ static int php_pgsql_convert_match(const zend_string *str, const char *regex , s
32 */
33 static zend_string *php_pgsql_add_quotes(zend_string *src)
34 {
35- return zend_string_concat3("E'", strlen("E'"), ZSTR_VAL(src), ZSTR_LEN(src), "'", strlen("'"));
36+ return zend_string_concat3("'", strlen("'"), ZSTR_VAL(src), ZSTR_LEN(src), "'", strlen("'"));
37 }
38 /* }}} */
39
40@@ -4841,7 +4841,6 @@ PHP_PGSQL_API zend_result php_pgsql_convert(PGconn *pg_link, const zend_string *
41 zend_string *str;
42 /* PostgreSQL ignores \0 */
43 str = zend_string_alloc(Z_STRLEN_P(val) * 2, 0);
44- /* better to use PGSQLescapeLiteral since PGescapeStringConn does not handle special \ */
45 ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str),
46 Z_STRVAL_P(val), Z_STRLEN_P(val), &escape_err);
47 if (escape_err) {
48diff --git a/ext/pgsql/tests/10pg_convert_9.phpt b/ext/pgsql/tests/10pg_convert_9.phpt
49index f4bfaf5e395..fb36e5801c8 100644
50--- a/ext/pgsql/tests/10pg_convert_9.phpt
51+++ b/ext/pgsql/tests/10pg_convert_9.phpt
52@@ -21,6 +21,8 @@ $converted = pg_convert($db, $table_name, $fields);
53
54 var_dump($converted);
55
56+var_dump(pg_convert($db, $table_name, ['str' => "\\' OR 1=1"]));
57+
58 /* Invalid values */
59 try {
60 $converted = pg_convert($db, $table_name, [5 => 'AAA']);
61@@ -48,18 +50,30 @@ try {
62 } catch (\TypeError $e) {
63 echo $e->getMessage(), \PHP_EOL;
64 }
65+
66+/* standard_conforming_strings = 1 */
67+pg_query($db, "SET standard_conforming_strings = 1");
68+var_dump(pg_convert($db, $table_name, ['str' => "\\' OR 1=1"]));
69 ?>
70 --EXPECT--
71 array(3) {
72 [""num""]=>
73 string(4) "1234"
74 [""str""]=>
75- string(6) "E'AAA'"
76+ string(5) "'AAA'"
77 [""bin""]=>
78- string(12) "E'\\x424242'"
79+ string(11) "'\\x424242'"
80+}
81+array(1) {
82+ [""str""]=>
83+ string(13) "'\\'' OR 1=1'"
84 }
85 Array of values must be an associative array with string keys
86 Array of values must be an associative array with string keys
87 Values must be of type string|int|float|bool|null, array given
88 Values must be of type string|int|float|bool|null, stdClass given
89 Values must be of type string|int|float|bool|null, PgSql\Connection given
90+array(1) {
91+ [""str""]=>
92+ string(12) "'\'' OR 1=1'"
93+}
94diff --git a/ext/pgsql/tests/10pg_convert_json_array.phpt b/ext/pgsql/tests/10pg_convert_json_array.phpt
95index 918765db06c..5353e04dac5 100644
96--- a/ext/pgsql/tests/10pg_convert_json_array.phpt
97+++ b/ext/pgsql/tests/10pg_convert_json_array.phpt
98@@ -32,8 +32,8 @@ if (!pg_insert($db, $table_name_92, $fields)) {
99 --EXPECT--
100 array(2) {
101 [""textary""]=>
102- string(51) "E'{"meeting", "lunch", "training", "presentation"}'"
103+ string(50) "'{"meeting", "lunch", "training", "presentation"}'"
104 [""jsn""]=>
105- string(22) "E'{"f1":1,"f2":"foo"}'"
106+ string(21) "'{"f1":1,"f2":"foo"}'"
107 }
108 OK
109diff --git a/ext/pgsql/tests/12pg_insert_9.phpt b/ext/pgsql/tests/12pg_insert_9.phpt
110index 892494fe6f0..699426c89b2 100644
111--- a/ext/pgsql/tests/12pg_insert_9.phpt
112+++ b/ext/pgsql/tests/12pg_insert_9.phpt
113@@ -54,7 +54,7 @@ try {
114 echo "Ok\n";
115 ?>
116 --EXPECTF--
117-INSERT INTO "php_pgsql_test" ("num","str","bin") VALUES (1234,E'AAA',E'\\x424242');
118+INSERT INTO "php_pgsql_test" ("num","str","bin") VALUES (1234,'AAA','\\x424242');
119 INSERT INTO "php_pgsql_test" ("num","str","bin") VALUES ('1234','AAA','BBB');
120 object(PgSql\Result)#%d (0) {
121 }
122diff --git a/ext/pgsql/tests/14pg_update_9.phpt b/ext/pgsql/tests/14pg_update_9.phpt
123index d34f05216ec..64493075490 100644
124--- a/ext/pgsql/tests/14pg_update_9.phpt
125+++ b/ext/pgsql/tests/14pg_update_9.phpt
126@@ -26,6 +26,6 @@ echo pg_update($db, $table_name, $fields, $ids, PGSQL_DML_STRING|PGSQL_DML_ESCAP
127 echo "Ok\n";
128 ?>
129 --EXPECT--
130-UPDATE "php_pgsql_test" SET "num"=1234,"str"=E'ABC',"bin"=E'\\x58595a' WHERE "num"=1234;
131+UPDATE "php_pgsql_test" SET "num"=1234,"str"='ABC',"bin"='\\x58595a' WHERE "num"=1234;
132 UPDATE "php_pgsql_test" SET "num"='1234',"str"='ABC',"bin"='XYZ' WHERE "num"='1234';
133 Ok
134diff --git a/ext/pgsql/tests/GHSA-7qpv-r5mr-78m4.phpt b/ext/pgsql/tests/GHSA-7qpv-r5mr-78m4.phpt
135new file mode 100644
136index 00000000000..88b37a8432f
137--- /dev/null
138+++ b/ext/pgsql/tests/GHSA-7qpv-r5mr-78m4.phpt
139@@ -0,0 +1,58 @@
140+--TEST--
141+GHSA-7qpv-r5mr-78m4: SQL injection via E'...' backslash breakout
142+--CREDITS--
143+expatch.llc
144+--EXTENSIONS--
145+pgsql
146+--SKIPIF--
147+<?php include("skipif.inc"); ?>
148+--FILE--
149+<?php
150+include 'config.inc';
151+
152+$db = pg_connect($conn_str);
153+
154+pg_query($db, "SET standard_conforming_strings = 1");
155+
156+pg_query($db, "DROP TABLE IF EXISTS ghsa_7qpv_r5mr_78m4");
157+pg_query($db, "CREATE TABLE ghsa_7qpv_r5mr_78m4 (id serial primary key, name text, admin boolean)");
158+pg_query($db, "INSERT INTO ghsa_7qpv_r5mr_78m4 (name, admin) VALUES ('alice', false), ('bob', false)");
159+
160+$params = ['name' => "zzz' OR 1=1 --"];
161+echo pg_select($db, 'ghsa_7qpv_r5mr_78m4', $params, PGSQL_DML_STRING) . "\n";
162+printf("returned: %d\n\n", count(pg_select($db, 'ghsa_7qpv_r5mr_78m4', $params)));
163+
164+$params = ['name' => "zzz\\' OR 1=1 --"];
165+echo pg_select($db, 'ghsa_7qpv_r5mr_78m4', $params, PGSQL_DML_STRING) . "\n";
166+printf("returned: %d\n\n", count(pg_select($db, 'ghsa_7qpv_r5mr_78m4', $params)));
167+
168+$params = ['name' => "john\\', true) --", 'admin' => 'false'];
169+echo pg_insert($db, 'ghsa_7qpv_r5mr_78m4', $params, PGSQL_DML_STRING) . "\n";
170+pg_insert($db, 'ghsa_7qpv_r5mr_78m4', $params);
171+var_dump(pg_select($db, 'ghsa_7qpv_r5mr_78m4', ['id' => 3])[0]['admin']);
172+echo "\n";
173+
174+$params = ['name' => "jake\\', true) --", 'admin' => 'f'];
175+echo pg_insert($db, 'ghsa_7qpv_r5mr_78m4', $params, PGSQL_DML_ESCAPE|PGSQL_DML_STRING) . "\n";
176+pg_insert($db, 'ghsa_7qpv_r5mr_78m4', $params, PGSQL_DML_EXEC|PGSQL_DML_ESCAPE);
177+var_dump(pg_select($db, 'ghsa_7qpv_r5mr_78m4', ['id' => 4])[0]['admin']);
178+
179+?>
180+--EXPECT--
181+SELECT * FROM "ghsa_7qpv_r5mr_78m4" WHERE "name"='zzz'' OR 1=1 --';
182+returned: 0
183+
184+SELECT * FROM "ghsa_7qpv_r5mr_78m4" WHERE "name"='zzz\'' OR 1=1 --';
185+returned: 0
186+
187+INSERT INTO "ghsa_7qpv_r5mr_78m4" ("name","admin") VALUES ('john\'', true) --','f');
188+string(1) "f"
189+
190+INSERT INTO "ghsa_7qpv_r5mr_78m4" ("name","admin") VALUES ('jake\'', true) --','f');
191+string(1) "f"
192+--CLEAN--
193+<?php
194+include('config.inc');
195+$db = pg_connect($conn_str);
196+pg_query($db, "DROP TABLE IF EXISTS ghsa_7qpv_r5mr_78m4");
197+?>
198diff --git a/ext/pgsql/tests/bug64609.phpt b/ext/pgsql/tests/bug64609.phpt
199index ebf878dbeed..ba27091ecf3 100644
200--- a/ext/pgsql/tests/bug64609.phpt
201+++ b/ext/pgsql/tests/bug64609.phpt
202@@ -28,5 +28,5 @@ var_dump($converted);
203 --EXPECT--
204 array(1) {
205 [""a""]=>
206- string(5) "E'ok'"
207+ string(4) "'ok'"
208 }
209diff --git a/ext/pgsql/tests/bug68638.phpt b/ext/pgsql/tests/bug68638.phpt
210index d95fa1e4412..2a1025cad8d 100644
211--- a/ext/pgsql/tests/bug68638.phpt
212+++ b/ext/pgsql/tests/bug68638.phpt
213@@ -34,7 +34,7 @@ pg_query($conn, "DROP TABLE $table");
214
215 ?>
216 --EXPECT--
217-string(52) "UPDATE "test_68638" SET "value"=E'inf' WHERE "id"=1;"
218+string(51) "UPDATE "test_68638" SET "value"='inf' WHERE "id"=1;"
219 array(2) {
220 ["id"]=>
221 string(1) "1"