dnsutils 0.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +212 -8
- data/dnsutils.gemspec +2 -2
- data/exe/dnslogger +3 -0
- data/exe/dnsmastermind +3 -0
- data/exe/dnstest +3 -0
- data/lib/dnslogger.rb +120 -72
- data/lib/dnsmastermind.rb +83 -81
- data/lib/dnstest.rb +90 -54
- data/lib/version.rb +4 -0
- metadata +10 -5
- data/lib/dnsutils.rb +0 -5
- data/lib/dnsutils/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24dbad9a1a131a3842e5d70b74b86d60cd1d728d
|
4
|
+
data.tar.gz: 5db0286584a47b840cc65350eff89618bfd62f99
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01e8e6a0e493718d70f04af4261435db657e2a9d6dceca62e1a63d62f2013393f3363991332c32ac7c5012b3be2eeace3adf7e6ee48819552c60d2223aeff84c
|
7
|
+
data.tar.gz: 9e5f19f0028d654f3c77462f30fbd5645ff500f15f664400a9a00585cc428c053c4b57c253fadb08fc698117cbb62d3f3d1cd2a1b8e9325f647b982c336585a3
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# Dnsutils
|
2
2
|
|
3
|
-
|
3
|
+
This package includes several handy DNS utilities that were written to
|
4
|
+
demonstrate the DNS library I wrote, [Nesser](https://github.com/iagox86/nesser)!
|
4
5
|
|
5
|
-
|
6
|
+
See the Usage section below for details.
|
6
7
|
|
7
8
|
## Installation
|
8
9
|
|
@@ -22,15 +23,218 @@ Or install it yourself as:
|
|
22
23
|
|
23
24
|
## Usage
|
24
25
|
|
25
|
-
|
26
|
+
This currently comes with several different utilities, and will likely have more
|
27
|
+
in the future!
|
28
|
+
|
29
|
+
### dnslogger
|
30
|
+
|
31
|
+
`dnslogger` is a simple DNS server that listens for queries, displays them, and
|
32
|
+
responds with :NXDomain (domain not found) by default.
|
33
|
+
|
34
|
+
The simplest usage is to run it without any arguments:
|
35
|
+
|
36
|
+
# dnslogger
|
37
|
+
Starting dnslogger (DnsUtils) 2.0.0 DNS server on 0.0.0.0:53
|
38
|
+
|
39
|
+
Then to make requests, either directly:
|
40
|
+
|
41
|
+
$ dig @localhost -t A example.org
|
42
|
+
|
43
|
+
; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost -t A example.org
|
44
|
+
; (1 server found)
|
45
|
+
;; global options: +cmd
|
46
|
+
;; Got answer:
|
47
|
+
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 48472
|
48
|
+
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
|
49
|
+
|
50
|
+
;; QUESTION SECTION:
|
51
|
+
;example.org. IN A
|
52
|
+
|
53
|
+
;; Query time: 1 msec
|
54
|
+
;; SERVER: 127.0.0.1#53535(127.0.0.1)
|
55
|
+
;; WHEN: Sun Jul 09 19:04:11 PDT 2017
|
56
|
+
;; MSG SIZE rcvd: 29
|
57
|
+
|
58
|
+
Or, if you're on an authoritative server, indirectly:
|
59
|
+
|
60
|
+
$ dig -t A test.skullseclabs.org
|
61
|
+
|
62
|
+
; <<>> DiG 9.10.3-P4-Ubuntu <<>> -t A test.skullseclabs.org
|
63
|
+
; (1 server found)
|
64
|
+
;; global options: +cmd
|
65
|
+
;; Got answer:
|
66
|
+
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 31731
|
67
|
+
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
|
68
|
+
|
69
|
+
;; QUESTION SECTION:
|
70
|
+
;test.skullseclabs.org. IN A
|
71
|
+
|
72
|
+
;; Query time: 0 msec
|
73
|
+
;; SERVER: 127.0.0.1#53535(127.0.0.1)
|
74
|
+
;; WHEN: Sun Jul 09 19:05:10 PDT 2017
|
75
|
+
;; MSG SIZE rcvd: 39
|
76
|
+
|
77
|
+
You'll notice that in both cases, the status came back as NXDOMAIN (name not
|
78
|
+
found), and no answers were given. That's expected! In the dnslogger window,
|
79
|
+
you will see the requests:
|
80
|
+
|
81
|
+
IN: Request for example.org [A IN]
|
82
|
+
OUT: Response for example.org [A IN]: error: :NXDomain (RCODE_NAME_ERROR)
|
83
|
+
IN: Request for test.skullseclabs.org [A IN]
|
84
|
+
OUT: Response for test.skullseclabs.org [A IN]: error: :NXDomain (RCODE_NAME_ERROR)
|
85
|
+
|
86
|
+
You can also provide records of various types that will always be returned (note
|
87
|
+
that I'm running this on a non-privileged port now, so I don't have to use
|
88
|
+
root):
|
89
|
+
|
90
|
+
$ dnslogger --port 53535 --A 1.2.3.4 --TXT 'this is txt'
|
91
|
+
Starting dnslogger (DnsUtils) 2.0.0 DNS server on 0.0.0.0:53535
|
92
|
+
|
93
|
+
And that will be returned:
|
94
|
+
|
95
|
+
$ dig @localhost +short -t A -p 53535 test.com
|
96
|
+
1.2.3.4
|
97
|
+
$ dig @localhost +short -t ANY -p 53535 test.com
|
98
|
+
1.2.3.4
|
99
|
+
"this is txt"
|
100
|
+
|
101
|
+
You can also provide a passthrough address, which will send all requests that
|
102
|
+
don't match one of the records you handle upstream:
|
103
|
+
|
104
|
+
$ dnslogger --port 53535 --A 1.2.3.4 --passthrough 8.8.8.8:53
|
105
|
+
Starting dnslogger (DnsUtils) 2.0.0 DNS server on 0.0.0.0:53535
|
106
|
+
|
107
|
+
And on the client:
|
108
|
+
|
109
|
+
$ dig @localhost +short -t A -p 53535 google.com
|
110
|
+
1.2.3.4
|
111
|
+
$ dig @localhost +short -t MX -p 53535 google.com
|
112
|
+
20 alt1.aspmx.l.google.com.
|
113
|
+
50 alt4.aspmx.l.google.com.
|
114
|
+
30 alt2.aspmx.l.google.com.
|
115
|
+
10 aspmx.l.google.com.
|
116
|
+
40 alt3.aspmx.l.google.com.
|
117
|
+
|
118
|
+
That can be useful for stealth, although I kinda prefer using the :NXDomain
|
119
|
+
approach, since it looks like literally nothing is there. :)
|
120
|
+
|
121
|
+
Note that this only makes sense to use if you expect somebody to query you
|
122
|
+
directly - you don't want to send your own authoritative requests upstream
|
123
|
+
because that would cause an infinite loop. So definitely use passthrough
|
124
|
+
sparingly. :)
|
125
|
+
|
126
|
+
### dnstest
|
127
|
+
|
128
|
+
`dnstest` is used to determine whether or not you're actually running on an
|
129
|
+
authoritative DNS server for the domain you specify.
|
130
|
+
|
131
|
+
This is done by generating a random subdomain, and prepending that in front of
|
132
|
+
the domain name. That domain is sent to an upstream DNS server (8.8.8.8 by
|
133
|
+
default), and we check if the request makes it back to us.
|
134
|
+
|
135
|
+
Typically, you'll simply run it with `--domain` set to your domain name:
|
136
|
+
|
137
|
+
# dnstest --domain skullseclabs.org
|
138
|
+
This script determines if you hold the authoritative record for a domain by
|
139
|
+
sending a request for a random subdomain. If the request goes through, you hold
|
140
|
+
the record!
|
141
|
+
--
|
142
|
+
Starting dnstest (DnsUtils) 2.0.0 DNS server on 0.0.0.0:53...
|
143
|
+
|
144
|
+
(Listening for yzpvshufndyqkhdd.skullseclabs.org)
|
145
|
+
(Requesting yzpvshufndyqkhdd.skullseclabs.org from 8.8.8.8:53)
|
146
|
+
Got a response from the DNS server, but didn't see the request... you probably don't have the authoritative server. :(
|
147
|
+
|
148
|
+
DNS RESPONSE: id=0x50f1, opcode = OPCODE_QUERY, flags = RD|RA, rcode = :ServFail (RCODE_SERVER_FAILURE)
|
149
|
+
, qdcount = 0x0001, ancount = 0x0000
|
150
|
+
Question: yzpvshufndyqkhdd.skullseclabs.org [A IN]
|
151
|
+
|
152
|
+
In that case, it failed because I'm not running on the authoritative server; in
|
153
|
+
fact, nothing is running there, so the upstream DNS server returned :ServFail.
|
154
|
+
|
155
|
+
If you try to request a domain that exists, but that you aren't the authority
|
156
|
+
for, you get a similar error:
|
157
|
+
|
158
|
+
# dnstest --domain google.com
|
159
|
+
This script determines if you hold the authoritative record for a domain by
|
160
|
+
sending a request for a random subdomain. If the request goes through, you hold
|
161
|
+
the record!
|
162
|
+
--
|
163
|
+
Starting dnstest (DnsUtils) 2.0.0 DNS server on 0.0.0.0:53...
|
164
|
+
|
165
|
+
(Listening for jqjvzkyhttbxoqlt.google.com)
|
166
|
+
(Requesting jqjvzkyhttbxoqlt.google.com from 8.8.8.8:53)
|
167
|
+
Got a response from the DNS server, but didn't see the request... you probably don't have the authoritative server. :(
|
168
|
+
|
169
|
+
DNS RESPONSE: id=0x2ef4, opcode = OPCODE_QUERY, flags = RD|RA, rcode = :NXDomain (RCODE_NAME_ERROR), qd
|
170
|
+
count = 0x0001, ancount = 0x0000
|
171
|
+
Question: jqjvzkyhttbxoqlt.google.com [A IN]
|
172
|
+
|
173
|
+
You'll notice that this time, the rcode was :NXDomain, aka, not found. And
|
174
|
+
finally, if you ARE running on the authoritative server, you'll get a happy
|
175
|
+
little message telling you so:
|
176
|
+
|
177
|
+
$ dnstest --domain skullseclabs.org
|
178
|
+
This script determines if you hold the authoritative record for a domain by
|
179
|
+
sending a request for a random subdomain. If the request goes through, you hold
|
180
|
+
the record!
|
181
|
+
--
|
182
|
+
Starting dnstest (DnsUtils) 2.0.0 DNS server on 0.0.0.0:53...
|
183
|
+
|
184
|
+
(Listening for blyonrwrwjpyugvc.google.com)
|
185
|
+
(Requesting blyonrwrwjpyugvc.google.com from localhost:53535)
|
186
|
+
(Received: blyonrwrwjpyugvc.google.com [A IN])
|
187
|
+
You have the authoritative server!
|
188
|
+
|
189
|
+
And that's pretty much all there is to it!
|
190
|
+
|
191
|
+
### dnsmastermind
|
192
|
+
|
193
|
+
`dnsmastermind` is a silly little game I wrote to demonstrate how DNS works.
|
194
|
+
|
195
|
+
It's based on the classic Mastermind game, where the player guesses a sequence
|
196
|
+
(in this case, of letters), and the server tells them how many are right, and
|
197
|
+
how many of them are in the right place.
|
198
|
+
|
199
|
+
I don't imagine any real-world use for it, so I'm not going to spend a lot of
|
200
|
+
time talking about usage. It essentially works the same way as other scripts.
|
201
|
+
|
202
|
+
I'll simply demonstrate it running locally on a non-privileged port (but it
|
203
|
+
will, like other scripts, work just fine indirectly if you're on the
|
204
|
+
authoritative server for the domain):
|
205
|
+
|
206
|
+
$ dnsmastermind --port 53535 --solution ABCD
|
207
|
+
Starting dnsmastermind (DnsUtils) 2.0.0 DNS server on 0.0.0.0:53535
|
208
|
+
|
209
|
+
The players can simply send a TXT request to get the instructions:
|
210
|
+
|
211
|
+
$ dig @localhost +short -t TXT -p 53535 instructions.test.com
|
212
|
+
"Instructions: guess the 4-character string: dig -t txt [guess].test.com! 'O' = correct, 'X' = correct, but wrong position"
|
213
|
+
|
214
|
+
And guess the solution the same way:
|
215
|
+
|
216
|
+
$ dig @localhost +short -p 53535 AAAA.test.com
|
217
|
+
"O"
|
218
|
+
$ dig @localhost +short -p 53535 AAAB.test.com
|
219
|
+
"OX"
|
220
|
+
$ dig @localhost +short -p 53535 ABCC.test.com
|
221
|
+
"OOO"
|
222
|
+
$ dig @localhost +short -p 53535 ABCD.test.com
|
223
|
+
"YOU WIN!!"
|
224
|
+
|
225
|
+
An 'O' represents the right character in the right place, and an 'X' represents
|
226
|
+
the right character in the wrong place.
|
26
227
|
|
27
|
-
##
|
228
|
+
## Contributing
|
28
229
|
|
29
|
-
|
230
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/iagox86/dnsutils
|
30
231
|
|
31
|
-
|
232
|
+
I'm also happy to take requests on other simple DNS utilities that can be
|
233
|
+
written with this library.
|
32
234
|
|
33
|
-
|
235
|
+
If you sent a pull request, please try to follow my style as much as possible!
|
236
|
+
There are no tests for these utilities, so be warned. :)
|
34
237
|
|
35
|
-
|
238
|
+
## Version history / changelog
|
36
239
|
|
240
|
+
* 2.0.0 - Initial port from the old DNS architecture
|
data/dnsutils.gemspec
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require '
|
4
|
+
require 'version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "dnsutils"
|
8
|
-
spec.version =
|
8
|
+
spec.version = DnsUtils::VERSION
|
9
9
|
spec.authors = ["iagox86"]
|
10
10
|
spec.email = ["ron-git@skullsecurity.org"]
|
11
11
|
|
data/exe/dnslogger
ADDED
data/exe/dnsmastermind
ADDED
data/exe/dnstest
ADDED
data/lib/dnslogger.rb
CHANGED
@@ -8,100 +8,148 @@
|
|
8
8
|
# Implements a stupidly simple DNS server.
|
9
9
|
##
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
require 'nesser'
|
12
|
+
require 'socket'
|
13
13
|
require 'trollop'
|
14
|
-
require '../server/libs/dnser'
|
15
14
|
|
16
|
-
|
17
|
-
NAME = "dnslogger"
|
18
|
-
VERSION = "v1.0.0"
|
15
|
+
require_relative 'version'
|
19
16
|
|
20
|
-
|
17
|
+
module DnsUtils
|
18
|
+
# version info
|
19
|
+
MY_NAME = "dnslogger (#{NAME}) #{VERSION}"
|
21
20
|
|
22
|
-
|
23
|
-
opts = Trollop::options do
|
24
|
-
version(NAME + " " + VERSION)
|
21
|
+
Thread.abort_on_exception = true
|
25
22
|
|
26
|
-
|
27
|
-
|
28
|
-
|
23
|
+
# Options
|
24
|
+
opts = Trollop::options do
|
25
|
+
version(MY_NAME)
|
29
26
|
|
30
|
-
|
31
|
-
|
27
|
+
opt :version, "Get the #{MY_NAME} version (spoiler alert)", :type => :boolean, :default => false
|
28
|
+
opt :host, "The ip address to listen on", :type => :string, :default => "0.0.0.0"
|
29
|
+
opt :port, "The port to listen on", :type => :integer, :default => 53
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
opt :CNAME, "Response to send back for 'CNAME' requests", :type => :string, :default => nil
|
36
|
-
opt :TXT, "Response to send back for 'TXT' requests", :type => :string, :default => nil
|
37
|
-
opt :MX, "Response to send back for 'MX' requests", :type => :string, :default => nil
|
38
|
-
opt :MX_PREF, "The preference order for the MX record", :type => :integer, :default => 10
|
39
|
-
opt :NS, "Response to send back for 'NS' requests", :type => :string, :default => nil
|
31
|
+
opt :passthrough, "Set to a host:port, and unanswered queries will be sent there", :type => :string, :default => nil
|
32
|
+
opt :packet_trace, "If enabled, print details about the packets", :type => :boolean, :default => false
|
40
33
|
|
41
|
-
|
42
|
-
|
34
|
+
opt :A, "Response to send back for 'A' requests (must be a dotted ip address)", :type => :string, :default => nil
|
35
|
+
opt :AAAA, "Response to send back for 'AAAA' requests (must be an ipv6 address)", :type => :string, :default => nil
|
36
|
+
opt :CNAME, "Response to send back for 'CNAME' requests (must be a dotted domain name)", :type => :string, :default => nil
|
37
|
+
opt :TXT, "Response to send back for 'TXT' requests",:type => :string, :default => nil
|
38
|
+
opt :MX, "Response to send back for 'MX' requests (must be a dotted domain name)", :type => :string, :default => nil
|
39
|
+
opt :MX_PREF, "The preference order for the MX record (must be a number)", :type => :integer, :default => 10
|
40
|
+
opt :NS, "Response to send back for 'NS' requests (must be a dotted domain name)", :type => :string, :default => nil
|
43
41
|
|
44
|
-
|
45
|
-
|
46
|
-
end
|
42
|
+
opt :ttl, "The TTL value to return", :type => :integer, :default => 60
|
43
|
+
end
|
47
44
|
|
48
|
-
|
45
|
+
if opts[:port] < 0 || opts[:port] > 65535
|
46
|
+
Trollop::die(:port, "must be a valid port (between 0 and 65535)")
|
47
|
+
end
|
49
48
|
|
50
|
-
pt_host = pt_port = nil
|
51
|
-
if
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
pt_host = pt_port = nil
|
50
|
+
if opts[:passthrough]
|
51
|
+
pt_host, pt_port = opts[:passthrough].split(/:/, 2)
|
52
|
+
if pt_port.nil?
|
53
|
+
pt_port = 53
|
54
|
+
else
|
55
|
+
pt_port = pt_port.to_i
|
56
|
+
end
|
57
|
+
end
|
56
58
|
|
57
|
-
|
59
|
+
puts("Starting #{MY_NAME} DNS server on #{opts[:host]}:#{opts[:port]}")
|
58
60
|
|
59
|
-
|
60
|
-
|
61
|
+
s = UDPSocket.new()
|
62
|
+
nesser = Nesser::Nesser.new(s: s, host: opts[:host], port: opts[:port]) do |transaction|
|
63
|
+
request = transaction.request
|
61
64
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
65
|
+
if(request.questions.length < 1)
|
66
|
+
puts("The request didn't ask any questions!")
|
67
|
+
next
|
68
|
+
end
|
66
69
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
70
|
+
if(request.questions.length > 1)
|
71
|
+
puts("The request asked multiple questions! This is super unusual, if you can reproduce, please report! I'd love to see an example of something that does this. :)")
|
72
|
+
next
|
73
|
+
end
|
71
74
|
|
72
|
-
|
75
|
+
question = request.questions[0]
|
73
76
|
|
74
|
-
|
77
|
+
# Display the long or short version of the request
|
78
|
+
puts("IN: " + request.to_s(brief: !opts[:packet_trace]))
|
75
79
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
80
|
+
# Get the type and class
|
81
|
+
type = Nesser::TYPES[question.type]
|
82
|
+
|
83
|
+
# If they provided a way to handle it, do that
|
84
|
+
rrs = []
|
85
|
+
if (type == 'A' || type == 'ANY') && opts[:A]
|
86
|
+
rrs << {
|
87
|
+
rr: Nesser::A.new(address: opts[:A]),
|
88
|
+
type: Nesser::TYPE_A,
|
89
|
+
}
|
83
90
|
end
|
84
91
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
92
|
+
if (type == 'AAAA' || type == 'ANY') && opts[:AAAA]
|
93
|
+
rrs << {
|
94
|
+
rr: Nesser::AAAA.new(address: opts[:AAAA]),
|
95
|
+
type: Nesser::TYPE_AAAA,
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
if (type == 'CNAME' || type == 'ANY') && opts[:CNAME]
|
100
|
+
rrs << {
|
101
|
+
rr: Nesser::CNAME.new(name: opts[:CNAME]),
|
102
|
+
type: Nesser::TYPE_CNAME,
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
if (type == 'TXT' || type == 'ANY') && opts[:TXT]
|
107
|
+
rrs << {
|
108
|
+
rr: Nesser::TXT.new(data: opts[:TXT]),
|
109
|
+
type: Nesser::TYPE_TXT,
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
if (type == 'MX' || type == 'ANY') && opts[:MX]
|
114
|
+
rrs << {
|
115
|
+
rr: Nesser::MX.new(name: opts[:MX], preference: opts[:MX_PREF]),
|
116
|
+
type: Nesser::TYPE_MX,
|
117
|
+
}
|
97
118
|
end
|
98
|
-
end
|
99
119
|
|
100
|
-
|
101
|
-
|
120
|
+
if (type == 'NS' || type == 'ANY') && opts[:NS]
|
121
|
+
rrs << {
|
122
|
+
rr: Nesser::NS.new(name: opts[:NS]),
|
123
|
+
type: Nesser::TYPE_NS,
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
# Translate the resource records into actual answers
|
128
|
+
answers = rrs.map() do |rr_pair|
|
129
|
+
Nesser::Answer.new(
|
130
|
+
name: question.name,
|
131
|
+
type: rr_pair[:type],
|
132
|
+
cls: question.cls,
|
133
|
+
ttl: opts[:ttl],
|
134
|
+
rr: rr_pair[:rr],
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Send back either the responses or an error code
|
139
|
+
if answers.length > 0
|
140
|
+
transaction.answer!(answers)
|
141
|
+
puts("OUT: " + transaction.response.to_s(brief: !opts[:packet_trace]))
|
142
|
+
else
|
143
|
+
if pt_host
|
144
|
+
transaction.passthrough!(host: pt_host, port: pt_port)
|
145
|
+
puts("OUT: [sent to #{pt_host}:#{pt_port}]")
|
146
|
+
else
|
147
|
+
transaction.error!(Nesser::RCODE_NAME_ERROR)
|
148
|
+
puts("OUT: " + transaction.response.to_s(brief: !opts[:packet_trace]))
|
149
|
+
end
|
150
|
+
end
|
102
151
|
end
|
103
152
|
|
153
|
+
# Wait for it to finish (never-ending, essentially)
|
154
|
+
nesser.wait()
|
104
155
|
end
|
105
|
-
|
106
|
-
# Wait for it to finish (never-ending, essentially)
|
107
|
-
dnser.wait()
|
data/lib/dnsmastermind.rb
CHANGED
@@ -8,108 +8,110 @@
|
|
8
8
|
# Simply checks if you're the authoritative server.
|
9
9
|
##
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
require 'nesser'
|
12
|
+
require 'socket'
|
13
13
|
require 'trollop'
|
14
|
-
require '../server/libs/dnser'
|
15
14
|
|
16
|
-
|
17
|
-
NAME = "dnsmastermind"
|
18
|
-
VERSION = "v1.0.0"
|
15
|
+
require_relative 'version'
|
19
16
|
|
20
|
-
|
17
|
+
module DnsUtils
|
18
|
+
MY_NAME = "dnsmastermind (#{NAME}) #{VERSION}"
|
21
19
|
|
22
|
-
|
23
|
-
opts = Trollop::options do
|
24
|
-
version(NAME + " " + VERSION)
|
20
|
+
Thread.abort_on_exception = true
|
25
21
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
opt :timeout, "The amount of time (seconds) to wait for a response", :type => :integer, :default => 10
|
30
|
-
opt :solution,"The answer; should be four letters, unless you're a jerk", :type => :string, :default => nil, :required => true
|
31
|
-
opt :win, "The message to display to winners", :type => :string, :default => "YOU WIN!!"
|
32
|
-
end
|
22
|
+
# Options
|
23
|
+
opts = Trollop::options do
|
24
|
+
version(MY_NAME)
|
33
25
|
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
opt :version, "Get the #{MY_NAME} version (spoiler alert!)", :type => :boolean, :default => false
|
27
|
+
opt :host, "The ip address to listen on", :type => :string, :default => "0.0.0.0"
|
28
|
+
opt :port, "The port to listen on", :type => :integer, :default => 53
|
29
|
+
opt :timeout, "The amount of time (seconds) to wait for a response", :type => :integer, :default => 10
|
30
|
+
opt :solution,"The answer; should be four letters, unless you're a jerk", :type => :string, :default => nil, :required => true
|
31
|
+
opt :win, "The message to display to winners", :type => :string, :default => "YOU WIN!!"
|
32
|
+
end
|
37
33
|
|
38
|
-
if(opts[:
|
39
|
-
|
40
|
-
end
|
41
|
-
solution = opts[:solution].upcase()
|
34
|
+
if(opts[:port] < 0 || opts[:port] > 65535)
|
35
|
+
Trollop::die :port, "must be a valid port (between 0 and 65535)"
|
36
|
+
end
|
42
37
|
|
43
|
-
|
38
|
+
if(opts[:solution].include?('.'))
|
39
|
+
Trollop::die :solution, "must not contain period; SHOULD only contain [a-z]{4} :)"
|
40
|
+
end
|
41
|
+
solution = opts[:solution].upcase()
|
44
42
|
|
45
|
-
|
43
|
+
puts("Starting #{MY_NAME} DNS server on #{opts[:host]}:#{opts[:port]}")
|
46
44
|
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
s = UDPSocket.new()
|
46
|
+
nesser = Nesser::Nesser.new(s: s, host: opts[:host], port: opts[:port]) do |transaction|
|
47
|
+
begin
|
48
|
+
request = transaction.request
|
50
49
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
50
|
+
if(request.questions.length < 1)
|
51
|
+
puts("The request didn't ask any questions!")
|
52
|
+
next
|
53
|
+
end
|
55
54
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
55
|
+
if(request.questions.length > 1)
|
56
|
+
puts("The request asked multiple questions! This is super unusual, if you can reproduce, please report!")
|
57
|
+
next
|
58
|
+
end
|
60
59
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
tmp_solution[i] = ""
|
80
|
-
guess[i] = ""
|
60
|
+
guess, domain = request.questions[0].name.split(/\./, 2)
|
61
|
+
guess.upcase!()
|
62
|
+
|
63
|
+
if(guess == solution)
|
64
|
+
puts("WINNER!!!")
|
65
|
+
answer = opts[:win]
|
66
|
+
elsif(guess.length == solution.length)
|
67
|
+
saved_guess = guess
|
68
|
+
tmp_solution = solution.chars.to_a()
|
69
|
+
guess = guess.chars.to_a()
|
70
|
+
answer = ""
|
71
|
+
|
72
|
+
0.upto(tmp_solution.length() - 1) do |i|
|
73
|
+
if(tmp_solution[i] == guess[i])
|
74
|
+
answer += "O"
|
75
|
+
tmp_solution[i] = ""
|
76
|
+
guess[i] = ""
|
77
|
+
end
|
81
78
|
end
|
82
|
-
end
|
83
79
|
|
84
|
-
|
85
|
-
|
86
|
-
|
80
|
+
guess.each do |c|
|
81
|
+
if(c == "")
|
82
|
+
next
|
83
|
+
end
|
84
|
+
|
85
|
+
if(tmp_solution.include?(c))
|
86
|
+
tmp_solution[tmp_solution.index(c)] = ""
|
87
|
+
answer += "X"
|
88
|
+
end
|
87
89
|
end
|
88
90
|
|
89
|
-
if(
|
90
|
-
|
91
|
-
answer += "X"
|
91
|
+
if(answer == "")
|
92
|
+
answer = "No correct character; keep trying!"
|
92
93
|
end
|
93
|
-
end
|
94
94
|
|
95
|
-
|
96
|
-
|
95
|
+
puts("Guess: #{saved_guess} => #{answer}")
|
96
|
+
else
|
97
|
+
puts("Invalid; sending instructions: #{guess}")
|
98
|
+
answer = "Instructions: guess the #{solution.length}-character string: dig -t txt [guess].#{domain}! 'O' = correct, 'X' = correct, but wrong position"
|
97
99
|
end
|
98
100
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
101
|
+
rr = Nesser::TXT.new(data: answer)
|
102
|
+
answer = Nesser::Answer.new(
|
103
|
+
name: request.questions[0].name,
|
104
|
+
type: Nesser::TYPE_TXT,
|
105
|
+
cls: Nesser::CLS_IN,
|
106
|
+
ttl: 10,
|
107
|
+
rr: rr,
|
108
|
+
)
|
109
|
+
transaction.answer!([answer])
|
110
|
+
rescue StandardError => e
|
111
|
+
puts("Error: #{e}")
|
112
|
+
puts(e.backtrace)
|
103
113
|
end
|
104
|
-
|
105
|
-
answer = DNSer::Packet::Answer.new(request.questions[0], DNSer::Packet::TYPE_TXT, DNSer::Packet::CLS_IN, 100, DNSer::Packet::TXT.new(answer))
|
106
|
-
|
107
|
-
transaction.add_answer(answer)
|
108
|
-
transaction.reply!()
|
109
|
-
rescue StandardError => e
|
110
|
-
puts("Error: #{e}")
|
111
|
-
puts(e.backtrace)
|
112
114
|
end
|
113
|
-
end
|
114
115
|
|
115
|
-
|
116
|
+
nesser.wait()
|
117
|
+
end
|
data/lib/dnstest.rb
CHANGED
@@ -8,75 +8,111 @@
|
|
8
8
|
# Simply checks if you're the authoritative server.
|
9
9
|
##
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
require 'nesser'
|
12
|
+
require 'socket'
|
13
13
|
require 'trollop'
|
14
|
-
require '../server/libs/dnser'
|
15
14
|
|
16
|
-
|
17
|
-
NAME = "dnstest"
|
18
|
-
VERSION = "v1.0.0"
|
15
|
+
require_relative 'version'
|
19
16
|
|
20
17
|
Thread.abort_on_exception = true
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
19
|
+
puts <<EOS
|
20
|
+
This script determines if you hold the authoritative record for a domain by
|
21
|
+
sending a request for a random subdomain. If the request goes through, you hold
|
22
|
+
the record!
|
23
|
+
--
|
24
|
+
EOS
|
25
|
+
|
26
|
+
module DnsUtils
|
27
|
+
MY_NAME = "dnstest (#{NAME}) #{VERSION}"
|
28
|
+
|
29
|
+
# Options
|
30
|
+
opts = Trollop::options do
|
31
|
+
version(MY_NAME)
|
32
|
+
|
33
|
+
opt :version, "Get the #{MY_NAME} version (spoiler alert)", :type => :boolean, :default => false
|
34
|
+
opt :host, "The ip address to listen on", :type => :string, :default => "0.0.0.0"
|
35
|
+
opt :port, "The port to listen on", :type => :integer, :default => 53
|
36
|
+
opt :domain, "The domain to check", :type => :string, :default => nil, :required => true
|
37
|
+
opt :timeout, "The amount of time (seconds) to wait for a response", :type => :integer, :default => 10
|
38
|
+
opt :upstream, "The upstream DNS server to send requests to, host:port", :type => :string, :default => "8.8.8.8:53"
|
39
|
+
end
|
32
40
|
|
33
|
-
if
|
34
|
-
|
35
|
-
end
|
41
|
+
if opts[:port] < 0 || opts[:port] > 65535
|
42
|
+
Trollop::die(:port, "must be a valid port (between 0 and 65535)")
|
43
|
+
end
|
36
44
|
|
37
|
-
if
|
38
|
-
|
39
|
-
end
|
45
|
+
if opts[:domain].nil?
|
46
|
+
Trollop::die :domain, "Domain is required!"
|
47
|
+
end
|
40
48
|
|
41
|
-
|
49
|
+
upstream_host, upstream_port = opts[:upstream].split(/:/, 2)
|
50
|
+
if upstream_host.nil?
|
51
|
+
upstream_host = '8.8.8.8'
|
52
|
+
end
|
53
|
+
if upstream_port.nil?
|
54
|
+
upstream_port = 53
|
55
|
+
else
|
56
|
+
upstream_port = upstream_port.to_i
|
57
|
+
end
|
42
58
|
|
43
|
-
|
59
|
+
puts("Starting #{MY_NAME} DNS server on #{opts[:host]}:#{opts[:port]}...")
|
60
|
+
puts()
|
44
61
|
|
45
|
-
|
62
|
+
# Generate a random subdomain
|
63
|
+
domain = (0...16).map { ('a'..'z').to_a[rand(26)] }.join() + "." + opts[:domain]
|
46
64
|
|
47
|
-
|
48
|
-
request = transaction.request
|
65
|
+
s = UDPSocket.new()
|
49
66
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
67
|
+
puts("(Listening for #{domain})")
|
68
|
+
Nesser::Nesser.new(s: s, host: opts[:host], port: opts[:port]) do |transaction|
|
69
|
+
request = transaction.request
|
54
70
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
71
|
+
if(request.questions.length < 1)
|
72
|
+
puts("The request didn't ask any questions!")
|
73
|
+
next
|
74
|
+
end
|
59
75
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
transaction.error!(DNSer::Packet::RCODE_NAME_ERROR)
|
65
|
-
exit()
|
66
|
-
else
|
67
|
-
puts("Received a different request: #{question}")
|
68
|
-
end
|
76
|
+
if(request.questions.length > 1)
|
77
|
+
puts("The request asked multiple questions! This is super unusual, if you can reproduce, please report!")
|
78
|
+
next
|
79
|
+
end
|
69
80
|
|
70
|
-
|
71
|
-
|
72
|
-
end
|
81
|
+
question = request.questions[0]
|
82
|
+
puts("(Received: #{question})")
|
73
83
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
end
|
84
|
+
# Check if it's the request that we sent
|
85
|
+
if(question.type == Nesser::TYPE_A && question.name == domain)
|
86
|
+
puts("You have the authoritative server!")
|
78
87
|
|
79
|
-
|
88
|
+
# Just sent back a name error
|
89
|
+
transaction.error!(Nesser::RCODE_NAME_ERROR)
|
90
|
+
exit()
|
91
|
+
else
|
92
|
+
puts("This is not the request we're looking for!")
|
93
|
+
transaction.error!(Nesser::RCODE_NAME_ERROR)
|
94
|
+
end
|
95
|
+
end
|
80
96
|
|
81
|
-
|
82
|
-
|
97
|
+
# Perform the request and ignore the result
|
98
|
+
puts("(Requesting #{domain} from #{upstream_host}:#{upstream_port})")
|
99
|
+
|
100
|
+
begin
|
101
|
+
result = Nesser::Nesser.query(
|
102
|
+
s: s,
|
103
|
+
hostname: domain,
|
104
|
+
server: upstream_host,
|
105
|
+
port: upstream_port,
|
106
|
+
type: Nesser::TYPE_A,
|
107
|
+
cls: Nesser::CLS_IN,
|
108
|
+
timeout: opts[:timeout],
|
109
|
+
)
|
110
|
+
puts("Got a response from the DNS server, but didn't see the request... you probably don't have the authoritative server. :(")
|
111
|
+
puts()
|
112
|
+
puts(result)
|
113
|
+
rescue Nesser::DnsException => e
|
114
|
+
puts("Request returned an error... you probably don't have the authoritative server. :(")
|
115
|
+
puts()
|
116
|
+
puts(e)
|
117
|
+
end
|
118
|
+
end
|
data/lib/version.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dnsutils
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- iagox86
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-07-
|
11
|
+
date: 2017-07-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -83,7 +83,10 @@ dependencies:
|
|
83
83
|
description:
|
84
84
|
email:
|
85
85
|
- ron-git@skullsecurity.org
|
86
|
-
executables:
|
86
|
+
executables:
|
87
|
+
- dnslogger
|
88
|
+
- dnsmastermind
|
89
|
+
- dnstest
|
87
90
|
extensions: []
|
88
91
|
extra_rdoc_files: []
|
89
92
|
files:
|
@@ -95,11 +98,13 @@ files:
|
|
95
98
|
- bin/console
|
96
99
|
- bin/setup
|
97
100
|
- dnsutils.gemspec
|
101
|
+
- exe/dnslogger
|
102
|
+
- exe/dnsmastermind
|
103
|
+
- exe/dnstest
|
98
104
|
- lib/dnslogger.rb
|
99
105
|
- lib/dnsmastermind.rb
|
100
106
|
- lib/dnstest.rb
|
101
|
-
- lib/
|
102
|
-
- lib/dnsutils/version.rb
|
107
|
+
- lib/version.rb
|
103
108
|
homepage: https://github.com/iagox86/dnsutils
|
104
109
|
licenses: []
|
105
110
|
metadata: {}
|
data/lib/dnsutils.rb
DELETED
data/lib/dnsutils/version.rb
DELETED