diff --git a/rules/http-events.rules b/rules/http-events.rules index 01054bdf13..a23997c25c 100644 --- a/rules/http-events.rules +++ b/rules/http-events.rules @@ -23,10 +23,17 @@ alert http any any -> any any (msg:"SURICATA HTTP invalid authority port"; flow: alert http any any -> any any (msg:"SURICATA HTTP request header invalid"; flow:established,to_server; app-layer-event:http.request_header_invalid; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221013; rev:1;) alert http any any -> any any (msg:"SURICATA HTTP response header invalid"; flow:established,to_client; app-layer-event:http.response_header_invalid; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221021; rev:1;) alert http any any -> any any (msg:"SURICATA HTTP missing Host header"; flow:established,to_server; app-layer-event:http.missing_host_header; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221014; rev:1;) -alert http any any -> any any (msg:"SURICATA HTTP Host header ambiguous"; flow:established,to_server; app-layer-event:http.host_header_ambiguous; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221015; rev:1;) +# If hostname is both part of URL and Host header. Not very useful as this matches on HTTP Proxy traffic. +#alert http any any -> any any (msg:"SURICATA HTTP Host header ambiguous"; flow:established,to_server; app-layer-event:http.host_header_ambiguous; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221015; rev:1;) alert http any any -> any any (msg:"SURICATA HTTP invalid request field folding"; flow:established,to_server; app-layer-event:http.invalid_request_field_folding; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221016; rev:1;) alert http any any -> any any (msg:"SURICATA HTTP invalid response field folding"; flow:established,to_client; app-layer-event:http.invalid_response_field_folding; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221017; rev:1;) alert http any any -> any any (msg:"SURICATA HTTP request field too long"; flow:established,to_server; app-layer-event:http.request_field_too_long; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221018; rev:1;) alert http any any -> any any (msg:"SURICATA HTTP response field too long"; flow:established,to_client; app-layer-event:http.response_field_too_long; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221019; rev:1;) -# next sid 2221022 +# Multipart parser detected generic error. +alert http any any -> any any (msg:"SURICATA HTTP multipart generic error"; flow:established,to_server; app-layer-event:http.multipart_generic_error; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221022; rev:1;) +# Multipart header claiming a file to present, but no actual filedata available. +alert http any any -> any any (msg:"SURICATA HTTP multipart no filedata"; flow:established,to_server; app-layer-event:http.multipart_no_filedata; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221023; rev:1;) +# Multipart header invalid. +alert http any any -> any any (msg:"SURICATA HTTP multipart invalid header"; flow:established,to_server; app-layer-event:http.multipart_invalid_header; flowint:http.anomaly.count,+,1; classtype:protocol-command-decode; sid:2221024; rev:1;) +# next sid 2221025 diff --git a/src/app-layer-htp-file.c b/src/app-layer-htp-file.c index c50ba19429..30f8ac0e98 100644 --- a/src/app-layer-htp-file.c +++ b/src/app-layer-htp-file.c @@ -1010,6 +1010,175 @@ end: return result; } +static int HTPFileParserTest08(void) { + int result = 0; + Flow *f = NULL; + uint8_t httpbuf1[] = "POST /upload.cgi HTTP/1.1\r\n" + "Host: www.server.lan\r\n" + "Content-Type: multipart/form-data; boundary=---------------------------277531038314945\r\n" + "Content-Length: 215\r\n" + "\r\n" + "-----------------------------277531038314945\r\n" + "Content-Disposition: form-data; name=\"uploadfile_0\"; filename=\"somepicture1.jpg\"\r\n" + "Content-Type: image/jpeg\r\n"; + + uint32_t httplen1 = sizeof(httpbuf1) - 1; /* minus the \0 */ + uint8_t httpbuf2[] = "filecontent\r\n\r\n" + "-----------------------------277531038314945--"; + uint32_t httplen2 = sizeof(httpbuf2) - 1; /* minus the \0 */ + + TcpSession ssn; + HtpState *http_state = NULL; + memset(&ssn, 0, sizeof(ssn)); + + f = UTHBuildFlow(AF_INET, "1.2.3.4", "1.2.3.5", 1024, 80); + if (f == NULL) + goto end; + f->protoctx = &ssn; + + StreamTcpInitConfig(TRUE); + + SCLogDebug("\n>>>> processing chunk 1 <<<<\n"); + int r = AppLayerParse(NULL, f, ALPROTO_HTTP, STREAM_TOSERVER|STREAM_START, httpbuf1, httplen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + result = 0; + goto end; + } + + SCLogDebug("\n>>>> processing chunk 2 size %u <<<<\n", httplen2); + r = AppLayerParse(NULL, f, ALPROTO_HTTP, STREAM_TOSERVER|STREAM_EOF, httpbuf2, httplen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + result = 0; + goto end; + } + + http_state = f->alstate; + if (http_state == NULL) { + printf("no http state: "); + result = 0; + goto end; + } + + AppLayerDecoderEvents *decoder_events = AppLayerGetDecoderEventsForFlow(f); + if (decoder_events == NULL) { + printf("no app events: "); + goto end; + } + + if (decoder_events->cnt != 2) { + printf("expected 2 events: "); + goto end; + } + + result = 1; +end: + StreamTcpFreeConfig(TRUE); + if (http_state != NULL) + HTPStateFree(http_state); + UTHFreeFlow(f); + return result; +} + +/** \test invalid header: Somereallylongheaderstr: has no value */ +static int HTPFileParserTest09(void) { + int result = 0; + Flow *f = NULL; + uint8_t httpbuf1[] = "POST /upload.cgi HTTP/1.1\r\n" + "Host: www.server.lan\r\n" + "Content-Type: multipart/form-data; boundary=---------------------------277531038314945\r\n" + "Content-Length: 337\r\n" + "\r\n"; + uint32_t httplen1 = sizeof(httpbuf1) - 1; /* minus the \0 */ + + uint8_t httpbuf2[] = "-----------------------------277531038314945\r\n" + "Content-Disposition: form-data; name=\"email\"\r\n" + "\r\n" + "someaddress@somedomain.lan\r\n"; + uint32_t httplen2 = sizeof(httpbuf2) - 1; /* minus the \0 */ + + uint8_t httpbuf3[] = "-----------------------------277531038314945\r\n" + "Content-Disposition: form-data; name=\"uploadfile_0\"; filename=\"somepicture1.jpg\"\r\n" + "Somereallylongheaderstr:\r\n" + "\r\n"; + uint32_t httplen3 = sizeof(httpbuf3) - 1; /* minus the \0 */ + + uint8_t httpbuf4[] = "filecontent\r\n" + "-----------------------------277531038314945--"; + uint32_t httplen4 = sizeof(httpbuf4) - 1; /* minus the \0 */ + + TcpSession ssn; + HtpState *http_state = NULL; + + memset(&ssn, 0, sizeof(ssn)); + + f = UTHBuildFlow(AF_INET, "1.2.3.4", "1.2.3.5", 1024, 80); + if (f == NULL) + goto end; + f->protoctx = &ssn; + + StreamTcpInitConfig(TRUE); + + SCLogDebug("\n>>>> processing chunk 1 <<<<\n"); + int r = AppLayerParse(NULL, f, ALPROTO_HTTP, STREAM_TOSERVER|STREAM_START, httpbuf1, httplen1); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + result = 0; + goto end; + } + + SCLogDebug("\n>>>> processing chunk 2 size %u <<<<\n", httplen2); + r = AppLayerParse(NULL, f, ALPROTO_HTTP, STREAM_TOSERVER|STREAM_EOF, httpbuf2, httplen2); + if (r != 0) { + printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); + result = 0; + goto end; + } + + SCLogDebug("\n>>>> processing chunk 3 size %u <<<<\n", httplen3); + r = AppLayerParse(NULL, f, ALPROTO_HTTP, STREAM_TOSERVER|STREAM_EOF, httpbuf3, httplen3); + if (r != 0) { + printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); + result = 0; + goto end; + } + + SCLogDebug("\n>>>> processing chunk 4 size %u <<<<\n", httplen4); + r = AppLayerParse(NULL, f, ALPROTO_HTTP, STREAM_TOSERVER|STREAM_EOF, httpbuf4, httplen4); + if (r != 0) { + printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r); + result = 0; + goto end; + } + + http_state = f->alstate; + if (http_state == NULL) { + printf("no http state: "); + result = 0; + goto end; + } + + AppLayerDecoderEvents *decoder_events = AppLayerGetDecoderEventsForFlow(f); + if (decoder_events == NULL) { + printf("no app events: "); + goto end; + } + + if (decoder_events->cnt != 1) { + printf("expected 1 event: "); + goto end; + } + + result = 1; +end: + StreamTcpFreeConfig(TRUE); + if (http_state != NULL) + HTPStateFree(http_state); + UTHFreeFlow(f); + return result; +} + #endif /* UNITTESTS */ void HTPFileParserRegisterTests(void) { @@ -1021,5 +1190,7 @@ void HTPFileParserRegisterTests(void) { UtRegisterTest("HTPFileParserTest05", HTPFileParserTest05, 1); UtRegisterTest("HTPFileParserTest06", HTPFileParserTest06, 1); UtRegisterTest("HTPFileParserTest07", HTPFileParserTest07, 1); + UtRegisterTest("HTPFileParserTest08", HTPFileParserTest08, 1); + UtRegisterTest("HTPFileParserTest09", HTPFileParserTest09, 1); #endif /* UNITTESTS */ } diff --git a/src/app-layer-htp.c b/src/app-layer-htp.c index 54a81d2c99..35e802fb78 100644 --- a/src/app-layer-htp.c +++ b/src/app-layer-htp.c @@ -150,6 +150,15 @@ SCEnumCharMap http_decoder_event_table[ ] = { HTTP_DECODER_EVENT_REQUEST_FIELD_TOO_LONG}, { "RESPONSE_FIELD_TOO_LONG", HTTP_DECODER_EVENT_RESPONSE_FIELD_TOO_LONG}, + + /* suricata warnings/errors */ + { "MULTIPART_GENERIC_ERROR", + HTTP_DECODER_EVENT_MULTIPART_GENERIC_ERROR}, + { "MULTIPART_NO_FILEDATA", + HTTP_DECODER_EVENT_MULTIPART_NO_FILEDATA}, + { "MULTIPART_INVALID_HEADER", + HTTP_DECODER_EVENT_MULTIPART_INVALID_HEADER}, + { NULL, -1 }, }; @@ -1098,7 +1107,8 @@ error: #define C_T_HDR "content-type:" #define C_T_HDR_LEN 13 -static void HtpRequestBodyMultipartParseHeader(uint8_t *header, uint32_t header_len, +static void HtpRequestBodyMultipartParseHeader(HtpState *hstate, + uint8_t *header, uint32_t header_len, uint8_t **filename, uint16_t *filename_len, uint8_t **filetype, uint16_t *filetype_len) { @@ -1124,6 +1134,18 @@ static void HtpRequestBodyMultipartParseHeader(uint8_t *header, uint32_t header_ line_len = next_line - header; } + uint8_t *sc = (uint8_t *)memchr(line, ':', line_len); + if (sc == NULL) { + AppLayerDecoderEventsSetEvent(hstate->f, + HTTP_DECODER_EVENT_MULTIPART_INVALID_HEADER); + } else { + /* if the : we found is the final char, it means we have + * no value */ + if (line_len > 0 && sc == &line[line_len - 1]) + AppLayerDecoderEventsSetEvent(hstate->f, + HTTP_DECODER_EVENT_MULTIPART_INVALID_HEADER); + } + #ifdef PRINT printf("LINE START: \n"); PrintRawDataFp(stdout, line, line_len); @@ -1274,7 +1296,11 @@ int HtpRequestBodyHandleMultipart(HtpState *hstate, HtpTxUserData *htud, flags = FILE_TRUNCATED; } - BUG_ON(filedata_len > chunks_buffer_len); + if (filedata_len > chunks_buffer_len) { + AppLayerDecoderEventsSetEvent(hstate->f, + HTTP_DECODER_EVENT_MULTIPART_GENERIC_ERROR); + goto end; + } #ifdef PRINT printf("FILEDATA (final chunk) START: \n"); PrintRawDataFp(stdout, filedata, filedata_len); @@ -1343,8 +1369,8 @@ int HtpRequestBodyHandleMultipart(HtpState *hstate, HtpTxUserData *htud, header = header_start + (expected_boundary_len + 2); // + for 0d 0a } - HtpRequestBodyMultipartParseHeader(header, header_len, &filename, - &filename_len, &filetype, &filetype_len); + HtpRequestBodyMultipartParseHeader(hstate, header, header_len, + &filename, &filename_len, &filetype, &filetype_len); if (filename != NULL) { uint8_t *filedata = NULL; @@ -1361,7 +1387,18 @@ int HtpRequestBodyHandleMultipart(HtpState *hstate, HtpTxUserData *htud, /* everything until the final boundary is the file */ if (form_end != NULL) { filedata = header_end + 4; + if (form_end == filedata) { + AppLayerDecoderEventsSetEvent(hstate->f, + HTTP_DECODER_EVENT_MULTIPART_NO_FILEDATA); + goto end; + } else if (form_end < filedata) { + AppLayerDecoderEventsSetEvent(hstate->f, + HTTP_DECODER_EVENT_MULTIPART_GENERIC_ERROR); + goto end; + } + filedata_len = form_end - (header_end + 4 + 2); + SCLogDebug("filedata_len %"PRIuMAX, (uintmax_t)filedata_len); /* or is it? */ uint8_t *header_next = Bs2bmSearch(filedata, filedata_len, @@ -1370,8 +1407,12 @@ int HtpRequestBodyHandleMultipart(HtpState *hstate, HtpTxUserData *htud, filedata_len -= (form_end - header_next); } + if (filedata_len > chunks_buffer_len) { + AppLayerDecoderEventsSetEvent(hstate->f, + HTTP_DECODER_EVENT_MULTIPART_GENERIC_ERROR); + goto end; + } SCLogDebug("filedata_len %"PRIuMAX, (uintmax_t)filedata_len); - #ifdef PRINT printf("FILEDATA START: \n"); PrintRawDataFp(stdout, filedata, filedata_len); @@ -1400,6 +1441,12 @@ int HtpRequestBodyHandleMultipart(HtpState *hstate, HtpTxUserData *htud, filedata_len = chunks_buffer_len - (filedata - chunks_buffer); SCLogDebug("filedata_len %u (chunks_buffer_len %u)", filedata_len, chunks_buffer_len); + if (filedata_len > chunks_buffer_len) { + AppLayerDecoderEventsSetEvent(hstate->f, + HTTP_DECODER_EVENT_MULTIPART_GENERIC_ERROR); + goto end; + } + #ifdef PRINT printf("FILEDATA START: \n"); PrintRawDataFp(stdout, filedata, filedata_len); diff --git a/src/app-layer-htp.h b/src/app-layer-htp.h index ac27f9034d..89937aa232 100644 --- a/src/app-layer-htp.h +++ b/src/app-layer-htp.h @@ -83,6 +83,7 @@ enum { }; enum { + /* libhtp errors/warnings */ HTTP_DECODER_EVENT_UNKNOWN_ERROR, HTTP_DECODER_EVENT_GZIP_DECOMPRESSION_FAILED, HTTP_DECODER_EVENT_REQUEST_FIELD_MISSING_COLON, @@ -105,6 +106,11 @@ enum { HTTP_DECODER_EVENT_INVALID_RESPONSE_FIELD_FOLDING, HTTP_DECODER_EVENT_REQUEST_FIELD_TOO_LONG, HTTP_DECODER_EVENT_RESPONSE_FIELD_TOO_LONG, + + /* suricata errors/warnings */ + HTTP_DECODER_EVENT_MULTIPART_GENERIC_ERROR, + HTTP_DECODER_EVENT_MULTIPART_NO_FILEDATA, + HTTP_DECODER_EVENT_MULTIPART_INVALID_HEADER, }; #define HTP_PCRE_NONE 0x00 /**< No pcre executed yet */