]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
file extract: improve multipart parsing and set events on some error conditions.
authorVictor Julien <victor@inliniac.net>
Fri, 2 Mar 2012 07:36:44 +0000 (08:36 +0100)
committerVictor Julien <victor@inliniac.net>
Fri, 2 Mar 2012 08:11:45 +0000 (09:11 +0100)
rules/http-events.rules
src/app-layer-htp-file.c
src/app-layer-htp.c
src/app-layer-htp.h

index 01054bdf13822c098af98bfa495712825882552c..a23997c25c8a1948f9e91f7edf7e6aa1a78b501b 100644 (file)
@@ -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
 
index c50ba19429db8dc607fdaf0ac0d819582dff441f..30f8ac0e987239517a98e87c2893901497c5b3f2 100644 (file)
@@ -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 */
 }
index 54a81d2c992b186cd798dc505799fca872b5cd87..35e802fb78ab2522983094f0a18a0ae73ac7248b 100644 (file)
@@ -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);
index ac27f9034df648b0e945d37f188a6b8074ba6490..89937aa23215bcd5a92a0ad843c96486fd3e5d65 100644 (file)
@@ -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 */