109 lines
4.3 KiB
Diff
109 lines
4.3 KiB
Diff
# UNDF: UNDF-2026-000001137
|
|
# openoffice-0002: CurlSession::curlDebugOutput logs HTTP headers verbatim — MOAD-0004 (CWE-312)
|
|
#
|
|
# In main/ucb/source/ucp/webdav/CurlSession.cxx, when our WebDAV logger is
|
|
# configured at LogLevel::FINEST, the curl debug callback (Curl_DebugCallback /
|
|
# curlDebugOutput) logs all outgoing and incoming HTTP headers using
|
|
# CURLOPT_VERBOSE mode. Outgoing headers (CURLINFO_HEADER_OUT) include the
|
|
# Authorization header with HTTP Basic or Digest credentials encoded as
|
|
# base64(user:password) or Digest response tokens.
|
|
#
|
|
# LogLevel::FINEST is a user-configurable runtime setting in OpenOffice (Tools →
|
|
# Options → OpenOffice.org → Advanced → Expert Configuration or via ooo-logging
|
|
# configuration). When activated for diagnostics, all WebDAV authentication
|
|
# credentials are written to the log file in plaintext base64.
|
|
#
|
|
# Example logged line:
|
|
# [CurlHDR ->] Authorization: Basic dXNlcjpteXBhc3N3b3Jk
|
|
#
|
|
# The base64 "dXNlcjpteXBhc3N3b3Jk" decodes trivially to "user:mypassword".
|
|
#
|
|
# Fix: strip the value from any outgoing header line whose name is a
|
|
# credential-bearing header (Authorization, Proxy-Authorization, X-Auth-Token,
|
|
# Cookie) before passing it to the logger. Log the header name with a redacted
|
|
# placeholder so debugging of auth flow is still possible without exposing secrets.
|
|
#
|
|
# Severity: MEDIUM — requires FINEST log level to be enabled, but that is
|
|
# a realistic diagnostics scenario; once enabled, all WebDAV credentials are
|
|
# recorded in persistent log files readable by any process with log read access.
|
|
--- a/main/ucb/source/ucp/webdav/CurlSession.cxx
|
|
+++ b/main/ucb/source/ucp/webdav/CurlSession.cxx
|
|
@@ -336,10 +336,38 @@
|
|
return session->curlDebugOutput( type, reinterpret_cast<char*>( data ), size );
|
|
}
|
|
|
|
+// Credential-bearing headers whose values must never appear in logs.
|
|
+// Names are lower-cased; comparison uses case-insensitive prefix match.
|
|
+static bool lcl_IsCredentialHeader( const rtl::OString& rHeader )
|
|
+{
|
|
+ // Extract header name (everything before the first ':')
|
|
+ sal_Int32 nColon = rHeader.indexOf( ':' );
|
|
+ if ( nColon <= 0 )
|
|
+ return false;
|
|
+ rtl::OString aName = rHeader.copy( 0, nColon ).trim().toAsciiLowerCase();
|
|
+ return aName == "authorization"
|
|
+ || aName == "proxy-authorization"
|
|
+ || aName == "x-auth-token"
|
|
+ || aName == "www-authenticate"
|
|
+ || aName == "proxy-authenticate"
|
|
+ || aName == "cookie"
|
|
+ || aName == "set-cookie";
|
|
+}
|
|
+
|
|
+// Return a sanitized version of an HTTP header line — value replaced with
|
|
+// "<redacted>" for credential-bearing headers.
|
|
+static rtl::OString lcl_RedactHeader( const rtl::OString& rHeader )
|
|
+{
|
|
+ sal_Int32 nColon = rHeader.indexOf( ':' );
|
|
+ if ( nColon <= 0 )
|
|
+ return rHeader;
|
|
+ rtl::OString aName = rHeader.copy( 0, nColon );
|
|
+ return aName + rtl::OString( ": <redacted>" );
|
|
+}
|
|
+
|
|
int CurlSession::curlDebugOutput( curl_infotype type, char *data, int size )
|
|
{
|
|
const char *prefix;
|
|
switch ( type )
|
|
{
|
|
case CURLINFO_TEXT:
|
|
prefix = "[CurlINFO ]";
|
|
break;
|
|
case CURLINFO_HEADER_IN:
|
|
prefix = "[CurlHDR <-]";
|
|
break;
|
|
case CURLINFO_HEADER_OUT:
|
|
prefix = "[CurlHDR ->]";
|
|
break;
|
|
case CURLINFO_DATA_IN:
|
|
prefix = "[CurlData<-]";
|
|
break;
|
|
case CURLINFO_DATA_OUT:
|
|
prefix = "[CurlData->]";
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
// Trim the trailing \r\n
|
|
if ( size >= 1 && ( data[size - 1] == '\r' || data[size - 1] == '\n' ) )
|
|
--size;
|
|
if ( size >= 1 && ( data[size - 1] == '\r' || data[size - 1] == '\n' ) )
|
|
--size;
|
|
rtl::OString message( data, size );
|
|
- m_aLogger.log( LogLevel::FINEST, "$1$ $2$", prefix, message );
|
|
+ // Sanitize credential-bearing HTTP headers before logging (CWE-312).
|
|
+ // Authorization, Proxy-Authorization, Cookie etc. must never be logged
|
|
+ // in plaintext regardless of log level.
|
|
+ if ( ( type == CURLINFO_HEADER_IN || type == CURLINFO_HEADER_OUT )
|
|
+ && lcl_IsCredentialHeader( message ) )
|
|
+ {
|
|
+ m_aLogger.log( LogLevel::FINEST, "$1$ $2$", prefix,
|
|
+ lcl_RedactHeader( message ) );
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ m_aLogger.log( LogLevel::FINEST, "$1$ $2$", prefix, message );
|
|
+ }
|
|
return 0;
|
|
}
|