Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
f095f3c8
Commit
f095f3c8
authored
Oct 04, 2013
by
Julian Arni
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'release'
parents
5d995ddb
602eb876
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
167 additions
and
2 deletions
+167
-2
cms/djangoapps/contentstore/tests/test_import_export.py
+92
-1
cms/djangoapps/contentstore/views/import_export.py
+12
-1
common/lib/extract_tar.py
+63
-0
No files found.
cms/djangoapps/contentstore/tests/test_import_export.py
View file @
f095f3c8
...
...
@@ -6,6 +6,7 @@ import shutil
import
tarfile
import
tempfile
import
copy
from
path
import
path
from
uuid
import
uuid4
from
pymongo
import
MongoClient
...
...
@@ -19,6 +20,7 @@ from xmodule.contentstore.django import _CONTENTSTORE
TEST_DATA_CONTENTSTORE
=
copy
.
deepcopy
(
settings
.
CONTENTSTORE
)
TEST_DATA_CONTENTSTORE
[
'OPTIONS'
][
'db'
]
=
'test_xcontent_
%
s'
%
uuid4
()
.
hex
@override_settings
(
CONTENTSTORE
=
TEST_DATA_CONTENTSTORE
)
class
ImportTestCase
(
CourseTestCase
):
"""
...
...
@@ -32,7 +34,7 @@ class ImportTestCase(CourseTestCase):
'course'
:
self
.
course
.
location
.
course
,
'name'
:
self
.
course
.
location
.
name
,
})
self
.
content_dir
=
tempfile
.
mkdtemp
(
)
self
.
content_dir
=
path
(
tempfile
.
mkdtemp
()
)
def
touch
(
name
):
""" Equivalent to shell's 'touch'"""
...
...
@@ -60,11 +62,15 @@ class ImportTestCase(CourseTestCase):
with
tarfile
.
open
(
self
.
bad_tar
,
"w:gz"
)
as
btar
:
btar
.
add
(
bad_dir
)
self
.
unsafe_common_dir
=
path
(
tempfile
.
mkdtemp
(
dir
=
self
.
content_dir
))
def
tearDown
(
self
):
shutil
.
rmtree
(
self
.
content_dir
)
MongoClient
()
.
drop_database
(
TEST_DATA_CONTENTSTORE
[
'OPTIONS'
][
'db'
])
_CONTENTSTORE
.
clear
()
def
test_no_coursexml
(
self
):
"""
Check that the response for a tar.gz import without a course.xml is
...
...
@@ -92,3 +98,88 @@ class ImportTestCase(CourseTestCase):
"course-data"
:
[
gtar
]
})
self
.
assertEquals
(
resp
.
status_code
,
200
)
## Unsafe tar methods #####################################################
# Each of these methods creates a tarfile with a single type of unsafe
# content.
def
_fifo_tar
(
self
):
"""
Tar file with FIFO
"""
fifop
=
self
.
unsafe_common_dir
/
"fifo.file"
fifo_tar
=
self
.
unsafe_common_dir
/
"fifo.tar.gz"
os
.
mkfifo
(
fifop
)
with
tarfile
.
open
(
fifo_tar
,
"w:gz"
)
as
tar
:
tar
.
add
(
fifop
)
return
fifo_tar
def
_symlink_tar
(
self
):
"""
Tarfile with symlink to path outside directory.
"""
outsidep
=
self
.
unsafe_common_dir
/
"unsafe_file.txt"
symlinkp
=
self
.
unsafe_common_dir
/
"symlink.txt"
symlink_tar
=
self
.
unsafe_common_dir
/
"symlink.tar.gz"
outsidep
.
symlink
(
symlinkp
)
with
tarfile
.
open
(
symlink_tar
,
"w:gz"
)
as
tar
:
tar
.
add
(
symlinkp
)
return
symlink_tar
def
_outside_tar
(
self
):
"""
Tarfile with file that extracts to outside directory.
Extracting this tarfile in directory <dir> will put its contents
directly in <dir> (rather than <dir/tarname>).
"""
outside_tar
=
self
.
unsafe_common_dir
/
"unsafe_file.tar.gz"
with
tarfile
.
open
(
outside_tar
,
"w:gz"
)
as
tar
:
tar
.
addfile
(
tarfile
.
TarInfo
(
str
(
self
.
content_dir
/
"a_file"
)))
return
outside_tar
def
_outside_tar2
(
self
):
"""
Tarfile with file that extracts to outside directory.
The path here matches the basename (`self.unsafe_common_dir`), but
then "cd's out". E.g. "/usr/../etc" == "/etc", but the naive basename
of the first (but not the second) is "/usr"
Extracting this tarfile in directory <dir> will also put its contents
directly in <dir> (rather than <dir/tarname>).
"""
outside_tar
=
self
.
unsafe_common_dir
/
"unsafe_file.tar.gz"
with
tarfile
.
open
(
outside_tar
,
"w:gz"
)
as
tar
:
tar
.
addfile
(
tarfile
.
TarInfo
(
str
(
self
.
unsafe_common_dir
/
"../a_file"
)))
return
outside_tar
def
test_unsafe_tar
(
self
):
"""
Check that safety measure work.
This includes:
'tarbombs' which include files or symlinks with paths
outside or directly in the working directory,
'special files' (character device, block device or FIFOs),
all raise exceptions/400s.
"""
def
try_tar
(
tarpath
):
with
open
(
tarpath
)
as
tar
:
resp
=
self
.
client
.
post
(
self
.
url
,
{
"name"
:
tarpath
,
"course-data"
:
[
tar
]
}
)
self
.
assertEquals
(
resp
.
status_code
,
400
)
self
.
assertTrue
(
"SuspiciousFileOperation"
in
resp
.
content
)
try_tar
(
self
.
_fifo_tar
())
try_tar
(
self
.
_symlink_tar
())
try_tar
(
self
.
_outside_tar
())
try_tar
(
self
.
_outside_tar2
())
cms/djangoapps/contentstore/views/import_export.py
View file @
f095f3c8
...
...
@@ -19,6 +19,7 @@ from django.core.urlresolvers import reverse
from
django.core.servers.basehttp
import
FileWrapper
from
django.core.files.temp
import
NamedTemporaryFile
from
django.views.decorators.http
import
require_http_methods
from
django.core.exceptions
import
SuspiciousOperation
from
mitxmako.shortcuts
import
render_to_response
from
auth.authz
import
create_all_course_groups
...
...
@@ -32,6 +33,7 @@ from xmodule.exceptions import SerializationError
from
.access
import
get_location_and_verify_access
from
util.json_request
import
JsonResponse
from
extract_tar
import
safetar_extractall
__all__
=
[
'import_course'
,
'generate_export_course'
,
'export_course'
]
...
...
@@ -154,7 +156,16 @@ def import_course(request, org, course, name):
sf
.
write
(
"Extracting"
)
tar_file
=
tarfile
.
open
(
temp_filepath
)
tar_file
.
extractall
((
course_dir
+
'/'
)
.
encode
(
'utf-8'
))
try
:
safetar_extractall
(
tar_file
,
(
course_dir
+
'/'
)
.
encode
(
'utf-8'
))
except
SuspiciousOperation
as
exc
:
return
JsonResponse
(
{
'ErrMsg'
:
'Unsafe tar file. Aborting import.'
,
'SuspiciousFileOperationMsg'
:
exc
.
args
[
0
]
},
status
=
400
)
with
open
(
status_file
,
'w+'
)
as
sf
:
sf
.
write
(
"Verifying"
)
...
...
common/lib/extract_tar.py
0 → 100644
View file @
f095f3c8
"""
Safe version of tarfile.extractall which does not extract any files that would
be, or symlink to a file that is, outside of the directory extracted in.
Adapted from:
http://stackoverflow.com/questions/10060069/safely-extract-zip-or-tar-using-python
"""
from
os.path
import
abspath
,
realpath
,
dirname
,
join
as
joinpath
from
django.core.exceptions
import
SuspiciousOperation
import
logging
log
=
logging
.
getLogger
(
__name__
)
#pylint: disable=C0103
def
resolved
(
rpath
):
"""
Returns the canonical absolute path of `rpath`.
"""
return
realpath
(
abspath
(
rpath
))
def
_is_bad_path
(
path
,
base
):
"""
Is (the canonical absolute path of) `path` outside `base`?
"""
return
not
resolved
(
joinpath
(
base
,
path
))
.
startswith
(
base
)
def
_is_bad_link
(
info
,
base
):
"""
Does the file sym- ord hard-link to files outside `base`?
"""
# Links are interpreted relative to the directory containing the link
tip
=
resolved
(
joinpath
(
base
,
dirname
(
info
.
name
)))
return
_is_bad_path
(
info
.
linkname
,
base
=
tip
)
def
safemembers
(
members
):
"""
Check that all elements of a tar file are safe.
"""
base
=
resolved
(
"."
)
for
finfo
in
members
:
if
_is_bad_path
(
finfo
.
name
,
base
):
log
.
debug
(
"File
%
r is blocked (illegal path)"
,
finfo
.
name
)
raise
SuspiciousOperation
(
"Illegal path"
)
elif
finfo
.
issym
()
and
_is_bad_link
(
finfo
,
base
):
log
.
debug
(
"File
%
r is blocked: Hard link to
%
r"
,
finfo
.
name
,
finfo
.
linkname
)
raise
SuspiciousOperation
(
"Hard link"
)
elif
finfo
.
islnk
()
and
_is_bad_link
(
finfo
,
base
):
log
.
debug
(
"File
%
r is blocked: Symlink to
%
r"
,
finfo
.
name
,
finfo
.
linkname
)
raise
SuspiciousOperation
(
"Symlink"
)
elif
finfo
.
isdev
():
log
.
debug
(
"File
%
r is blocked: FIFO, device or character file"
,
finfo
.
name
)
raise
SuspiciousOperation
(
"Dev file"
)
return
members
def
safetar_extractall
(
tarf
,
*
args
,
**
kwargs
):
"""
Safe version of `tarf.extractall()`.
"""
return
tarf
.
extractall
(
members
=
safemembers
(
tarf
),
*
args
,
**
kwargs
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment