[Helix-server-dev] CR: 165604 - server vulnerable to infinite http request
atin atin at real.comSynopsis
========
sending a http request with an infinite number of headers (or very large
headers) caused the server to run out of memory.
Branches: SERVER_11_1, SERVER_CURRENT
Suggested Reviewer: dcollins
Description
===========
the http parser does not check for bounds on the number of http headers that
can be present in a http request, as a result of this an attacker can send a
valid http message (GET/POST/PUT/HEAD) followed by a very large number of http
headers to the server and cause it to run out of memory. the server basically
allocates memory for each incoming header and thus runs out of memory.
the fix was to do a limit check on the number of headers that are being
parsed and return a NULL HTTPMessage pointer and a boolean indicating that the
message was too large. this involved modifying the HTTPParser::parse() command
to take 3 arguments instead of its existing 2. when bMsgTooLarge is TRUE from
inside HTTPDemux we just close the connection.
another check to limit the size of the headers has also been put in, because
currently if the server receives a huge http header, it tries to allocate the
buffer which in isolation with a huge buffer or in conjunction with the above
method can be used to run the server out of memory.
it would b helpful to log an error/warning message in the log file identifying
the source address of the connection, but that is a non-trivial change and
given the time constraints it is not going to b done now.
the most brain dead part of this parsing is that each time the server receives
an incomplete message, it parses the message, but gets returned a NULL because
the message is incomplete. then as it receives more headers, it re-parses the
entire message even tho there were sufficient headers in the previous message
to create a valid HTTPMessage object. this is not being fixed in the cr.
Files Affected
==============
protocol/common/util/hxcloakedsocket.cpp
protocol/common/util/hxcloaksockv2.cpp
protocol/http/httpmsg.cpp
protocol/http/httppars.cpp
protocol/http/pub/httppars.h
server/protocol/http/http_demux.cpp
server/protocol/http/servhttppost.cpp
server/protocol/http/pub/http_demux.h
Testing Performed
=================
Unit Tests:
N/A
Integration Tests:
(1) ran jsimpson's endless-http-headers-jrs.pl script from the pr attachment.
$ endless-http-headers-jrs.pl joust:19080
(2) ran the script with huge headers.
$ endless-http-headers-jrs.pl joust:19080 "y" 100000 100
Leak Tests:
(1) against jsimpson's script.
Performance Tests:
N/A
Platforms Tested: linux-rhel4-i686
Build verified: linux-rhel4-i686
QA Hints
========
(1) Repeat Integration tests (1) and (2) as described above.
-------------- next part --------------
Index: protocol/common/util/hxcloakedsocket.cpp
===================================================================
RCS file: /cvsroot/protocol/common/util/hxcloakedsocket.cpp,v
retrieving revision 1.3
diff -u -w -r1.3 hxcloakedsocket.cpp
--- protocol/common/util/hxcloakedsocket.cpp 15 Nov 2004 18:40:10 -0000 1.3
+++ protocol/common/util/hxcloakedsocket.cpp 12 May 2006 00:42:18 -0000
@@ -1986,7 +1986,12 @@
{
pBufferContents = (char*)(const char*)m_pHTTPHeaderBuffer->GetBuffer();
nMsgLen = m_pHTTPHeaderBuffer->GetSize();
+#if !defined HELIX_FEATURE_SERVER
pMessage = (HTTPResponseMessage*)Parser.parse( pBufferContents, nMsgLen );
+#else
+ BOOL bMsgTooLarge = FALSE;
+ pMessage = (HTTPResponseMessage*)Parser.parse( pBufferContents, nMsgLen, bMsgTooLarge );
+#endif /* !HELIX_FEATURE_SERVER */
hr = HXR_FAIL;
if( pMessage && HTTPMessage::T_UNKNOWN != pMessage->tag())
@@ -2130,7 +2135,12 @@
pBufferContents = (char*)(const char*)pBuf->GetBuffer();
nMsgLen = pBuf->GetSize();
+#if !defined HELIX_FEATURE_SERVER
pMessage = (HTTPResponseMessage*)Parser.parse( pBufferContents, nMsgLen );
+#else
+ BOOL bMsgTooLarge = FALSE;
+ pMessage = (HTTPResponseMessage*)Parser.parse( pBufferContents, nMsgLen, bMsgTooLarge );
+#endif /* !HELIX_FEATURE_SERVER */
ulHTTPStatus = atoi(pMessage->errorCode());
HX_DELETE(pMessage);
@@ -3012,7 +3022,12 @@
HTTPParser Parser;
char* pBufferContents = (char*)(const char*)pBuf->GetBuffer();
ULONG32 nMsgLen = pBuf->GetSize();
+#if !defined HELIX_FEATURE_SERVER
HTTPResponseMessage* pMessage = (HTTPResponseMessage*)Parser.parse( pBufferContents, nMsgLen );
+#else
+ BOOL bMsgTooLarge = FALSE;
+ HTTPResponseMessage* pMessage = (HTTPResponseMessage*)Parser.parse( pBufferContents, nMsgLen, bMsgTooLarge );
+#endif /* !HELIX_FEATURE_SERVER */
//
// ignore non-HTTP responses; thoseh will be processed by the
Index: protocol/common/util/hxcloaksockv2.cpp
===================================================================
RCS file: /cvsroot/protocol/common/util/hxcloaksockv2.cpp,v
retrieving revision 1.4
diff -u -w -r1.4 hxcloaksockv2.cpp
--- protocol/common/util/hxcloaksockv2.cpp 15 Nov 2004 18:40:10 -0000 1.4
+++ protocol/common/util/hxcloaksockv2.cpp 12 May 2006 00:42:18 -0000
@@ -1206,7 +1206,12 @@
{
res = HXR_FAIL;
ulLength = pBuf->GetSize();
+#if !defined HELIX_FEATURE_SERVER
pMess = (HTTPResponseMessage*)parser.parse( (const char*)pBuf->GetBuffer(), ulLength);
+#else
+ BOOL bMsgTooLarge = FALSE;
+ pMess = (HTTPResponseMessage*)parser.parse( (const char*)pBuf->GetBuffer(), ulLength, bMsgTooLarge);
+#endif /* !HELIX_FEATURE_SERVER */
HX_ASSERT(pMess);
//Verify we have HTTP response message.
Index: protocol/http/httpmsg.cpp
===================================================================
RCS file: /cvsroot/protocol/http/httpmsg.cpp,v
retrieving revision 1.11
diff -u -w -r1.11 httpmsg.cpp
--- protocol/http/httpmsg.cpp 9 Jul 2004 18:41:32 -0000 1.11
+++ protocol/http/httpmsg.cpp 12 May 2006 00:42:18 -0000
@@ -186,6 +186,7 @@
}
HTTPMessage::HTTPMessage()
+ : m_isCloakedMsg(FALSE)
{
setVersion(MAJ_VERSION, MIN_VERSION);
}
Index: protocol/http/httppars.cpp
===================================================================
RCS file: /cvsroot/protocol/http/httppars.cpp,v
retrieving revision 1.5
diff -u -w -r1.5 httppars.cpp
--- protocol/http/httppars.cpp 27 Sep 2004 20:14:53 -0000 1.5
+++ protocol/http/httppars.cpp 12 May 2006 00:42:18 -0000
@@ -71,6 +71,9 @@
HTTPParser::HTTPParser()
{
+#if defined HELIX_FEATURE_SERVER
+ m_bMessageTooLarge = FALSE;
+#endif /* HELIX_FEATURE_SERVER */
}
HTTPParser::~HTTPParser()
@@ -467,7 +470,16 @@
{
tok = scanner.nextToken("\n");
if (tok.hasValue())
+ {
+#if defined HELIX_FEATURE_SERVER
+ if (tok.value().GetLength() > MAX_HTTP_HEADER_SIZE)
+ {
+ m_bMessageTooLarge = TRUE;
+ return 0;
+ }
+#endif /* HELIX_FEATURE_SERVER */
m_msglines.AddTail((void*)(new CHXString((const char*)tok.value())));
+ }
if(tok.lastChar() == MIMEToken::T_EOL)
{
if(gotEOL && !tok.hasValue())
@@ -496,6 +508,8 @@
}
}
+#if !defined HELIX_FEATURE_SERVER
+
/*
* Parse message pointed to by pMsg with length nMsgLen.
* If not a complete message, return 0, otherwise return
@@ -539,18 +553,83 @@
nMsgLen = 0;
return 0;
}
+ pHTTPMsg = parseRequest();
+ if (!pHTTPMsg || pHTTPMsg->majorVersion() > 0)
+ {
+ nMsgLen = 0;
+ HX_DELETE(pHTTPMsg);
+ }
+ }
+ return pHTTPMsg;
+}
+
+#else /* !HELIX_FEATURE_SERVER */
+/*
+ * Parse message pointed to by pMsg with length nMsgLen.
+ * If not a complete message, return 0, otherwise return
+ * HTTPMessage pointer.
+ */
+HTTPMessage*
+HTTPParser::parse(const char* pMsg, UINT32& nMsgLen, BOOL& bMessageTooLarge)
+{
+ HTTPMessage* pHTTPMsg = 0;
+ clearMessageLines();
+ int msgOffset = scanMessageHeader(pMsg, nMsgLen);
+ if (m_bMessageTooLarge)
+ {
+ bMessageTooLarge = TRUE;
+ return 0;
+ }
+ else if (m_msglines.GetCount() > MAX_HTTP_HEADER_COUNT)
+ {
+ m_bMessageTooLarge = TRUE;
+ bMessageTooLarge = TRUE;
+ return 0;
+ }
+ if(msgOffset > 0)
+ {
+ if(m_msglines.GetCount() == 0)
+ {
+ nMsgLen = 0;
+ return 0;
+ }
CHXString* str = (CHXString*)m_msglines.GetHead();
+ if(strncasecmp((*str), "HTTP/", 5) == 0)
+ {
+ pHTTPMsg = parseResponse();
+ }
+ else
+ {
+ pHTTPMsg = parseRequest();
+ }
+
+ if(pHTTPMsg)
+ {
+ nMsgLen = msgOffset;
+ }
+ }
+ else
+ {
+ //nMsgLen = 0;
+
+ /* Might be a HTTP/0.9 request. No extra blank line. Drat. */
+ if(m_msglines.GetCount() == 0)
+ {
+ nMsgLen = 0;
+ return 0;
+ }
pHTTPMsg = parseRequest();
if(!pHTTPMsg || pHTTPMsg->majorVersion() > 0)
{
nMsgLen = 0;
- delete pHTTPMsg;
- pHTTPMsg = 0;
+ HX_DELETE(pHTTPMsg);
}
}
return pHTTPMsg;
}
+#endif /* !HELIX_FEATURE_SERVER */
+
/*
* Delete contents of m_msglines
*/
Index: protocol/http/pub/httppars.h
===================================================================
RCS file: /cvsroot/protocol/http/pub/httppars.h,v
retrieving revision 1.2
diff -u -w -r1.2 httppars.h
--- protocol/http/pub/httppars.h 9 Jul 2004 18:41:28 -0000 1.2
+++ protocol/http/pub/httppars.h 12 May 2006 00:42:18 -0000
@@ -64,13 +64,25 @@
#include "hxslist.h"
#include "httpmsg.h"
+#ifdef HELIX_FEATURE_SERVER
+// the limits are being put to prevent a DOS attack with infinite HTTP
+// requests (infinite headers within a HTTP message)
+#define MAX_HTTP_HEADER_COUNT 256
+#define MAX_HTTP_HEADER_SIZE 65536
+#endif /* HELIX_FEATURE_SERVER */
+
class HTTPParser
{
public:
HTTPParser();
virtual ~HTTPParser();
+#if !defined HELIX_FEATURE_SERVER
virtual HTTPMessage* parse(const char* pMsg, UINT32& nMsgLen);
+#else
+ virtual HTTPMessage* parse(const char* pMsg, UINT32& nMsgLen,
+ BOOL& bMessageTooLarge);
+#endif /* !HELIX_FEATURE_SERVER */
protected:
MIMEHeader* parseHeader(CHXString& str);
@@ -92,6 +104,7 @@
void clearMessageLines();
CHXSimpleList m_msglines;
+ BOOL m_bMessageTooLarge;
};
#endif /* _HTTPPARS_H_ */
Index: server/protocol/http/http_demux.cpp
===================================================================
RCS file: /cvsroot/server/protocol/http/http_demux.cpp,v
retrieving revision 1.10.2.7.16.4
diff -u -w -r1.10.2.7.16.4 http_demux.cpp
--- server/protocol/http/http_demux.cpp 7 Apr 2006 20:28:35 -0000 1.10.2.7.16.4
+++ server/protocol/http/http_demux.cpp 12 May 2006 00:42:23 -0000
@@ -86,7 +86,8 @@
m_pResponse(NULL),
m_pSavedMessage(NULL),
m_ReadState(DEMUX_READ_MSG),
- m_bClosed(FALSE)
+ m_bClosed(FALSE),
+ m_ulMsgLen(0)
{
m_pParser = new HTTPParser;
}
@@ -337,14 +338,23 @@
IHXBuffer* pNewBuf = NULL;
IHXBuffer* pContentBuf = NULL;
BOOL bFirstRun = FALSE;
+ BOOL bMsgTooLarge = FALSE;
if (m_ReadState == DEMUX_READ_MSG)
{
HTTPMessage* pMsg = NULL;
uDataUsed = uDataLen;
- pMsg = m_pParser->parse((const char*)pData, uDataUsed);
+ m_ulMsgLen += uDataLen;
+ pMsg = m_pParser->parse((const char*)pData, uDataUsed, bMsgTooLarge);
if (pMsg == NULL)
{
+ if (bMsgTooLarge)
+ {
+ if (pBuf)
+ pBuf->Release();
+ Close(HXR_FAIL);
+ return;
+ }
break;
}
@@ -407,6 +417,8 @@
if (iLen <= 0 || iLen > 0xffff)
{
DPRINTF(D_ERROR, ("HTTP: Bad content length %d\n", iLen));
+ if (pBuf)
+ pBuf->Release();
Close(HXR_FAIL);
return;
}
Index: server/protocol/http/servhttppost.cpp
===================================================================
RCS file: /cvsroot/server/protocol/http/servhttppost.cpp,v
retrieving revision 1.1
diff -u -w -r1.1 servhttppost.cpp
--- server/protocol/http/servhttppost.cpp 16 Nov 2004 03:02:43 -0000 1.1
+++ server/protocol/http/servhttppost.cpp 12 May 2006 00:42:23 -0000
@@ -1413,13 +1413,14 @@
// Force to new length (including possible bin data at end)
m_strResultHeader.ReleaseBuffer(ulLength + size);
+ BOOL bMsgTooLarge = FALSE;
HTTPParser Parser;
HTTPResponseMessage* pMessage = 0;
UINT32 ulHeaderLength = m_strResultHeader.GetLength();
// Parse headers from message
- pMessage = (HTTPResponseMessage*)Parser.parse((const char*)m_strResultHeader, ulHeaderLength);
+ pMessage = (HTTPResponseMessage*)Parser.parse((const char*)m_strResultHeader, ulHeaderLength, bMsgTooLarge);
if (pMessage && pMessage->tag() == HTTPMessage::T_RESP)
{
// We now have the entire header!
Index: server/protocol/http/pub/http_demux.h
===================================================================
RCS file: /cvsroot/server/protocol/http/pub/http_demux.h,v
retrieving revision 1.5.2.2.18.1
diff -u -w -r1.5.2.2.18.1 http_demux.h
--- server/protocol/http/pub/http_demux.h 7 Apr 2006 20:28:36 -0000 1.5.2.2.18.1
+++ server/protocol/http/pub/http_demux.h 12 May 2006 00:42:23 -0000
@@ -171,6 +171,7 @@
HTTPMessage* m_pSavedMessage;
DEMUX_READ_STATE m_ReadState;
BOOL m_bClosed;
+ UINT32 m_ulMsgLen;
};
#endif /* _HTTP_DEMUX_H_ */