origami 1.2.7 → 2.0.0

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