plunder 2.0.0a

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.
@@ -0,0 +1,84 @@
1
+ #
2
+ # Plunder - SMB scanning and auditing tool
3
+ # Copyright (C) 2017 Joshua Stone
4
+ #
5
+ # This program is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU General Public License
7
+ # as published by the Free Software Foundation; either version 2
8
+ # of the License, or (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
+ #
19
+
20
+ module Plunder
21
+ module PlunderIO
22
+ def banner
23
+ puts ""
24
+ puts "\x1b[34;1m------------------------------------------------------------------------------\x1b[0m"
25
+ puts "\x1b[32;1m Plunder - SMB Scanner - by Joshua Stone - yakovdk@gmail.com\x1b[0m"
26
+ puts "\x1b[34;1m------------------------------------------------------------------------------\x1b[0m"
27
+ puts ""
28
+ puts(Report.colorize(" <gra>Plunder, Copyright (C) 2017 Joshua Stone\n" +
29
+ " Plunder comes with ABSOLUTELY NO WARRANTY; for details\n" +
30
+ " refer to the file 'LICENSE' in the source distribution.<rst>"))
31
+ puts ""
32
+ end
33
+
34
+ def usage
35
+ puts "usage: plunder <cmd> [args...]"
36
+ puts ""
37
+ puts " Description:"
38
+ puts " ------------"
39
+ puts ""
40
+ puts " Plunder is an SMB share enumeration and scanning tool that can be used for"
41
+ puts " security assessments, penetration tests, risk analysis, or auditing. The"
42
+ puts " core of Plunder is a tunable, highly efficient, breadth-first share scanner"
43
+ puts " that quickly indexes an environment according to a specified config file."
44
+ puts ""
45
+ puts " Commands:"
46
+ puts " ---------"
47
+ puts ""
48
+ puts " CMD ARGS DESCRIPTION"
49
+ puts " --- ---- -----------"
50
+ puts " init <name> Initialize config file for new project"
51
+ puts " scan <cfg> <depth> Scan using indicated config to indicated depth"
52
+ puts " target <cfg> <ip> Add <ip> to list of targets in config file"
53
+ puts " targets <cfg> [<file>] Add targets from STDIN or indicated file"
54
+ puts " search <cfg> <expr> Search filenames for <expr>"
55
+ puts " mirror <cfg> [<file>] Mirror files by ID from STDIN or indicated file"
56
+ puts " listing <cfg> List all files and IDs"
57
+ puts " summary <cfg> Describe current configuration"
58
+ puts " debug <cfg> Sets up scanner and enters pry (if 'pry' exists)"
59
+ puts " client <cfg> Rudimentary interactive SMB client"
60
+ puts ""
61
+ puts " creds <cfg> <dom> <user> <pass>"
62
+ puts " Set indicated credentials for scan config"
63
+ puts ""
64
+ puts " Note that for downloading purposes, files are to be referenced by ID, which"
65
+ puts " is a unique index into the FS database. This can be obtained via the "
66
+ puts " 'listing' command or by searching for matches with 'search'."
67
+ puts ""
68
+ puts " Procedure:"
69
+ puts " ----------"
70
+ puts ""
71
+ puts " 1. Run 'plunder init foo' to create a config file"
72
+ puts " 2. Edit the config file (foo.yaml)"
73
+ puts " 3. Add targets, e.g. with 'plunder targets foo ips.txt'"
74
+ puts " 4. Scan to desired depth: 'plunder scan foo 8'"
75
+ puts " 5. Search for interesting files: 'plunder search foo \"^web.config\$\""
76
+ puts " 6. Save IDs: \"... | grep ^FILE | awk '{print $2}'\" > ids.txt"
77
+ puts " 7. Mirror the interesting files: 'plunder mirror foo < ids.txt'"
78
+ puts ""
79
+ end
80
+
81
+ module_function :usage
82
+ module_function :banner
83
+ end
84
+ end
@@ -0,0 +1,208 @@
1
+ #
2
+ # Plunder - SMB scanning and auditing tool
3
+ # Copyright (C) 2017 Joshua Stone
4
+ #
5
+ # This program is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU General Public License
7
+ # as published by the Free Software Foundation; either version 2
8
+ # of the License, or (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
+ #
19
+
20
+ require 'fileutils'
21
+ require 'time'
22
+
23
+ require_relative '../../ext/fsdb/fsdb.so'
24
+ require_relative '../../ext/smbclient/smbclient.so'
25
+ require_relative 'report.rb'
26
+ require_relative 'analyzer.rb'
27
+ require_relative 'agent.rb'
28
+ require_relative 'target.rb'
29
+
30
+ module Plunder
31
+ class Plunder
32
+ attr_accessor :name, :client, :fsdb, :next, :depth, :clients, :pool, :agents, :targets, :config
33
+
34
+ def initialize(name)
35
+ @name = name
36
+ @config = YAML.load(File.read(name + ".yaml"))
37
+ @fsdb = FSDB.new
38
+ @targets = @config[:targets].map { |i| Target.new i }
39
+ @count = 0
40
+ @depth = 0
41
+ @dom = @config[:domain]
42
+ @user = @config[:user]
43
+ @pass = @config[:pass]
44
+ @dbmut = Mutex.new
45
+ @agmut = Mutex.new
46
+ @iomut = Mutex.new
47
+ @percent = 0
48
+
49
+ @analyzer = Analyzer.new(@config)
50
+ @analyzer.run
51
+
52
+ @config[:tarpit] = @config[:tarpit].to_i
53
+ @config[:threads] = @config[:threads].to_i
54
+
55
+ @agents = []
56
+ @config[:threads].times { @agents << Agent.new(@dom, @user, @pass) }
57
+
58
+ @client = SMBClient.new(@dom, @user, @pass)
59
+
60
+ Report.notify("Tarpit threshold is #{@config[:tarpit]} subdirs")
61
+ end
62
+
63
+ def load
64
+ if File.exists? @config[:file]
65
+ @fsdb = FSDB.read(@config[:file])
66
+ Report.notify("Loaded FSDB from '<grn>#{@config[:file]}<rst>'")
67
+ else
68
+ Report.notify("FSDB file at <grn>#{@config[:file]}<rst> does not exist yet")
69
+ end
70
+ end
71
+
72
+ def mirror(url)
73
+ fields = url.split(/\//)
74
+ localdir = "./mirror/" + fields[2..-2].join("/")
75
+ FileUtils::mkdir_p localdir
76
+ open(localdir + "/" + fields[-1], "w") do |file|
77
+ content = @client.slurp(url)
78
+ file.write(content)
79
+ end
80
+ end
81
+
82
+ def save
83
+ file = @config[:file]
84
+ @fsdb.write(file)
85
+ Report.notify("Wrote output to '<grn>#{file}<rst>'")
86
+ end
87
+
88
+ def scan(depth)
89
+ return if depth == 0
90
+
91
+ @depth += 1
92
+
93
+ Report.notify("<mag>#{Time::now.to_s}<rst> : Starting scan at depth <grn>#{@depth}<rst>" + " " * 25)
94
+
95
+ length = @targets.inject(0) { |i,j| i + j.paths.length }
96
+ count = 0
97
+
98
+ return if length == 0
99
+
100
+ @targets.each do |target|
101
+ agent = free_agent
102
+ Thread.new(target, agent) do |t, a|
103
+ t.paths.each do |path|
104
+ t.rest += scan_path(path, a)
105
+ count += 1
106
+ stats(count, length)
107
+ end
108
+ a.checkin
109
+ t.paths = t.rest
110
+ t.rest = []
111
+ end
112
+ end
113
+
114
+ sleep 0.1 until @agents.all? { |a| a.free }
115
+
116
+ clear_line
117
+
118
+ self.scan(depth - 1)
119
+ end
120
+
121
+ def free_agent
122
+ while true
123
+ @agents.each do |agent|
124
+ if agent.free
125
+ return agent.checkout
126
+ end
127
+ end
128
+ sleep 0.1
129
+ end
130
+ end
131
+
132
+ def stats(count, total)
133
+ amount = (100.0 * count / total).floor
134
+ if amount != @last
135
+ @iomut.synchronize do
136
+ @last = amount
137
+ clear_line
138
+ print(" Progress: #{amount} % (#{@fsdb.length})")
139
+ STDOUT.flush
140
+ end
141
+ end
142
+ end
143
+
144
+ def clear_line
145
+ 100.times { print("\x08") }
146
+ STDOUT.flush
147
+ end
148
+
149
+ def scan_path(path, agent)
150
+ path += "/" unless path.end_with? "/"
151
+ fields = path.split(/\//)[2..-1]
152
+ rest = []
153
+
154
+ listing = agent.list path
155
+ if listing == nil
156
+ Report.error("Authentication failure at #{path}, exiting for safety!")
157
+ exit!
158
+ end
159
+ (dirs, files) = listing
160
+ @count += files.length
161
+
162
+ files.each do |file|
163
+ url = "#{path}#{file}"
164
+ index = 0
165
+ @dbmut.synchronize { index = @fsdb.put("#{path}#{file}/") }
166
+ @analyzer << [path, file, url, index]
167
+ mirror(url) if @analyzer.mirror?(path, file)
168
+ end
169
+
170
+ if dirs.length > @config[:tarpit]
171
+ Report.warning("TARPIT: #{path}, truncating to #{@config[:tarpit]}")
172
+ dirs = dirs[0..@config[:tarpit]]
173
+ end
174
+
175
+ dirs.each do |dir|
176
+ rest << "#{path}#{dir}" if scan_dir?(path, dir)
177
+ end
178
+
179
+ return rest
180
+ end
181
+
182
+ def filter(list, dir)
183
+ list.each do |entry|
184
+ return true if dir =~ /#{entry}/i
185
+ end
186
+ return false
187
+ end
188
+
189
+ def scan_dir?(target, dir)
190
+ return false if filter(@config[:blacklist][:dirs], dir)
191
+ if @depth == 1
192
+ return false if filter(@config[:blacklist][:shares], dir)
193
+ end
194
+ return true
195
+ end
196
+
197
+ def select_files(expr, &block)
198
+ @fsdb.each_name do |name, index|
199
+ if name =~ /#{expr}/i
200
+ fsdb.select_index(index) do |id|
201
+ yield(fsdb[id], id)
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ end
208
+ end
@@ -0,0 +1,78 @@
1
+ #
2
+ # Plunder - SMB scanning and auditing tool
3
+ # Copyright (C) 2017 Joshua Stone
4
+ #
5
+ # This program is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU General Public License
7
+ # as published by the Free Software Foundation; either version 2
8
+ # of the License, or (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
+ #
19
+
20
+ require 'time'
21
+ require 'thread'
22
+
23
+ module Plunder
24
+ class Report
25
+ @@log = nil
26
+ @@mut = Mutex.new
27
+
28
+ def Report.init(file)
29
+ now = Time::now
30
+ stamp = sprintf("%04d-%02d-%02d-%02d:%02d:%02d",
31
+ now.year, now.month, now.day,
32
+ now.hour, now.min, now.sec)
33
+ name = "#{file}_#{stamp}.log"
34
+ self.notify(colorize("Logging hits to '<grn>#{name}<rst>'"))
35
+ @@log = open(name, "a") unless @@log
36
+ @@log.puts("CONTROL #{Time::now().to_s} init log")
37
+ end
38
+
39
+ def Report.notify(message)
40
+ @@mut.synchronize do
41
+ puts(" [-] #{colorize(message)}")
42
+ end
43
+ end
44
+
45
+ def Report.error(message)
46
+ @@mut.synchronize do
47
+ puts(colorize(" [!] <red>ERROR:<rst> #{message}"))
48
+ end
49
+ end
50
+
51
+ def Report.colorize(str)
52
+ return str.
53
+ gsub("<gra>", "\x1b[30;1m").
54
+ gsub("<red>", "\x1b[31;1m").
55
+ gsub("<grn>", "\x1b[32;1m").
56
+ gsub("<yel>", "\x1b[33;1m").
57
+ gsub("<blu>", "\x1b[34;1m").
58
+ gsub("<mag>", "\x1b[35;1m").
59
+ gsub("<cya>", "\x1b[36;1m").
60
+ gsub("<whi>", "\x1b[37;1m").
61
+ gsub("<rst>", "\x1b[0m")
62
+ end
63
+
64
+ def Report.warning(name)
65
+ @@mut.synchronize do
66
+ puts(colorize(" [!] <yel>WARNING:<rst> #{message}"))
67
+ end
68
+ end
69
+
70
+ def Report.alert(name)
71
+ @@mut.synchronize do
72
+ # puts(colorize(" [+] #{name}"))
73
+ @@log.puts("ALERT " + name) if @@log
74
+ end
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,33 @@
1
+ #
2
+ # Plunder - SMB scanning and auditing tool
3
+ # Copyright (C) 2017 Joshua Stone
4
+ #
5
+ # This program is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU General Public License
7
+ # as published by the Free Software Foundation; either version 2
8
+ # of the License, or (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
+ #
19
+
20
+ require 'uri'
21
+
22
+ module Plunder
23
+ class Target
24
+ attr_accessor :url, :paths, :rest, :host
25
+
26
+ def initialize(url)
27
+ @host = URI::parse(url).host
28
+ @url = url
29
+ @paths = [url]
30
+ @rest = []
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,59 @@
1
+ #
2
+ # Plunder - SMB scanning and auditing tool
3
+ # Copyright (C) 2017 Joshua Stone
4
+ #
5
+ # This program is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU General Public License
7
+ # as published by the Free Software Foundation; either version 2
8
+ # of the License, or (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
+ #
19
+
20
+ class Tree
21
+ attr_accessor :root, :node, :children
22
+
23
+ def initialize(nodeid)
24
+ @node = nodeid
25
+ @children = {}
26
+ end
27
+
28
+ def <<(child)
29
+ @children[child.node] << child
30
+ end
31
+
32
+ def [](id)
33
+ return @children[id]
34
+ end
35
+
36
+ def insert(nodes)
37
+ return unless nodes.length > 0
38
+ @children[nodes[0]] = Tree.new(nodes[0]) unless @children[nodes[0]]
39
+ @children[nodes[0]].insert(nodes[1..-1])
40
+ end
41
+
42
+ def member?(id)
43
+ return @children[id] ? true : false
44
+ end
45
+
46
+ def pprint(fsdb=nil, depth=0)
47
+ if @node > 0
48
+ print " " + " + " * (depth - 1)
49
+ puts (fsdb ? fsdb.lookup(@node) : @node.to_s)
50
+ end
51
+
52
+ @children.each do |id,child|
53
+ child.pprint(fsdb, depth + 1)
54
+ end
55
+
56
+ return true
57
+ end
58
+ end
59
+