Create a gist now

Instantly share code, notes, and snippets.

Simple rebinding nameserver.
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <time.h>
#include <pwd.h>
#include <err.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <netinet/in.h>
// Very (very) simple nameserver for AAAA rebinding attacks.
//
// This is the server used for testing CVE-2013-3694, http://blog.cmpxchg8b.com/2013/11/qnx.html
//
// Tavis Ormandy <taviso@cmpxchg8b.com>
//
//
// $ host aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us
// aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us has IPv6 address aabb:ccdd:eeff:aabb:ccdd:eeff:aabb:ccdd
// $ host aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us
// aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us has IPv6 address aabb:ccdd:eeff:aabb:ccdd:eeff:aabb:ccdd
// $ host aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us
// aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us has IPv6 address 1122:3344:5566:7788:99aa:1122:3344:5566
// $ host aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us
// aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us has IPv6 address aabb:ccdd:eeff:aabb:ccdd:eeff:aabb:ccdd
// $ host aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us
// aabbccddeeffaabbccddeeffaabbccdd.112233445566778899aa112233445566.rbndr.us has IPv6 address 1122:3344:5566:7788:99aa:1122:3344:5566
//
//
#define __packed __attribute__((packed))
struct qname {
uint8_t len;
uint8_t label[32];
} __packed;
struct __packed root {
struct __packed {
uint8_t len; // 5
uint8_t data[5]; // 'r' 'b' 'n' 'd' 'r'
} domain;
struct __packed {
uint8_t len; // 2
uint8_t data[2]; // 'u' 's'
} tld;
uint8_t root; // 0
};
static const struct root kExpectedDomain = {
.domain = { 5, { 'r', 'b', 'n', 'd', 'r' } },
.tld = { 2, { 'u', 's' } },
.root = 0,
};
struct __packed header {
uint16_t id;
struct __packed {
unsigned rd : 1;
unsigned tc : 1;
unsigned aa : 1;
unsigned opcode : 4;
unsigned qr : 1;
unsigned rcode : 4;
unsigned ra : 1;
unsigned ad : 1;
unsigned z : 2;
} flags;
uint16_t qdcount;
uint16_t ancount;
uint16_t nscount;
uint16_t arcount;
struct __packed {
struct qname primary;
struct qname secondary;
struct root domain;
} labels;
uint16_t qtype;
uint16_t qclass;
struct __packed {
uint8_t flag;
uint8_t offset;
} ptr;
uint16_t type;
uint16_t class;
uint32_t ttl;
uint16_t rdlength;
struct in6_addr rdata;
} __packed;
bool parse_ip6_label(struct in6_addr *out, const uint8_t label[32])
{
char ip6addr[] = {
label[ 0], label[ 1], label[ 2], label[ 3], ':',
label[ 4], label[ 5], label[ 6], label[ 7], ':',
label[ 8], label[ 9], label[10], label[11], ':',
label[12], label[13], label[14], label[15], ':',
label[16], label[17], label[18], label[19], ':',
label[20], label[21], label[22], label[23], ':',
label[24], label[25], label[26], label[27], ':',
label[28], label[29], label[30], label[31], 0,
};
return inet_pton(AF_INET6, ip6addr, out) == 1;
}
int main(int argc, char **argv)
{
struct servent *domain;
struct passwd *nobody;
struct sockaddr_in server;
struct sockaddr_in address;
struct header reply;
struct header query;
socklen_t addrlen;
time_t querytime;
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
err(EXIT_FAILURE, "failed to create socket");
}
if ((domain = getservbyname("domain", "udp")) == NULL) {
errx(EXIT_FAILURE, "unable to lookup domain properties");
}
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = domain->s_port;
addrlen = sizeof(address);
nobody = getpwnam("nobody");
if (nobody == NULL) {
errx(EXIT_FAILURE, "unable to lookup unprivileged user");
}
// Start listening for queries.
if (bind(sockfd, (struct sockaddr *) &server, sizeof(server)) != 0) {
errx(EXIT_FAILURE, "unable to bind server");
}
// Privileges no longer needed, so change to a chroot directory and setuid.
if (chdir("/var/empty") != 0 || chroot(".") != 0) {
errx(EXIT_FAILURE, "unable to change root directory");
}
// Change user.
if (setgid(nobody->pw_gid) != 0 || setuid(nobody->pw_uid) != 0) {
errx(EXIT_FAILURE, "unable to change to unprivileged user");
}
while (true) {
char clientaddr[INET_ADDRSTRLEN];
memset(&query, 0, sizeof query);
memset(&reply, 0, sizeof reply);
// Attempt to read a DNS query.
if (recvfrom(sockfd, &query, sizeof query, 0, (struct sockaddr *) &address, &addrlen) < 0) {
warn("error reading query packet from network");
continue;
}
// Record time of request.
time(&querytime);
// Log query.
fprintf(stdout, "%s\t%s", inet_ntop(AF_INET, &address.sin_addr, clientaddr, sizeof(clientaddr)), ctime(&querytime));
// Some quick validation.
if (query.labels.primary.len != 32) {
warnx("ignoring query with %u byte primary label (must be 32)", query.labels.primary.len);
continue;
}
if (query.labels.secondary.len != 32) {
warnx("ignoring query with %u byte secondary label (must be 32)", query.labels.secondary.len);
continue;
}
if (memcmp(&query.labels.domain, &kExpectedDomain, sizeof kExpectedDomain) != 0) {
warnx("ignoring query for unrecognised domain (must be .rbndr.us)");
continue;
}
// Choose a random label to return based on ID.
if (!parse_ip6_label(&reply.rdata, (query.id & 1) ? query.labels.primary.label : query.labels.secondary.label)) {
warnx("client provided an invalid ip6 address, ignoring request");
continue;
}
// Duplicate the question.
memcpy(&reply.labels, &query.labels, sizeof reply.labels);
reply.id = query.id;
reply.flags.qr = true;
reply.flags.aa = true;
reply.ptr.flag = NS_CMPRSFLGS;
reply.ptr.offset = offsetof(struct header, labels);
reply.type = htons(ns_t_aaaa);
reply.class = htons(ns_c_in);
reply.ttl = htonl(1),
reply.rdlength = htons(sizeof reply.rdata);
reply.qtype = query.qtype;
reply.qclass = query.qclass;
reply.qdcount = query.qdcount;
reply.ancount = query.qdcount;
// I currently only support AAAA records.
if (reply.qtype != htons(ns_t_aaaa)) {
// We have no matching records for this type.
reply.ancount = 0;
// Send an empty response (stop after question)
if (sendto(sockfd, &reply, offsetof(struct header, ptr), 0, (struct sockaddr *) &address, addrlen) != offsetof(struct header, ptr)) {
warn("error sending response to query");
continue;
}
} else {
// Send full response.
if (sendto(sockfd, &reply, sizeof reply, 0, (struct sockaddr *) &address, addrlen) != sizeof(reply)) {
warn("error sending response to query");
continue;
}
}
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment