ronin-web 0.3.0.rc1 → 1.0.0.beta2
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 +7 -0
- data/.editorconfig +11 -0
- data/.github/workflows/ruby.yml +31 -0
- data/.gitignore +13 -0
- data/.mailmap +1 -0
- data/.ruby-version +1 -0
- data/COPYING.txt +3 -3
- data/ChangeLog.md +115 -70
- data/Gemfile +42 -37
- data/README.md +160 -145
- data/Rakefile +12 -3
- data/bin/ronin-web +9 -17
- data/data/new/nokogiri.rb.erb +12 -0
- data/data/new/server.rb.erb +22 -0
- data/data/new/spider.rb.erb +26 -0
- data/data/new/webapp/.gitignore +15 -0
- data/data/new/webapp/.ruby-version.erb +1 -0
- data/data/new/webapp/Dockerfile.erb +11 -0
- data/data/new/webapp/Gemfile +6 -0
- data/data/new/webapp/app.rb.erb +15 -0
- data/data/new/webapp/config.ru +4 -0
- data/data/new/webapp/docker-compose.yml.erb +9 -0
- data/gemspec.yml +38 -16
- data/lib/ronin/web/cli/command.rb +36 -0
- data/lib/ronin/web/cli/commands/diff.rb +106 -0
- data/lib/ronin/web/cli/commands/html.rb +174 -0
- data/lib/ronin/web/cli/commands/irb.rb +56 -0
- data/lib/ronin/web/cli/commands/new/nokogiri.rb +85 -0
- data/lib/ronin/web/cli/commands/new/server.rb +96 -0
- data/lib/ronin/web/cli/commands/new/spider.rb +315 -0
- data/lib/ronin/web/cli/commands/new/webapp.rb +123 -0
- data/lib/ronin/web/cli/commands/new.rb +64 -0
- data/lib/ronin/web/cli/commands/reverse_proxy.rb +215 -0
- data/lib/ronin/web/cli/commands/server.rb +155 -0
- data/lib/ronin/web/cli/commands/spider.rb +822 -0
- data/lib/ronin/web/cli/ruby_shell.rb +50 -0
- data/lib/ronin/web/cli.rb +49 -0
- data/lib/ronin/web/html.rb +85 -0
- data/lib/ronin/web/mechanize.rb +34 -36
- data/lib/ronin/web/root.rb +27 -0
- data/lib/ronin/web/version.rb +7 -10
- data/lib/ronin/web/xml.rb +85 -0
- data/lib/ronin/web.rb +372 -13
- data/man/ronin-web-diff.1 +41 -0
- data/man/ronin-web-diff.1.md +30 -0
- data/man/ronin-web-html.1 +89 -0
- data/man/ronin-web-html.1.md +66 -0
- data/man/ronin-web-irb.1 +31 -0
- data/man/ronin-web-irb.1.md +22 -0
- data/man/ronin-web-new-nokogiri.1 +41 -0
- data/man/ronin-web-new-nokogiri.1.md +30 -0
- data/man/ronin-web-new-server.1 +45 -0
- data/man/ronin-web-new-server.1.md +33 -0
- data/man/ronin-web-new-spider.1 +173 -0
- data/man/ronin-web-new-spider.1.md +129 -0
- data/man/ronin-web-new-webapp.1 +53 -0
- data/man/ronin-web-new-webapp.1.md +39 -0
- data/man/ronin-web-new.1 +59 -0
- data/man/ronin-web-new.1.md +44 -0
- data/man/ronin-web-reverse-proxy.1 +63 -0
- data/man/ronin-web-reverse-proxy.1.md +47 -0
- data/man/ronin-web-server.1 +59 -0
- data/man/ronin-web-server.1.md +43 -0
- data/man/ronin-web-spider.1 +225 -0
- data/man/ronin-web-spider.1.md +168 -0
- data/man/ronin-web.1 +41 -0
- data/man/ronin-web.1.md +30 -0
- data/ronin-web.gemspec +39 -109
- data/spec/cli/ruby_shell_spec.rb +14 -0
- data/spec/html_spec.rb +43 -0
- data/spec/mechanize_spec.rb +72 -0
- data/spec/spec_helper.rb +5 -3
- data/spec/web_spec.rb +97 -0
- data/spec/xml_spec.rb +42 -0
- metadata +236 -224
- data/.gemtest +0 -0
- data/data/ronin/web/user_agents.yml +0 -247
- data/lib/ronin/network/mixins/web.rb +0 -258
- data/lib/ronin/web/config.rb +0 -34
- data/lib/ronin/web/extensions/nokogiri/xml/attr.rb +0 -47
- data/lib/ronin/web/extensions/nokogiri/xml/document.rb +0 -48
- data/lib/ronin/web/extensions/nokogiri/xml/element.rb +0 -57
- data/lib/ronin/web/extensions/nokogiri/xml/node.rb +0 -86
- data/lib/ronin/web/extensions/nokogiri/xml/text.rb +0 -47
- data/lib/ronin/web/extensions/nokogiri/xml.rb +0 -27
- data/lib/ronin/web/extensions/nokogiri.rb +0 -23
- data/lib/ronin/web/extensions.rb +0 -23
- data/lib/ronin/web/middleware/base.rb +0 -144
- data/lib/ronin/web/middleware/directories.rb +0 -179
- data/lib/ronin/web/middleware/files.rb +0 -144
- data/lib/ronin/web/middleware/filters/campaign_filter.rb +0 -77
- data/lib/ronin/web/middleware/filters/ip_filter.rb +0 -73
- data/lib/ronin/web/middleware/filters/path_filter.rb +0 -73
- data/lib/ronin/web/middleware/filters/referer_filter.rb +0 -71
- data/lib/ronin/web/middleware/filters/user_agent_filter.rb +0 -71
- data/lib/ronin/web/middleware/filters/vhost_filter.rb +0 -71
- data/lib/ronin/web/middleware/filters.rb +0 -28
- data/lib/ronin/web/middleware/helpers.rb +0 -145
- data/lib/ronin/web/middleware/proxy.rb +0 -265
- data/lib/ronin/web/middleware/proxy_request.rb +0 -262
- data/lib/ronin/web/middleware/request.rb +0 -79
- data/lib/ronin/web/middleware/response.rb +0 -33
- data/lib/ronin/web/middleware/router.rb +0 -167
- data/lib/ronin/web/middleware/rule.rb +0 -103
- data/lib/ronin/web/middleware.rb +0 -27
- data/lib/ronin/web/proxy/app.rb +0 -32
- data/lib/ronin/web/proxy/base.rb +0 -46
- data/lib/ronin/web/proxy/web.rb +0 -46
- data/lib/ronin/web/proxy.rb +0 -25
- data/lib/ronin/web/server/app.rb +0 -32
- data/lib/ronin/web/server/base.rb +0 -461
- data/lib/ronin/web/server/web.rb +0 -66
- data/lib/ronin/web/server.rb +0 -25
- data/lib/ronin/web/spider.rb +0 -120
- data/lib/ronin/web/user_agents.rb +0 -196
- data/lib/ronin/web/web.rb +0 -560
- data/spec/helpers/output.rb +0 -3
- data/spec/web/extensions/nokogiri_spec.rb +0 -38
- data/spec/web/helpers/rack_app.rb +0 -24
- data/spec/web/helpers/root/test1/index.html +0 -1
- data/spec/web/helpers/root/test1/test1.txt +0 -1
- data/spec/web/helpers/root/test1.txt +0 -1
- data/spec/web/helpers/root/test2/test2.txt +0 -1
- data/spec/web/helpers/root/test2.txt +0 -1
- data/spec/web/helpers/root/test3/test3.txt +0 -1
- data/spec/web/helpers/root/test3.txt +0 -1
- data/spec/web/helpers/root.rb +0 -15
- data/spec/web/mechanize_spec.rb +0 -62
- data/spec/web/middleware/directories_spec.rb +0 -86
- data/spec/web/middleware/files_spec.rb +0 -57
- data/spec/web/middleware/filters/campaign_filter_spec.rb +0 -30
- data/spec/web/middleware/filters/ip_filter_spec.rb +0 -25
- data/spec/web/middleware/filters/path_filter_spec.rb +0 -29
- data/spec/web/middleware/filters/referer_filter_spec.rb +0 -25
- data/spec/web/middleware/filters/user_agent_filter_spec.rb +0 -25
- data/spec/web/middleware/filters/vhost_filter_spec.rb +0 -23
- data/spec/web/middleware/proxy_spec.rb +0 -67
- data/spec/web/middleware/response_spec.rb +0 -20
- data/spec/web/middleware/router_spec.rb +0 -65
- data/spec/web/middleware/rule_spec.rb +0 -37
- data/spec/web/proxy/base_spec.rb +0 -8
- data/spec/web/server/base_spec.rb +0 -77
- data/spec/web/server/classes/public1/static1.txt +0 -1
- data/spec/web/server/classes/public2/static2.txt +0 -1
- data/spec/web/server/classes/sub_app.rb +0 -13
- data/spec/web/server/classes/test_app.rb +0 -20
- data/spec/web/user_agents_spec.rb +0 -56
- data/spec/web/web_spec.rb +0 -101
|
@@ -0,0 +1,822 @@
|
|
|
1
|
+
#
|
|
2
|
+
# ronin-web - A collection of useful web helper methods and commands.
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
|
|
5
|
+
#
|
|
6
|
+
# ronin-web is free software: you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# ronin-web is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU General Public License
|
|
17
|
+
# along with ronin-web. If not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
require 'ronin/web/cli/command'
|
|
21
|
+
require 'ronin/web/spider'
|
|
22
|
+
require 'ronin/web/spider/archive'
|
|
23
|
+
require 'ronin/web/spider/git_archive'
|
|
24
|
+
require 'ronin/support/network/http/user_agents'
|
|
25
|
+
|
|
26
|
+
require 'command_kit/colors'
|
|
27
|
+
require 'command_kit/printing/indent'
|
|
28
|
+
require 'command_kit/options/verbose'
|
|
29
|
+
|
|
30
|
+
module Ronin
|
|
31
|
+
module Web
|
|
32
|
+
class CLI
|
|
33
|
+
module Commands
|
|
34
|
+
#
|
|
35
|
+
# Spiders a website.
|
|
36
|
+
#
|
|
37
|
+
# ## Usage
|
|
38
|
+
#
|
|
39
|
+
# ronin-web spider [options] {--host HOST | --domain DOMAIN | --site URL}
|
|
40
|
+
#
|
|
41
|
+
# ## Options
|
|
42
|
+
#
|
|
43
|
+
# -v, --verbose Enables verbose output
|
|
44
|
+
# --open-timeout SECS Sets the connection open timeout
|
|
45
|
+
# --read-timeout SECS Sets the read timeout
|
|
46
|
+
# --ssl-timeout SECS Sets the SSL connection timeout
|
|
47
|
+
# --continue-timeout SECS Sets the continue timeout
|
|
48
|
+
# --keep-alive-timeout SECS Sets the connection keep alive timeout
|
|
49
|
+
# -P, --proxy PROXY Sets the proxy to use.
|
|
50
|
+
# -H, --header NAME: VALUE Sets a default header
|
|
51
|
+
# --host-header NAME=VALUE Sets a default header
|
|
52
|
+
# -u chrome_linux|chrome_macos|chrome_windows|chrome_iphone|chrome_ipad|chrome_android|firefox_linux|firefox_macos|firefox_windows|firefox_iphone|firefox_ipad|firefox_android|safari_macos|safari_iphone|safari_ipad|edge,
|
|
53
|
+
# --user-agent The User-Agent to use
|
|
54
|
+
# -U, --user-agent-string STRING The User-Agent string to use
|
|
55
|
+
# -R, --referer URL Sets the Referer URL
|
|
56
|
+
# --delay SECS Sets the delay in seconds between each request
|
|
57
|
+
# -l, --limit COUNT Only spiders up to COUNT pages
|
|
58
|
+
# -d, --max-depth DEPTH Only spiders up to max depth
|
|
59
|
+
# --enqueue URL Adds the URL to the queue
|
|
60
|
+
# --visited URL Marks the URL as previously visited
|
|
61
|
+
# --strip-fragments Enables/disables stripping the fragment component of every URL
|
|
62
|
+
# --strip-query Enables/disables stripping the query component of every URL
|
|
63
|
+
# --visit-host HOST Visit URLs with the matching host name
|
|
64
|
+
# --visit-hosts-like /REGEX/ Visit URLs with hostnames that match the REGEX
|
|
65
|
+
# --ignore-host HOST Ignore the host name
|
|
66
|
+
# --ignore-hosts-like /REGEX/ Ignore the host names matching the REGEX
|
|
67
|
+
# --visit-port PORT Visit URLs with the matching port number
|
|
68
|
+
# --visit-ports-like /REGEX/ Visit URLs with port numbers that match the REGEX
|
|
69
|
+
# --ignore-port PORT Ignore the port number
|
|
70
|
+
# --ignore-ports-like /REGEX/ Ignore the port numbers matching the REGEXP
|
|
71
|
+
# --visit-link URL Visit the URL
|
|
72
|
+
# --visit-links-like /REGEX/ Visit URLs that match the REGEX
|
|
73
|
+
# --ignore-link URL Ignore the URL
|
|
74
|
+
# --ignore-links-like /REGEX/ Ignore URLs matching the REGEX
|
|
75
|
+
# --visit-ext FILE_EXT Visit URLs with the matching file ext
|
|
76
|
+
# --visit-exts-like /REGEX/ Visit URLs with file exts that match the REGEX
|
|
77
|
+
# --ignore-ext FILE_EXT Ignore the URLs with the file ext
|
|
78
|
+
# --ignore-exts-like /REGEX/ Ignore URLs with file exts matching the REGEX
|
|
79
|
+
# -r, --robots Specifies whether to honor robots.txt
|
|
80
|
+
# --host HOST Spiders the specific HOST
|
|
81
|
+
# --domain DOMAIN Spiders the whole domain
|
|
82
|
+
# --site URL Spiders the website, starting at the URL
|
|
83
|
+
# --print-status Print the status codes for each URL
|
|
84
|
+
# --print-headers Print response headers for each URL
|
|
85
|
+
# --print-header NAME Prints a specific header
|
|
86
|
+
# --archive DIR Archive every visited page to the DIR
|
|
87
|
+
# --git-archive DIR Archive every visited page to the git repository
|
|
88
|
+
# -X, --xpath XPATH Evaluates the XPath on each HTML page
|
|
89
|
+
# -C, --css-path XPATH Evaluates the CSS-path on each HTML page
|
|
90
|
+
# -h, --help Print help information
|
|
91
|
+
#
|
|
92
|
+
# ## Examples
|
|
93
|
+
#
|
|
94
|
+
# ronin-web spider --host scanme.nmap.org
|
|
95
|
+
# ronin-web spider --domain nmap.org
|
|
96
|
+
# ronin-web spider --site https://scanme.nmap.org/
|
|
97
|
+
#
|
|
98
|
+
class Spider < Command
|
|
99
|
+
|
|
100
|
+
include CommandKit::Colors
|
|
101
|
+
include CommandKit::Printing::Indent
|
|
102
|
+
include CommandKit::Options::Verbose
|
|
103
|
+
|
|
104
|
+
usage '[options] {--host HOST | --domain DOMAIN | --site URL}'
|
|
105
|
+
|
|
106
|
+
option :open_timeout, value: {
|
|
107
|
+
type: Integer,
|
|
108
|
+
usage: 'SECS',
|
|
109
|
+
default: Spidr.open_timeout
|
|
110
|
+
},
|
|
111
|
+
desc: 'Sets the connection open timeout'
|
|
112
|
+
|
|
113
|
+
option :read_timeout, value: {
|
|
114
|
+
type: Integer,
|
|
115
|
+
usage: 'SECS',
|
|
116
|
+
default: Spidr.read_timeout
|
|
117
|
+
},
|
|
118
|
+
desc: 'Sets the read timeout'
|
|
119
|
+
|
|
120
|
+
option :ssl_timeout, value: {
|
|
121
|
+
type: Integer,
|
|
122
|
+
usage: 'SECS',
|
|
123
|
+
default: Spidr.ssl_timeout
|
|
124
|
+
},
|
|
125
|
+
desc: 'Sets the SSL connection timeout'
|
|
126
|
+
|
|
127
|
+
option :continue_timeout, value: {
|
|
128
|
+
type: Integer,
|
|
129
|
+
usage: 'SECS',
|
|
130
|
+
default: Spidr.continue_timeout
|
|
131
|
+
},
|
|
132
|
+
desc: 'Sets the continue timeout'
|
|
133
|
+
|
|
134
|
+
option :keep_alive_timeout, value: {
|
|
135
|
+
type: Integer,
|
|
136
|
+
usage: 'SECS',
|
|
137
|
+
default: Spidr.keep_alive_timeout
|
|
138
|
+
},
|
|
139
|
+
desc: 'Sets the connection keep alive timeout'
|
|
140
|
+
|
|
141
|
+
option :proxy, short: '-P',
|
|
142
|
+
value: {
|
|
143
|
+
type: String,
|
|
144
|
+
usage: 'PROXY'
|
|
145
|
+
},
|
|
146
|
+
desc: 'Sets the proxy to use'
|
|
147
|
+
|
|
148
|
+
option :header, short: '-H',
|
|
149
|
+
value: {
|
|
150
|
+
type: /\A[^\s:]+:.*\z/,
|
|
151
|
+
usage: 'NAME: VALUE'
|
|
152
|
+
},
|
|
153
|
+
desc: 'Sets a default header' do |header|
|
|
154
|
+
name, value = header.split(/:\s*/,2)
|
|
155
|
+
|
|
156
|
+
@default_headers[name] = value
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
option :host_header, value: {
|
|
160
|
+
type: /\A[^\s=]+=[^\s=]+\z/,
|
|
161
|
+
usage: 'NAME=VALUE'
|
|
162
|
+
},
|
|
163
|
+
desc: 'Sets a default header' do |name_value|
|
|
164
|
+
name, value = name_value.split('=',2)
|
|
165
|
+
|
|
166
|
+
@host_headers[name] = value
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
option :user_agent, value: {
|
|
170
|
+
type: String,
|
|
171
|
+
usage: 'USER-AGENT'
|
|
172
|
+
},
|
|
173
|
+
desc: 'Sets the User-Agent string'
|
|
174
|
+
|
|
175
|
+
option :user_agent_string, short: '-U',
|
|
176
|
+
value: {
|
|
177
|
+
type: String,
|
|
178
|
+
usage: 'STRING'
|
|
179
|
+
},
|
|
180
|
+
desc: 'The User-Agent string to use' do |ua|
|
|
181
|
+
@user_agent = ua
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
option :user_agent, short: '-u',
|
|
185
|
+
value: {
|
|
186
|
+
type: Support::Network::HTTP::UserAgents::ALIASES.keys
|
|
187
|
+
},
|
|
188
|
+
desc: 'The User-Agent to use' do |name|
|
|
189
|
+
@user_agent = name
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
option :referer, short: '-R',
|
|
193
|
+
value: {
|
|
194
|
+
type: String,
|
|
195
|
+
usage: 'URL'
|
|
196
|
+
},
|
|
197
|
+
desc: 'Sets the Referer URL'
|
|
198
|
+
|
|
199
|
+
option :delay, short: '-d',
|
|
200
|
+
value: {
|
|
201
|
+
type: Numeric,
|
|
202
|
+
usage: 'SECS'
|
|
203
|
+
},
|
|
204
|
+
desc: 'Sets the delay in seconds between each request'
|
|
205
|
+
|
|
206
|
+
option :limit, short: '-l',
|
|
207
|
+
value: {
|
|
208
|
+
type: Integer,
|
|
209
|
+
usage: 'COUNT'
|
|
210
|
+
},
|
|
211
|
+
desc: 'Only spiders up to COUNT pages'
|
|
212
|
+
|
|
213
|
+
option :max_depth, short: '-d',
|
|
214
|
+
value: {
|
|
215
|
+
type: Integer,
|
|
216
|
+
usage: 'DEPTH',
|
|
217
|
+
},
|
|
218
|
+
desc: 'Only spiders up to max depth'
|
|
219
|
+
|
|
220
|
+
option :enqueue, value: {
|
|
221
|
+
type: String,
|
|
222
|
+
usage: 'URL'
|
|
223
|
+
},
|
|
224
|
+
desc: 'Adds the URL to the queue' do |url|
|
|
225
|
+
@queue << url
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
option :visited, value: {
|
|
229
|
+
type: String,
|
|
230
|
+
usage: 'URL'
|
|
231
|
+
},
|
|
232
|
+
desc: 'Marks the URL as previously visited' do |url|
|
|
233
|
+
@history << url
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
option :strip_fragments, desc: 'Enables/disables stripping the fragment component of every URL'
|
|
237
|
+
|
|
238
|
+
option :strip_query, desc: 'Enables/disables stripping the query component of every URL'
|
|
239
|
+
|
|
240
|
+
option :visit_host, value: {
|
|
241
|
+
type: String,
|
|
242
|
+
usage: 'HOST'
|
|
243
|
+
},
|
|
244
|
+
desc: 'Visit URLs with the matching host name' do |host|
|
|
245
|
+
@visit_hosts << host
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
option :visit_hosts_like, value: {
|
|
249
|
+
type: Regexp,
|
|
250
|
+
usage: '/REGEX/'
|
|
251
|
+
},
|
|
252
|
+
desc: 'Visit URLs with hostnames that match the REGEX' do |regex|
|
|
253
|
+
@visit_hosts << regex
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
option :ignore_host, value: {
|
|
257
|
+
type: String,
|
|
258
|
+
usage: 'HOST'
|
|
259
|
+
},
|
|
260
|
+
desc: 'Ignore the host name' do |host|
|
|
261
|
+
@ignore_hosts << host
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
option :ignore_hosts_like, value: {
|
|
265
|
+
type: Regexp,
|
|
266
|
+
usage: '/REGEX/'
|
|
267
|
+
},
|
|
268
|
+
desc: 'Ignore the host names matching the REGEX' do |regex|
|
|
269
|
+
@ignore_hosts << regex
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
option :visit_port, value: {
|
|
273
|
+
type: Integer,
|
|
274
|
+
usage: 'PORT'
|
|
275
|
+
},
|
|
276
|
+
desc: 'Visit URLs with the matching port number' do |port|
|
|
277
|
+
@visit_ports << port
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
option :visit_ports_like, value: {
|
|
281
|
+
type: Regexp,
|
|
282
|
+
usage: '/REGEX/'
|
|
283
|
+
},
|
|
284
|
+
desc: 'Visit URLs with port numbers that match the REGEX' do |regex|
|
|
285
|
+
@visit_ports << regex
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
option :ignore_port, value: {
|
|
289
|
+
type: Integer,
|
|
290
|
+
usage: 'PORT'
|
|
291
|
+
},
|
|
292
|
+
desc: 'Ignore the port number' do |port|
|
|
293
|
+
@ignore_ports << port
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
option :ignore_ports_like, value: {
|
|
297
|
+
type: Regexp,
|
|
298
|
+
usage: '/REGEX/'
|
|
299
|
+
},
|
|
300
|
+
desc: 'Ignore the port numbers matching the REGEXP' do |regex|
|
|
301
|
+
@ignore_ports << regex
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
option :visit_link, value: {
|
|
305
|
+
type: String,
|
|
306
|
+
usage: 'URL'
|
|
307
|
+
},
|
|
308
|
+
desc: 'Visit the URL' do |link|
|
|
309
|
+
@visit_links << link
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
option :visit_links_like, value: {
|
|
313
|
+
type: Regexp,
|
|
314
|
+
usage: '/REGEX/'
|
|
315
|
+
},
|
|
316
|
+
desc: 'Visit URLs that match the REGEX' do |regex|
|
|
317
|
+
@visit_links << regex
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
option :ignore_link, value: {
|
|
321
|
+
type: String,
|
|
322
|
+
usage: 'URL'
|
|
323
|
+
},
|
|
324
|
+
desc: 'Ignore the URL' do |link|
|
|
325
|
+
@ignore_links << link
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
option :ignore_links_like, value: {
|
|
329
|
+
type: Regexp,
|
|
330
|
+
usage: '/REGEX/'
|
|
331
|
+
},
|
|
332
|
+
desc: 'Ignore URLs matching the REGEX' do |regex|
|
|
333
|
+
@ignore_links << regex
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
option :visit_ext, value: {
|
|
337
|
+
type: String,
|
|
338
|
+
usage: 'FILE_EXT'
|
|
339
|
+
},
|
|
340
|
+
desc: 'Visit URLs with the matching file ext' do |ext|
|
|
341
|
+
@visit_exts << ext
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
option :visit_exts_like, value: {
|
|
345
|
+
type: Regexp,
|
|
346
|
+
usage: '/REGEX/'
|
|
347
|
+
},
|
|
348
|
+
desc: 'Visit URLs with file exts that match the REGEX' do |regex|
|
|
349
|
+
@visit_exts << regex
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
option :ignore_ext, value: {
|
|
353
|
+
type: String,
|
|
354
|
+
usage: 'FILE_EXT'
|
|
355
|
+
},
|
|
356
|
+
desc: 'Ignore the URLs with the file ext' do |ext|
|
|
357
|
+
@ignore_exts << ext
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
option :ignore_exts_like, value: {
|
|
361
|
+
type: Regexp,
|
|
362
|
+
usage: '/REGEX/'
|
|
363
|
+
},
|
|
364
|
+
desc: 'Ignore URLs with file exts matching the REGEX' do |regex|
|
|
365
|
+
@ignore_exts << regex
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
option :robots, short: '-r',
|
|
369
|
+
desc: 'Specifies whether to honor robots.txt'
|
|
370
|
+
|
|
371
|
+
option :host, value: {
|
|
372
|
+
type: String,
|
|
373
|
+
usage: 'HOST'
|
|
374
|
+
},
|
|
375
|
+
desc: 'Spiders the specific HOST'
|
|
376
|
+
|
|
377
|
+
option :domain, value: {
|
|
378
|
+
type: String,
|
|
379
|
+
usage: 'DOMAIN',
|
|
380
|
+
},
|
|
381
|
+
desc: 'Spiders the whole domain'
|
|
382
|
+
|
|
383
|
+
option :site, value: {
|
|
384
|
+
type: String,
|
|
385
|
+
usage: 'URL'
|
|
386
|
+
},
|
|
387
|
+
desc: 'Spiders the website, starting at the URL'
|
|
388
|
+
|
|
389
|
+
option :print_verbose, desc: 'Print the status codes for each URL'
|
|
390
|
+
|
|
391
|
+
option :print_headers, desc: 'Print response headers for each URL'
|
|
392
|
+
|
|
393
|
+
option :print_header, value: {
|
|
394
|
+
type: String,
|
|
395
|
+
usage: 'NAME'
|
|
396
|
+
},
|
|
397
|
+
desc: 'Prints a specific header'
|
|
398
|
+
|
|
399
|
+
option :archive, value: {
|
|
400
|
+
type: String,
|
|
401
|
+
usage: 'DIR'
|
|
402
|
+
},
|
|
403
|
+
desc: 'Archive every visited page to the DIR'
|
|
404
|
+
|
|
405
|
+
option :git_archive, value: {
|
|
406
|
+
type: String,
|
|
407
|
+
usage: 'DIR'
|
|
408
|
+
},
|
|
409
|
+
desc: 'Archive every visited page to the git repository'
|
|
410
|
+
|
|
411
|
+
option :xpath, short: '-X',
|
|
412
|
+
value: {
|
|
413
|
+
type: String,
|
|
414
|
+
usage: 'XPATH'
|
|
415
|
+
},
|
|
416
|
+
desc: 'Evaluates the XPath on each HTML page'
|
|
417
|
+
|
|
418
|
+
option :css_path, short: '-C',
|
|
419
|
+
value: {
|
|
420
|
+
type: String,
|
|
421
|
+
usage: 'XPATH'
|
|
422
|
+
},
|
|
423
|
+
desc: 'Evaluates the CSS-path on each HTML page'
|
|
424
|
+
|
|
425
|
+
option :print_hosts, desc: 'Print all discovered hostnames'
|
|
426
|
+
|
|
427
|
+
option :print_certs, desc: 'Print all encountered SSL/TLS certificates'
|
|
428
|
+
|
|
429
|
+
option :save_certs, desc: 'Saves all encountered SSL/TLS certificates'
|
|
430
|
+
|
|
431
|
+
option :print_js_strings, desc: 'Print all JavaScript strings'
|
|
432
|
+
|
|
433
|
+
option :print_html_comments, desc: 'Print HTML comments'
|
|
434
|
+
|
|
435
|
+
option :print_js_comments, desc: 'Print JavaScript comments'
|
|
436
|
+
|
|
437
|
+
option :print_comments, desc: 'Print all HTML and JavaScript comments'
|
|
438
|
+
|
|
439
|
+
description 'Spiders a website'
|
|
440
|
+
|
|
441
|
+
examples [
|
|
442
|
+
"--host scanme.nmap.org",
|
|
443
|
+
"--domain nmap.org",
|
|
444
|
+
"--site https://scanme.nmap.org/"
|
|
445
|
+
]
|
|
446
|
+
|
|
447
|
+
man_page 'ronin-web-spider.1'
|
|
448
|
+
|
|
449
|
+
# The default HTTP headers to send with every request.
|
|
450
|
+
#
|
|
451
|
+
# @return [Hash{String => String}]
|
|
452
|
+
attr_reader :default_headers
|
|
453
|
+
|
|
454
|
+
# The mapping of custom `Host` headers.
|
|
455
|
+
#
|
|
456
|
+
# @return [Hash{String => String}]
|
|
457
|
+
attr_reader :host_headers
|
|
458
|
+
|
|
459
|
+
# The pre-existing queue of URLs to start spidering with.
|
|
460
|
+
#
|
|
461
|
+
# @return [Array<String>]
|
|
462
|
+
attr_reader :queue
|
|
463
|
+
|
|
464
|
+
# The pre-existing of previously visited URLs to start spidering with.
|
|
465
|
+
#
|
|
466
|
+
# @return [Array<String>]
|
|
467
|
+
attr_reader :history
|
|
468
|
+
|
|
469
|
+
# The schemes to visit.
|
|
470
|
+
#
|
|
471
|
+
# @return [Array<String>]
|
|
472
|
+
attr_reader :visit_schemes
|
|
473
|
+
|
|
474
|
+
# The hosts to visit.
|
|
475
|
+
#
|
|
476
|
+
# @return [Array<String, Regexp>]
|
|
477
|
+
attr_reader :visit_hosts
|
|
478
|
+
|
|
479
|
+
# The port numbers to visit.
|
|
480
|
+
#
|
|
481
|
+
# @return [Array<Integer, Regexp>]
|
|
482
|
+
attr_reader :visit_ports
|
|
483
|
+
|
|
484
|
+
# The links to visit.
|
|
485
|
+
#
|
|
486
|
+
# @return [Array<String, Regexp>]
|
|
487
|
+
attr_reader :visit_links
|
|
488
|
+
|
|
489
|
+
# The URL file extensions to visit.
|
|
490
|
+
#
|
|
491
|
+
# @return [Array<String, Regexp>]
|
|
492
|
+
attr_reader :visit_exts
|
|
493
|
+
|
|
494
|
+
# The hosts to ignore.
|
|
495
|
+
#
|
|
496
|
+
# @return [Array<String, Regexp>]
|
|
497
|
+
attr_reader :ignore_hosts
|
|
498
|
+
|
|
499
|
+
# The port numbers to ignore.
|
|
500
|
+
#
|
|
501
|
+
# @return [Array<Integer, Regexp>]
|
|
502
|
+
attr_reader :ignore_ports
|
|
503
|
+
|
|
504
|
+
# The links to ignore.
|
|
505
|
+
#
|
|
506
|
+
# @return [Array<String, Regexp>]
|
|
507
|
+
attr_reader :ignore_links
|
|
508
|
+
|
|
509
|
+
# The URL file extensions to ignore.
|
|
510
|
+
#
|
|
511
|
+
# @return [Array<String, Regexp>]
|
|
512
|
+
attr_reader :ignore_exts
|
|
513
|
+
|
|
514
|
+
#
|
|
515
|
+
# Initializes the spider command.
|
|
516
|
+
#
|
|
517
|
+
# @param [Hash{Symbol => Object}] kwargs
|
|
518
|
+
# Additional keyword arguments.
|
|
519
|
+
#
|
|
520
|
+
def initialize(**kwargs)
|
|
521
|
+
super(**kwargs)
|
|
522
|
+
|
|
523
|
+
@default_headers = {}
|
|
524
|
+
@host_headers = {}
|
|
525
|
+
|
|
526
|
+
@queue = []
|
|
527
|
+
@history = []
|
|
528
|
+
|
|
529
|
+
@visit_schemes = []
|
|
530
|
+
@visit_hosts = []
|
|
531
|
+
@visit_ports = []
|
|
532
|
+
@visit_links = []
|
|
533
|
+
@visit_exts = []
|
|
534
|
+
|
|
535
|
+
@ignore_hosts = []
|
|
536
|
+
@ignore_ports = []
|
|
537
|
+
@ignore_links = []
|
|
538
|
+
@ignore_exts = []
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
#
|
|
542
|
+
# Runs the `ronin-web spider` command.
|
|
543
|
+
#
|
|
544
|
+
def run
|
|
545
|
+
archive = if options[:archive]
|
|
546
|
+
Web::Spider::Archive.open(options[:archive])
|
|
547
|
+
elsif options[:git_archive]
|
|
548
|
+
Web::Spider::GitArchive.open(options[:git_archive])
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
agent = new_agent do |agent|
|
|
552
|
+
agent.every_page do |page|
|
|
553
|
+
print_page(page)
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
agent.every_failed_url do |url|
|
|
557
|
+
print_verbose "failed to request #{url}"
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
if options[:print_hosts]
|
|
561
|
+
agent.every_host do |host|
|
|
562
|
+
print_verbose "spidering new host #{host}"
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
if options[:print_certs]
|
|
567
|
+
agent.every_cert do |cert|
|
|
568
|
+
print_verbose "encountered new certificate for #{cert.subject.common_name}"
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
if options[:print_js_strings]
|
|
573
|
+
agent.every_js_string do |string|
|
|
574
|
+
print_content string
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
if options[:print_html_comments]
|
|
579
|
+
agent.every_html_comment do |comment|
|
|
580
|
+
print_content comment
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
if options[:print_js_comments]
|
|
585
|
+
agent.every_js_comment do |comment|
|
|
586
|
+
print_content comment
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
if options[:print_comments]
|
|
591
|
+
agent.every_comment do |comment|
|
|
592
|
+
print_content comment
|
|
593
|
+
end
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
if archive
|
|
597
|
+
agent.every_ok_page do |page|
|
|
598
|
+
archive.write(page.url,page.body)
|
|
599
|
+
end
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
if options[:git_archive]
|
|
604
|
+
archive.commit "Updated #{Time.now}"
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
if options[:print_hosts]
|
|
608
|
+
puts
|
|
609
|
+
puts "Spidered the following hosts:"
|
|
610
|
+
puts
|
|
611
|
+
|
|
612
|
+
indent do
|
|
613
|
+
agent.visited_hosts.each do |host|
|
|
614
|
+
puts host
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
if options[:print_certs]
|
|
620
|
+
puts
|
|
621
|
+
puts "Discovered the following certs:"
|
|
622
|
+
puts
|
|
623
|
+
|
|
624
|
+
agent.collected_certs.each do |cert|
|
|
625
|
+
puts cert
|
|
626
|
+
puts
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
#
|
|
632
|
+
# Creates a new web spider agent.
|
|
633
|
+
#
|
|
634
|
+
# @yield [agent]
|
|
635
|
+
# The given block will be given the newly created and configured
|
|
636
|
+
# web spider agent.
|
|
637
|
+
#
|
|
638
|
+
# @yieldparam [Ronin::Web::Spider::Agent] agent
|
|
639
|
+
# The newly created web spider agent.
|
|
640
|
+
#
|
|
641
|
+
# @return [Ronin::Web::Spider::Agent]
|
|
642
|
+
# The newly created web spider agent, after the agent has completed
|
|
643
|
+
# it's spidering.
|
|
644
|
+
#
|
|
645
|
+
def new_agent(&block)
|
|
646
|
+
if options[:host]
|
|
647
|
+
Web::Spider.host(options[:host],**agent_kwargs,&block)
|
|
648
|
+
elsif options[:domain]
|
|
649
|
+
Web::Spider.domain(options[:domain],**agent_kwargs,&block)
|
|
650
|
+
elsif options[:site]
|
|
651
|
+
Web::Spider.site(options[:site],**agent_kwargs,&block)
|
|
652
|
+
else
|
|
653
|
+
print_error "must specify --host, --domain, or --site"
|
|
654
|
+
exit(-1)
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
#
|
|
659
|
+
# Builds keyword arguments for `Ronin::Web::Spider::Agent#initialize`.
|
|
660
|
+
#
|
|
661
|
+
# @return [Hash{Symbol => Object}]
|
|
662
|
+
# The keyword arguments for `Ronin::Web::Spider::Agent#initialize`.
|
|
663
|
+
#
|
|
664
|
+
def agent_kwargs
|
|
665
|
+
kwargs = {}
|
|
666
|
+
kwargs[:proxy] = options[:proxy] if options[:proxy]
|
|
667
|
+
|
|
668
|
+
unless @default_headers.empty?
|
|
669
|
+
kwargs[:default_headers] = @default_headers
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
unless @host_headers.empty?
|
|
673
|
+
kwargs[:host_headers] = @host_headers
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
kwargs[:user_agent] = @user_agent if @user_agent
|
|
677
|
+
kwargs[:referer] = options[:referer] if options[:referer]
|
|
678
|
+
|
|
679
|
+
kwargs[:delay] = options[:delay] if options[:delay]
|
|
680
|
+
kwargs[:limit] = options[:limit] if options[:limit]
|
|
681
|
+
kwargs[:max_depth] = options[:max_depth] if options[:max_depth]
|
|
682
|
+
|
|
683
|
+
kwargs[:queue] = @queue unless @queue.empty?
|
|
684
|
+
kwargs[:history] = @history unless @history.empty?
|
|
685
|
+
|
|
686
|
+
if options.has_key?(:strip_fragments)
|
|
687
|
+
kwargs[:strip_fragments] = options[:strip_fragments]
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
if options.has_key?(:strip_query)
|
|
691
|
+
kwargs[:strip_query] = options[:strip_query]
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
kwargs[:schemes] = @visit_schemes unless @visit_schemes.empty?
|
|
695
|
+
kwargs[:hosts] = @visit_hosts unless @visit_hosts.empty?
|
|
696
|
+
kwargs[:ports] = @visit_ports unless @visit_ports.empty?
|
|
697
|
+
kwargs[:links] = @visit_links unless @visit_links.empty?
|
|
698
|
+
kwargs[:exts] = @visit_exts unless @visit_exts.empty?
|
|
699
|
+
|
|
700
|
+
kwargs[:ignore_hosts] = @ignore_hosts unless @ignore_hosts.empty?
|
|
701
|
+
kwargs[:ignore_ports] = @ignore_ports unless @ignore_ports.empty?
|
|
702
|
+
kwargs[:ignore_links] = @ignore_links unless @ignore_links.empty?
|
|
703
|
+
kwargs[:ignore_exts] = @ignore_exts unless @ignore_exts.empty?
|
|
704
|
+
|
|
705
|
+
kwargs[:robots] = options[:robots] if options.has_key?(:robots)
|
|
706
|
+
|
|
707
|
+
return kwargs
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
#
|
|
711
|
+
# Prints the status of a page.
|
|
712
|
+
#
|
|
713
|
+
# @param [Spidr::Page] page
|
|
714
|
+
# A spidered page.
|
|
715
|
+
#
|
|
716
|
+
def print_verbose(page)
|
|
717
|
+
if page.code < 300
|
|
718
|
+
print "#{colors.bright_green(page.code)} "
|
|
719
|
+
elsif page.code < 400
|
|
720
|
+
print "#{colors.bright_yellow(page.code)} "
|
|
721
|
+
elsif page.code < 500
|
|
722
|
+
print "#{colors.bright_red(page.code)} "
|
|
723
|
+
else
|
|
724
|
+
print "#{colors.bold(colors.bright_red(page.code))} "
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
#
|
|
729
|
+
# Prints the URL for a page.
|
|
730
|
+
#
|
|
731
|
+
# @param [Spidr::Page] page
|
|
732
|
+
# A spidered page.
|
|
733
|
+
#
|
|
734
|
+
def print_url(page)
|
|
735
|
+
if page.code < 300
|
|
736
|
+
puts "#{colors.green(page.url)} "
|
|
737
|
+
elsif page.code < 400
|
|
738
|
+
puts "#{colors.yellow(page.url)} "
|
|
739
|
+
elsif page.code < 500
|
|
740
|
+
puts "#{colors.red(page.url)} "
|
|
741
|
+
else
|
|
742
|
+
puts "#{colors.bold(colors.red(page.url))} "
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
#
|
|
747
|
+
# Prints a page.
|
|
748
|
+
#
|
|
749
|
+
# @param [Spidr::Page] page
|
|
750
|
+
# A spidered page.
|
|
751
|
+
#
|
|
752
|
+
def print_page(page)
|
|
753
|
+
print_verbose(page) if options[:print_verbose]
|
|
754
|
+
print_url(page)
|
|
755
|
+
|
|
756
|
+
if options[:print_headers]
|
|
757
|
+
print_headers(page)
|
|
758
|
+
elsif options[:print_header]
|
|
759
|
+
if (header = page.response[options[:print_header]])
|
|
760
|
+
print_content header
|
|
761
|
+
end
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
print_query(page) if (options[:xpath] || options[:css_path])
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
#
|
|
768
|
+
# Prints the headers of a page.
|
|
769
|
+
#
|
|
770
|
+
# @param [Spidr::Page] page
|
|
771
|
+
# A spidered page.
|
|
772
|
+
#
|
|
773
|
+
def print_headers(page)
|
|
774
|
+
page.response.each_capitalized do |name,value|
|
|
775
|
+
print_content "#{name}: #{value}"
|
|
776
|
+
end
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
#
|
|
780
|
+
# Prints the XPath or CSS-path query result for the page.
|
|
781
|
+
#
|
|
782
|
+
# @param [Spidr::Page] page
|
|
783
|
+
# A spidered page.
|
|
784
|
+
#
|
|
785
|
+
def print_query(page)
|
|
786
|
+
if page.html?
|
|
787
|
+
if options[:xpath]
|
|
788
|
+
print_content page.doc.xpath(options[:xpath])
|
|
789
|
+
elsif options[:css_path]
|
|
790
|
+
print_content page.doc.css(options[:css_path])
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
#
|
|
796
|
+
# Prints an information message.
|
|
797
|
+
#
|
|
798
|
+
# @param [String] message
|
|
799
|
+
#
|
|
800
|
+
def print_verbose(message)
|
|
801
|
+
if verbose?
|
|
802
|
+
puts colors.yellow("* #{message}")
|
|
803
|
+
end
|
|
804
|
+
end
|
|
805
|
+
|
|
806
|
+
#
|
|
807
|
+
# Print content from a page.
|
|
808
|
+
#
|
|
809
|
+
# @param [#to_s] content
|
|
810
|
+
# The content to print.
|
|
811
|
+
#
|
|
812
|
+
def print_content(content)
|
|
813
|
+
content.to_s.each_line do |line|
|
|
814
|
+
puts " #{line}"
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
end
|
|
819
|
+
end
|
|
820
|
+
end
|
|
821
|
+
end
|
|
822
|
+
end
|