diff --git a/SOURCES/1010-Fix-HTML-injection-in-Abyss-error-responses-and-dire.patch b/SOURCES/1010-Fix-HTML-injection-in-Abyss-error-responses-and-dire.patch new file mode 100644 index 0000000..8adcaf0 --- /dev/null +++ b/SOURCES/1010-Fix-HTML-injection-in-Abyss-error-responses-and-dire.patch @@ -0,0 +1,285 @@ +From d96aa99815b26058353964fd2c72145a9e748c51 Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Fri, 14 Aug 2026 08:42:22 +0200 +Subject: [PATCH] Fix HTML injection in Abyss error responses and directory + listings + +Backport of upstream SVN r3342. User-supplied URIs and filenames were +inserted unescaped into HTML documents generated by the Abyss server +(error pages and directory listings), allowing reflected XSS via +crafted request URIs or filenames containing HTML control characters. + +Add html_escapedForHtml() to replace &, <, >, ", ' with their HTML +entity equivalents before embedding user-derived strings in HTML output. + +Resolves: CVE-2026-15928 + +Co-Authored-By: Claude Opus 4.6 +--- + lib/abyss/src/Makefile | 1 + + lib/abyss/src/handler.c | 42 +++++++++++++++++-- + lib/abyss/src/html.c | 87 +++++++++++++++++++++++++++++++++++++++ + lib/abyss/src/html.h | 7 ++++ + lib/abyss/src/meson.build | 1 + + lib/abyss/src/response.c | 15 ++++--- + 6 files changed, 144 insertions(+), 9 deletions(-) + create mode 100644 lib/abyss/src/html.c + create mode 100644 lib/abyss/src/html.h + +diff --git a/lib/abyss/src/Makefile b/lib/abyss/src/Makefile +index 86f2552..133bf61 100644 +--- a/lib/abyss/src/Makefile ++++ b/lib/abyss/src/Makefile +@@ -45,6 +45,7 @@ TARGET_MODS = \ + date \ + file \ + handler \ ++ html \ + http \ + init \ + response \ +diff --git a/lib/abyss/src/handler.c b/lib/abyss/src/handler.c +index 80a2914..98431eb 100644 +--- a/lib/abyss/src/handler.c ++++ b/lib/abyss/src/handler.c +@@ -36,6 +36,7 @@ + #include "file.h" + #include "conn.h" + #include "http.h" ++#include "html.h" + #include "date.h" + #include "abyss_info.h" + +@@ -291,11 +292,15 @@ sendDirectoryDocumentHeading(TSession * const sessionP, + "------------------------------------" + "--------------------------------------------\r\n"); + } else { ++ const char * const escapedUri = html_escapedForHtml(uri); ++ + sprintf(z, "Index of %s" + "

Index of %s

