Skip to content

Commit d864ce1

Browse files
committed
Add Gather PDF Authors auxiliary module
1 parent aceeedc commit d864ce1

File tree

1 file changed

+174
-0
lines changed

1 file changed

+174
-0
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'pdf-reader'
7+
8+
class MetasploitModule < Msf::Auxiliary
9+
10+
def initialize(info = {})
11+
super(update_info(info,
12+
'Name' => 'Gather PDF Authors',
13+
'Description' => %q{
14+
This module takes a list of PDF URLs, downloads the PDFs,
15+
and extracts the author's name from the document metadata.
16+
},
17+
'License' => MSF_LICENSE,
18+
'Author' => 'Brendan Coles <bcoles[at]gmail.com>'))
19+
register_options(
20+
[
21+
OptPath.new('URL_LIST', [ true, 'File containing a list of PDF URLs to analyze', '' ]),
22+
OptString.new('OUTFILE', [ false, 'File to store output', '' ])
23+
])
24+
register_advanced_options(
25+
[
26+
OptString.new('SSL_VERIFY', [ true, 'Verify SSL certificate', true ]),
27+
OptString.new('PROXY', [ false, 'Proxy server to route connection. <host>:<port>', nil ]),
28+
OptString.new('PROXY_USER', [ false, 'Proxy Server User', nil ]),
29+
OptString.new('PROXY_PASS', [ false, 'Proxy Server Password', nil ])
30+
])
31+
end
32+
33+
def progress(current, total)
34+
done = (current.to_f / total.to_f) * 100
35+
percent = "%3.2f%%" % done.to_f
36+
print_status "%7s done (%d/%d files)" % [percent, current, total]
37+
end
38+
39+
def load_urls
40+
File.open(datastore['URL_LIST'], 'rb') {|f| f.read}.split(/\r?\n/)
41+
end
42+
43+
def read(data)
44+
begin
45+
reader = PDF::Reader.new data
46+
return parse reader
47+
rescue PDF::Reader::MalformedPDFError
48+
print_error "Could not parse PDF: PDF is malformed"
49+
return
50+
rescue PDF::Reader::UnsupportedFeatureError
51+
print_error "Could not parse PDF: PDF::Reader::UnsupportedFeatureError"
52+
return
53+
rescue => e
54+
print_error "Could not parse PDF: Unhandled exception: #{e}"
55+
return
56+
end
57+
end
58+
59+
def parse(reader)
60+
# PDF
61+
#print_status "PDF Version: #{reader.pdf_version}"
62+
#print_status "PDF Title: #{reader.info['title']}"
63+
#print_status "PDF Info: #{reader.info}"
64+
#print_status "PDF Metadata: #{reader.metadata}"
65+
#print_status "PDF Pages: #{reader.page_count}"
66+
67+
# Software
68+
#print_status "PDF Creator: #{reader.info[:Creator]}"
69+
#print_status "PDF Producer: #{reader.info[:Producer]}"
70+
71+
# Author
72+
reader.info[:Author].class == String ? reader.info[:Author].split(/\r?\n/).first : ''
73+
end
74+
75+
def download(url)
76+
print_status "Downloading '#{url}'"
77+
78+
begin
79+
target = URI.parse url
80+
rescue => e
81+
print_error "Could not parse URL: #{e}"
82+
return
83+
end
84+
85+
clnt = Net::HTTP::Proxy(@proxysrv, @proxyport, @proxyuser, @proxypass).new(target.host, target.port)
86+
87+
if target.scheme.eql? 'https'
88+
clnt.use_ssl = true
89+
clnt.verify_mode = datastore['SSL_VERIFY'] ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
90+
end
91+
92+
headers = {
93+
'User-Agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/4.0.221.6 Safari/525.13'
94+
}
95+
96+
begin
97+
res = clnt.get2 target.request_uri, headers
98+
rescue => e
99+
print_error "Connection failed: #{e}"
100+
return
101+
end
102+
103+
unless res
104+
print_error 'Connection failed'
105+
return
106+
end
107+
108+
print_status "HTTP #{res.code} -- Downloaded PDF (#{res.body.length} bytes)"
109+
110+
contents = StringIO.new
111+
contents.puts res.body
112+
contents
113+
end
114+
115+
def write_output(data)
116+
return if datastore['OUTFILE'].to_s.eql? ''
117+
118+
print_status "Writing data to #{datastore['OUTFILE']}..."
119+
file_name = datastore['OUTFILE']
120+
121+
if FileTest::exist?(file_name)
122+
print_status 'OUTFILE already exists, appending..'
123+
end
124+
125+
File.open(file_name, 'ab') do |fd|
126+
fd.write(data)
127+
end
128+
end
129+
130+
def run
131+
unless File.file? datastore['URL_LIST']
132+
fail_with Failure::BadConfig, "File '#{datastore['URL_LIST']}' does not exit"
133+
end
134+
135+
if datastore['PROXY']
136+
@proxysrv, @proxyport = datastore['PROXY'].split(':')
137+
@proxyuser = datastore['PROXY_USER']
138+
@proxypass = datastore['PROXY_PASS']
139+
else
140+
@proxysrv, @proxyport = nil, nil
141+
end
142+
143+
urls = load_urls
144+
print_status "Processing #{urls.size} URLs..."
145+
authors = []
146+
max_len = 256
147+
urls.each_with_index do |url, index|
148+
next if url.blank?
149+
contents = download url
150+
next if contents.blank?
151+
author = read contents
152+
unless author.blank?
153+
print_good "PDF Author: #{author}"
154+
if author.length > max_len
155+
print_warning "Warning: Truncated author's name at #{max_len} characters"
156+
authors << author[0...max_len]
157+
else
158+
authors << author
159+
end
160+
end
161+
progress(index + 1, urls.size)
162+
end
163+
164+
print_line
165+
166+
if authors.empty?
167+
print_status 'Found no authors'
168+
return
169+
end
170+
171+
print_good "Found #{authors.size} authors: #{authors.join ', '}"
172+
write_output authors.join "\n"
173+
end
174+
end

0 commit comments

Comments
 (0)