origami 1.0.2
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.
- data/COPYING.LESSER +165 -0
- data/README +77 -0
- data/VERSION +1 -0
- data/bin/config/pdfcop.conf.yml +237 -0
- data/bin/gui/about.rb +46 -0
- data/bin/gui/config.rb +132 -0
- data/bin/gui/file.rb +385 -0
- data/bin/gui/hexdump.rb +74 -0
- data/bin/gui/hexview.rb +91 -0
- data/bin/gui/imgview.rb +72 -0
- data/bin/gui/menu.rb +392 -0
- data/bin/gui/properties.rb +132 -0
- data/bin/gui/signing.rb +635 -0
- data/bin/gui/textview.rb +107 -0
- data/bin/gui/treeview.rb +409 -0
- data/bin/gui/walker.rb +282 -0
- data/bin/gui/xrefs.rb +79 -0
- data/bin/pdf2graph +121 -0
- data/bin/pdf2ruby +353 -0
- data/bin/pdfcocoon +104 -0
- data/bin/pdfcop +455 -0
- data/bin/pdfdecompress +104 -0
- data/bin/pdfdecrypt +95 -0
- data/bin/pdfencrypt +112 -0
- data/bin/pdfextract +221 -0
- data/bin/pdfmetadata +123 -0
- data/bin/pdfsh +13 -0
- data/bin/pdfwalker +7 -0
- data/bin/shell/.irbrc +104 -0
- data/bin/shell/console.rb +136 -0
- data/bin/shell/hexdump.rb +83 -0
- data/origami.rb +36 -0
- data/origami/3d.rb +239 -0
- data/origami/acroform.rb +321 -0
- data/origami/actions.rb +299 -0
- data/origami/adobe/fdf.rb +259 -0
- data/origami/adobe/ppklite.rb +489 -0
- data/origami/annotations.rb +775 -0
- data/origami/array.rb +187 -0
- data/origami/boolean.rb +101 -0
- data/origami/catalog.rb +486 -0
- data/origami/destinations.rb +213 -0
- data/origami/dictionary.rb +188 -0
- data/origami/docmdp.rb +96 -0
- data/origami/encryption.rb +1293 -0
- data/origami/export.rb +283 -0
- data/origami/file.rb +222 -0
- data/origami/filters.rb +250 -0
- data/origami/filters/ascii.rb +189 -0
- data/origami/filters/ccitt.rb +515 -0
- data/origami/filters/crypt.rb +47 -0
- data/origami/filters/dct.rb +61 -0
- data/origami/filters/flate.rb +112 -0
- data/origami/filters/jbig2.rb +63 -0
- data/origami/filters/jpx.rb +53 -0
- data/origami/filters/lzw.rb +195 -0
- data/origami/filters/predictors.rb +276 -0
- data/origami/filters/runlength.rb +117 -0
- data/origami/font.rb +209 -0
- data/origami/functions.rb +93 -0
- data/origami/graphics.rb +33 -0
- data/origami/graphics/colors.rb +191 -0
- data/origami/graphics/instruction.rb +126 -0
- data/origami/graphics/path.rb +154 -0
- data/origami/graphics/patterns.rb +180 -0
- data/origami/graphics/state.rb +164 -0
- data/origami/graphics/text.rb +224 -0
- data/origami/graphics/xobject.rb +493 -0
- data/origami/header.rb +90 -0
- data/origami/linearization.rb +318 -0
- data/origami/metadata.rb +114 -0
- data/origami/name.rb +170 -0
- data/origami/null.rb +75 -0
- data/origami/numeric.rb +188 -0
- data/origami/obfuscation.rb +233 -0
- data/origami/object.rb +527 -0
- data/origami/outline.rb +59 -0
- data/origami/page.rb +559 -0
- data/origami/parser.rb +268 -0
- data/origami/parsers/fdf.rb +45 -0
- data/origami/parsers/pdf.rb +27 -0
- data/origami/parsers/pdf/linear.rb +113 -0
- data/origami/parsers/ppklite.rb +86 -0
- data/origami/pdf.rb +1144 -0
- data/origami/reference.rb +113 -0
- data/origami/signature.rb +474 -0
- data/origami/stream.rb +575 -0
- data/origami/string.rb +416 -0
- data/origami/trailer.rb +173 -0
- data/origami/webcapture.rb +87 -0
- data/origami/xfa.rb +3027 -0
- data/origami/xreftable.rb +447 -0
- data/templates/patterns.rb +66 -0
- data/templates/widgets.rb +173 -0
- data/templates/xdp.rb +92 -0
- data/tests/dataset/test.dummycrt +28 -0
- data/tests/dataset/test.dummykey +27 -0
- data/tests/tc_actions.rb +32 -0
- data/tests/tc_annotations.rb +85 -0
- data/tests/tc_pages.rb +37 -0
- data/tests/tc_pdfattach.rb +24 -0
- data/tests/tc_pdfencrypt.rb +110 -0
- data/tests/tc_pdfnew.rb +32 -0
- data/tests/tc_pdfparse.rb +98 -0
- data/tests/tc_pdfsig.rb +37 -0
- data/tests/tc_streams.rb +129 -0
- data/tests/ts_pdf.rb +45 -0
- metadata +193 -0
data/bin/pdfcop
ADDED
@@ -0,0 +1,455 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
=begin
|
4
|
+
|
5
|
+
= Author:
|
6
|
+
Guillaume Delugré <guillaume/at/security-labs.org>
|
7
|
+
|
8
|
+
= Info:
|
9
|
+
This is a PDF document filtering engine using Origami.
|
10
|
+
Security policies are based on a white list of PDF features.
|
11
|
+
Default policies details can be found in the default configuration file.
|
12
|
+
You can also add your own policy and activate it using the -p switch.
|
13
|
+
|
14
|
+
= License:
|
15
|
+
Origami is free software: you can redistribute it and/or modify
|
16
|
+
it under the terms of the GNU Lesser General Public License as published by
|
17
|
+
the Free Software Foundation, either version 3 of the License, or
|
18
|
+
(at your option) any later version.
|
19
|
+
|
20
|
+
Origami is distributed in the hope that it will be useful,
|
21
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
22
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
23
|
+
GNU Lesser General Public License for more details.
|
24
|
+
|
25
|
+
You should have received a copy of the GNU Lesser General Public License
|
26
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
27
|
+
|
28
|
+
=end
|
29
|
+
|
30
|
+
begin
|
31
|
+
require 'origami'
|
32
|
+
rescue LoadError
|
33
|
+
ORIGAMIDIR = "#{File.dirname(__FILE__)}/.."
|
34
|
+
$: << ORIGAMIDIR
|
35
|
+
require 'origami'
|
36
|
+
end
|
37
|
+
include Origami
|
38
|
+
|
39
|
+
require 'optparse'
|
40
|
+
require 'yaml'
|
41
|
+
require 'rexml/document'
|
42
|
+
require 'digest/md5'
|
43
|
+
|
44
|
+
DEFAULT_CONFIG_FILE = "#{File.dirname(__FILE__)}/config/pdfcop.conf.yml"
|
45
|
+
DEFAULT_POLICY = "standard"
|
46
|
+
SECURITY_POLICIES = {}
|
47
|
+
|
48
|
+
def load_config_file(path)
|
49
|
+
SECURITY_POLICIES.update(Hash.new(false).update YAML.load(File.read(path)))
|
50
|
+
end
|
51
|
+
|
52
|
+
class OptParser
|
53
|
+
BANNER = <<USAGE
|
54
|
+
Usage: #{$0} [options] <PDF-file>
|
55
|
+
The PDF filtering engine. Scans PDF documents for malicious structures.
|
56
|
+
Bug reports or feature requests at: http://origami-pdf.googlecode.com/
|
57
|
+
|
58
|
+
Options:
|
59
|
+
USAGE
|
60
|
+
|
61
|
+
def self.parse(args)
|
62
|
+
options = {:colors => true}
|
63
|
+
|
64
|
+
opts = OptionParser.new do |opts|
|
65
|
+
opts.banner = BANNER
|
66
|
+
|
67
|
+
opts.on("-o", "--output LOG_FILE", "Output log file (default STDOUT)") do |o|
|
68
|
+
options[:output_log] = o
|
69
|
+
end
|
70
|
+
|
71
|
+
opts.on("-c", "--config CONFIG_FILE", "Load security policies from given configuration file") do |cf|
|
72
|
+
options[:config_file] = cf
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.on("-p", "--policy POLICY_NAME", "Specify applied policy. Predefined policies: 'none', 'standard', 'strong', 'paranoid'") do |p|
|
76
|
+
options[:policy] = p
|
77
|
+
end
|
78
|
+
|
79
|
+
opts.on("-n", "--no-color", "Suppress colored output") do
|
80
|
+
options[:colors] = false
|
81
|
+
end
|
82
|
+
|
83
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
84
|
+
puts opts
|
85
|
+
exit
|
86
|
+
end
|
87
|
+
end
|
88
|
+
opts.parse!(args)
|
89
|
+
|
90
|
+
options
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
@options = OptParser.parse(ARGV)
|
95
|
+
if @options.has_key?(:output_log)
|
96
|
+
LOGGER = File.open(@options[:output_log], "a+")
|
97
|
+
else
|
98
|
+
LOGGER = STDOUT
|
99
|
+
end
|
100
|
+
|
101
|
+
if not @options.has_key?(:policy)
|
102
|
+
@options[:policy] = DEFAULT_POLICY
|
103
|
+
end
|
104
|
+
|
105
|
+
load_config_file(@options[:config_file] || DEFAULT_CONFIG_FILE)
|
106
|
+
unless SECURITY_POLICIES.has_key?("POLICY_#{@options[:policy].upcase}")
|
107
|
+
STDERR.puts "Undeclared policy `#{@options[:policy]}'"
|
108
|
+
exit(1)
|
109
|
+
end
|
110
|
+
|
111
|
+
if ARGV.empty?
|
112
|
+
STDERR.puts "Error: No filename was specified. #{$0} --help for details."
|
113
|
+
exit 1
|
114
|
+
else
|
115
|
+
TARGET = ARGV.shift
|
116
|
+
end
|
117
|
+
|
118
|
+
def log(str, color = Colors::GREY)
|
119
|
+
if @options[:colors]
|
120
|
+
colorprint("[#{Time.now}] ", Colors::CYAN, LOGGER)
|
121
|
+
colorprint(str, color, LOGGER)
|
122
|
+
else
|
123
|
+
LOGGER.print("[#{Time.now}] #{str}")
|
124
|
+
end
|
125
|
+
|
126
|
+
LOGGER.puts
|
127
|
+
end
|
128
|
+
|
129
|
+
def reject(cause)
|
130
|
+
log("Document rejected by policy `#{@options[:policy]}', caused by #{cause.inspect}.", Colors::RED)
|
131
|
+
exit(1)
|
132
|
+
end
|
133
|
+
|
134
|
+
def check_rights(*required_rights)
|
135
|
+
current_rights = SECURITY_POLICIES["POLICY_#{@options[:policy].upcase}"]
|
136
|
+
|
137
|
+
reject(required_rights) if required_rights.any?{|right| current_rights[right.to_s] == false}
|
138
|
+
end
|
139
|
+
|
140
|
+
def analyze_xfa_forms(xfa)
|
141
|
+
case xfa
|
142
|
+
when Array then
|
143
|
+
xml = ""
|
144
|
+
i = 0
|
145
|
+
xfa.each do |packet|
|
146
|
+
if i % 2 == 1
|
147
|
+
xml << packet.solve.data
|
148
|
+
end
|
149
|
+
|
150
|
+
i = i + 1
|
151
|
+
end
|
152
|
+
when Stream then
|
153
|
+
xml = xfa.data
|
154
|
+
else
|
155
|
+
reject("Malformed XFA dictionary")
|
156
|
+
end
|
157
|
+
|
158
|
+
xfadoc = REXML::Document.new(xml)
|
159
|
+
REXML::XPath.match(xfadoc, "//script").each do |script|
|
160
|
+
case script.attributes["contentType"]
|
161
|
+
when "application/x-formcalc" then
|
162
|
+
check_rights(:allowFormCalc)
|
163
|
+
else
|
164
|
+
check_rights(:allowJS)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def analyze_annotation(annot, level = 0)
|
170
|
+
check_rights(:allowAnnotations)
|
171
|
+
|
172
|
+
if annot.is_a?(Dictionary) and annot.has_key?(:Subtype)
|
173
|
+
case annot[:Subtype].solve.value
|
174
|
+
when :FileAttachment then
|
175
|
+
check_rights(:allowAttachments, :allowFileAttachmentAnnotation)
|
176
|
+
|
177
|
+
when :Sound then
|
178
|
+
check_rights(:allowSoundAnnotation)
|
179
|
+
|
180
|
+
when :Movie then
|
181
|
+
check_rights(:allowMovieAnnotation)
|
182
|
+
|
183
|
+
when :Screen then
|
184
|
+
check_rights(:allowScreenAnnotation)
|
185
|
+
|
186
|
+
when :Widget then
|
187
|
+
check_rights(:allowAcroforms)
|
188
|
+
|
189
|
+
when :"3D" then
|
190
|
+
check_rights(:allow3DAnnotation)
|
191
|
+
|
192
|
+
# 3D annotation might pull in JavaScript for real-time driven behavior.
|
193
|
+
if annot.has_key?(:"3DD")
|
194
|
+
dd = annot[:"3DD"].solve
|
195
|
+
u3dstream = nil
|
196
|
+
|
197
|
+
case dd
|
198
|
+
when Stream then
|
199
|
+
u3dstream = dd
|
200
|
+
when Dictionary then
|
201
|
+
u3dstream = dd[:"3DD"]
|
202
|
+
end
|
203
|
+
|
204
|
+
if u3dstream and u3dstream.has_field?(:OnInstantiate)
|
205
|
+
check_rights(:allowJS)
|
206
|
+
|
207
|
+
if annot.has_key?(:"3DA") # is 3d view instantiated automatically?
|
208
|
+
u3dactiv = annot[:"3DA"].solve
|
209
|
+
|
210
|
+
check_rights(:allowJSAtOpening) if u3dactiv.is_a?(Dictionary) and (u3dactiv[:A] == :PO or u3dactiv[:A] == :PV)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
when :RichMedia then
|
216
|
+
check_rights(:allowRichMediaAnnotation)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def analyze_page(page, level = 0)
|
222
|
+
section_prefix = " " * 2 * level + ">" * (level + 1)
|
223
|
+
log(section_prefix + " Inspecting page...")
|
224
|
+
|
225
|
+
text_prefix = " " * 2 * (level + 1) + "." * (level + 1)
|
226
|
+
if page.is_a?(Dictionary)
|
227
|
+
|
228
|
+
#
|
229
|
+
# Checking page additional actions.
|
230
|
+
#
|
231
|
+
if page.has_key?(:AA)
|
232
|
+
if page.AA.is_a?(Dictionary)
|
233
|
+
log(text_prefix + " Page has an action dictionary.")
|
234
|
+
|
235
|
+
aa = PageAdditionalActions.new(page.AA); aa.parent = page.AA.parent
|
236
|
+
analyze_action(aa.O, true, level + 1) if aa.has_key?(:O)
|
237
|
+
analyze_action(aa.C, false, level + 1) if aa.has_key?(:C)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
#
|
242
|
+
# Looking for page annotations.
|
243
|
+
#
|
244
|
+
page.each_annot do |annot|
|
245
|
+
analyze_annotation(annot, level + 1)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def analyze_action(action, triggered_at_opening, level = 0)
|
251
|
+
section_prefix = " " * 2 * level + ">" * (level + 1)
|
252
|
+
log(section_prefix + " Inspecting action...")
|
253
|
+
|
254
|
+
text_prefix = " " * 2 * (level + 1) + "." * (level + 1)
|
255
|
+
if action.is_a?(Dictionary)
|
256
|
+
log(text_prefix + " Found #{action[:S]} action.")
|
257
|
+
type = action[:S].is_a?(Reference) ? action[:S].solve : action[:S]
|
258
|
+
|
259
|
+
case type.value
|
260
|
+
when :JavaScript
|
261
|
+
check_rights(:allowJS)
|
262
|
+
check_rights(:allowJSAtOpening) if triggered_at_opening
|
263
|
+
|
264
|
+
when :Launch
|
265
|
+
check_rights(:allowLaunchAction)
|
266
|
+
|
267
|
+
when :Named
|
268
|
+
check_rights(:allowNamedAction)
|
269
|
+
|
270
|
+
when :GoTo
|
271
|
+
check_rights(:allowGoToAction)
|
272
|
+
dest = action[:D].is_a?(Reference) ? action[:D].solve : action[:D]
|
273
|
+
if dest.is_a?(Array) and dest.length > 0 and dest.first.is_a?(Reference)
|
274
|
+
dest_page = dest.first.solve
|
275
|
+
if dest_page.is_a?(Page)
|
276
|
+
log(text_prefix + " Destination page found.")
|
277
|
+
analyze_page(dest_page, level + 1)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
when :GoToE
|
282
|
+
check_rights(:allowAttachments,:allowGoToEAction)
|
283
|
+
|
284
|
+
when :GoToR
|
285
|
+
check_rights(:allowGoToRAction)
|
286
|
+
|
287
|
+
when :Thread
|
288
|
+
check_rights(:allowGoToRAction) if action.has_key?(:F)
|
289
|
+
|
290
|
+
when :URI
|
291
|
+
check_rights(:allowURIAction)
|
292
|
+
|
293
|
+
when :SubmitForm
|
294
|
+
check_rights(:allowAcroForms,:allowSubmitFormAction)
|
295
|
+
|
296
|
+
when :ImportData
|
297
|
+
check_rights(:allowAcroForms,:allowImportDataAction)
|
298
|
+
|
299
|
+
when :Rendition
|
300
|
+
check_rights(:allowScreenAnnotation,:allowRenditionAction)
|
301
|
+
|
302
|
+
when :Sound
|
303
|
+
check_rights(:allowSoundAnnotation,:allowSoundAction)
|
304
|
+
|
305
|
+
when :Movie
|
306
|
+
check_rights(:allowMovieAnnotation,:allowMovieAction)
|
307
|
+
|
308
|
+
when :RichMediaExecute
|
309
|
+
check_rights(:allowRichMediaAnnotation,:allowRichMediaAction)
|
310
|
+
|
311
|
+
when :GoTo3DView
|
312
|
+
check_rights(:allow3DAnnotation,:allowGoTo3DAction)
|
313
|
+
end
|
314
|
+
|
315
|
+
if action.has_key?(:Next)
|
316
|
+
log(text_prefix + "This action is chained to another action!")
|
317
|
+
check_rights(:allowChainedActions)
|
318
|
+
analyze_action(action.Next)
|
319
|
+
end
|
320
|
+
elsif action.is_a?(Array)
|
321
|
+
dest = action
|
322
|
+
if dest.length > 0 and dest.first.is_a?(Reference)
|
323
|
+
dest_page = dest.first.solve
|
324
|
+
if dest_page.is_a?(Page)
|
325
|
+
log(text_prefix + " Destination page found.")
|
326
|
+
check_rights(:allowGoToAction)
|
327
|
+
analyze_page(dest_page, level + 1)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
begin
|
334
|
+
log("PDFcop is running on target `#{TARGET}', policy = `#{@options[:policy]}'", Colors::GREEN)
|
335
|
+
log(" File size: #{File.size(TARGET)} bytes", Colors::MAGENTA)
|
336
|
+
log(" MD5: #{Digest::MD5.hexdigest(File.read(TARGET))}", Colors::MAGENTA)
|
337
|
+
|
338
|
+
@pdf = PDF.read(TARGET,
|
339
|
+
:verbosity => Parser::VERBOSE_QUIET,
|
340
|
+
:ignore_errors => SECURITY_POLICIES["POLICY_#{@options[:policy].upcase}"]['allowParserErrors']
|
341
|
+
)
|
342
|
+
|
343
|
+
log("> Inspecting document structure...", Colors::YELLOW)
|
344
|
+
if @pdf.is_encrypted?
|
345
|
+
log(" . Encryption = YES")
|
346
|
+
check_rights(:allowEncryption)
|
347
|
+
end
|
348
|
+
|
349
|
+
log("> Inspecting document catalog...", Colors::YELLOW)
|
350
|
+
catalog = @pdf.Catalog
|
351
|
+
reject("Invalid document catalog") unless catalog.is_a?(Catalog)
|
352
|
+
|
353
|
+
if catalog.has_key?(:OpenAction)
|
354
|
+
log(" . OpenAction entry = YES")
|
355
|
+
check_rights(:allowOpenAction)
|
356
|
+
action = catalog.OpenAction
|
357
|
+
analyze_action(action, true, 1)
|
358
|
+
end
|
359
|
+
|
360
|
+
if catalog.has_key?(:AA)
|
361
|
+
if catalog.AA.is_a?(Dictionary)
|
362
|
+
aa = CatalogAdditionalActions.new(catalog.AA); aa.parent = catalog;
|
363
|
+
log(" . Additional actions dictionary = YES")
|
364
|
+
analyze_action(aa.WC, false, 1) if aa.has_key?(:WC)
|
365
|
+
analyze_action(aa.WS, false, 1) if aa.has_key?(:WS)
|
366
|
+
analyze_action(aa.DS, false, 1) if aa.has_key?(:DS)
|
367
|
+
analyze_action(aa.WP, false, 1) if aa.has_key?(:WP)
|
368
|
+
analyze_action(aa.DP, false, 1) if aa.has_key?(:DP)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
if catalog.has_key?(:AcroForm)
|
373
|
+
acroform = catalog.AcroForm
|
374
|
+
if acroform.is_a?(Dictionary)
|
375
|
+
log(" . AcroForm = YES")
|
376
|
+
check_rights(:allowAcroForms)
|
377
|
+
if acroform.has_key?(:XFA)
|
378
|
+
log(" . XFA = YES")
|
379
|
+
check_rights(:allowXFAForms)
|
380
|
+
|
381
|
+
analyze_xfa_forms(acroform[:XFA].solve)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
log("> Inspecting JavaScript names directory...", Colors::YELLOW)
|
387
|
+
unless @pdf.ls_names(Names::Root::JAVASCRIPT).empty?
|
388
|
+
check_rights(:allowJS)
|
389
|
+
check_rights(:allowJSAtOpening)
|
390
|
+
end
|
391
|
+
|
392
|
+
log("> Inspecting attachment names directory...", Colors::YELLOW)
|
393
|
+
unless @pdf.ls_names(Names::Root::EMBEDDEDFILES).empty?
|
394
|
+
check_rights(:allowAttachments)
|
395
|
+
end
|
396
|
+
|
397
|
+
log("> Inspecting document pages...", Colors::YELLOW)
|
398
|
+
@pdf.each_page do |page|
|
399
|
+
analyze_page(page, 1)
|
400
|
+
end
|
401
|
+
|
402
|
+
log("> Inspecting document streams...", Colors::YELLOW)
|
403
|
+
@pdf.indirect_objects.find_all{|obj| obj.is_a?(Stream)}.each do |stream|
|
404
|
+
if stream.dictionary.has_key?(:Filter)
|
405
|
+
filters = stream.Filter
|
406
|
+
filters = [ filters ] if filters.is_a?(Name)
|
407
|
+
|
408
|
+
if filters.is_a?(Array)
|
409
|
+
filters.each do |filter|
|
410
|
+
case filter.value
|
411
|
+
when :ASCIIHexDecode
|
412
|
+
check_rights(:allowASCIIHexFilter)
|
413
|
+
when :ASCII85Decode
|
414
|
+
check_rights(:allowASCII85Filter)
|
415
|
+
when :LZWDecode
|
416
|
+
check_rights(:allowLZWFilter)
|
417
|
+
when :FlateDecode
|
418
|
+
check_rights(:allowFlateDecode)
|
419
|
+
when :RunLengthDecode
|
420
|
+
check_rights(:allowRunLengthFilter)
|
421
|
+
when :CCITTFaxDecode
|
422
|
+
check_rights(:allowCCITTFaxFilter)
|
423
|
+
when :JBIG2Decode
|
424
|
+
check_rights(:allowJBIG2Filter)
|
425
|
+
when :DCTDecode
|
426
|
+
check_rights(:allowDCTFilter)
|
427
|
+
when :JPXDecode
|
428
|
+
check_rights(:allowJPXFilter)
|
429
|
+
when :Crypt
|
430
|
+
check_rights(:allowCryptFilter)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
#
|
438
|
+
# TODO: Detect JS at opening in XFA (check event tag)
|
439
|
+
# Check image encoding in XFA ?
|
440
|
+
# Only allow valid signed documents ?
|
441
|
+
# Recursively scan attached files.
|
442
|
+
# On-the-fly injection of prerun JS code to hook vulnerable methods (dynamic exploit detection) ???
|
443
|
+
# ...
|
444
|
+
#
|
445
|
+
|
446
|
+
log("Document accepted by policy `#{@options[:policy]}'.", Colors::GREEN)
|
447
|
+
|
448
|
+
rescue SystemExit
|
449
|
+
rescue Exception => e
|
450
|
+
log("An error occured during analysis : #{e.class} (#{e.message})")
|
451
|
+
reject("Analysis failure")
|
452
|
+
ensure
|
453
|
+
LOGGER.close
|
454
|
+
end
|
455
|
+
|