]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
ssh: improve large and fragmented banner handling
authorVictor Julien <victor@inliniac.net>
Sat, 1 Mar 2014 21:23:18 +0000 (22:23 +0100)
committerVictor Julien <victor@inliniac.net>
Mon, 3 Mar 2014 16:34:57 +0000 (17:34 +0100)
Including tests.

src/app-layer-ssh.c

index 8f85a57c507a0f9ad3dd5924eb2aa546452fb3c4..ad8d588d702040afbefe36ecc2e7519f92c467cb 100644 (file)
@@ -71,7 +71,8 @@ static int SSHParseBanner(SshState *state, SshHeader *header, const uint8_t *inp
         SCReturnInt(-1);
     }
     if (line_len > 255) {
-        SCLogDebug("Invalid version string, it should be less than 255 characters including <CR><NL>");
+        SCLogDebug("Invalid version string, it should be less than 255 "
+                "characters including <CR><NL>, input value is %u", line_len);
         SCReturnInt(-1);
     }
 
@@ -108,7 +109,8 @@ static int SSHParseBanner(SshState *state, SshHeader *header, const uint8_t *inp
     if (sw_end == NULL) {
         sw_end = BasicSearch(line_ptr, line_len, (uint8_t*)"\n", 1);
         if (sw_end == NULL) {
-            sw_end = line_ptr + line_len;
+            SCLogDebug("No EOL at the end of banner buffer");
+            SCReturnInt(-1);
         }
     }
 
@@ -329,6 +331,8 @@ static int EnoughData(uint8_t *input, uint32_t input_len)
     return FALSE;
 }
 
+#define MAX_BANNER_LEN 256
+
 static int SSHParseData(SshState *state, SshHeader *header,
                         uint8_t *input, uint32_t input_len)
 {
@@ -349,31 +353,43 @@ static int SSHParseData(SshState *state, SshHeader *header,
         } else if (banner_eol) {
             SCLogDebug("banner EOL with existing buffer");
 
-            uint32_t tocopy = 256 - header->banner_len;
+            uint32_t tocopy = MAX_BANNER_LEN - header->banner_len;
             if (tocopy > input_len)
                 tocopy = input_len;
 
+            SCLogDebug("tocopy %u", tocopy);
             memcpy(header->banner_buffer + header->banner_len, input, tocopy);
             header->banner_len += tocopy;
 
+            SCLogDebug("header->banner_len %u", header->banner_len);
             int r = SSHParseRecord(state, header,
                     header->banner_buffer, header->banner_len);
+            if (r == 0) {
+                input += tocopy;
+                input_len -= tocopy;
+                if (input_len > 0) {
+                    SCLogDebug("handling remaining data");
+                    r = SSHParseRecord(state, header, input, input_len);
+                }
+            }
             SCReturnInt(r);
 
         /* no banner EOL, so we need to buffer */
         } else if (!banner_eol) {
             if (header->banner_buffer == NULL) {
-                header->banner_buffer = SCMalloc(256);
+                header->banner_buffer = SCMalloc(MAX_BANNER_LEN);
                 if (header->banner_buffer == NULL)
                     SCReturnInt(-1);
             }
 
-            uint32_t tocopy = 256 - header->banner_len;
+            uint32_t tocopy = MAX_BANNER_LEN - header->banner_len;
             if (tocopy > input_len)
                 tocopy = input_len;
+            SCLogDebug("tocopy %u", tocopy);
 
             memcpy(header->banner_buffer + header->banner_len, input, tocopy);
             header->banner_len += tocopy;
+            SCLogDebug("header->banner_len %u", header->banner_len);
         }
 
     /* we have a banner, the rest is just records */
@@ -1961,6 +1977,293 @@ end:
     return result;
 }
 
