From b425d6d92dc83c397d49f2b723f5db393f23c485 Mon Sep 17 00:00:00 2001 From: Robert Graham <robert_david_graham@yahoo.com> Date: Wed, 7 Mar 2018 23:51:00 -0500 Subject: [PATCH] memcached --- src/masscan-app.c | 18 +- src/masscan-app.h | 1 + src/proto-banner1.c | 12 + src/proto-banner1.h | 7 + src/proto-memcached.c | 430 +++++++++++++++++++++++++++++++++++ src/proto-memcached.h | 28 +++ src/proto-udp.c | 6 + src/templ-payloads.c | 5 +- vs10/masscan.vcxproj | 2 + vs10/masscan.vcxproj.filters | 6 + 10 files changed, 506 insertions(+), 9 deletions(-) create mode 100644 src/proto-memcached.c create mode 100644 src/proto-memcached.h diff --git a/src/masscan-app.c b/src/masscan-app.c index edd86d8..a7d88a4 100644 --- a/src/masscan-app.c +++ b/src/masscan-app.c @@ -34,6 +34,7 @@ masscan_app_to_string(enum ApplicationProtocol proto) case PROTO_TICKETBLEED: return "ticketbleed"; case PROTO_VNC_RFB: return "vnc"; case PROTO_SAFE: return "safe"; + case PROTO_MEMCACHED: return "memcached"; default: sprintf_s(tmp, sizeof(tmp), "(%u)", proto); @@ -63,15 +64,16 @@ masscan_string_to_app(const char *str) {"pop", PROTO_POP3}, {"imap", PROTO_IMAP4}, {"x509", PROTO_X509_CERT}, - {"zeroaccess", PROTO_UDP_ZEROACCESS}, - {"title", PROTO_HTML_TITLE}, - {"html", PROTO_HTML_FULL}, - {"ntp", PROTO_NTP}, - {"vuln", PROTO_VULN}, - {"heartbleed", PROTO_HEARTBLEED}, + {"zeroaccess", PROTO_UDP_ZEROACCESS}, + {"title", PROTO_HTML_TITLE}, + {"html", PROTO_HTML_FULL}, + {"ntp", PROTO_NTP}, + {"vuln", PROTO_VULN}, + {"heartbleed", PROTO_HEARTBLEED}, {"ticketbleed", PROTO_TICKETBLEED}, - {"vnc", PROTO_VNC_RFB}, - {"safe", PROTO_SAFE}, + {"vnc", PROTO_VNC_RFB}, + {"safe", PROTO_SAFE}, + {"memcached", PROTO_MEMCACHED}, {0,0} }; size_t i; diff --git a/src/masscan-app.h b/src/masscan-app.h index 07cc863..854ed66 100644 --- a/src/masscan-app.h +++ b/src/masscan-app.h @@ -29,6 +29,7 @@ enum ApplicationProtocol { PROTO_TICKETBLEED, PROTO_VNC_RFB, PROTO_SAFE, + PROTO_MEMCACHED, }; const char * diff --git a/src/proto-banner1.c b/src/proto-banner1.c index b2e2e5c..6257765 100644 --- a/src/proto-banner1.c +++ b/src/proto-banner1.c @@ -14,6 +14,7 @@ #include "proto-imap4.h" #include "proto-pop3.h" #include "proto-vnc.h" +#include "proto-memcached.h" #include "masscan-app.h" #include <ctype.h> #include <stdlib.h> @@ -49,6 +50,7 @@ struct Patterns patterns[] = { {"RFB 004.000\n", 12, PROTO_VNC_RFB, SMACK_ANCHOR_BEGIN, 8}, /* Intel AMT KVM */ {"RFB 004.001\n", 12, PROTO_VNC_RFB, SMACK_ANCHOR_BEGIN, 8}, /* RealVNC 4.6 */ {"RFB 004.002\n", 12, PROTO_VNC_RFB, SMACK_ANCHOR_BEGIN, 8}, + {"STAT pid ", 9, PROTO_MEMCACHED,SMACK_ANCHOR_BEGIN}, /* memcached stat response */ {0,0} }; @@ -203,6 +205,14 @@ banner1_parse( banout, more); break; + case PROTO_MEMCACHED: + banner_memcached.parse( banner1, + banner1->http_fields, + tcb_state, + px, length, + banout, + more); + break; default: fprintf(stderr, "banner1: internal error\n"); break; @@ -242,6 +252,8 @@ banner1_create(void) banner_http.init(b); + banner_vnc.init(b); + banner_memcached.init(b); b->tcp_payloads[80] = &banner_http; b->tcp_payloads[8080] = &banner_http; diff --git a/src/proto-banner1.h b/src/proto-banner1.h index a4b754a..0eb1998 100644 --- a/src/proto-banner1.h +++ b/src/proto-banner1.h @@ -13,6 +13,8 @@ struct Banner1 struct SMACK *smack; struct SMACK *http_fields; struct SMACK *html_fields; + struct SMACK *memcached_responses; + struct SMACK *memcached_stats; unsigned is_capture_html:1; unsigned is_capture_cert:1; @@ -117,6 +119,10 @@ struct POP3STUFF { unsigned is_last:1; }; +struct MEMCACHEDSTUFF { + unsigned match; +}; + struct ProtocolState { unsigned state; unsigned remaining; @@ -132,6 +138,7 @@ struct ProtocolState { struct FTPSTUFF ftp; struct SMTPSTUFF smtp; struct POP3STUFF pop3; + struct MEMCACHEDSTUFF memcached; } sub; }; diff --git a/src/proto-memcached.c b/src/proto-memcached.c new file mode 100644 index 0000000..458e90a --- /dev/null +++ b/src/proto-memcached.c @@ -0,0 +1,430 @@ +/* + memcached banner check +*/ + +#include "proto-memcached.h" +#include "proto-banner1.h" +#include "smack.h" +#include "unusedparm.h" +#include "masscan-app.h" +#include "output.h" +#include "proto-interactive.h" +#include "proto-preprocess.h" +#include "proto-ssl.h" +#include "syn-cookie.h" +#include "templ-port.h" +#include <ctype.h> +#include <string.h> +#include <stdlib.h> + +struct SMACK *sm_memcached_responses; +struct SMACK *sm_memcached_stats; + +enum { + MC_ERROR, + MC_CLIENT_ERROR, + MC_SERVER_ERROR, + MC_STORED, + MC_NOT_STORED, + MC_EXISTS, + MC_NOT_FOUND, + MC_END, + MC_VALUE, + MC_DELETED, + MC_TOUCHED, + MC_OK, + MC_BUSY, + MC_BADCLASS, + MC_NOSPARE, + MC_NOTFULL, + MC_UNSAFE, + MC_SAME, + MC_STAT, + MC_empty, +}; +static struct Patterns memcached_responses[] = { + {"ERROR", 0, MC_ERROR, SMACK_ANCHOR_BEGIN}, + {"CLIENT_ERROR", 0, MC_CLIENT_ERROR, SMACK_ANCHOR_BEGIN}, + {"SERVER_ERROR", 0, MC_SERVER_ERROR, SMACK_ANCHOR_BEGIN}, + {"STORED", 0, MC_STORED, SMACK_ANCHOR_BEGIN}, + {"NOT_STORED", 0, MC_NOT_STORED, SMACK_ANCHOR_BEGIN}, + {"EXISTS", 0, MC_EXISTS, SMACK_ANCHOR_BEGIN}, + {"NOT_FOUND", 0, MC_NOT_FOUND, SMACK_ANCHOR_BEGIN}, + {"END", 0, MC_END, SMACK_ANCHOR_BEGIN}, + {"VALUE", 0, MC_VALUE, SMACK_ANCHOR_BEGIN}, + {"DELETED", 0, MC_DELETED, SMACK_ANCHOR_BEGIN}, + {"TOUCHED", 0, MC_TOUCHED, SMACK_ANCHOR_BEGIN}, + {"OK", 0, MC_OK, SMACK_ANCHOR_BEGIN}, + {"BUSY", 0, MC_BUSY, SMACK_ANCHOR_BEGIN}, + {"BADCLASS", 0, MC_BADCLASS, SMACK_ANCHOR_BEGIN}, + {"NOSPARE", 0, MC_NOSPARE, SMACK_ANCHOR_BEGIN}, + {"NOTFULL", 0, MC_NOTFULL, SMACK_ANCHOR_BEGIN}, + {"UNSAFE", 0, MC_UNSAFE, SMACK_ANCHOR_BEGIN}, + {"SAME", 0, MC_SAME, SMACK_ANCHOR_BEGIN}, + {"STAT", 0, MC_STAT, SMACK_ANCHOR_BEGIN}, + {"", 0, MC_empty, SMACK_ANCHOR_BEGIN}, + {0,0,0,0} +}; + +enum { + MS_PID, + MS_UPTIME, + MS_TIME, + MS_VERSION, + MS_POINTER_SIZE, + MS_RUSAGE_USER, + MS_RUSAGE_SYSTEM, + MS_CURR_TIMES, + MS_TOTAL_ITEMS, + MS_BYTES, + MS_MAX_CONNECTIONS, + MS_CURR_CONNECTIONS, + MS_TOTAL_CONNECTIONS, +}; + +static struct Patterns memcached_stats[] = { +{"pid", 0, MS_PID, SMACK_ANCHOR_BEGIN}, +{"uptime", 0, MS_UPTIME, SMACK_ANCHOR_BEGIN}, +{"time", 0, MS_TIME, SMACK_ANCHOR_BEGIN}, +{"version", 0, MS_VERSION, SMACK_ANCHOR_BEGIN}, +{"pointer_size", 0, MS_POINTER_SIZE, SMACK_ANCHOR_BEGIN}, +{"rusage_user", 0, MS_RUSAGE_USER, SMACK_ANCHOR_BEGIN}, +{"rusage_system", 0, MS_RUSAGE_SYSTEM, SMACK_ANCHOR_BEGIN}, +{"curr_items", 0, MS_CURR_TIMES, SMACK_ANCHOR_BEGIN}, +{"total_items", 0, MS_TOTAL_ITEMS, SMACK_ANCHOR_BEGIN}, +{"bytes", 0, MS_BYTES, SMACK_ANCHOR_BEGIN}, +{"max_connections", 0, MS_MAX_CONNECTIONS, SMACK_ANCHOR_BEGIN}, +{"curr_connections", 0, MS_CURR_CONNECTIONS, SMACK_ANCHOR_BEGIN}, +{"total_connections", 0, MS_TOTAL_CONNECTIONS, SMACK_ANCHOR_BEGIN}, +{0,0,0,0} +}; + +/*************************************************************************** + ***************************************************************************/ +static void +memcached_tcp_parse( + const struct Banner1 *banner1, + void *banner1_private, + struct ProtocolState *pstate, + const unsigned char *px, size_t length, + struct BannerOutput *banout, + struct InteractiveData *more) +{ + unsigned state = pstate->state; + unsigned i; + struct MEMCACHEDSTUFF *memcached = &pstate->sub.memcached; + size_t id; + + UNUSEDPARM(banner1_private); + UNUSEDPARM(banner1); + UNUSEDPARM(more); + + if (sm_memcached_responses == 0) + return; + + for (i=0; i<length; i++) { + switch (state) { + case 0: /* command */ + memcached->match = 0; + /* drop through */ + case 1: + id = smack_search_next( + sm_memcached_responses, + &memcached->match, + px, &i, (unsigned)length); + i--; + switch (id) { + case SMACK_NOT_FOUND: + /* continue processing */ + break; + case MC_STAT: + if (px[i] == '\n') + state = 2; /* premature end of line */ + else + state = 100; + break; + case MC_END: + state = 3; + break; + default: + state = 2; + } + break; + + /* We've reached the end of input */ + case 3: + i = length; + break; + + /* Ignore until end of line */ + case 2: + while (i < length && px[i] != '\n') + i++; + if (px[i] == '\n') + state = 0; + break; + + /* process stat */ + case 100: + case 200: + if (px[i] == '\n') + state = 0; + else if (isspace(px[i])) + continue; /* stay in this space until end of whitespace */ + else { + state++; + memcached->match = 0; + i--; + } + break; + case 101: + id = smack_search_next( + sm_memcached_stats, + &memcached->match, + px, &i, (unsigned)length); + i--; + switch (id) { + case SMACK_NOT_FOUND: + /* continue processing */ + break; + case MS_UPTIME: + case MS_TIME: + case MS_VERSION: + banout_append(banout, PROTO_MEMCACHED, memcached_stats[id].pattern, AUTO_LEN); + if (px[i] == '\n') + state = 0; + state = 200; + banout_append_char(banout, PROTO_MEMCACHED, '='); + break; + default: + if (px[i] == '\n') + state = 0; + else + state = 2; + } + break; + + case 201: + if (px[i] == '\r') + continue; + else if (px[i] == '\n') { + banout_append_char(banout, PROTO_MEMCACHED, ' '); + state = 0; + break; + } else + banout_append_char(banout, PROTO_MEMCACHED, px[i]); + break; + } + } + pstate->state = state; +} + +/*************************************************************************** + ***************************************************************************/ +static void * +memcached_init(struct Banner1 *b) +{ + unsigned i; + + /* + * These match response codes + */ + b->memcached_responses = smack_create("memcached-responses", SMACK_CASE_INSENSITIVE); + for (i=0; memcached_responses[i].pattern; i++) { + char *tmp; + unsigned j; + size_t len; + + len = strlen(memcached_responses[i].pattern); + tmp = malloc(len + 2); + memcpy(tmp, memcached_responses[i].pattern, len); + tmp[len+1] = '\0'; + + /* Add all patterns 4 times, once each for the possible whitespace */ + for (j=0; j<4; j++) { + tmp[len] = " \t\r\n"[j]; + smack_add_pattern( + b->memcached_responses, + tmp, + len+1, + memcached_responses[i].id, + memcached_responses[i].is_anchored); + } + + free(tmp); + } + smack_compile(b->memcached_responses); + sm_memcached_responses = b->memcached_responses; + + /* + * These match stats we might be interested in + */ + b->memcached_stats = smack_create("memcached-stats", SMACK_CASE_INSENSITIVE); + for (i=0; memcached_stats[i].pattern; i++) { + char *tmp; + unsigned j; + size_t len; + + len = strlen(memcached_stats[i].pattern); + tmp = malloc(len + 2); + memcpy(tmp, memcached_stats[i].pattern, len); + tmp[len+1] = '\0'; + + /* Add all patterns 4 times, once each for the possible whitespace */ + for (j=0; j<4; j++) { + tmp[len] = " \t\r\n"[j]; + smack_add_pattern( + b->memcached_stats, + tmp, + len+1, + memcached_stats[i].id, + memcached_stats[i].is_anchored); + } + + free(tmp); + } + smack_compile(b->memcached_stats); + sm_memcached_stats = b->memcached_stats; + + return b->http_fields; +} + + +/*************************************************************************** + ***************************************************************************/ +unsigned +memcached_udp_parse(struct Output *out, time_t timestamp, + const unsigned char *px, unsigned length, + struct PreprocessedInfo *parsed, + uint64_t entropy + ) +{ + unsigned ip_them; + unsigned ip_me; + unsigned port_them = parsed->port_src; + unsigned port_me = parsed->port_dst; + unsigned request_id = 0; + unsigned sequence_num = 0; + unsigned total_dgrams = 0; + unsigned reserved = 0; + unsigned cookie = 0; + struct BannerOutput banout[1]; + + /* All memcached responses will be at least 8 bytes */ + if (length < 8) + return 0; + + /* + The frame header is 8 bytes long, as follows (all values are 16-bit integers + in network byte order, high byte first): + + 0-1 Request ID + 2-3 Sequence number + 4-5 Total number of datagrams in this message + 6-7 Reserved for future use; must be 0 + */ + request_id = px[0]<<8 | px[1]; + sequence_num = px[2]<<8 | px[3]; + total_dgrams = px[4]<<8 | px[5]; + reserved = px[6]<<8 | px[7]; + + /* Ignore high sequence numbers. This should be zero normally */ + if (sequence_num > 100) + return 0; + + /* Ignore too many dgrams, should be one normally */ + if (total_dgrams > 100) + return 0; + + /* Make sure reserved field is zero */ + if (reserved != 0) + return 0; + + /* Grab IP addresses */ + ip_them = parsed->ip_src[0]<<24 | parsed->ip_src[1]<<16 + | parsed->ip_src[2]<< 8 | parsed->ip_src[3]<<0; + ip_me = parsed->ip_dst[0]<<24 | parsed->ip_dst[1]<<16 + | parsed->ip_dst[2]<< 8 | parsed->ip_dst[3]<<0; + + /* Validate the "syn-cookie" style information. In the case of SNMP, + * this will be held in the "request-id" field. If the cookie isn't + * a good one, then we'll ignore the response */ + cookie = (unsigned)syn_cookie(ip_them, port_them | Templ_UDP, ip_me, port_me, entropy); + /*if ((seqno&0xffff) != request_id) + return 1;*/ + + /* Initialize the "banner output" module that we'll use to print + * pretty text in place of the raw packet */ + banout_init(banout); + + /* Parse the remainder of the packet as if this were TCP */ + { + struct ProtocolState stuff[1]; + + memset(stuff, 0, sizeof(stuff[0])); + + memcached_tcp_parse( + 0, 0, + stuff, px+8, length-8, banout, + 0); + } + + if ((cookie&0xffff) != request_id) + banout_append(banout, PROTO_MEMCACHED, " IP-MISMATCH", AUTO_LEN); + + /* Print the banner information, or save to a file, depending */ + output_report_banner( + out, timestamp, + ip_them, 17 /*udp*/, parsed->port_src, + PROTO_MEMCACHED, + parsed->ip_ttl, + banout_string(banout, PROTO_MEMCACHED), + banout_string_length(banout, PROTO_MEMCACHED)); + + /* Free memory for the banner, if there was any allocated */ + banout_release(banout); + + return 0; +} + +/**************************************************************************** + ****************************************************************************/ +unsigned +memcached_udp_set_cookie(unsigned char *px, size_t length, uint64_t seqno) +{ + /* + The frame header is 8 bytes long, as follows (all values are 16-bit integers + in network byte order, high byte first): + + 0-1 Request ID + 2-3 Sequence number + 4-5 Total number of datagrams in this message + 6-7 Reserved for future use; must be 0 + */ + + if (length < 2) + return 0; + + px[0] = (unsigned char)(seqno >> 8); + px[1] = (unsigned char)(seqno >> 0); + + + return 0; +} + +/*************************************************************************** + ***************************************************************************/ +static int +memcached_selftest(void) +{ + return 0; +} + +/*************************************************************************** + ***************************************************************************/ +const struct ProtocolParserStream banner_memcached = { + "memcached", 11211, "stats\r\n", 7, 0, + memcached_selftest, + memcached_init, + memcached_tcp_parse, +}; diff --git a/src/proto-memcached.h b/src/proto-memcached.h new file mode 100644 index 0000000..af8a766 --- /dev/null +++ b/src/proto-memcached.h @@ -0,0 +1,28 @@ +#ifndef PROTO_MEMCACHED_H +#define PROTO_MEMCACHED_H +#include "proto-banner1.h" +struct Output; +struct PreprocessedInfo; + +/* + * For sending TCP requests and parsing TCP responses. + */ +extern const struct ProtocolParserStream banner_memcached; + +/* + * For parsing UDP responses + */ +unsigned +memcached_udp_parse(struct Output *out, time_t timestamp, + const unsigned char *px, unsigned length, + struct PreprocessedInfo *parsed, + uint64_t entropy + ); + +/* + * For creating UDP request + */ +unsigned +memcached_udp_set_cookie(unsigned char *px, size_t length, uint64_t seqno); + +#endif diff --git a/src/proto-udp.c b/src/proto-udp.c index c9850ce..ce41a95 100644 --- a/src/proto-udp.c +++ b/src/proto-udp.c @@ -2,6 +2,7 @@ #include "proto-dns.h" #include "proto-netbios.h" #include "proto-snmp.h" +#include "proto-memcached.h" #include "proto-ntp.h" #include "proto-zeroaccess.h" #include "proto-preprocess.h" @@ -42,6 +43,11 @@ handle_udp(struct Output *out, time_t timestamp, case 161: status = handle_snmp(out, timestamp, px, length, parsed, entropy); break; + case 11211: + px += parsed->app_offset; + length = parsed->app_length; + status = memcached_udp_parse(out, timestamp, px, length, parsed, entropy); + break; case 16464: case 16465: case 16470: diff --git a/src/templ-payloads.c b/src/templ-payloads.c index 8914c55..a79db0d 100644 --- a/src/templ-payloads.c +++ b/src/templ-payloads.c @@ -17,6 +17,7 @@ #include "logger.h" #include "proto-zeroaccess.h" /* botnet p2p protocol */ #include "proto-snmp.h" +#include "proto-memcached.h" #include "proto-ntp.h" #include "proto-dns.h" @@ -104,7 +105,9 @@ struct Payload2 hard_coded_payloads[] = { "Content-Length: 0\r\n" }, - {11211, 65536, 15, 0, 0, + /* memcached "stats" request. This looks for memcached systems that can + * be used for DDoS amplifiers */ + {11211, 65536, 15, 0, memcached_udp_set_cookie, "\x00\x00\x00\x00\x00\x01\x00\x00stats\r\n" }, diff --git a/vs10/masscan.vcxproj b/vs10/masscan.vcxproj index 0c22cac..2534578 100644 --- a/vs10/masscan.vcxproj +++ b/vs10/masscan.vcxproj @@ -51,6 +51,7 @@ <ClCompile Include="..\src\proto-icmp.c" /> <ClCompile Include="..\src\proto-imap4.c" /> <ClCompile Include="..\src\proto-interactive.c" /> + <ClCompile Include="..\src\proto-memcached.c" /> <ClCompile Include="..\src\proto-netbios.c" /> <ClCompile Include="..\src\proto-ntp.c" /> <ClCompile Include="..\src\proto-pop3.c" /> @@ -139,6 +140,7 @@ <ClInclude Include="..\src\proto-icmp.h" /> <ClInclude Include="..\src\proto-imap4.h" /> <ClInclude Include="..\src\proto-interactive.h" /> + <ClInclude Include="..\src\proto-memcached.h" /> <ClInclude Include="..\src\proto-netbios.h" /> <ClInclude Include="..\src\proto-ntp.h" /> <ClInclude Include="..\src\proto-pop3.h" /> diff --git a/vs10/masscan.vcxproj.filters b/vs10/masscan.vcxproj.filters index 0d269f5..efbcef0 100644 --- a/vs10/masscan.vcxproj.filters +++ b/vs10/masscan.vcxproj.filters @@ -288,6 +288,9 @@ <ClCompile Include="..\src\out-ndjson.c"> <Filter>Source Files\output</Filter> </ClCompile> + <ClCompile Include="..\src\proto-memcached.c"> + <Filter>Source Files\proto</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="..\src\proto-arp.h"> @@ -491,6 +494,9 @@ <ClInclude Include="..\src\rawsock-pcap.h"> <Filter>Source Files\rawsock</Filter> </ClInclude> + <ClInclude Include="..\src\proto-memcached.h"> + <Filter>Source Files\proto</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <None Include="..\README.md" /> -- GitLab