# 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( 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 +// "" 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( ": " ); +} + 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; }