forked from taviso/rbndr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrebinder.c
251 lines (213 loc) · 7.71 KB
/
rebinder.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <netdb.h>
#include <time.h>
#include <pwd.h>
#include <err.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>
// This is a very very simple non-conforming nameserver for DNS rebinding
// attacks. Do not use this code for anything important.
//
// Tavis Ormandy <[email protected]>, January 2016
//lint -e754 -e716 -e801
#define __packed __attribute__((packed))
struct qname {
uint8_t len;
uint8_t label[8];
} __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 in_addr rdata;
} __packed;
//lint -efunc(713, parse_ip4_label) Loss of precision (initialization) (unsigned char to char)
bool parse_ip4_label(struct in_addr *out, const uint8_t label[8])
{
char ip4addr[] = {
'0', 'x', label[0], label[1],
label[2], label[3], label[4],
label[5], label[6], label[7],
0,
};
// Check for invalid characters, lowercase hexadecimal digits only.
if (strspn(ip4addr + 2, "0123456789abcdef") != 8)
return false;
return inet_aton(ip4addr, out) != 0;
}
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; //lint !e734
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 receiving query packet from network");
continue;
}
// Record time of request.
time(&querytime); //lint !e534
// Log query.
fprintf(stdout, "%s\t%s", inet_ntop(AF_INET, &address.sin_addr, clientaddr, sizeof(clientaddr)), ctime(&querytime));
// Duplicate the question into answer.
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); //lint !e507
reply.type = htons(ns_t_a); //lint !e641
reply.class = htons(ns_c_in); //lint !e641
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;
// Some quick validation.
if (query.qdcount != htons(1)) {
warnx("more than one question per query is not supported (%u queries)", ntohs(query.qdcount));
reply.flags.rcode = ns_r_notimpl; //lint !e641
goto error;
}
// Check that these labels are the right size (8 hexadecimal digits).
if (query.labels.primary.len != 8) {
warnx("query with %u byte primary label (must be 8)", query.labels.primary.len);
reply.flags.rcode = ns_r_nxdomain; //lint !e641
goto error;
}
if (query.labels.secondary.len != 8) {
warnx("query with %u byte secondary label (must be 8)", query.labels.secondary.len);
reply.flags.rcode = ns_r_nxdomain; //lint !e641
goto error;
}
// This service is for testing dns rebinding, not free hostnames!
if (memcmp(query.labels.primary.label, query.labels.secondary.label, 8) == 0) {
warnx("query with matching labels disallowed to discourage abuse");
reply.flags.rcode = ns_r_refused; //lint !e641
goto error;
}
// Make sure the root matches.
if (memcmp(&query.labels.domain, &kExpectedDomain, sizeof kExpectedDomain) != 0) {
warnx("query for unrecognised domain (must be .rbndr.us)");
reply.flags.rcode = ns_r_nxdomain; //lint !e641
goto error;
}
// I only support A queries.
if (query.qtype != htons(ns_t_a)) { //lint !e641
warnx("unsupported qtype in question, returning no answers (qtype %u)", ntohs(query.qtype));
goto error;
}
// Choose a random label to return based on ID.
if (!parse_ip4_label(&reply.rdata, (query.id & 1) ? query.labels.primary.label : query.labels.secondary.label)) {
warnx("client provided an invalid ip4 address, ignoring reqest");
reply.flags.rcode = ns_r_nxdomain; //lint !e641
goto error;
}
// Send response.
if (sendto(sockfd, &reply, sizeof reply, 0, (struct sockaddr *) &address, addrlen) != sizeof(reply)) { //lint !e737
warn("sendto failed to send response to query");
}
continue;
error:
reply.ancount = 0;
// Send an empty response (stop after question)
if (sendto(sockfd,
&reply,
offsetof(struct header, ptr), //lint !e507
0,
(struct sockaddr *) &address,
addrlen) != offsetof(struct header, ptr)) { //lint !e737 !e507
warn("sendto failed to sending error response to unsupported query");
}
}
//lint -unreachable
return 0;
}