+/** \test Really long banner handling: bannel exactly 255 */
+static int SSHParserTest19(void) {
+    int result = 0;
+    Flow f;
+    uint8_t sshbuf1[] = "SSH-";
+    uint32_t sshlen1 = sizeof(sshbuf1) - 1;
+    uint8_t sshbuf2[] = "2.0-";
+    uint32_t sshlen2 = sizeof(sshbuf2) - 1; // 8
+    uint8_t sshbuf3[] = "abcdefghijklmnopqrstuvwxyz"
+                        "abcdefghijklmnopqrstuvwxyz"//60
+                        "abcdefghijklmnopqrstuvwxyz"
+                        "abcdefghijklmnopqrstuvwxyz"//112
+                        "abcdefghijklmnopqrstuvwxyz"
+                        "abcdefghijklmnopqrstuvwxyz"//164
+                        "abcdefghijklmnopqrstuvwxyz"
+                        "abcdefghijklmnopqrstuvwxyz"//216
+                        "abcdefghijklmnopqrstuvwxyz"//242
+                        "abcdefghijkl\r";//255
+    uint32_t sshlen3 = sizeof(sshbuf3) - 1;
+
+    uint8_t sshbuf4[] = { 0x00, 0x00, 0x00, 0x03, 0x01, 21, 0x00};
+    uint32_t sshlen4 = sizeof(sshbuf4);
+    TcpSession ssn;
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+
+    memset(&f, 0, sizeof(f));
+    memset(&ssn, 0, sizeof(ssn));
+    f.protoctx = (void *)&ssn;
+
+    StreamTcpInitConfig(TRUE);
+
+    SCMutexLock(&f.m);
+    int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf1, sshlen1);
+    if (r != 0) {
+        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+        SCMutexUnlock(&f.m);
+        goto end;
+    }
+    SCMutexUnlock(&f.m);
+
+    SCMutexLock(&f.m);
+    r = AppLayerParserParse(alp_tctx, &f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf2, sshlen2);
+    if (r != 0) {
+        printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
+        SCMutexUnlock(&f.m);
+        goto end;
+    }
+    SCMutexUnlock(&f.m);
+
+    SCMutexLock(&f.m);
+    r = AppLayerParserParse(alp_tctx, &f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf3, sshlen3);
+    if (r != 0) {
+        printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
+        SCMutexUnlock(&f.m);
+        goto end;
+    }
+    SCMutexUnlock(&f.m);
+
+    SCMutexLock(&f.m);
+    r = AppLayerParserParse(alp_tctx, &f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf4, sshlen4);
+    if (r != 0) {
+        printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r);
+        SCMutexUnlock(&f.m);
+        goto end;
+    }
+    SCMutexUnlock(&f.m);
+
+    SshState *ssh_state = f.alstate;
+    if (ssh_state == NULL) {
+        printf("no ssh state: ");
+        goto end;
+    }
+
+    if (!(ssh_state->srv_hdr.flags & SSH_FLAG_VERSION_PARSED)) {
+        printf("Client version string not parsed: ");
+        goto end;
+    }
+
+    if (ssh_state->srv_hdr.software_version == NULL) {
+        printf("Client version string not parsed: ");
+        goto end;
+    }
+
+    if (ssh_state->srv_hdr.proto_version == NULL) {
+        printf("Client version string not parsed: ");
+        goto end;
+    }
+
+    char *name = SCMalloc(256);
+    if (name == NULL)
+        goto end;
+    memset(name, 0x00, 256);
+    strlcpy(name, (char *)sshbuf3, strlen((char *)sshbuf3) - 1);
+
+    if (strncmp((char*)ssh_state->srv_hdr.software_version, name, strlen(name)) != 0) {
+        printf("Client version string not parsed correctly: ");
+        goto end;
+    }
+
+    if (strncmp((char*)ssh_state->srv_hdr.proto_version, "2.0", strlen("2.0")) != 0) {
+        printf("Client version string not parsed correctly: ");
+        goto end;
+    }
+
+    if ( !(ssh_state->srv_hdr.flags & SSH_FLAG_PARSER_DONE)) {
+        printf("Didn't detect the msg code of new keys (ciphered data starts): ");
+        goto end;
+    }
+
+    result = 1;
+end:
+    if (alp_tctx != NULL)
+        AppLayerParserThreadCtxFree(alp_tctx);
+    StreamTcpFreeConfig(TRUE);
+    return result;
+}
+
+/** \test Really long banner handling: bannel exactly 255 */
+static int SSHParserTest20(void) {
+    int result = 0;
+    Flow f;
+    uint8_t sshbuf1[] = "SSH-";
+    uint32_t sshlen1 = sizeof(sshbuf1) - 1;
+    uint8_t sshbuf2[] = "2.0-";
+    uint32_t sshlen2 = sizeof(sshbuf2) - 1; // 8
+    uint8_t sshbuf3[] = "abcdefghijklmnopqrstuvwxyz"
+                        "abcdefghijklmnopqrstuvwxyz"//60
+                        "abcdefghijklmnopqrstuvwxyz"
+                        "abcdefghijklmnopqrstuvwxyz"//112
+                        "abcdefghijklmnopqrstuvwxyz"
+                        "abcdefghijklmnopqrstuvwxyz"//164
+                        "abcdefghijklmnopqrstuvwxyz"
+                        "abcdefghijklmnopqrstuvwxyz"//216
+                        "abcdefghijklmnopqrstuvwxyz"//242
+                        "abcdefghijklm\r";//256
+    uint32_t sshlen3 = sizeof(sshbuf3) - 1;
+
+    TcpSession ssn;
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+
+    memset(&f, 0, sizeof(f));
+    memset(&ssn, 0, sizeof(ssn));
+    f.protoctx = (void *)&ssn;
+
+    StreamTcpInitConfig(TRUE);
+
+    SCMutexLock(&f.m);
+    int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf1, sshlen1);
+    if (r != 0) {
+        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+        SCMutexUnlock(&f.m);
+        goto end;
+    }
+    SCMutexUnlock(&f.m);
+
+    SCMutexLock(&f.m);
+    r = AppLayerParserParse(alp_tctx, &f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf2, sshlen2);
+    if (r != 0) {
+        printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
+        SCMutexUnlock(&f.m);
+        goto end;
+    }
+    SCMutexUnlock(&f.m);
+
+    SCMutexLock(&f.m);
+    r = AppLayerParserParse(alp_tctx, &f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf3, sshlen3);
+    if (r != -1) {
+        printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
+        SCMutexUnlock(&f.m);
+        goto end;
+    }
+    SCMutexUnlock(&f.m);
+
+    result = 1;
+end:
+    if (alp_tctx != NULL)
+        AppLayerParserThreadCtxFree(alp_tctx);
+    StreamTcpFreeConfig(TRUE);
+    return result;
+}
+
+/** \test Fragmented banner handling: chunk has final part of bannel plus
+ *        a record. */
+static int SSHParserTest21(void) {
+    int result = 0;
+    Flow f;
+    uint8_t sshbuf1[] = "SSH-";
+    uint32_t sshlen1 = sizeof(sshbuf1) - 1;
+    uint8_t sshbuf2[] = "2.0-";
+    uint32_t sshlen2 = sizeof(sshbuf2) - 1; // 8
+    uint8_t sshbuf3[] = "abcdefghijklmnopqrstuvwxyz"
+                        "abcdefghijklmnopqrstuvwxyz"//60
+                        "abcdefghijklmnopqrstuvwxyz"
+                        "abcdefghijklmnopqrstuvwxyz"//112
+                        "abcdefghijklmnopqrstuvwxyz"
+                        "abcdefghijklmnopqrstuvwxyz"//164
+                        "abcdefghijklmnopqrstuvwxyz"
+                        "abcdefghijklmnopqrstuvwxyz"//216
+                        "abcdefghijklmnopqrstuvwxyz";//242
+    uint32_t sshlen3 = sizeof(sshbuf3) - 1;
+    uint8_t sshbuf4[] = {'l','i','b','s','s','h', '\r',
+                         0x00, 0x00, 0x00, 0x04, 0x01, 21, 0x00};
+    uint32_t sshlen4 = sizeof(sshbuf4) - 1;
+    TcpSession ssn;
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+
+    memset(&f, 0, sizeof(f));
+    memset(&ssn, 0, sizeof(ssn));
+    f.protoctx = (void *)&ssn;
+
+    StreamTcpInitConfig(TRUE);
+
+    SCMutexLock(&f.m);
+    int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf1, sshlen1);
+    if (r != 0) {
+        printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
+        SCMutexUnlock(&f.m);
+        goto end;
+    }
+    SCMutexUnlock(&f.m);
+
+    SCMutexLock(&f.m);
+    r = AppLayerParserParse(alp_tctx, &f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf2, sshlen2);
+    if (r != 0) {
+        printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
+        SCMutexUnlock(&f.m);
+        goto end;
+    }
+    SCMutexUnlock(&f.m);
+
+    SCMutexLock(&f.m);
+    r = AppLayerParserParse(alp_tctx, &f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf3, sshlen3);
+    if (r != 0) {
+        printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
+        SCMutexUnlock(&f.m);
+        goto end;
+    }
+    SCMutexUnlock(&f.m);
+
+    SCMutexLock(&f.m);
+    r = AppLayerParserParse(alp_tctx, &f, ALPROTO_SSH, STREAM_TOCLIENT, sshbuf4, sshlen4);
+    if (r != 0) {
+        printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r);
+        SCMutexUnlock(&f.m);
+        goto end;
+    }
+    SCMutexUnlock(&f.m);
+
+    SshState *ssh_state = f.alstate;
+    if (ssh_state == NULL) {
+        printf("no ssh state: ");
+        goto end;
+    }
+
+    if (!(ssh_state->srv_hdr.flags & SSH_FLAG_VERSION_PARSED)) {
+        printf("Client version string not parsed: ");
+        goto end;
+    }
+
+    if (ssh_state->srv_hdr.software_version == NULL) {
+        printf("Client version string not parsed: ");
+        goto end;
+    }
+
+    if (ssh_state->srv_hdr.proto_version == NULL) {
+        printf("Client version string not parsed: ");
+        goto end;
+    }
+
+    if (strncmp((char*)ssh_state->srv_hdr.proto_version, "2.0", strlen("2.0")) != 0) {
+        printf("Client version string not parsed correctly: ");
+        goto end;
+    }
+
+    if ( !(ssh_state->srv_hdr.flags & SSH_FLAG_PARSER_DONE)) {
+        printf("Didn't detect the msg code of new keys (ciphered data starts): ");
+        goto end;
+    }
+
+    result = 1;
+end:
+    if (alp_tctx != NULL)
+        AppLayerParserThreadCtxFree(alp_tctx);
+    StreamTcpFreeConfig(TRUE);
+    return result;
+}
+
 #endif /* UNITTESTS */
 
 void SSHParserRegisterTests(void) {
@@ -1983,6 +2286,9 @@ void SSHParserRegisterTests(void) {
     UtRegisterTest("SSHParserTest16", SSHParserTest16, 1);
     UtRegisterTest("SSHParserTest17", SSHParserTest17, 1);
     UtRegisterTest("SSHParserTest18", SSHParserTest18, 1);
+    UtRegisterTest("SSHParserTest19", SSHParserTest19, 1);
+    UtRegisterTest("SSHParserTest20", SSHParserTest20, 1);
+    UtRegisterTest("SSHParserTest21", SSHParserTest21, 1);
 #endif /* UNITTESTS */
 }