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
|