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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a9c80e817d2c54f1e244125038d9e18993cc5d5a
4
- data.tar.gz: 67348af902374c176dd947795bd2dc5c2a8ef752
3
+ metadata.gz: 24dbad9a1a131a3842e5d70b74b86d60cd1d728d
4
+ data.tar.gz: 5db0286584a47b840cc65350eff89618bfd62f99
5
5
  SHA512:
6
- metadata.gz: 7093a62fa5f7bda31ee273524488ce83df92507c8a454b8cec28506984b580487be01ffb11e85b079afe782146311ff13a5264ff10bfc109f1fb6bd731f9b024
7
- data.tar.gz: 9d76dd0cd7c4e4bd5f637e1aab6cdd187dc3ccebb8a88b9f93c96da5bbf4eca4b0b2beb3b110ea5cd7878083440b28ffe64efb1d764ab3d6d04319a29f49eb91
6
+ metadata.gz: 01e8e6a0e493718d70f04af4261435db657e2a9d6dceca62e1a63d62f2013393f3363991332c32ac7c5012b3be2eeace3adf7e6ee48819552c60d2223aeff84c
7
+ data.tar.gz: 9e5f19f0028d654f3c77462f30fbd5645ff500f15f664400a9a00585cc428c053c4b57c253fadb08fc698117cbb62d3f3d1cd2a1b8e9325f647b982c336585a3
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # Dnsutils
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/dnsutils`. To experiment with that code, run `bin/console` for an interactive prompt.
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
- TODO: Delete this and the text above, and describe your gem
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
- TODO: Write usage instructions here
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
- ## Development
228
+ ## Contributing
28
229
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
230
+ Bug reports and pull requests are welcome on GitHub at https://github.com/iagox86/dnsutils
30
231
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
232
+ I'm also happy to take requests on other simple DNS utilities that can be
233
+ written with this library.
32
234
 
33
- ## Contributing
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
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dnsutils.
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 'dnsutils/version'
4
+ require 'version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "dnsutils"
8
- spec.version = Dnsutils::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
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/dnslogger'
data/exe/dnsmastermind ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/dnsmastermind'
data/exe/dnstest ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/dnstest'
data/lib/dnslogger.rb CHANGED
@@ -8,100 +8,148 @@
8
8
  # Implements a stupidly simple DNS server.
9
9
  ##
10
10
 
11
- $LOAD_PATH << File.dirname(__FILE__) # A hack to make this work on 1.8/1.9
12
-
11
+ require 'nesser'
12
+ require 'socket'
13
13
  require 'trollop'
14
- require '../server/libs/dnser'
15
14
 
16
- # version info
17
- NAME = "dnslogger"
18
- VERSION = "v1.0.0"
15
+ require_relative 'version'
19
16
 
20
- Thread.abort_on_exception = true
17
+ module DnsUtils
18
+ # version info
19
+ MY_NAME = "dnslogger (#{NAME}) #{VERSION}"
21
20
 
22
- # Options
23
- opts = Trollop::options do
24
- version(NAME + " " + VERSION)
21
+ Thread.abort_on_exception = true
25
22
 
26
- opt :version, "Get the #{NAME} version", :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
23
+ # Options
24
+ opts = Trollop::options do
25
+ version(MY_NAME)
29
26
 
30
- opt :passthrough, "Set to a host:port, and unanswered queries will be sent there", :type => :string, :default => nil
31
- opt :packet_trace, "If enabled, print details about the packets", :type => :boolean, :default => false
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
- opt :A, "Response to send back for 'A' requests", :type => :string, :default => nil
34
- opt :AAAA, "Response to send back for 'AAAA' requests", :type => :string, :default => nil
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
- opt :ttl, "The TTL value to return", :type => :integer, :default => 60
42
- end
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
- if(opts[:port] < 0 || opts[:port] > 65535)
45
- Trollop::die :port, "must be a valid port (between 0 and 65535)"
46
- end
42
+ opt :ttl, "The TTL value to return", :type => :integer, :default => 60
43
+ end
47
44
 
48
- puts("Starting #{NAME} #{VERSION} DNS server on #{opts[:host]}:#{opts[:port]}")
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(opts[:passthrough])
52
- pt_host, pt_port = opts[:passthrough].split(/:/, 2)
53
- pt_port = pt_port || 53
54
- puts("Any queries without a specific answer will be sent to #{pt_host}:#{pt_port}")
55
- end
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
- dnser = DNSer.new(opts[:host], opts[:port])
59
+ puts("Starting #{MY_NAME} DNS server on #{opts[:host]}:#{opts[:port]}")
58
60
 
59
- dnser.on_request() do |transaction|
60
- request = transaction.request
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
- if(request.questions.length < 1)
63
- puts("The request didn't ask any questions!")
64
- next
65
- end
65
+ if(request.questions.length < 1)
66
+ puts("The request didn't ask any questions!")
67
+ next
68
+ end
66
69
 
67
- if(request.questions.length > 1)
68
- puts("The request asked multiple questions! This is super unusual, if you can reproduce, please report!")
69
- next
70
- end
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
- question = request.questions[0]
75
+ question = request.questions[0]
73
76
 
74
- puts(request.to_s(!opts[:packet_trace]))
77
+ # Display the long or short version of the request
78
+ puts("IN: " + request.to_s(brief: !opts[:packet_trace]))
75
79
 
76
- # If they provided a way to handle it, to that
77
- response = question.type_s ? opts[question.type_s.to_sym] : nil
78
- if(response)
79
- if(question.type == DNSer::Packet::TYPE_MX)
80
- answer = question.answer(opts[:ttl], response, opts[:MX_PREF])
81
- else
82
- answer = question.answer(opts[:ttl], response)
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
- transaction.add_answer(answer)
86
- puts(transaction.response.to_s(!opts[:packet_trace]))
87
- transaction.reply!()
88
- else
89
- if(pt_host)
90
- transaction.passthrough!(pt_host, pt_port, Proc.new() do |packet|
91
- puts(packet.to_s(!opts[:packet_trace]))
92
- end)
93
- puts("OUT: (...forwarding upstream...)")
94
- else
95
- transaction.error!(DNSer::Packet::RCODE_NAME_ERROR)
96
- puts(transaction.response.to_s(!opts[:packet_trace]))
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
- if(!transaction.sent)
101
- raise(StandardError, "Oops! We didn't send the response! Please file a bug")
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
- $LOAD_PATH << File.dirname(__FILE__) # A hack to make this work on 1.8/1.9
12
-
11
+ require 'nesser'
12
+ require 'socket'
13
13
  require 'trollop'
14
- require '../server/libs/dnser'
15
14
 
16
- # version info
17
- NAME = "dnsmastermind"
18
- VERSION = "v1.0.0"
15
+ require_relative 'version'
19
16
 
20
- Thread.abort_on_exception = true
17
+ module DnsUtils
18
+ MY_NAME = "dnsmastermind (#{NAME}) #{VERSION}"
21
19
 
22
- # Options
23
- opts = Trollop::options do
24
- version(NAME + " " + VERSION)
20
+ Thread.abort_on_exception = true
25
21
 
26
- opt :version, "Get the #{NAME} version", :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
22
+ # Options
23
+ opts = Trollop::options do
24
+ version(MY_NAME)
33
25
 
34
- if(opts[:port] < 0 || opts[:port] > 65535)
35
- Trollop::die :port, "must be a valid port (between 0 and 65535)"
36
- end
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[:solution].include?('.'))
39
- Trollop::die :solution, "must not contain period; SHOULD only contain [a-z]{4} :)"
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
- puts("Starting #{NAME} #{VERSION} DNS server on #{opts[:host]}:#{opts[:port]}")
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
- dnser = DNSer.new(opts[:host], opts[:port])
43
+ puts("Starting #{MY_NAME} DNS server on #{opts[:host]}:#{opts[:port]}")
46
44
 
47
- dnser.on_request() do |transaction|
48
- begin
49
- request = transaction.request
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
- if(request.questions.length < 1)
52
- puts("The request didn't ask any questions!")
53
- next
54
- end
50
+ if(request.questions.length < 1)
51
+ puts("The request didn't ask any questions!")
52
+ next
53
+ end
55
54
 
56
- if(request.questions.length > 1)
57
- puts("The request asked multiple questions! This is super unusual, if you can reproduce, please report!")
58
- next
59
- end
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
- if(request.questions[0].type != DNSer::Packet::TYPE_TXT)
62
- next
63
- end
64
- guess, domain = request.questions[0].name.split(/\./, 2)
65
- guess.upcase!()
66
-
67
- if(guess == solution)
68
- puts("WINNER!!!")
69
- answer = opts[:win]
70
- elsif(guess.length == solution.length)
71
- saved_guess = guess
72
- tmp_solution = solution.chars.to_a()
73
- guess = guess.chars.to_a()
74
- answer = ""
75
-
76
- 0.upto(tmp_solution.length() - 1) do |i|
77
- if(tmp_solution[i] == guess[i])
78
- answer += "O"
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
- guess.each do |c|
85
- if(c == "")
86
- next
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(tmp_solution.include?(c))
90
- tmp_solution[tmp_solution.index(c)] = ""
91
- answer += "X"
91
+ if(answer == "")
92
+ answer = "No correct character; keep trying!"
92
93
  end
93
- end
94
94
 
95
- if(answer == "")
96
- answer = "No correct character; keep trying!"
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
- puts("Guess: #{saved_guess} => #{answer}")
100
- else
101
- puts("Invalid; sending instructions: #{guess}")
102
- answer = "Instructions: guess the #{solution.length}-character string: dig -t txt [guess].#{domain}! 'O' = correct, 'X' = correct, but wrong position"
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
- dnser.wait()
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
- $LOAD_PATH << File.dirname(__FILE__) # A hack to make this work on 1.8/1.9
12
-
11
+ require 'nesser'
12
+ require 'socket'
13
13
  require 'trollop'
14
- require '../server/libs/dnser'
15
14
 
16
- # version info
17
- NAME = "dnstest"
18
- VERSION = "v1.0.0"
15
+ require_relative 'version'
19
16
 
20
17
  Thread.abort_on_exception = true
21
18
 
22
- # Options
23
- opts = Trollop::options do
24
- version(NAME + " " + VERSION)
25
-
26
- opt :version, "Get the #{NAME} version", :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 :domain, "The domain to check", :type => :string, :default => nil, :required => true
30
- opt :timeout, "The amount of time (seconds) to wait for a response", :type => :integer, :default => 10
31
- end
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(opts[:port] < 0 || opts[:port] > 65535)
34
- Trollop::die :port, "must be a valid port (between 0 and 65535)"
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(opts[:domain].nil?)
38
- Trollop::die :domain, "Domain is required!"
39
- end
45
+ if opts[:domain].nil?
46
+ Trollop::die :domain, "Domain is required!"
47
+ end
40
48
 
41
- puts("Starting #{NAME} #{VERSION} DNS server on #{opts[:host]}:#{opts[:port]}")
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
- domain = (0...16).map { ('a'..'z').to_a[rand(26)] }.join() + "." + opts[:domain]
59
+ puts("Starting #{MY_NAME} DNS server on #{opts[:host]}:#{opts[:port]}...")
60
+ puts()
44
61
 
45
- dnser = DNSer.new(opts[:host], opts[:port])
62
+ # Generate a random subdomain
63
+ domain = (0...16).map { ('a'..'z').to_a[rand(26)] }.join() + "." + opts[:domain]
46
64
 
47
- dnser.on_request() do |transaction|
48
- request = transaction.request
65
+ s = UDPSocket.new()
49
66
 
50
- if(request.questions.length < 1)
51
- puts("The request didn't ask any questions!")
52
- next
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
- 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
71
+ if(request.questions.length < 1)
72
+ puts("The request didn't ask any questions!")
73
+ next
74
+ end
59
75
 
60
- question = request.questions[0]
61
- puts("Received: #{question}")
62
- if(question.type == DNSer::Packet::TYPE_A && question.name == domain)
63
- puts("You have the authoritative server!")
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
- # Always respond with an error
71
- transaction.error!(DNSer::Packet::RCODE_NAME_ERROR)
72
- end
81
+ question = request.questions[0]
82
+ puts("(Received: #{question})")
73
83
 
74
- puts("Sending: #{domain}!")
75
- DNSer.query(domain, { :type => DNSer::Packet::TYPE_A }) do |response|
76
- # Do nothing
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
- sleep(opts[:timeout])
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
- puts("Request timed out... you probably don't have the authoritative server. :(")
82
- exit(0)
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
@@ -0,0 +1,4 @@
1
+ module DnsUtils
2
+ NAME = 'DnsUtils'
3
+ VERSION = '2.0.0'
4
+ end
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.1
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-09 00:00:00.000000000 Z
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/dnsutils.rb
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
@@ -1,5 +0,0 @@
1
- require "dnsutils/version"
2
-
3
- module Dnsutils
4
- # Your code goes here...
5
- end
@@ -1,3 +0,0 @@
1
- module Dnsutils
2
- VERSION = "0.0.1"
3
- end