new_cms_scanner 0.13.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +26 -0
- data/app/app.rb +24 -0
- data/app/controllers/core/cli_options.rb +117 -0
- data/app/controllers/core.rb +82 -0
- data/app/controllers/interesting_findings.rb +25 -0
- data/app/finders/interesting_findings/fantastico_fileslist.rb +21 -0
- data/app/finders/interesting_findings/headers.rb +17 -0
- data/app/finders/interesting_findings/robots_txt.rb +20 -0
- data/app/finders/interesting_findings/search_replace_db_2.rb +19 -0
- data/app/finders/interesting_findings/xml_rpc.rb +61 -0
- data/app/finders/interesting_findings.rb +25 -0
- data/app/formatters/cli.rb +65 -0
- data/app/formatters/cli_no_color.rb +9 -0
- data/app/formatters/cli_no_colour.rb +17 -0
- data/app/formatters/json.rb +14 -0
- data/app/models/fantastico_fileslist.rb +34 -0
- data/app/models/headers.rb +44 -0
- data/app/models/interesting_finding.rb +48 -0
- data/app/models/robots_txt.rb +31 -0
- data/app/models/search_replace_db_2.rb +17 -0
- data/app/models/user.rb +35 -0
- data/app/models/version.rb +49 -0
- data/app/models/xml_rpc.rb +78 -0
- data/app/user_agents.txt +46 -0
- data/app/views/cli/core/banner.erb +1 -0
- data/app/views/cli/core/finished.erb +8 -0
- data/app/views/cli/core/help.erb +4 -0
- data/app/views/cli/core/started.erb +6 -0
- data/app/views/cli/core/version.erb +1 -0
- data/app/views/cli/interesting_findings/_array.erb +10 -0
- data/app/views/cli/interesting_findings/findings.erb +23 -0
- data/app/views/cli/scan_aborted.erb +5 -0
- data/app/views/cli/usage.erb +3 -0
- data/app/views/json/core/banner.erb +1 -0
- data/app/views/json/core/finished.erb +10 -0
- data/app/views/json/core/help.erb +4 -0
- data/app/views/json/core/started.erb +5 -0
- data/app/views/json/core/version.erb +1 -0
- data/app/views/json/interesting_findings/findings.erb +24 -0
- data/app/views/json/scan_aborted.erb +5 -0
- data/lib/cms_scanner/browser/actions.rb +48 -0
- data/lib/cms_scanner/browser/options.rb +90 -0
- data/lib/cms_scanner/browser.rb +96 -0
- data/lib/cms_scanner/cache/file_store.rb +77 -0
- data/lib/cms_scanner/cache/typhoeus.rb +25 -0
- data/lib/cms_scanner/controller.rb +105 -0
- data/lib/cms_scanner/controllers.rb +67 -0
- data/lib/cms_scanner/errors/http.rb +72 -0
- data/lib/cms_scanner/errors/scan.rb +14 -0
- data/lib/cms_scanner/errors.rb +11 -0
- data/lib/cms_scanner/exit_code.rb +25 -0
- data/lib/cms_scanner/finders/base_finders.rb +45 -0
- data/lib/cms_scanner/finders/finder/breadth_first_dictionary_attack.rb +121 -0
- data/lib/cms_scanner/finders/finder/enumerator.rb +77 -0
- data/lib/cms_scanner/finders/finder/fingerprinter.rb +48 -0
- data/lib/cms_scanner/finders/finder/smart_url_checker/findings.rb +33 -0
- data/lib/cms_scanner/finders/finder/smart_url_checker.rb +60 -0
- data/lib/cms_scanner/finders/finder.rb +75 -0
- data/lib/cms_scanner/finders/finding.rb +54 -0
- data/lib/cms_scanner/finders/findings.rb +26 -0
- data/lib/cms_scanner/finders/independent_finder.rb +30 -0
- data/lib/cms_scanner/finders/independent_finders.rb +26 -0
- data/lib/cms_scanner/finders/same_type_finder.rb +19 -0
- data/lib/cms_scanner/finders/same_type_finders.rb +26 -0
- data/lib/cms_scanner/finders/unique_finder.rb +19 -0
- data/lib/cms_scanner/finders/unique_finders.rb +47 -0
- data/lib/cms_scanner/finders.rb +12 -0
- data/lib/cms_scanner/formatter/buffer.rb +17 -0
- data/lib/cms_scanner/formatter.rb +149 -0
- data/lib/cms_scanner/helper.rb +7 -0
- data/lib/cms_scanner/numeric.rb +13 -0
- data/lib/cms_scanner/parsed_cli.rb +37 -0
- data/lib/cms_scanner/progressbar_null_output.rb +23 -0
- data/lib/cms_scanner/public_suffix/domain.rb +42 -0
- data/lib/cms_scanner/references.rb +132 -0
- data/lib/cms_scanner/scan.rb +88 -0
- data/lib/cms_scanner/target/hashes.rb +45 -0
- data/lib/cms_scanner/target/platform/php.rb +62 -0
- data/lib/cms_scanner/target/platform.rb +3 -0
- data/lib/cms_scanner/target/scope.rb +103 -0
- data/lib/cms_scanner/target/server/apache.rb +27 -0
- data/lib/cms_scanner/target/server/generic.rb +72 -0
- data/lib/cms_scanner/target/server/iis.rb +29 -0
- data/lib/cms_scanner/target/server/nginx.rb +27 -0
- data/lib/cms_scanner/target/server.rb +6 -0
- data/lib/cms_scanner/target.rb +124 -0
- data/lib/cms_scanner/typhoeus/hydra.rb +12 -0
- data/lib/cms_scanner/typhoeus/response.rb +27 -0
- data/lib/cms_scanner/version.rb +6 -0
- data/lib/cms_scanner/vulnerability.rb +46 -0
- data/lib/cms_scanner/web_site.rb +145 -0
- data/lib/cms_scanner.rb +141 -0
- metadata +426 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 56edd064a963293e585d678c18e58db39cbd514a56586ab7f445bc9f28d4d10b
|
4
|
+
data.tar.gz: 52a249bb09f8b840e67bce8e51f1f2170a1a4fc9efaf3ab9726417bb4b6ca83c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e1c015727d38e5019eeff523c3951e0b8291968b9c437c986fd483d73d012aece1bc8c92e79d3a048a17389c2d62532d8d0039b0833b1ac30b93f449b26acf09
|
7
|
+
data.tar.gz: 237d542e023caf48867ed491e53c51b5a63a3fbffaeb043405644c6346462ebd3b11860fc2d1cec151ae76a5c89d67d012b12c1c1adb540e8ae9078096254674
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2014-2015 - WPScanTeam
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# CMSScanner
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/cms_scanner.svg)](https://badge.fury.io/rb/cms_scanner)
|
4
|
+
![Build](https://github.com/wpscanteam/CMSScanner/workflows/Build/badge.svg)
|
5
|
+
[![Coverage Status](https://img.shields.io/coveralls/wpscanteam/CMSScanner.svg)](https://coveralls.io/r/wpscanteam/CMSScanner)
|
6
|
+
[![Code Climate](https://api.codeclimate.com/v1/badges/b90b7f9f6982792ef8d6/maintainability)](https://codeclimate.com/github/wpscanteam/CMSScanner/maintainability)
|
7
|
+
|
8
|
+
The goal of this gem is to provide a quick and easy way to create a CMS/WebSite Scanner by acting like a Framework and providing classes, formatters etc.
|
9
|
+
|
10
|
+
## /!\ This gem is currently Experimental /!\
|
11
|
+
|
12
|
+
## A basic implementation example is available in the example folder.
|
13
|
+
|
14
|
+
To start to play with it, copy all its files and folders into a new git repository and run `bundle install && rake install` inside it.
|
15
|
+
It will create a `cmsscan` command that you can run against a target, ie `cmsscan --url https://www.google.com`
|
16
|
+
|
17
|
+
|
18
|
+
Install Dependencies: `bundle install`
|
19
|
+
|
20
|
+
## Contributing
|
21
|
+
|
22
|
+
1. Fork it ( https://github.com/wpscanteam/CMSScanner/fork )
|
23
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
24
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
25
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
26
|
+
5. Create new Pull Request
|
data/app/app.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Formatters
|
4
|
+
require_relative 'formatters/cli'
|
5
|
+
require_relative 'formatters/cli_no_colour'
|
6
|
+
require_relative 'formatters/cli_no_color'
|
7
|
+
require_relative 'formatters/json'
|
8
|
+
|
9
|
+
# Controllers
|
10
|
+
require_relative 'controllers/core'
|
11
|
+
require_relative 'controllers/interesting_findings'
|
12
|
+
|
13
|
+
# Models
|
14
|
+
require_relative 'models/interesting_finding'
|
15
|
+
require_relative 'models/robots_txt'
|
16
|
+
require_relative 'models/fantastico_fileslist'
|
17
|
+
require_relative 'models/search_replace_db_2'
|
18
|
+
require_relative 'models/headers'
|
19
|
+
require_relative 'models/xml_rpc'
|
20
|
+
require_relative 'models/version'
|
21
|
+
require_relative 'models/user'
|
22
|
+
|
23
|
+
# Finders
|
24
|
+
require_relative 'finders/interesting_findings'
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Controller
|
5
|
+
# CLI Options for the Core Controller
|
6
|
+
class Core < Base
|
7
|
+
def cli_options
|
8
|
+
formats = NS::Formatter.availables
|
9
|
+
|
10
|
+
[
|
11
|
+
OptURL.new(['-u', '--url URL', 'The URL to scan'],
|
12
|
+
required_unless: %i[help hh version],
|
13
|
+
default_protocol: 'http'),
|
14
|
+
OptBoolean.new(['--force', 'Do not check if target returns a 403'])
|
15
|
+
] + mixed_cli_options + [
|
16
|
+
OptFilePath.new(['-o', '--output FILE', 'Output to FILE'], writable: true, exists: false),
|
17
|
+
OptChoice.new(['-f', '--format FORMAT',
|
18
|
+
'Output results in the format supplied'], choices: formats),
|
19
|
+
OptChoice.new(['--detection-mode MODE'],
|
20
|
+
choices: %w[mixed passive aggressive],
|
21
|
+
normalize: :to_sym,
|
22
|
+
default: :mixed),
|
23
|
+
OptArray.new(['--scope DOMAINS',
|
24
|
+
'Comma separated (sub-)domains to consider in scope. ',
|
25
|
+
'Wildcard(s) allowed in the trd of valid domains, e.g: *.target.tld'], advanced: true)
|
26
|
+
] + cli_browser_options
|
27
|
+
end
|
28
|
+
|
29
|
+
def mixed_cli_options
|
30
|
+
[
|
31
|
+
OptBoolean.new(['-h', '--help', 'Display the simple help and exit']),
|
32
|
+
OptBoolean.new(['--hh', 'Display the full help and exit']),
|
33
|
+
OptBoolean.new(['--version', 'Display the version and exit']),
|
34
|
+
OptBoolean.new(['--ignore-main-redirect', 'Ignore the main redirect (if any) and scan the target url'],
|
35
|
+
advanced: true),
|
36
|
+
OptBoolean.new(['-v', '--verbose', 'Verbose mode']),
|
37
|
+
OptBoolean.new(['--[no-]banner', 'Whether or not to display the banner'], default: true),
|
38
|
+
OptPositiveInteger.new(['--max-scan-duration SECONDS',
|
39
|
+
'Abort the scan if it exceeds the time provided in seconds'],
|
40
|
+
advanced: true)
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [ Array<OptParseValidator::OptBase> ]
|
45
|
+
def cli_browser_options
|
46
|
+
cli_browser_headers_options + [
|
47
|
+
OptBoolean.new(['--random-user-agent', '--rua',
|
48
|
+
'Use a random user-agent for each scan']),
|
49
|
+
OptFilePath.new(['--user-agents-list FILE-PATH',
|
50
|
+
'List of agents to use with --random-user-agent'],
|
51
|
+
exists: true,
|
52
|
+
advanced: true,
|
53
|
+
default: APP_DIR.join('user_agents.txt')),
|
54
|
+
OptCredentials.new(['--http-auth login:password']),
|
55
|
+
OptPositiveInteger.new(['-t', '--max-threads VALUE', 'The max threads to use'],
|
56
|
+
default: 5),
|
57
|
+
OptPositiveInteger.new(['--throttle MilliSeconds', 'Milliseconds to wait before doing another web request. ' \
|
58
|
+
'If used, the max threads will be set to 1.']),
|
59
|
+
OptPositiveInteger.new(['--request-timeout SECONDS', 'The request timeout in seconds'],
|
60
|
+
default: 60),
|
61
|
+
OptPositiveInteger.new(['--connect-timeout SECONDS', 'The connection timeout in seconds'],
|
62
|
+
default: 30),
|
63
|
+
OptBoolean.new(['--disable-tls-checks',
|
64
|
+
'Disables SSL/TLS certificate verification, and downgrade to TLS1.0+ ' \
|
65
|
+
'(requires cURL 7.66 for the latter)'])
|
66
|
+
] + cli_browser_proxy_options + cli_browser_cookies_options + cli_browser_cache_options
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [ Array<OptParseValidator::OptBase> ]
|
70
|
+
def cli_browser_headers_options
|
71
|
+
[
|
72
|
+
OptString.new(['--user-agent VALUE', '--ua']),
|
73
|
+
OptHeaders.new(['--headers HEADERS', 'Additional headers to append in requests'], advanced: true),
|
74
|
+
OptString.new(['--vhost VALUE', 'The virtual host (Host header) to use in requests'], advanced: true)
|
75
|
+
]
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [ Array<OptParseValidator::OptBase> ]
|
79
|
+
def cli_browser_proxy_options
|
80
|
+
[
|
81
|
+
OptProxy.new(['--proxy protocol://IP:port',
|
82
|
+
'Supported protocols depend on the cURL installed']),
|
83
|
+
OptCredentials.new(['--proxy-auth login:password'])
|
84
|
+
]
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [ Array<OptParseValidator::OptBase> ]
|
88
|
+
def cli_browser_cookies_options
|
89
|
+
[
|
90
|
+
OptString.new(['--cookie-string COOKIE',
|
91
|
+
'Cookie string to use in requests, ' \
|
92
|
+
'format: cookie1=value1[; cookie2=value2]']),
|
93
|
+
OptFilePath.new(['--cookie-jar FILE-PATH', 'File to read and write cookies'],
|
94
|
+
writable: true,
|
95
|
+
readable: true,
|
96
|
+
create: true,
|
97
|
+
default: File.join(tmp_directory, 'cookie_jar.txt'))
|
98
|
+
]
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [ Array<OptParseValidator::OptBase> ]
|
102
|
+
def cli_browser_cache_options
|
103
|
+
[
|
104
|
+
OptInteger.new(['--cache-ttl TIME_TO_LIVE', 'The cache time to live in seconds'],
|
105
|
+
default: 600, advanced: true),
|
106
|
+
OptBoolean.new(['--clear-cache', 'Clear the cache before the scan'], advanced: true),
|
107
|
+
OptDirectoryPath.new(['--cache-dir PATH'],
|
108
|
+
readable: true,
|
109
|
+
writable: true,
|
110
|
+
create: true,
|
111
|
+
default: File.join(tmp_directory, 'cache'),
|
112
|
+
advanced: true)
|
113
|
+
]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'core/cli_options'
|
4
|
+
|
5
|
+
module CMSScanner
|
6
|
+
module Controller
|
7
|
+
# Core Controller
|
8
|
+
class Core < Base
|
9
|
+
def setup_cache
|
10
|
+
return unless NS::ParsedCli.cache_dir
|
11
|
+
|
12
|
+
storage_path = File.join(NS::ParsedCli.cache_dir, Digest::MD5.hexdigest(target.url))
|
13
|
+
|
14
|
+
Typhoeus::Config.cache = Cache::Typhoeus.new(storage_path)
|
15
|
+
Typhoeus::Config.cache.clean if NS::ParsedCli.clear_cache
|
16
|
+
end
|
17
|
+
|
18
|
+
def before_scan
|
19
|
+
maybe_output_banner_help_and_version
|
20
|
+
|
21
|
+
setup_cache
|
22
|
+
check_target_availability
|
23
|
+
end
|
24
|
+
|
25
|
+
def maybe_output_banner_help_and_version
|
26
|
+
output('banner') if NS::ParsedCli.banner
|
27
|
+
output('help', help: option_parser.simple_help, simple: true) if NS::ParsedCli.help
|
28
|
+
output('help', help: option_parser.full_help, simple: false) if NS::ParsedCli.hh
|
29
|
+
output('version') if NS::ParsedCli.version
|
30
|
+
|
31
|
+
exit(NS::ExitCode::OK) if NS::ParsedCli.help || NS::ParsedCli.hh || NS::ParsedCli.version
|
32
|
+
end
|
33
|
+
|
34
|
+
# Checks that the target is accessible, raises related errors otherwise
|
35
|
+
#
|
36
|
+
# @return [ Void ]
|
37
|
+
def check_target_availability
|
38
|
+
res = NS::Browser.get(target.url)
|
39
|
+
|
40
|
+
case res.code
|
41
|
+
when 0
|
42
|
+
raise Error::TargetDown, res
|
43
|
+
when 401
|
44
|
+
raise Error::HTTPAuthRequired
|
45
|
+
when 403
|
46
|
+
raise Error::AccessForbidden, NS::ParsedCli.random_user_agent unless NS::ParsedCli.force
|
47
|
+
when 407
|
48
|
+
raise Error::ProxyAuthRequired
|
49
|
+
end
|
50
|
+
|
51
|
+
# Checks for redirects
|
52
|
+
# An out of scope redirect will raise an Error::HTTPRedirect
|
53
|
+
effective_url = target.homepage_res.effective_url
|
54
|
+
|
55
|
+
return if target.in_scope?(effective_url)
|
56
|
+
|
57
|
+
raise Error::HTTPRedirect, effective_url unless NS::ParsedCli.ignore_main_redirect
|
58
|
+
|
59
|
+
target.homepage_res = res
|
60
|
+
end
|
61
|
+
|
62
|
+
def run
|
63
|
+
@start_time = Time.now
|
64
|
+
@start_memory = NS.start_memory
|
65
|
+
|
66
|
+
output('started', url: target.url, ip: target.ip, effective_url: target.homepage_url)
|
67
|
+
end
|
68
|
+
|
69
|
+
def after_scan
|
70
|
+
@stop_time = Time.now
|
71
|
+
@elapsed = @stop_time - @start_time
|
72
|
+
@used_memory = GetProcessMem.new.bytes - @start_memory
|
73
|
+
|
74
|
+
output('finished',
|
75
|
+
cached_requests: NS.cached_requests,
|
76
|
+
requests_done: NS.total_requests,
|
77
|
+
data_sent: NS.total_data_sent,
|
78
|
+
data_received: NS.total_data_received)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Controller
|
5
|
+
# InterestingFindings Controller
|
6
|
+
class InterestingFindings < Base
|
7
|
+
def cli_options
|
8
|
+
[
|
9
|
+
OptChoice.new(
|
10
|
+
['--interesting-findings-detection MODE',
|
11
|
+
'Use the supplied mode for the interesting findings detection. '],
|
12
|
+
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
13
|
+
)
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
mode = NS::ParsedCli.interesting_findings_detection || NS::ParsedCli.detection_mode
|
19
|
+
findings = target.interesting_findings(mode: mode)
|
20
|
+
|
21
|
+
output('findings', findings: findings) unless findings.empty?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
module InterestingFindings
|
6
|
+
# FantasticoFileslist finder
|
7
|
+
class FantasticoFileslist < Finder
|
8
|
+
# @return [ InterestingFinding ]
|
9
|
+
def aggressive(_opts = {})
|
10
|
+
path = 'fantastico_fileslist.txt'
|
11
|
+
res = target.head_and_get(path)
|
12
|
+
|
13
|
+
return if res.body.strip.empty?
|
14
|
+
return unless res.headers && res.headers['Content-Type']&.start_with?('text/plain')
|
15
|
+
|
16
|
+
NS::Model::FantasticoFileslist.new(target.url(path), confidence: 70, found_by: found_by)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
module InterestingFindings
|
6
|
+
# Interesting Headers finder
|
7
|
+
class Headers < Finder
|
8
|
+
# @return [ InterestingFinding ]
|
9
|
+
def passive(_opts = {})
|
10
|
+
r = NS::Model::Headers.new(target.homepage_url, confidence: 100, found_by: found_by)
|
11
|
+
|
12
|
+
r.interesting_entries.empty? ? nil : r
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
module InterestingFindings
|
6
|
+
# Robots.txt finder
|
7
|
+
class RobotsTxt < Finder
|
8
|
+
# @return [ InterestingFinding ]
|
9
|
+
def aggressive(_opts = {})
|
10
|
+
path = 'robots.txt'
|
11
|
+
res = target.head_and_get(path)
|
12
|
+
|
13
|
+
return unless res&.code == 200 && res.body =~ /(?:user-agent|(?:dis)?allow):/i
|
14
|
+
|
15
|
+
NS::Model::RobotsTxt.new(target.url(path), confidence: 100, found_by: found_by)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
module InterestingFindings
|
6
|
+
# SearchReplaceDB2 finder
|
7
|
+
class SearchReplaceDB2 < Finder
|
8
|
+
# @return [ InterestingFinding ]
|
9
|
+
def aggressive(_opts = {})
|
10
|
+
path = 'searchreplacedb2.php'
|
11
|
+
|
12
|
+
return unless /by interconnect/i.match?(target.head_and_get(path).body)
|
13
|
+
|
14
|
+
NS::Model::SearchReplaceDB2.new(target.url(path), confidence: 100, found_by: found_by)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Finders
|
5
|
+
module InterestingFindings
|
6
|
+
# XML RPC finder
|
7
|
+
class XMLRPC < Finder
|
8
|
+
# @return [ Array<String> ] The potential urls to the XMl RPC file
|
9
|
+
def potential_urls
|
10
|
+
@potential_urls ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [ Array<XMLRPC> ]
|
14
|
+
def passive(opts = {})
|
15
|
+
[passive_headers(opts), passive_body(opts)].compact
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [ XMLRPC ]
|
19
|
+
def passive_headers(_opts = {})
|
20
|
+
url = target.homepage_res.headers['X-Pingback']
|
21
|
+
|
22
|
+
return unless target.in_scope?(url)
|
23
|
+
|
24
|
+
potential_urls << url
|
25
|
+
|
26
|
+
NS::Model::XMLRPC.new(url, confidence: 30, found_by: 'Headers (Passive Detection)')
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [ XMLRPC ]
|
30
|
+
def passive_body(_opts = {})
|
31
|
+
target.homepage_res.html.css('link[rel="pingback"]').each do |tag|
|
32
|
+
url = tag.attribute('href').to_s
|
33
|
+
|
34
|
+
next unless target.in_scope?(url)
|
35
|
+
|
36
|
+
potential_urls << url
|
37
|
+
|
38
|
+
return NS::Model::XMLRPC.new(url, confidence: 30, found_by: 'Link Tag (Passive Detection)')
|
39
|
+
end
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [ XMLRPC ]
|
44
|
+
def aggressive(_opts = {})
|
45
|
+
potential_urls << target.url('xmlrpc.php')
|
46
|
+
|
47
|
+
potential_urls.uniq.each do |potential_url|
|
48
|
+
next unless target.in_scope?(potential_url)
|
49
|
+
|
50
|
+
res = NS::Browser.post(potential_url, body: Digest::MD5.hexdigest(rand(999_999).to_s[0..5]))
|
51
|
+
|
52
|
+
next unless /<methodResponse>/i.match?(res&.body)
|
53
|
+
|
54
|
+
return NS::Model::XMLRPC.new(potential_url, confidence: 100, found_by: DIRECT_ACCESS)
|
55
|
+
end
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'interesting_findings/headers'
|
4
|
+
require_relative 'interesting_findings/robots_txt'
|
5
|
+
require_relative 'interesting_findings/fantastico_fileslist'
|
6
|
+
require_relative 'interesting_findings/search_replace_db_2'
|
7
|
+
require_relative 'interesting_findings/xml_rpc'
|
8
|
+
|
9
|
+
module CMSScanner
|
10
|
+
module Finders
|
11
|
+
module InterestingFindings
|
12
|
+
# Interesting Files Finder
|
13
|
+
class Base
|
14
|
+
include IndependentFinder
|
15
|
+
|
16
|
+
# @param [ CMSScanner::Target ] target
|
17
|
+
def initialize(target)
|
18
|
+
%w[Headers RobotsTxt FantasticoFileslist SearchReplaceDB2 XMLRPC].each do |f|
|
19
|
+
finders << NS::Finders::InterestingFindings.const_get(f).new(target)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Formatter
|
5
|
+
# CLI Formatter
|
6
|
+
class Cli < Base
|
7
|
+
# @return [ String ]
|
8
|
+
def info_icon
|
9
|
+
green('[+]')
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [ String ]
|
13
|
+
def notice_icon
|
14
|
+
blue('[i]')
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [ String ]
|
18
|
+
def warning_icon
|
19
|
+
amber('[!]')
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [ String ]
|
23
|
+
def critical_icon
|
24
|
+
red('[!]')
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [ String ] text
|
28
|
+
# @return [ String ]
|
29
|
+
def bold(text)
|
30
|
+
colorize(text, 1)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [ String ] text
|
34
|
+
# @return [ String ]
|
35
|
+
def red(text)
|
36
|
+
colorize(text, 31)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param [ String ] text
|
40
|
+
# @return [ String ]
|
41
|
+
def green(text)
|
42
|
+
colorize(text, 32)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param [ String ] text
|
46
|
+
# @return [ String ]
|
47
|
+
def amber(text)
|
48
|
+
colorize(text, 33)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param [ String ] text
|
52
|
+
# @return [ String ]
|
53
|
+
def blue(text)
|
54
|
+
colorize(text, 34)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param [ String ] text
|
58
|
+
# @param [ Integer ] color_code
|
59
|
+
# @return [ String ]
|
60
|
+
def colorize(text, color_code)
|
61
|
+
"\e[#{color_code}m#{text}\e[0m"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Formatter
|
5
|
+
# CLI No Colour Formatter
|
6
|
+
class CliNoColour < Cli
|
7
|
+
# Override to get the cli views
|
8
|
+
def format
|
9
|
+
'cli'
|
10
|
+
end
|
11
|
+
|
12
|
+
def colorize(text, _color_code)
|
13
|
+
text
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Model
|
5
|
+
# Fantastico is a commercial script library that automates the installation of web applications to a website.
|
6
|
+
# Fantastico scripts are executed from the administration area of a website control panel such as cPanel.
|
7
|
+
# It creates a file named fantastico_fileslist.txt that is publicly available and contains a list of all the
|
8
|
+
# files from the current directory. The contents of this file may expose sensitive information to an attacker.
|
9
|
+
class FantasticoFileslist < InterestingFinding
|
10
|
+
# @return [ String ]
|
11
|
+
def to_s
|
12
|
+
@to_s ||= "Fantastico list found: #{url}"
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [ Array<String> ] The interesting files/dirs detected
|
16
|
+
def interesting_entries
|
17
|
+
results = []
|
18
|
+
|
19
|
+
entries.each do |entry|
|
20
|
+
next unless /(?:admin|\.log|\.sql|\.db)/i.match?(entry)
|
21
|
+
|
22
|
+
results << entry
|
23
|
+
end
|
24
|
+
results
|
25
|
+
end
|
26
|
+
|
27
|
+
def references
|
28
|
+
@references ||= {
|
29
|
+
url: ['https://web.archive.org/web/20140518040021/http://www.acunetix.com/vulnerabilities/fantastico-fileslist/']
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CMSScanner
|
4
|
+
module Model
|
5
|
+
# Interesting Headers
|
6
|
+
class Headers < InterestingFinding
|
7
|
+
# @return [ Hash ] The headers
|
8
|
+
def entries
|
9
|
+
res = NS::Browser.get(url)
|
10
|
+
return [] unless res&.headers
|
11
|
+
|
12
|
+
res.headers
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [ Array<String> ] The interesting headers detected
|
16
|
+
def interesting_entries
|
17
|
+
results = []
|
18
|
+
|
19
|
+
entries.each do |header, value|
|
20
|
+
next if known_headers.include?(header.downcase)
|
21
|
+
|
22
|
+
results << "#{header}: #{Array(value).join(', ')}"
|
23
|
+
end
|
24
|
+
results
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [ Array<String> ] Downcased known headers
|
28
|
+
def known_headers
|
29
|
+
%w[
|
30
|
+
age accept-ranges cache-control content-encoding content-length content-type connection date
|
31
|
+
etag expires keep-alive location last-modified link pragma set-cookie strict-transport-security
|
32
|
+
transfer-encoding vary x-cache x-content-security-policy x-content-type-options
|
33
|
+
x-frame-options x-language x-permitted-cross-domain-policies x-pingback x-varnish
|
34
|
+
x-webkit-csp x-xss-protection
|
35
|
+
]
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [ String ]
|
39
|
+
def to_s
|
40
|
+
@to_s ||= 'Headers'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|