Created
November 12, 2013 16:34
-
-
Save taviso/7434095 to your computer and use it in GitHub Desktop.
Simple rebinding nameserver.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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