436 lines
13 KiB
C
436 lines
13 KiB
C
/*
|
|
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 "stack-tcp-api.h"
|
|
#include "proto-preprocess.h"
|
|
#include "proto-ssl.h"
|
|
#include "proto-udp.h"
|
|
#include "syn-cookie.h"
|
|
#include "massip-port.h"
|
|
#include "util-malloc.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 StreamState *pstate,
|
|
const unsigned char *px, size_t length,
|
|
struct BannerOutput *banout,
|
|
struct stack_handle_t *socket)
|
|
{
|
|
unsigned state = pstate->state;
|
|
unsigned i;
|
|
struct MEMCACHEDSTUFF *memcached = &pstate->sub.memcached;
|
|
size_t id;
|
|
|
|
UNUSEDPARM(banner1_private);
|
|
UNUSEDPARM(banner1);
|
|
UNUSEDPARM(socket);
|
|
|
|
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 = (unsigned)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;
|
|
else
|
|
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,
|
|
(unsigned)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,
|
|
(unsigned)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
|
|
)
|
|
{
|
|
ipaddress ip_them;
|
|
ipaddress 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)
|
|
goto not_memcached;
|
|
|
|
/* Ignore too many dgrams, should be one normally */
|
|
if (total_dgrams > 100)
|
|
goto not_memcached;
|
|
|
|
/* Make sure reserved field is zero */
|
|
if (reserved != 0)
|
|
goto not_memcached;
|
|
|
|
/* Grab IP addresses */
|
|
ip_them = parsed->src_ip;
|
|
ip_me = parsed->dst_ip;
|
|
|
|
/* 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 StreamState 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;
|
|
|
|
not_memcached:
|
|
return default_udp_parse(out, timestamp, px, length, parsed, entropy);
|
|
}
|
|
|
|
/****************************************************************************
|
|
****************************************************************************/
|
|
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,
|
|
};
|
|
|