238 lines
7.5 KiB
Diff
238 lines
7.5 KiB
Diff
From 59478dd8977985c7e431698a76da9263bd102016 Mon Sep 17 00:00:00 2001
|
|
From: RHEL Packaging Agent <redhat-ymir-agent@redhat.com>
|
|
Date: Tue, 4 Aug 2026 09:02:45 +0000
|
|
Subject: [PATCH] Fix HTML injection -- putting URI or file name containing
|
|
HTML control characters inside HTML response
|
|
|
|
Backport of upstream SVN revision 3342.
|
|
CVE-2026-15928
|
|
---
|
|
lib/abyss/src/handler.c | 42 +++++++++++++++++--
|
|
lib/abyss/src/html.c | 87 ++++++++++++++++++++++++++++++++++++++++
|
|
lib/abyss/src/html.h | 7 ++++
|
|
lib/abyss/src/response.c | 7 +++-
|
|
4 files changed, 138 insertions(+), 5 deletions(-)
|
|
create mode 100644 lib/abyss/src/html.c
|
|
create mode 100644 lib/abyss/src/html.h
|
|
|
|
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, "<HTML><HEAD><TITLE>Index of %s</TITLE></HEAD><BODY>"
|
|
"<H1>Index of %s</H1><PRE>",
|
|
- uri, uri);
|
|
+ escapedUri, escapedUri);
|
|
strcat(z, "Name Size "
|
|
"Date-Time Type<HR WIDTH=100%>\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, "<A HREF=\"%s%s\">%s</A>%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, "<A HREF=\"%s%s\">%s</A>%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..58f085f
|
|
--- /dev/null
|
|
+++ b/lib/abyss/src/html.c
|
|
@@ -0,0 +1,87 @@
|
|
+/*=============================================================================
|
|
+ html
|
|
+===============================================================================
|
|
+ This module contains utilities for processing HTML.
|
|
+=============================================================================*/
|
|
+
|
|
+#include <assert.h>
|
|
+#include <stdlib.h>
|
|
+
|
|
+#include <xmlrpc-c/string_int.h>
|
|
+
|
|
+#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/response.c b/lib/abyss/src/response.c
|
|
index cc0980a..5cf145d 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,6 +42,8 @@ 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");
|
|
@@ -54,11 +57,13 @@ ResponseError2(TSession * const sessionP,
|
|
"<P>%s</P>" SERVER_HTML_INFO
|
|
"</BODY>"
|
|
"</HTML>",
|
|
- sessionP->status, sessionP->status, explanation);
|
|
+ sessionP->status, sessionP->status, escapedExplanation);
|
|
|
|
ConnWrite(sessionP->connP, errorDocument, strlen(errorDocument));
|
|
|
|
xmlrpc_strfree(errorDocument);
|
|
+
|
|
+ xmlrpc_strfree(escapedExplanation);
|
|
}
|
|
|
|
|