734 lines
25 KiB
C
734 lines
25 KiB
C
|
/*
|
||
|
CoAP - Constrained Application Protocol
|
||
|
https://en.wikipedia.org/wiki/Constrained_Application_Protocol
|
||
|
|
||
|
This is a very simple protocol for interacting with IoT devices
|
||
|
that have a minimal amount of resources, such as less than a
|
||
|
megabyte of RAM.
|
||
|
|
||
|
From a scanner point of view, we want to execute the equivalent
|
||
|
of:
|
||
|
GET /.well-known/core
|
||
|
This will return the list of additional items that we can access
|
||
|
on the target device.
|
||
|
|
||
|
0 1 2 3
|
||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
|Ver| T | TKL | Code | Message ID |
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
| Token (if any, TKL bytes) ...
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
| Options (if any) ...
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
|1 1 1 1 1 1 1 1| Payload (if any) ...
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
|
||
|
*/
|
||
|
|
||
|
#include "proto-coap.h"
|
||
|
#include "proto-banner1.h"
|
||
|
#include "smack.h"
|
||
|
#include "unusedparm.h"
|
||
|
#include "util-logger.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 "util-safefunc.h"
|
||
|
#include "util-bool.h"
|
||
|
#include <ctype.h>
|
||
|
#include <string.h>
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
struct CoapLink
|
||
|
{
|
||
|
unsigned link_offset;
|
||
|
unsigned link_length;
|
||
|
unsigned parms_offset;
|
||
|
unsigned parms_length;
|
||
|
};
|
||
|
|
||
|
/****************************************************************************
|
||
|
****************************************************************************/
|
||
|
static const char *
|
||
|
response_code(unsigned code)
|
||
|
{
|
||
|
#define CODE(x,y) (((x)<<5) | (y))
|
||
|
switch (code) {
|
||
|
case CODE(2,0): return "Okay";
|
||
|
case CODE(2,1): return "Created";
|
||
|
case CODE(2,2): return "Deleted";
|
||
|
case CODE(2,3): return "Valid";
|
||
|
case CODE(2,4): return "Changed";
|
||
|
case CODE(2,5): return "Content";
|
||
|
|
||
|
case CODE(4,0): return "Bad Request";
|
||
|
case CODE(4,1): return "Unauthorized";
|
||
|
case CODE(4,2): return "Bad Option";
|
||
|
case CODE(4,3): return "Forbidden";
|
||
|
case CODE(4,4): return "Not Found";
|
||
|
case CODE(4,5): return "Method Not Allowed";
|
||
|
case CODE(4,6): return "Not Acceptable";
|
||
|
case CODE(4,12): return "Precondition Failed";
|
||
|
case CODE(4,13): return "Request Too Large";
|
||
|
case CODE(4,15): return "Unsupported Content-Format";
|
||
|
|
||
|
case CODE(5,0): return "Internal Server Error";
|
||
|
case CODE(5,1): return "Not Implemented";
|
||
|
case CODE(5,2): return "Bad Gateway";
|
||
|
case CODE(5,3): return "Service Unavailable";
|
||
|
case CODE(5,4): return "Gateway Timeout";
|
||
|
case CODE(5,5): return "Proxying Not Supported";
|
||
|
}
|
||
|
|
||
|
switch (code>>5) {
|
||
|
case 2: return "Okay";
|
||
|
case 4: return "Error";
|
||
|
default: return "PARSE_ERR";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
* RFC5987
|
||
|
* attr-char = ALPHA / DIGIT
|
||
|
* / "!" / "#" / "$" / "&" / "+" / "-" / "."
|
||
|
* / "^" / "_" / "`" / "|" / "~"
|
||
|
* ; token except ( "*" / "'" / "%" )
|
||
|
* We need this in parsing the links, which may have parameters afterwards
|
||
|
* whose names are in this format.
|
||
|
****************************************************************************/
|
||
|
static bool
|
||
|
is_attr_char(unsigned c)
|
||
|
{
|
||
|
switch (c) {
|
||
|
case '!': case '#': case '$': case '&': case '+': case '-': case '.':
|
||
|
case '^': case '_': case '`': case '|': case '~':
|
||
|
return true;
|
||
|
default:
|
||
|
return isalnum(c) != 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
****************************************************************************/
|
||
|
static struct CoapLink *
|
||
|
parse_links(const unsigned char *px, unsigned offset, unsigned length, size_t *r_count)
|
||
|
{
|
||
|
struct CoapLink *l;
|
||
|
struct CoapLink *links;
|
||
|
unsigned count = 0;
|
||
|
enum {
|
||
|
LINK_BEGIN=0,
|
||
|
LINK_VALUE,
|
||
|
LINK_END,
|
||
|
PARM_BEGIN,
|
||
|
PARM_NAME_BEGIN,
|
||
|
PARM_VALUE_BEGIN,
|
||
|
PARM_QUOTED,
|
||
|
PARM_QUOTED_ESCAPE,
|
||
|
PARM_NAME,
|
||
|
PARM_VALUE,
|
||
|
INVALID
|
||
|
} state = LINK_BEGIN;
|
||
|
|
||
|
/* For selftesting purposes, we pass in nul-terminated strings,
|
||
|
* indicated by a length of (~0) */
|
||
|
if (length == ~0)
|
||
|
length = (unsigned)strlen((const char *)px);
|
||
|
|
||
|
/* Allocate space for at least one result */
|
||
|
links = CALLOC(1, sizeof(*links));
|
||
|
l = &links[0];
|
||
|
l->parms_offset = offset;
|
||
|
l->link_offset = offset;
|
||
|
|
||
|
for (; offset < length; offset++)
|
||
|
switch (state) {
|
||
|
case INVALID:
|
||
|
offset = length;
|
||
|
break;
|
||
|
case LINK_BEGIN:
|
||
|
/* Ignore leading whitespace */
|
||
|
if (isspace(px[offset]))
|
||
|
continue;
|
||
|
|
||
|
/* Links must start with "<" character */
|
||
|
if (px[offset] != '<') {
|
||
|
state = INVALID;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Reserve space for next link */
|
||
|
links = REALLOCARRAY(links, ++count+1, sizeof(*links));
|
||
|
links[count].link_offset = length; /* indicate end-of-list by pointing to end-of-input */
|
||
|
links[count].link_length = 0;
|
||
|
links[count].parms_offset = length;
|
||
|
links[count].parms_length = 0;
|
||
|
|
||
|
/* Grab a pointer to this <link> */
|
||
|
l = &links[count-1];
|
||
|
l->link_offset = offset+1;
|
||
|
l->parms_offset = l->link_offset;
|
||
|
|
||
|
state = LINK_VALUE;
|
||
|
break;
|
||
|
case LINK_VALUE:
|
||
|
if (px[offset] == '>') {
|
||
|
/* End of the link, it may be followed by parameters */
|
||
|
state = LINK_END;
|
||
|
} else {
|
||
|
l->link_length++;
|
||
|
}
|
||
|
break;
|
||
|
case LINK_END:
|
||
|
l->parms_offset = offset+1;
|
||
|
l->parms_length = 0;
|
||
|
if (isspace(px[offset])) {
|
||
|
continue;
|
||
|
} else if (px[offset] == ',') {
|
||
|
/* next link */
|
||
|
state = LINK_BEGIN;
|
||
|
} else if (px[offset] == ';') {
|
||
|
state = PARM_NAME_BEGIN;
|
||
|
} else {
|
||
|
state = INVALID;
|
||
|
}
|
||
|
break;
|
||
|
case PARM_BEGIN:
|
||
|
if (isspace(px[offset])) {
|
||
|
continue;
|
||
|
} else if (px[offset] == ',') {
|
||
|
/* next link */
|
||
|
l->parms_length = offset - l->parms_offset;
|
||
|
state = LINK_BEGIN;
|
||
|
} else if (px[offset] == ';') {
|
||
|
state = PARM_NAME_BEGIN;
|
||
|
} else {
|
||
|
state = INVALID;
|
||
|
}
|
||
|
break;
|
||
|
case PARM_NAME_BEGIN:
|
||
|
if (isspace(px[offset]))
|
||
|
continue;
|
||
|
if (!is_attr_char(px[offset]))
|
||
|
state = INVALID;
|
||
|
else
|
||
|
state = PARM_NAME;
|
||
|
break;
|
||
|
case PARM_NAME:
|
||
|
if (isspace(px[offset])) {
|
||
|
continue;
|
||
|
} else if (px[offset] == '=') {
|
||
|
state = PARM_VALUE_BEGIN;
|
||
|
} else if (!is_attr_char(px[offset])) {
|
||
|
state = INVALID;
|
||
|
}
|
||
|
break;
|
||
|
case PARM_VALUE_BEGIN:
|
||
|
if (isspace(px[offset]))
|
||
|
continue;
|
||
|
else if (px[offset] == '\"') {
|
||
|
state = PARM_QUOTED;
|
||
|
} else if (offset == ';') {
|
||
|
state = PARM_NAME_BEGIN;
|
||
|
} else if (px[offset] == ',') {
|
||
|
l->parms_length = offset - l->parms_offset;
|
||
|
state = LINK_BEGIN;
|
||
|
} else
|
||
|
state = PARM_VALUE;
|
||
|
break;
|
||
|
case PARM_VALUE:
|
||
|
if (isspace(px[offset]))
|
||
|
continue;
|
||
|
else if (px[offset] == ';')
|
||
|
state = PARM_NAME_BEGIN;
|
||
|
else if (px[offset] == ',') {
|
||
|
l->parms_length = offset - l->parms_offset;
|
||
|
state = LINK_BEGIN;
|
||
|
} else {
|
||
|
; /* do nothing */
|
||
|
}
|
||
|
break;
|
||
|
case PARM_QUOTED:
|
||
|
/* RFC2616:
|
||
|
quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
|
||
|
qdtext = <any TEXT except <">>
|
||
|
quoted-pair = "\" CHAR
|
||
|
*/
|
||
|
if (px[offset] == '\\') {
|
||
|
state = PARM_QUOTED_ESCAPE;
|
||
|
} else if (px[offset] == '\"') {
|
||
|
state = PARM_VALUE;
|
||
|
}
|
||
|
break;
|
||
|
case PARM_QUOTED_ESCAPE:
|
||
|
state = PARM_QUOTED;
|
||
|
break;
|
||
|
default:
|
||
|
fprintf(stderr, "invalid state\n");
|
||
|
state = INVALID;
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
/* Return an array of links and a count of the number of links */
|
||
|
*r_count = count;
|
||
|
return links;
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
****************************************************************************/
|
||
|
static bool
|
||
|
coap_parse(const unsigned char *px, size_t length, struct BannerOutput *banout,
|
||
|
unsigned *request_id)
|
||
|
{
|
||
|
unsigned version;
|
||
|
unsigned type;
|
||
|
unsigned code = 0;
|
||
|
unsigned token_length = 0;
|
||
|
unsigned long long token = 0;
|
||
|
unsigned offset;
|
||
|
unsigned optnum;
|
||
|
unsigned content_format;
|
||
|
size_t i;
|
||
|
|
||
|
/* All coap responses will be at least 8 bytes */
|
||
|
if (length < 4) {
|
||
|
LOG(3, "[-] CoAP: short length\n");
|
||
|
goto not_this_protocol;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
|Ver| T | TKL | Code | Message ID |
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
| Token (if any, TKL bytes) ...
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
| Options (if any) ...
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
|1 1 1 1 1 1 1 1| Payload (if any) ...
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
*/
|
||
|
version = (px[0]>>6) & 3;
|
||
|
type = (px[0]>>4) & 3;
|
||
|
|
||
|
token_length = px[0] & 0x0F;
|
||
|
code = px[1];
|
||
|
*request_id = px[2]<<8 | px[3];
|
||
|
|
||
|
/* Only version supported is v1 */
|
||
|
if (version != 1) {
|
||
|
LOG(3, "[-] CoAP: version=%u\n", version);
|
||
|
goto not_this_protocol;
|
||
|
}
|
||
|
|
||
|
/* Only ACKs suported */
|
||
|
if (type != 2) {
|
||
|
LOG(3, "[-] CoAP: type=%u\n", type);
|
||
|
goto not_this_protocol;
|
||
|
}
|
||
|
|
||
|
/* Only token lengths up to 8 bytes are supported.
|
||
|
* Token length must fit within the packet */
|
||
|
if (token_length > 8 || 4 + token_length > length) {
|
||
|
LOG(3, "[-] CoAP: token-length=%u\n", token_length);
|
||
|
goto not_this_protocol;
|
||
|
}
|
||
|
|
||
|
token = 0;
|
||
|
for (i=0; i<token_length; i++) {
|
||
|
token = token << 8ULL;
|
||
|
token = token | (unsigned long long)px[i];
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Response code */
|
||
|
{
|
||
|
char buf[64];
|
||
|
snprintf(buf, sizeof(buf), "rsp=%u.%u(%s)", code>>5, code&0x1F, response_code(code));
|
||
|
banout_append(banout, PROTO_COAP, buf, AUTO_LEN);
|
||
|
//code >>= 5;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* If there was a token, the print it. */
|
||
|
if (token) {
|
||
|
char buf[64];
|
||
|
snprintf(buf, sizeof(buf), " token=0x%llu", token);
|
||
|
banout_append(banout, PROTO_COAP, buf, AUTO_LEN);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Now process the options fields
|
||
|
|
||
|
0 1 2 3 4 5 6 7
|
||
|
+---------------+---------------+
|
||
|
| | |
|
||
|
| Option Delta | Option Length | 1 byte
|
||
|
| | |
|
||
|
+---------------+---------------+
|
||
|
\ \
|
||
|
/ Option Delta / 0-2 bytes
|
||
|
\ (extended) \
|
||
|
+-------------------------------+
|
||
|
\ \
|
||
|
/ Option Length / 0-2 bytes
|
||
|
\ (extended) \
|
||
|
+-------------------------------+
|
||
|
\ \
|
||
|
/ /
|
||
|
\ \
|
||
|
/ Option Value / 0 or more bytes
|
||
|
\ \
|
||
|
/ /
|
||
|
\ \
|
||
|
+-------------------------------+
|
||
|
*/
|
||
|
offset = 4 + token_length;
|
||
|
optnum = 0;
|
||
|
content_format = 0;
|
||
|
while (offset < length) {
|
||
|
unsigned delta;
|
||
|
unsigned opt;
|
||
|
unsigned optlen;
|
||
|
|
||
|
/* Get the 'opt' byte */
|
||
|
opt = px[offset++];
|
||
|
if (opt == 0xFF)
|
||
|
break;
|
||
|
optlen = (opt>>0) & 0x0F;
|
||
|
delta = (opt>>4) & 0x0F;
|
||
|
|
||
|
/* Decode the delta field */
|
||
|
switch (delta) {
|
||
|
default:
|
||
|
optnum += delta;
|
||
|
break;
|
||
|
case 13:
|
||
|
if (offset >= length) {
|
||
|
banout_append(banout, PROTO_COAP, " PARSE_ERR", AUTO_LEN);
|
||
|
optnum = 0xFFFFFFFF;
|
||
|
} else {
|
||
|
delta = px[offset++] + 13;
|
||
|
optnum += delta;
|
||
|
}
|
||
|
break;
|
||
|
case 14:
|
||
|
if (offset + 1 >= length) {
|
||
|
banout_append(banout, PROTO_COAP, " PARSE_ERR", AUTO_LEN);
|
||
|
optnum = 0xFFFFFFFF;
|
||
|
} else {
|
||
|
delta = px[offset+0]<<8 | px[offset+1];
|
||
|
delta += 269;
|
||
|
offset += 2;
|
||
|
optnum += delta;
|
||
|
}
|
||
|
break;
|
||
|
case 15:
|
||
|
if (optlen != 15)
|
||
|
banout_append(banout, PROTO_COAP, " PARSE_ERR", AUTO_LEN);
|
||
|
optnum = 0xFFFFFFFF;
|
||
|
}
|
||
|
|
||
|
/* Decode the optlen field */
|
||
|
switch (optlen) {
|
||
|
default:
|
||
|
break;
|
||
|
case 13:
|
||
|
if (offset >= length) {
|
||
|
banout_append(banout, PROTO_COAP, " PARSE_ERR", AUTO_LEN);
|
||
|
optnum = 0xFFFFFFFF;
|
||
|
} else {
|
||
|
optlen = px[offset++] + 13;
|
||
|
}
|
||
|
break;
|
||
|
case 14:
|
||
|
if (offset + 1 >= length) {
|
||
|
banout_append(banout, PROTO_COAP, " PARSE_ERR", AUTO_LEN);
|
||
|
optnum = 0xFFFFFFFF;
|
||
|
} else {
|
||
|
optlen = px[offset+0]<<8 | px[offset+1];
|
||
|
optlen += 269;
|
||
|
offset += 2;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
if (offset + optlen > length) {
|
||
|
banout_append(banout, PROTO_COAP, " PARSE_ERR", AUTO_LEN);
|
||
|
optnum = 0xFFFFFFFF;
|
||
|
}
|
||
|
|
||
|
/* Process the option contents */
|
||
|
switch (optnum) {
|
||
|
case 0xFFFFFFFF:
|
||
|
break;
|
||
|
case 1: banout_append(banout, PROTO_COAP, " /If-Match/", AUTO_LEN); break;
|
||
|
case 3: banout_append(banout, PROTO_COAP, " /Uri-Host/", AUTO_LEN); break;
|
||
|
case 4: banout_append(banout, PROTO_COAP, " /Etag", AUTO_LEN); break;
|
||
|
case 5: banout_append(banout, PROTO_COAP, " /If-None-Match/", AUTO_LEN); break;
|
||
|
case 7: banout_append(banout, PROTO_COAP, " /Uri-Port/", AUTO_LEN); break;
|
||
|
case 8: banout_append(banout, PROTO_COAP, " /Location-Path/", AUTO_LEN); break;
|
||
|
case 11: banout_append(banout, PROTO_COAP, " /Uri-Path/", AUTO_LEN); break;
|
||
|
case 12:
|
||
|
banout_append(banout, PROTO_COAP, " /Content-Format/", AUTO_LEN);
|
||
|
content_format = 0;
|
||
|
|
||
|
for (i=0; i<optlen; i++) {
|
||
|
content_format = content_format<<8 | px[offset+i];
|
||
|
}
|
||
|
break;
|
||
|
case 14: banout_append(banout, PROTO_COAP, " /Max-Age/", AUTO_LEN); break;
|
||
|
case 15: banout_append(banout, PROTO_COAP, " /Uri-Query/", AUTO_LEN); break;
|
||
|
case 17: banout_append(banout, PROTO_COAP, " /Accept/", AUTO_LEN); break;
|
||
|
case 20: banout_append(banout, PROTO_COAP, " /Location-Query/", AUTO_LEN); break;
|
||
|
case 35: banout_append(banout, PROTO_COAP, " /Proxy-Uri/", AUTO_LEN); break;
|
||
|
case 39: banout_append(banout, PROTO_COAP, " /Proxy-Scheme/", AUTO_LEN); break;
|
||
|
case 60: banout_append(banout, PROTO_COAP, " /Size1/", AUTO_LEN); break;
|
||
|
default: banout_append(banout, PROTO_COAP, " /(Unknown)/", AUTO_LEN); break;
|
||
|
|
||
|
}
|
||
|
|
||
|
if (optnum == 0xFFFFFFFF)
|
||
|
break;
|
||
|
|
||
|
offset += optlen;
|
||
|
}
|
||
|
|
||
|
switch (content_format) {
|
||
|
case 0: banout_append(banout, PROTO_COAP, " text-plain", AUTO_LEN); break;
|
||
|
case 40:
|
||
|
banout_append(banout, PROTO_COAP, " application/link-format", AUTO_LEN);
|
||
|
{
|
||
|
struct CoapLink *links;
|
||
|
size_t count = 0;
|
||
|
|
||
|
links = parse_links(px, offset, (unsigned)length, &count);
|
||
|
for (i=0; i<count; i++) {
|
||
|
banout_append(banout, PROTO_COAP, " ", AUTO_LEN);
|
||
|
banout_append(banout, PROTO_COAP, px+links[i].link_offset, links[i].link_length);
|
||
|
}
|
||
|
free(links);
|
||
|
}
|
||
|
break;
|
||
|
case 41: banout_append(banout, PROTO_COAP, " application/xml", AUTO_LEN); break;
|
||
|
case 42: banout_append(banout, PROTO_COAP, " application/octet-stream", AUTO_LEN); break;
|
||
|
case 47: banout_append(banout, PROTO_COAP, " application/exi", AUTO_LEN); break;
|
||
|
case 50: banout_append(banout, PROTO_COAP, " application/json", AUTO_LEN); break;
|
||
|
default: banout_append(banout, PROTO_COAP, " (unknown-content-type)", AUTO_LEN); break;
|
||
|
}
|
||
|
|
||
|
LOG(3, "[+] CoAP: valid\n");
|
||
|
return true;
|
||
|
not_this_protocol:
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
***************************************************************************/
|
||
|
unsigned
|
||
|
coap_handle_response(struct Output *out, time_t timestamp,
|
||
|
const unsigned char *px, unsigned length,
|
||
|
struct PreprocessedInfo *parsed,
|
||
|
uint64_t entropy
|
||
|
)
|
||
|
{
|
||
|
ipaddress ip_them = parsed->src_ip;
|
||
|
ipaddress ip_me = parsed->dst_ip;
|
||
|
unsigned port_them = parsed->port_src;
|
||
|
unsigned port_me = parsed->port_dst;
|
||
|
unsigned message_id = 0;
|
||
|
unsigned cookie;
|
||
|
struct BannerOutput banout[1];
|
||
|
bool is_valid;
|
||
|
|
||
|
LOG(1, "[+] COAP\n");
|
||
|
|
||
|
/* Initialize the "banner output" module that we'll use to print
|
||
|
* pretty text in place of the raw packet */
|
||
|
banout_init(banout);
|
||
|
|
||
|
/*
|
||
|
* Do the protocol parsing
|
||
|
*/
|
||
|
is_valid = coap_parse(px, length, banout, &message_id);
|
||
|
|
||
|
|
||
|
/* Validate the "syn-cookie" style information, which should match the "Message ID field*/
|
||
|
cookie = (unsigned)syn_cookie(ip_them, port_them | Templ_UDP, ip_me, port_me, entropy);
|
||
|
/*if ((seqno&0xffff) != message_id)
|
||
|
goto not_this_protocol;*/
|
||
|
|
||
|
/* See if cookies match. So far, we are allowing responses with the
|
||
|
* wrong cookie */
|
||
|
if ((cookie&0xffff) != message_id)
|
||
|
banout_append(banout, PROTO_COAP, " IP-MISMATCH", AUTO_LEN);
|
||
|
|
||
|
|
||
|
/* Print the banner information, or save to a file, depending */
|
||
|
if (is_valid) {
|
||
|
output_report_banner(
|
||
|
out, timestamp,
|
||
|
ip_them, 17 /*udp*/, parsed->port_src,
|
||
|
PROTO_COAP,
|
||
|
parsed->ip_ttl,
|
||
|
banout_string(banout, PROTO_COAP),
|
||
|
banout_string_length(banout, PROTO_COAP));
|
||
|
banout_release(banout);
|
||
|
return 0;
|
||
|
} else {
|
||
|
banout_release(banout);
|
||
|
return default_udp_parse(out, timestamp, px, length, parsed, entropy);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
****************************************************************************/
|
||
|
unsigned
|
||
|
coap_udp_set_cookie(unsigned char *px, size_t length, uint64_t seqno)
|
||
|
{
|
||
|
/*
|
||
|
The frame header is 4 bytes long, with bytes 2 and 3 being
|
||
|
the Message ID.
|
||
|
We can also put up to 8 bytes of a "token" here instead of
|
||
|
just using the message ID.
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
|Ver| T | TKL | Code | Message ID |
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
| Token (if any, TKL bytes) ...
|
||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
*/
|
||
|
|
||
|
if (length < 4)
|
||
|
return 0;
|
||
|
|
||
|
px[2] = (unsigned char)(seqno >> 8);
|
||
|
px[3] = (unsigned char)(seqno >> 0);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/****************************************************************************
|
||
|
* For the selftest code, tests whether the indicated link is within the
|
||
|
* given list.
|
||
|
****************************************************************************/
|
||
|
static int
|
||
|
test_is_link(const char *name, const unsigned char *vinput, struct CoapLink *links, size_t count, int line_number)
|
||
|
{
|
||
|
size_t i;
|
||
|
size_t name_length = strlen(name);
|
||
|
const char *input = (const char *)vinput;
|
||
|
|
||
|
for (i=0; i<count; i++) {
|
||
|
const char *name2;
|
||
|
if (name_length != links[i].link_length)
|
||
|
continue;
|
||
|
name2 = input + links[i].link_offset;
|
||
|
if (memcmp(name2, name, name_length) != 0)
|
||
|
continue;
|
||
|
return 1; /* found */
|
||
|
}
|
||
|
|
||
|
fprintf(stderr, "[-] proto-coap failed at line number %d\n", line_number);
|
||
|
return 0; /* not found */
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
****************************************************************************/
|
||
|
int
|
||
|
proto_coap_selftest(void)
|
||
|
{
|
||
|
|
||
|
|
||
|
struct CoapLink *links;
|
||
|
size_t count=0;
|
||
|
|
||
|
/* test quoted */
|
||
|
{
|
||
|
static const unsigned char *input = (const unsigned char *)
|
||
|
"</sensors/temp>;if=\"se\\\"\\;\\,\\<\\>\\\\nsor\",</success>";
|
||
|
links = parse_links(input, 0, (unsigned)(~0), &count);
|
||
|
if (!test_is_link("/success", input, links, count, __LINE__))
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* test a simple link */
|
||
|
{
|
||
|
static const unsigned char *input = (const unsigned char *)
|
||
|
"</sensors/temp>;if=\"sensor\"";
|
||
|
links = parse_links(input, 0, (unsigned)(~0), &count);
|
||
|
if (!test_is_link("/sensors/temp", input, links, count, __LINE__))
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Test a complex dump */
|
||
|
{
|
||
|
static const unsigned char *input = (const unsigned char *)
|
||
|
"</sensors/temp>;if=\"sensor\","
|
||
|
"</sensors/light>;if=\"sensor\","
|
||
|
"</sensors>;ct=40,"
|
||
|
"</sensors/temp>;rt=\"temperature-c\";if=\"sensor\","
|
||
|
"</sensors/light>;rt=\"light-lux\";if=\"sensor\","
|
||
|
"</sensors/light>;rt=\"light-lux\";if=\"sensor\","
|
||
|
"</sensors/light>;rt=\"light-lux core.sen-light\";if=\"sensor\","
|
||
|
"</sensors>;ct=40;title=\"Sensor Index\","
|
||
|
"</sensors/temp>;rt=\"temperature-c\";if=\"sensor\","
|
||
|
"</sensors/light>;rt=\"light-lux\";if=\"sensor\","
|
||
|
"<http://www.example.com/sensors/t123>;anchor=\"/sensors/temp\";rel=\"describedby\","
|
||
|
"</t>;anchor=\"/sensors/temp\";rel=\"alternate\","
|
||
|
"</firmware/v2.1>;rt=\"firmware\";sz=262144"
|
||
|
;
|
||
|
links = parse_links(input, 0, (unsigned)(~0), &count);
|
||
|
if (!test_is_link("/firmware/v2.1", input, links, count, __LINE__))
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* Now test an entire packet */
|
||
|
{
|
||
|
const char input[] =
|
||
|
"\x60\x45\x01\xce\xc1\x28\xff\x3c\x2f\x72\x65\x67\x69\x73\x74\x65"
|
||
|
"\x72\x3e\x2c\x3c\x2f\x6e\x64\x6d\x2f\x64\x69\x73\x3e\x2c\x3c\x2f"
|
||
|
"\x6e\x64\x6d\x2f\x63\x69\x3e\x2c\x3c\x2f\x6d\x69\x72\x72\x6f\x72"
|
||
|
"\x3e\x2c\x3c\x2f\x75\x68\x70\x3e\x2c\x3c\x2f\x6e\x64\x6d\x2f\x6c"
|
||
|
"\x6f\x67\x6f\x75\x74\x3e\x2c\x3c\x2f\x6e\x64\x6d\x2f\x6c\x6f\x67"
|
||
|
"\x69\x6e\x3e\x2c\x3c\x2f\x69\x6e\x66\x6f\x3e";
|
||
|
unsigned request_id = 0;
|
||
|
struct BannerOutput banout[1];
|
||
|
bool is_valid;
|
||
|
banout_init(banout);
|
||
|
|
||
|
/* parse a test packet */
|
||
|
is_valid = coap_parse( (const unsigned char*)input,
|
||
|
sizeof(input)-1,
|
||
|
banout,
|
||
|
&request_id
|
||
|
);
|
||
|
//fprintf(stderr, "[+] %.*s\n", (int)banout_string_length(banout, PROTO_COAP), banout_string(banout, PROTO_COAP));
|
||
|
|
||
|
if (!is_valid)
|
||
|
return 1;
|
||
|
if (request_id != 462)
|
||
|
return 1;
|
||
|
|
||
|
{
|
||
|
const unsigned char *str = banout_string(banout, PROTO_COAP);
|
||
|
size_t str_length = banout_string_length(banout, PROTO_COAP);
|
||
|
if (str_length <= 16 && memcmp(str, "rsp=2.5(Content)", 16) != 0)
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
banout_release(banout);
|
||
|
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|