File: path_traversal_test.rb

package info (click to toggle)
ruby-zip 3.2.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 11,120 kB
  • sloc: ruby: 9,958; makefile: 23
file content (202 lines) | stat: -rw-r--r-- 5,955 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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
# frozen_string_literal: true

require_relative 'test_helper'

class PathTraversalTest < Minitest::Test
  TEST_FILE_ROOT = File.absolute_path('test/data/path_traversal')

  def setup
    # With apologies to anyone using these files... but they are the files in
    # the sample zips, so we don't have much choice here.
    FileUtils.rm_f '/tmp/moo'
    FileUtils.rm_f '/tmp/file.txt'
  end

  def extract_paths(zip_path, entries)
    ::Zip::File.open(::File.join(TEST_FILE_ROOT, zip_path)) do |zip|
      entries.each do |entry, test|
        if test == :error
          assert_raises(Errno::ENOENT) do
            zip.find_entry(entry).extract
          end
        else
          assert_output('', test) do
            zip.find_entry(entry).extract
          end
        end
      end
    end
  end

  def in_tmpdir
    Dir.mktmpdir do |tmp|
      test_path = File.join(tmp, 'test')
      Dir.mkdir test_path
      Dir.chdir test_path do
        yield test_path
      end
    end
  end

  def test_leading_slash
    entries = { '/tmp/moo' => '' }
    in_tmpdir do |test_path|
      Dir.mkdir('tmp') # Create 'tmp' dir within test directory.
      extract_paths(['jwilk', 'absolute1.zip'], entries)

      # Check that only the relative file is created.
      refute File.exist?('/tmp/moo')
      assert File.exist?(File.join(test_path, 'tmp', 'moo'))
    end
  end

  def test_multiple_leading_slashes
    entries = { '//tmp/moo' => '' }
    in_tmpdir do |test_path|
      Dir.mkdir('tmp') # Create 'tmp' dir within test directory.
      extract_paths(['jwilk', 'absolute2.zip'], entries)

      # Check that only the relative file is created.
      refute File.exist?('/tmp/moo')
      assert File.exist?(File.join(test_path, 'tmp', 'moo'))
    end
  end

  def test_leading_dot_dot
    entries = { '../moo' => /WARNING: skipped extracting '\.\.\/moo'/ }
    in_tmpdir do
      extract_paths(['jwilk', 'relative0.zip'], entries)
      refute File.exist?('../moo')
    end
  end

  def test_non_leading_dot_dot_with_existing_folder
    entries = {
      'tmp/'          => '',
      'tmp/../../moo' => /WARNING: skipped extracting 'tmp\/\.\.\/\.\.\/moo'/
    }
    in_tmpdir do
      extract_paths('relative1.zip', entries)
      assert Dir.exist?('tmp')
      refute File.exist?('../moo')
    end
  end

  def test_non_leading_dot_dot_without_existing_folder
    entries = { 'tmp/../../moo' => /WARNING: skipped extracting 'tmp\/\.\.\/\.\.\/moo'/ }
    in_tmpdir do
      extract_paths(['jwilk', 'relative2.zip'], entries)
      refute File.exist?('../moo')
    end
  end

  def test_file_symlink
    entries = { 'moo' => '' }
    in_tmpdir do
      extract_paths(['jwilk', 'symlink.zip'], entries)
      assert File.exist?('moo')
      refute File.exist?('/tmp/moo')
    end
  end

  def test_directory_symlink
    # Can't create tmp/moo, because the tmp symlink is skipped.
    entries = {
      'tmp'     => /WARNING: skipped symlink '.*\/tmp'/,
      'tmp/moo' => :error
    }
    in_tmpdir do
      extract_paths(['jwilk', 'dirsymlink.zip'], entries)
      refute File.exist?('/tmp/moo')
    end
  end

  def test_two_directory_symlinks_a
    # Can't create par/moo because the symlinks are skipped.
    entries = {
      'cur'     => /WARNING: skipped symlink '.*\/cur'/,
      'par'     => /WARNING: skipped symlink '.*\/par'/,
      'par/moo' => :error
    }
    in_tmpdir do
      extract_paths(['jwilk', 'dirsymlink2a.zip'], entries)
      refute File.exist?('cur')
      refute File.exist?('par')
      refute File.exist?('par/moo')
    end
  end

  def test_two_directory_symlinks_b
    # Can't create par/moo, because the symlinks are skipped.
    entries = {
      'cur'     => /WARNING: skipped symlink '.*\/cur'/,
      'cur/par' => /WARNING: skipped symlink '.*\/cur\/par'/,
      'par/moo' => :error
    }
    in_tmpdir do
      extract_paths(['jwilk', 'dirsymlink2b.zip'], entries)
      refute File.exist?('cur')
      refute File.exist?('../moo')
    end
  end

  def test_entry_name_with_absolute_path_does_not_extract_by_accident
    in_tmpdir do |test_path|
      zip_path = File.join(TEST_FILE_ROOT, 'tuzovakaoff', 'absolutepath.zip')
      Zip::File.open(zip_path) do |zip_file|
        zip_file.each do |entry|
          entry.extract(entry.name, destination_directory: nil)
          assert File.exist?(File.join(test_path, entry.name))
          refute File.exist?(entry.name) unless entry.name == '/tmp/'
        end
      end
    end
  end

  def test_entry_name_with_absolute_path_extracts_to_cwd_by_default
    in_tmpdir do |test_path|
      zip_path = File.join(TEST_FILE_ROOT, 'tuzovakaoff', 'absolutepath.zip')
      Zip::File.open(zip_path) do |zip_file|
        zip_file.each(&:extract)
      end

      # Check that only the relative file is created.
      refute File.exist?('/tmp/file.txt')
      assert File.exist?(File.join(test_path, 'tmp', 'file.txt'))
    end
  end

  def test_entry_name_with_absolute_path_extract_when_given_different_path
    in_tmpdir do |test_path|
      zip_path = File.join(TEST_FILE_ROOT, 'tuzovakaoff', 'absolutepath.zip')
      Zip::File.open(zip_path) do |zip_file|
        zip_file.each do |entry|
          entry.extract(destination_directory: test_path)
        end
      end

      # Check that only the relative file is created.
      refute File.exist?('/tmp/file.txt')
      assert File.exist?(File.join(test_path, 'tmp', 'file.txt'))
    end
  end

  def test_entry_name_with_relative_symlink
    # Doesn't create the symlink path, so can't create path/file.txt.
    entries = {
      'path'          => /WARNING: skipped symlink '.*\/path'/,
      'path/file.txt' => :error
    }
    in_tmpdir do
      extract_paths(['tuzovakaoff', 'symlink.zip'], entries)
      refute File.exist?('/tmp/file.txt')
    end
  end

  def test_entry_name_with_tilde
    in_tmpdir do
      extract_paths('tilde.zip', '~tilde~' => '')
      assert File.exist?('~tilde~')
    end
  end
end