",
+-                uri, uri);
++                escapedUri, escapedUri);
+         strcat(z, "Name                      Size      "
+                "Date-Time             Type
\r\n"); ++ ++ xmlrpc_strfree(escapedUri); + } + + HTTPWriteBodyChunk(sessionP, z, strlen(z)); +@@ -303,6 +308,37 @@ sendDirectoryDocumentHeading(TSession * const sessionP, + + + ++static void ++formatHtmlForDirEntry(char * const buffer, ++ const char * const name, ++ int const attrib, ++ const char * const z1, ++ const char * const z2, ++ const char * const z3, ++ const char * const z4, ++ const char * const p) { ++ ++ const char * const escapedName = html_escapedForHtml(name); ++ const char * const escapedZ1 = html_escapedForHtml(z1); ++ const char * const escapedZ2 = html_escapedForHtml(z2); ++ const char * const escapedZ3 = html_escapedForHtml(z3); ++ const char * const escapedZ4 = html_escapedForHtml(z4); ++ const char * const escapedP = html_escapedForHtml(p); ++ ++ sprintf(buffer, "%s%s %s %s %s\r\n", ++ escapedName, attrib & A_SUBDIR ? "/" : "", ++ escapedZ1, escapedP, escapedZ3, escapedZ2, escapedZ4); ++ ++ xmlrpc_strfree(escapedP); ++ xmlrpc_strfree(escapedZ1); ++ xmlrpc_strfree(escapedZ2); ++ xmlrpc_strfree(escapedZ3); ++ xmlrpc_strfree(escapedZ4); ++ xmlrpc_strfree(escapedName); ++} ++ ++ ++ + static void + sendDirectoryDocument(TList * const listP, + bool const ascending, +@@ -405,9 +441,7 @@ sendDirectoryDocument(TList * const listP, + if (text) + sprintf(z, "%s%s %s %s %s\r\n", z1, p, z3, z2, z4); + else +- sprintf(z, "%s%s %s %s %s\r\n", +- fi->name, fi->attrib & A_SUBDIR ? "/" : "", +- z1, p, z3, z2, z4); ++ formatHtmlForDirEntry(z, fi->name, fi->attrib, z1, z2, z3, z4, p); + + HTTPWriteBodyChunk(sessionP, z, strlen(z)); + } +diff --git a/lib/abyss/src/html.c b/lib/abyss/src/html.c +new file mode 100644 +index 0000000..eeb8b60 +--- /dev/null ++++ b/lib/abyss/src/html.c +@@ -0,0 +1,87 @@ ++/*============================================================================= ++ html ++=============================================================================== ++ This module contains utilities for processing HTML. ++=============================================================================*/ ++ ++#include ++#include ++ ++#include ++ ++#include "html.h" ++ ++ ++ ++static void ++insertCharEntity(char * const buffer, ++ unsigned int * const cursorP, ++ const char * const name) { ++ ++ size_t i; ++ ++ buffer[(*cursorP)++] = '&'; ++ ++ for (i = 0; i < strlen(name); ++i) ++ buffer[(*cursorP)++] = name[i]; ++ ++ buffer[(*cursorP)++] = ';'; ++} ++ ++ ++ ++const char * ++html_escapedForHtml(const char * const plainText) { ++/*---------------------------------------------------------------------------- ++ The text 'plainText' with HTML control characters replaced with ++ HTML character entities so that the result can be used inside an HTML ++ document without any chance of interfering with HTML controls. ++ ++ In particular, we substitute one of the following for each associated ++ character: ++ ++ & ++ < ++ > ++ " ++ ' ++ ++ We return newly malloced storage or a constant if we can't get allocate ++ enough, like 'xmlrpc_strdupsol', which must be freed with 'xmlrpc_strfree'. ++-----------------------------------------------------------------------------*/ ++ ++ size_t const maxPossibleReturnSz = strlen(plainText) * 6 + 1; ++ ++ const char * retval; ++ char * buffer; /* malloc'ed */ ++ ++ buffer = malloc(maxPossibleReturnSz); ++ ++ if (!buffer) ++ retval = xmlrpc_strnomemval(); ++ else { ++ unsigned int inCursor, outCursor; ++ ++ for (inCursor = 0, outCursor = 0; ++ inCursor < strlen(plainText); ++ ++inCursor) { ++ ++ switch (plainText[inCursor]) { ++ case '&': insertCharEntity(buffer, &outCursor, "amp"); break; ++ case '<': insertCharEntity(buffer, &outCursor, "lt"); break; ++ case '>': insertCharEntity(buffer, &outCursor, "gt"); break; ++ case '"': insertCharEntity(buffer, &outCursor, "quot"); break; ++ case '\'': insertCharEntity(buffer, &outCursor, "apos"); break; ++ default: ++ buffer[outCursor++] = plainText[inCursor]; ++ } ++ } ++ ++ buffer[outCursor++] = '\0'; /* string-terminating NUL */ ++ ++ assert(outCursor <= maxPossibleReturnSz); ++ ++ retval = buffer; ++ } ++ return retval; ++} +diff --git a/lib/abyss/src/html.h b/lib/abyss/src/html.h +new file mode 100644 +index 0000000..97235f6 +--- /dev/null ++++ b/lib/abyss/src/html.h +@@ -0,0 +1,7 @@ ++#ifndef HTML_H_INCLUDED ++#define HTML_H_INCLUDED ++ ++const char * ++html_escapedForHtml(const char * const plainText); ++ ++#endif +diff --git a/lib/abyss/src/meson.build b/lib/abyss/src/meson.build +index daa91fc..73c94d9 100644 +--- a/lib/abyss/src/meson.build ++++ b/lib/abyss/src/meson.build +@@ -30,6 +30,7 @@ libxmlrpc_abyss = library( + 'date.c', + 'file.c', + 'handler.c', ++ 'html.c', + 'http.c', + 'init.c', + 'response.c', +diff --git a/lib/abyss/src/response.c b/lib/abyss/src/response.c +index cc0980a..7024d32 100644 +--- a/lib/abyss/src/response.c ++++ b/lib/abyss/src/response.c +@@ -34,6 +34,7 @@ + #include "data.h" + #include "abyss_info.h" + #include "http.h" ++#include "html.h" + + + +@@ -41,24 +42,28 @@ void + ResponseError2(TSession * const sessionP, + const char * const explanation) { + ++ const char * const escapedExplanation = html_escapedForHtml(explanation); ++ + const char * errorDocument; + + ResponseAddField(sessionP, "Content-type", "text/html"); + + ResponseWriteStart(sessionP); +- ++ + xmlrpc_asprintf(&errorDocument, + "Error %d" + "" + "

