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