Merge pull request #2016 from MarkLee131/fix/addheader-crlf-guard

HTTPSock: reject CR/LF in AddHeader name/value
This commit is contained in:
Alexey Sokolov
2026-04-27 01:38:00 +01:00
committed by GitHub
4 changed files with 54 additions and 1 deletions
+4
View File
@@ -52,6 +52,10 @@ class CHTTPSock : public CSocket {
unsigned int uStatusId = 200,
const CString& sStatusMsg = "OK");
void AddHeader(const CString& sName, const CString& sValue);
/** Returns false if `s` contains CR or LF. Used by AddHeader to reject
* inputs that could split the response into a separate header or body
* (RFC 7230 disallows obs-fold; treat any bare CR/LF as invalid). */
static bool IsValidHeaderField(const CString& s);
void SetContentType(const CString& sContentType);
bool PrintNotFound();
+10
View File
@@ -762,7 +762,17 @@ void CHTTPSock::SetContentType(const CString& sContentType) {
m_sContentType = sContentType;
}
bool CHTTPSock::IsValidHeaderField(const CString& s) {
return s.find_first_of("\r\n") == CString::npos;
}
void CHTTPSock::AddHeader(const CString& sName, const CString& sValue) {
// Reject CR/LF in either half so we never emit a malformed header or
// give a caller (e.g. a future module) a cheap response-splitting
// primitive. No in-tree caller reaches this with attacker-controlled
// bytes today; this is a defensive guard, not a fix for an existing
// exploit.
if (!IsValidHeaderField(sName) || !IsValidHeaderField(sValue)) return;
m_msHeaders[sName] = sValue;
}
+1 -1
View File
@@ -61,7 +61,7 @@ add_executable(unittest_bin EXCLUDE_FROM_ALL
"ThreadTest.cpp" "NickTest.cpp" "ClientTest.cpp" "NetworkTest.cpp"
"MessageTest.cpp" "ModulesTest.cpp" "IRCSockTest.cpp" "QueryTest.cpp"
"StringTest.cpp" "ConfigTest.cpp" "BufferTest.cpp" "UtilsTest.cpp"
"UserTest.cpp" "DebugTest.cpp")
"UserTest.cpp" "DebugTest.cpp" "HTTPSockTest.cpp")
target_link_libraries(unittest_bin PRIVATE znclib)
target_include_directories(unittest_bin PRIVATE
"${GTEST_ROOT}" "${GTEST_ROOT}/include"
+39
View File
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2004-2026 ZNC, see the NOTICE file for details.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gtest/gtest.h>
#include <znc/HTTPSock.h>
// Validation contract used by AddHeader to keep CR/LF (and therefore
// response-splitting bytes) out of the response stream (#2010).
TEST(HTTPSockTest, IsValidHeaderField) {
// Plain field names and values are accepted.
EXPECT_TRUE(CHTTPSock::IsValidHeaderField(""));
EXPECT_TRUE(CHTTPSock::IsValidHeaderField("X-Custom"));
EXPECT_TRUE(CHTTPSock::IsValidHeaderField("text/html; charset=utf-8"));
EXPECT_TRUE(CHTTPSock::IsValidHeaderField("a value with spaces and tabs\t"));
// CR or LF anywhere is rejected; both halves of a CRLF pair are
// rejected even individually.
EXPECT_FALSE(CHTTPSock::IsValidHeaderField("\r"));
EXPECT_FALSE(CHTTPSock::IsValidHeaderField("\n"));
EXPECT_FALSE(CHTTPSock::IsValidHeaderField("\r\n"));
EXPECT_FALSE(CHTTPSock::IsValidHeaderField("X\rFoo"));
EXPECT_FALSE(CHTTPSock::IsValidHeaderField("X\nFoo"));
EXPECT_FALSE(CHTTPSock::IsValidHeaderField("safe\r\nInjected: yes"));
EXPECT_FALSE(CHTTPSock::IsValidHeaderField("trailing\n"));
EXPECT_FALSE(CHTTPSock::IsValidHeaderField("\rleading"));
}