Error %d

" +- "

%s

" SERVER_HTML_INFO ++ "

%s

" SERVER_HTML_INFO + "" + "", +- sessionP->status, sessionP->status, explanation); +- +- ConnWrite(sessionP->connP, errorDocument, strlen(errorDocument)); ++ sessionP->status, sessionP->status, escapedExplanation); ++ ++ ConnWrite(sessionP->connP, errorDocument, strlen(errorDocument)); + + xmlrpc_strfree(errorDocument); ++ ++ xmlrpc_strfree(escapedExplanation); + } + + +-- +2.55.0 + diff --git a/SPECS/xmlrpc-c.spec b/SPECS/xmlrpc-c.spec index 0982abe..f24544c 100644 --- a/SPECS/xmlrpc-c.spec +++ b/SPECS/xmlrpc-c.spec @@ -6,7 +6,7 @@ Name: xmlrpc-c Version: 1.51.0 -Release: 16%{?dist} +Release: 17%{?dist} Summary: Lightweight RPC library based on XML and HTTP # See doc/COPYING for details. # The Python 1.5.2 license used by a few files is just BSD. @@ -35,6 +35,8 @@ Patch201: 0001-Remove-trace-statements-accidentally-committed-with-.patch Patch1001: 0001-add-meson-buildsystem-definitions.patch Patch1002: 0002-chmod-x-xml-rpc-api2txt.patch +Patch1010: 1010-Fix-HTML-injection-in-Abyss-error-responses-and-dire.patch + BuildRequires: git-core BuildRequires: meson >= 0.36.0 BuildRequires: gcc @@ -197,9 +199,12 @@ rm -Rf lib/expat %{_bindir}/xmlrpc_dumpserver %changelog +* Fri Aug 14 2026 Michal Srb - 1.51.0-17 +- Fix HTML injection in Abyss error responses and directory listings +- Resolves: CVE-2026-15928 + * Thu Mar 17 2022 Michal Srb - 1.51.0-16 - Drop bundled expat and build against libxml2 -- Resolves: CVE-2022-25235 - Resolves: CVE-2022-25236 * Tue Aug 10 2021 Mohan Boddu - 1.51.0-15