1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
From cc2960e782eb5cc262d7bd572a7d18979a811954 Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Sun, 3 May 2026 20:01:41 +0200
Subject: [PATCH 04/10] GHSA-7qg2-v9fj-4mwv: [fpm] XSS within status endpoint
Fixes GHSA-7qg2-v9fj-4mwv
Fixes CVE-2026-6735
(cherry picked from commit 99a5ad7441de9914246c7863adb6997396008b9d)
---
sapi/fpm/fpm/fpm_status.c | 34 +++++++++++--
.../tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt | 48 +++++++++++++++++++
2 files changed, 78 insertions(+), 4 deletions(-)
create mode 100644 sapi/fpm/tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt
diff --git a/sapi/fpm/fpm/fpm_status.c b/sapi/fpm/fpm/fpm_status.c
index f698753cf4c..81292aa3bf8 100644
--- a/sapi/fpm/fpm/fpm_status.c
+++ b/sapi/fpm/fpm/fpm_status.c
@@ -525,8 +525,8 @@ int fpm_status_handle_request(void) /* {{{ */
if (full_syntax) {
unsigned int i;
int first;
- zend_string *tmp_query_string;
- char *query_string;
+ zend_string *tmp_query_string, *tmp_request_uri_string;
+ char *query_string, *request_uri_string;
struct timeval duration, now;
float cpu;
@@ -551,13 +551,36 @@ int fpm_status_handle_request(void) /* {{{ */
}
}
+ request_uri_string = NULL;
+ tmp_request_uri_string = NULL;
+ if (proc->request_uri[0] != '\0') {
+ if (encode_html) {
+ tmp_request_uri_string = php_escape_html_entities_ex(
+ (const unsigned char *) proc->request_uri,
+ strlen(proc->request_uri), 1, ENT_DISALLOWED | ENT_HTML_DOC_XML1 | ENT_COMPAT,
+ NULL, /* double_encode */ 1, /* quiet */ 0);
+ request_uri_string = ZSTR_VAL(tmp_request_uri_string);
+ } else if (encode_json) {
+ tmp_request_uri_string = php_json_encode_string(proc->request_uri,
+ strlen(proc->request_uri), PHP_JSON_INVALID_UTF8_IGNORE);
+ request_uri_string = ZSTR_VAL(tmp_request_uri_string);
+ /* remove quotes around the string */
+ if (ZSTR_LEN(tmp_request_uri_string) >= 2) {
+ request_uri_string[ZSTR_LEN(tmp_request_uri_string) - 1] = '\0';
+ ++request_uri_string;
+ }
+ } else {
+ request_uri_string = proc->request_uri;
+ }
+ }
+
query_string = NULL;
tmp_query_string = NULL;
if (proc->query_string[0] != '\0') {
if (encode_html) {
tmp_query_string = php_escape_html_entities_ex(
(const unsigned char *) proc->query_string,
- strlen(proc->query_string), 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT,
+ strlen(proc->query_string), 1, ENT_DISALLOWED | ENT_HTML_DOC_XML1 | ENT_COMPAT,
NULL, /* double_encode */ 1, /* quiet */ 0);
} else if (encode_json) {
tmp_query_string = php_json_encode_string(proc->query_string,
@@ -596,7 +619,7 @@ int fpm_status_handle_request(void) /* {{{ */
proc->requests,
duration.tv_sec * 1000000UL + duration.tv_usec,
proc->request_method[0] != '\0' ? proc->request_method : "-",
- proc->request_uri[0] != '\0' ? proc->request_uri : "-",
+ request_uri_string ? request_uri_string : "-",
query_string ? "?" : "",
query_string ? query_string : "",
proc->content_length,
@@ -607,6 +630,9 @@ int fpm_status_handle_request(void) /* {{{ */
PUTS(buffer);
efree(buffer);
+ if (tmp_request_uri_string) {
+ zend_string_free(tmp_request_uri_string);
+ }
if (tmp_query_string) {
zend_string_free(tmp_query_string);
}
diff --git a/sapi/fpm/tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt b/sapi/fpm/tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt
new file mode 100644
index 00000000000..475bc130a42
--- /dev/null
+++ b/sapi/fpm/tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt
@@ -0,0 +1,48 @@
+--TEST--
+FPM: GHSA-7qg2-v9fj-4mwv - status xss
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[unconfined]
+listen = {{ADDR}}
+pm = static
+pm.max_children = 2
+pm.status_path = /status
+catch_workers_output = yes
+EOT;
+
+$code = <<<EOT
+<?php
+usleep(200000);
+EOT;
+
+$tester = new FPM\Tester($cfg, $code);
+$tester->start();
+$tester->expectLogStartNotices();
+$responses = $tester
+ ->multiRequest([
+ ['uri' => '/<script>alert(1)</script>', 'query' => '<script>alert(2)</script>'],
+ ['uri' => '/status', 'query' => 'full&html', 'delay' => 100000],
+ ]);
+var_dump(strpos($responses[1]->getBody(), '<script>'));
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+bool(false)
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
--
2.54.0
|