origami 1.2.7 → 2.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +66 -0
- data/README.md +112 -0
- data/bin/config/pdfcop.conf.yml +232 -233
- data/bin/gui/about.rb +27 -37
- data/bin/gui/config.rb +108 -117
- data/bin/gui/file.rb +416 -365
- data/bin/gui/gtkhex.rb +1138 -1153
- data/bin/gui/hexview.rb +55 -57
- data/bin/gui/imgview.rb +48 -51
- data/bin/gui/menu.rb +388 -386
- data/bin/gui/properties.rb +114 -130
- data/bin/gui/signing.rb +571 -617
- data/bin/gui/textview.rb +77 -95
- data/bin/gui/treeview.rb +382 -387
- data/bin/gui/walker.rb +227 -232
- data/bin/gui/xrefs.rb +56 -60
- data/bin/pdf2pdfa +53 -57
- data/bin/pdf2ruby +212 -228
- data/bin/pdfcop +338 -348
- data/bin/pdfdecompress +58 -65
- data/bin/pdfdecrypt +56 -60
- data/bin/pdfencrypt +75 -80
- data/bin/pdfexplode +185 -182
- data/bin/pdfextract +201 -218
- data/bin/pdfmetadata +83 -82
- data/bin/pdfsh +4 -5
- data/bin/pdfwalker +1 -2
- data/bin/shell/.irbrc +45 -82
- data/bin/shell/console.rb +105 -130
- data/bin/shell/hexdump.rb +40 -64
- data/examples/README.md +34 -0
- data/examples/attachments/attachment.rb +38 -0
- data/examples/attachments/nested_document.rb +51 -0
- data/examples/encryption/encryption.rb +28 -0
- data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
- data/examples/flash/flash.rb +37 -0
- data/{samples → examples}/flash/helloworld.swf +0 -0
- data/examples/forms/javascript.rb +54 -0
- data/examples/forms/xfa.rb +115 -0
- data/examples/javascript/hello_world.rb +22 -0
- data/examples/javascript/js_emulation.rb +54 -0
- data/examples/loop/goto.rb +32 -0
- data/examples/loop/named.rb +33 -0
- data/examples/signature/signature.rb +65 -0
- data/examples/uri/javascript.rb +56 -0
- data/examples/uri/open-uri.rb +21 -0
- data/examples/uri/submitform.rb +47 -0
- data/lib/origami.rb +29 -42
- data/lib/origami/3d.rb +350 -225
- data/lib/origami/acroform.rb +262 -288
- data/lib/origami/actions.rb +268 -288
- data/lib/origami/annotations.rb +697 -722
- data/lib/origami/array.rb +258 -184
- data/lib/origami/boolean.rb +74 -84
- data/lib/origami/catalog.rb +397 -434
- data/lib/origami/collections.rb +144 -0
- data/lib/origami/destinations.rb +233 -194
- data/lib/origami/dictionary.rb +253 -232
- data/lib/origami/encryption.rb +1274 -1243
- data/lib/origami/export.rb +232 -268
- data/lib/origami/extensions/fdf.rb +307 -220
- data/lib/origami/extensions/ppklite.rb +368 -435
- data/lib/origami/filespec.rb +197 -0
- data/lib/origami/filters.rb +301 -295
- data/lib/origami/filters/ascii.rb +177 -180
- data/lib/origami/filters/ccitt.rb +528 -535
- data/lib/origami/filters/crypt.rb +26 -35
- data/lib/origami/filters/dct.rb +46 -52
- data/lib/origami/filters/flate.rb +95 -94
- data/lib/origami/filters/jbig2.rb +49 -55
- data/lib/origami/filters/jpx.rb +38 -44
- data/lib/origami/filters/lzw.rb +189 -183
- data/lib/origami/filters/predictors.rb +221 -235
- data/lib/origami/filters/runlength.rb +103 -104
- data/lib/origami/font.rb +173 -186
- data/lib/origami/functions.rb +67 -81
- data/lib/origami/graphics.rb +25 -21
- data/lib/origami/graphics/colors.rb +178 -187
- data/lib/origami/graphics/instruction.rb +79 -85
- data/lib/origami/graphics/path.rb +142 -148
- data/lib/origami/graphics/patterns.rb +160 -167
- data/lib/origami/graphics/render.rb +43 -50
- data/lib/origami/graphics/state.rb +138 -153
- data/lib/origami/graphics/text.rb +188 -205
- data/lib/origami/graphics/xobject.rb +819 -815
- data/lib/origami/header.rb +63 -78
- data/lib/origami/javascript.rb +596 -597
- data/lib/origami/linearization.rb +285 -290
- data/lib/origami/metadata.rb +139 -148
- data/lib/origami/name.rb +112 -148
- data/lib/origami/null.rb +53 -62
- data/lib/origami/numeric.rb +162 -175
- data/lib/origami/obfuscation.rb +186 -174
- data/lib/origami/object.rb +593 -573
- data/lib/origami/outline.rb +42 -47
- data/lib/origami/outputintents.rb +73 -82
- data/lib/origami/page.rb +703 -592
- data/lib/origami/parser.rb +238 -290
- data/lib/origami/parsers/fdf.rb +41 -33
- data/lib/origami/parsers/pdf.rb +75 -95
- data/lib/origami/parsers/pdf/lazy.rb +137 -0
- data/lib/origami/parsers/pdf/linear.rb +64 -66
- data/lib/origami/parsers/ppklite.rb +34 -70
- data/lib/origami/pdf.rb +1030 -1005
- data/lib/origami/reference.rb +102 -102
- data/lib/origami/signature.rb +591 -609
- data/lib/origami/stream.rb +668 -551
- data/lib/origami/string.rb +397 -373
- data/lib/origami/template/patterns.rb +56 -0
- data/lib/origami/template/widgets.rb +151 -0
- data/lib/origami/trailer.rb +144 -158
- data/lib/origami/tree.rb +62 -0
- data/lib/origami/version.rb +23 -0
- data/lib/origami/webcapture.rb +88 -79
- data/lib/origami/xfa.rb +2863 -2882
- data/lib/origami/xreftable.rb +472 -384
- data/test/dataset/calc.pdf +85 -0
- data/test/dataset/crypto.pdf +82 -0
- data/test/dataset/empty.pdf +49 -0
- data/test/test_actions.rb +27 -0
- data/test/test_annotations.rb +90 -0
- data/test/test_pages.rb +31 -0
- data/test/test_pdf.rb +16 -0
- data/test/test_pdf_attachment.rb +34 -0
- data/test/test_pdf_create.rb +24 -0
- data/test/test_pdf_encrypt.rb +95 -0
- data/test/test_pdf_parse.rb +96 -0
- data/test/test_pdf_sign.rb +58 -0
- data/test/test_streams.rb +182 -0
- data/test/test_xrefs.rb +67 -0
- metadata +88 -58
- data/README +0 -67
- data/bin/pdf2graph +0 -121
- data/bin/pdfcocoon +0 -104
- data/lib/origami/file.rb +0 -233
- data/samples/README.txt +0 -45
- data/samples/actions/launch/calc.rb +0 -87
- data/samples/actions/launch/winparams.rb +0 -22
- data/samples/actions/loop/loopgoto.rb +0 -24
- data/samples/actions/loop/loopnamed.rb +0 -21
- data/samples/actions/named/named.rb +0 -31
- data/samples/actions/samba/smbrelay.rb +0 -26
- data/samples/actions/webbug/submitform.js +0 -26
- data/samples/actions/webbug/webbug-browser.rb +0 -68
- data/samples/actions/webbug/webbug-js.rb +0 -67
- data/samples/actions/webbug/webbug-reader.rb +0 -90
- data/samples/attachments/attach.rb +0 -40
- data/samples/attachments/attached.txt +0 -1
- data/samples/crypto/crypto.rb +0 -28
- data/samples/digsig/signed.rb +0 -46
- data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
- data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
- data/samples/exploits/exploit_customdictopen.rb +0 -55
- data/samples/exploits/getannots.rb +0 -69
- data/samples/flash/flash.rb +0 -31
- data/samples/javascript/attached.txt +0 -1
- data/samples/javascript/js.rb +0 -52
- data/templates/patterns.rb +0 -66
- data/templates/widgets.rb +0 -173
- data/templates/xdp.rb +0 -92
- data/test/ts_pdf.rb +0 -50
data/bin/pdfcop
CHANGED
@@ -2,454 +2,444 @@
|
|
2
2
|
|
3
3
|
=begin
|
4
4
|
|
5
|
-
=
|
6
|
-
|
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
|
-
=
|
9
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
26
|
-
|
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
|
-
|
30
|
+
require 'origami'
|
32
31
|
rescue LoadError
|
33
|
-
|
34
|
-
|
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
|
-
|
47
|
+
SECURITY_POLICIES.update(Hash.new(false).update YAML.load(File.read(path)))
|
50
48
|
end
|
51
49
|
|
52
50
|
class OptParser
|
53
|
-
|
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://
|
54
|
+
Bug reports or feature requests at: http://github.com/gdelugre/origami
|
57
55
|
|
58
56
|
Options:
|
59
57
|
USAGE
|
60
58
|
|
61
|
-
|
62
|
-
|
59
|
+
def self.parse(args)
|
60
|
+
options = {colors: true}
|
63
61
|
|
64
|
-
|
65
|
-
|
62
|
+
opts = OptionParser.new do |opts|
|
63
|
+
opts.banner = BANNER
|
66
64
|
|
67
|
-
|
68
|
-
|
69
|
-
|
65
|
+
opts.on("-o", "--output LOG_FILE", "Output log file (default STDOUT)") do |o|
|
66
|
+
options[:output_log] = o
|
67
|
+
end
|
70
68
|
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
77
|
+
opts.on("-n", "--no-color", "Turn off colorized output") do
|
78
|
+
options[:disable_colors] = true
|
79
|
+
end
|
82
80
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
91
|
-
|
89
|
+
options
|
90
|
+
end
|
92
91
|
end
|
93
92
|
|
94
93
|
@options = OptParser.parse(ARGV)
|
95
94
|
if @options.has_key?(:output_log)
|
96
|
-
|
95
|
+
LOGGER = File.open(@options[:output_log], "a+")
|
97
96
|
else
|
98
|
-
|
97
|
+
LOGGER = STDOUT
|
99
98
|
end
|
100
99
|
|
101
100
|
if not @options.has_key?(:policy)
|
102
|
-
|
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
|
-
|
108
|
-
exit(1)
|
108
|
+
abort "Undeclared policy `#{@options[:policy]}'"
|
109
109
|
end
|
110
110
|
|
111
111
|
if ARGV.empty?
|
112
|
-
|
113
|
-
exit 1
|
112
|
+
abort "Error: No filename was specified. #{$0} --help for details."
|
114
113
|
else
|
115
|
-
|
114
|
+
TARGET = ARGV.shift
|
116
115
|
end
|
117
116
|
|
118
|
-
def log(str, color =
|
119
|
-
|
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
|
-
|
131
|
-
|
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
|
-
|
127
|
+
current_rights = SECURITY_POLICIES["POLICY_#{@options[:policy].upcase}"]
|
136
128
|
|
137
|
-
|
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
|
-
|
142
|
-
when Array then
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
-
|
151
|
-
|
152
|
-
when Stream then
|
153
|
-
|
142
|
+
i = i + 1
|
143
|
+
end
|
144
|
+
when Origami::Stream then
|
145
|
+
xml = xfa.data
|
154
146
|
else
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
216
|
-
|
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
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
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
|
-
|
282
|
-
|
283
|
-
|
284
|
-
when :GoToR
|
285
|
-
check_rights(:allowGoToRAction)
|
272
|
+
when :GoToE
|
273
|
+
check_rights(:allowAttachments,:allowGoToEAction)
|
286
274
|
|
287
|
-
|
288
|
-
|
275
|
+
when :GoToR
|
276
|
+
check_rights(:allowGoToRAction)
|
289
277
|
|
290
|
-
|
291
|
-
|
278
|
+
when :Thread
|
279
|
+
check_rights(:allowGoToRAction) if action.has_key?(:F)
|
292
280
|
|
293
|
-
|
294
|
-
|
281
|
+
when :URI
|
282
|
+
check_rights(:allowURIAction)
|
295
283
|
|
296
|
-
|
297
|
-
|
284
|
+
when :SubmitForm
|
285
|
+
check_rights(:allowAcroForms,:allowSubmitFormAction)
|
298
286
|
|
299
|
-
|
300
|
-
|
287
|
+
when :ImportData
|
288
|
+
check_rights(:allowAcroForms,:allowImportDataAction)
|
301
289
|
|
302
|
-
|
303
|
-
|
290
|
+
when :Rendition
|
291
|
+
check_rights(:allowScreenAnnotation,:allowRenditionAction)
|
304
292
|
|
305
|
-
|
306
|
-
|
293
|
+
when :Sound
|
294
|
+
check_rights(:allowSoundAnnotation,:allowSoundAction)
|
307
295
|
|
308
|
-
|
309
|
-
|
296
|
+
when :Movie
|
297
|
+
check_rights(:allowMovieAnnotation,:allowMovieAction)
|
310
298
|
|
311
|
-
|
312
|
-
|
313
|
-
end
|
299
|
+
when :RichMediaExecute
|
300
|
+
check_rights(:allowRichMediaAnnotation,:allowRichMediaAction)
|
314
301
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
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
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
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
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
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
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
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
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
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
|
-
|
444
|
+
LOGGER.close
|
454
445
|
end
|
455
|
-
|