Repository: Cobertos/unitypackage_extractor (language: python)

Issue:
Multiple Arbitrary File Write vulnerabilities
A specially crafted malicious .unitypackage can save or overwrite arbitrary files on the system when extracted with unitypackage_extractor due to several vulnerabilities. This could be used to install malware or overwrite important files.

## tarfile.extractAll

https://github.com/Cobertos/unitypackage_extractor/blob/a05765abcd23884df619f370d471c17314ec6445/unitypackage_extractor/extractor.py#L16-L17

Please note the warning in [`TarFile.extractall`'s docs](https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extractall): It doesn't ensure that files aren't created outside of the specified path. Here's a proof of concept:

```shell
touch /tmp/badfile
# Make a tarfile with an absolute path
tar -cf bad.tar -PC / /tmp/badfile
rm /tmp/badfile
python -c "import tarfile; tarfile.open('bad.tar').extractall('.')"
ls /tmp/badfile # It exists again!
```

When fixing this please keep in mind that tarfiles can also contain symlinks and other weird things. So maybe [this approach](https://github.com/beatsbears/tarsafe/blob/master/tarsafe/tarsafe.py) might help to mitigate this issue.

## No validation of "pathname" in .unitypackage

https://github.com/Cobertos/unitypackage_extractor/blob/a05765abcd23884df619f370d471c17314ec6445/unitypackage_extractor/extractor.py#L28-L36

This very similar to the above. If a specially crafted .unitypackage has a modified `pathname` file in it that contains an absolute path, then the asset file will be moved to that location. This issue exists regardless of whether the user specifies an extraction output path or not, as `os.path.join()` will ignore its first argument when the second argument is an absolute path (tested on Windows and Linux). 

As above the path should be validated. I don't think any valid .unitypackage will have absolute paths in it (in the tar itself, or in the `pathfile`s) so it would make sense to abort extraction if this is the case and inform the user.




Discussion / hints:
Thank you for the link to tarsafe, I will give that a look.

For the second vulnerability, you think it would just be enough to skip w/ notify versus abort? I like the idea of it at least being able to continue if one pathname is malformed/absolute, for whatever reason

Thank you for the submit
At the end of the day, I don't think it really matters whether it'll skip or abort. I just have a personal preference for abort here (of course notifying the user why it aborted) and not extracting anything, because I don't see any reason why Unity (or external stuff, which seems rarely used) would generate them. If for some reason there'd be a lot of .unitypackage files out there that had invalid entries in them (e.g. if, say, Unity 1.0 stored some metadata files under absolute paths or something) I'd choose skipping, but since this is entirely unexpected (and a potential security issue) I'm going for aborting. 

Fix patch:
diff --git a/Pipfile b/Pipfile
index 1887b41..eaff263 100644
--- a/Pipfile
+++ b/Pipfile
@@ -9,4 +9,7 @@ twine = "*"
 setuptools = "*"
 wheel = "*"
 # Don't put pyinstaller in here bc it has trouble in CI
-# PyInstaller = "*"
\ No newline at end of file
+# PyInstaller = "*"
+
+[packages]
+unitypackage_extractor = {editable = true, path = "."}
\ No newline at end of file
diff --git a/Pipfile.lock b/Pipfile.lock
index 527d4d8..35d79dc 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "4ffaba7825db69410b8512f1983ac1a2fe97fd227f953034faa89a5c06fb5485"
+            "sha256": "ed1b3e36b10fe965d611dae7d6ed210af376d546ab80ce6558f2eaaf2a7e0675"
         },
         "pipfile-spec": 6,
         "requires": {},
@@ -13,13 +13,27 @@
             }
         ]
     },
-    "default": {},
+    "default": {
+        "tarsafe": {
+            "hashes": [
+                "sha256:7acf73a11b576fcbb003b52adecf4706d6c3009c6af28a01fd21949235ffd806",
+                "sha256:f8181c5559aea70af2579a0dc0811d2e9e3eabd706dd594555b4dfa52c0be51a"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==0.0.3"
+        },
+        "unitypackage-extractor": {
+            "editable": true,
+            "path": "."
+        }
+    },
     "develop": {
         "attrs": {
             "hashes": [
                 "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
                 "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
             "version": "==21.2.0"
         },
         "bleach": {
@@ -27,6 +41,7 @@
                 "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125",
                 "sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
             "version": "==3.3.0"
         },
         "certifi": {
@@ -38,64 +53,69 @@
         },
         "cffi": {
             "hashes": [
-                "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813",
-                "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373",
-                "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69",
-                "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f",
-                "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
-                "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05",
-                "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
-                "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
-                "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0",
-                "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
-                "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7",
-                "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f",
-                "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
-                "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
-                "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76",
-                "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
-                "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
-                "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed",
-                "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
-                "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
-                "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
-                "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5",
-                "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
-                "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
-                "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
-                "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55",
-                "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
-                "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
-                "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
-                "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369",
-                "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827",
-                "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053",
-                "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa",
-                "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4",
-                "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322",
-                "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132",
-                "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62",
-                "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa",
-                "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0",
-                "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396",
-                "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
-                "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
-                "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
-                "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc",
-                "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
-                "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
-                "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333",
-                "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
-                "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
-            ],
-            "version": "==1.14.5"
-        },
-        "chardet": {
-            "hashes": [
-                "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
-                "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
-            ],
-            "version": "==4.0.0"
+                "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d",
+                "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771",
+                "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872",
+                "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c",
+                "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc",
+                "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762",
+                "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202",
+                "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5",
+                "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548",
+                "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a",
+                "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f",
+                "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20",
+                "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218",
+                "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c",
+                "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e",
+                "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56",
+                "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224",
+                "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a",
+                "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2",
+                "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a",
+                "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819",
+                "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346",
+                "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b",
+                "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e",
+                "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534",
+                "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb",
+                "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0",
+                "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156",
+                "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd",
+                "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87",
+                "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc",
+                "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195",
+                "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33",
+                "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f",
+                "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d",
+                "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd",
+                "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728",
+                "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7",
+                "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca",
+                "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99",
+                "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf",
+                "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e",
+                "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c",
+                "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5",
+                "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"
+            ],
+            "version": "==1.14.6"
+        },
+        "charset-normalizer": {
+            "hashes": [
+                "sha256:ad0da505736fc7e716a8da15bf19a985db21ac6415c26b34d2fafd3beb3d927e",
+                "sha256:b68b38179052975093d71c1b5361bf64afd80484697c1f27056e50593e695ceb"
+            ],
+            "markers": "python_version >= '3'",
+            "version": "==2.0.1"
+        },
+        "colorama": {
+            "hashes": [
+                "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
+                "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+            "version": "==0.4.4"
         },
         "cryptography": {
             "hashes": [
@@ -112,6 +132,7 @@
                 "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d",
                 "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"
             ],
+            "markers": "python_version >= '3.6'",
             "version": "==3.4.7"
         },
         "docutils": {
@@ -119,63 +140,69 @@
                 "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125",
                 "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
             "version": "==0.17.1"
         },
         "idna": {
             "hashes": [
-                "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
-                "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
+                "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
+                "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
             ],
-            "version": "==2.10"
+            "markers": "python_version >= '3'",
+            "version": "==3.2"
         },
         "importlib-metadata": {
             "hashes": [
-                "sha256:960d52ba7c21377c990412aca380bf3642d734c2eaab78a2c39319f67c6a5786",
-                "sha256:e592faad8de1bda9fe920cf41e15261e7131bcf266c30306eec00e8e225c1dd5"
+                "sha256:079ada16b7fc30dfbb5d13399a5113110dab1aa7c2bc62f66af75f0b717c8cac",
+                "sha256:9f55f560e116f8643ecf2922d9cd3e1c7e8d52e683178fecd9d08f6aa357e11e"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==4.6.1"
+        },
+        "iniconfig": {
+            "hashes": [
+                "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
+                "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
             ],
-            "version": "==4.4.0"
+            "version": "==1.1.1"
         },
         "jeepney": {
             "hashes": [
-                "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657",
-                "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"
+                "sha256:1237cd64c8f7ac3aa4b3f332c4d0fb4a8216f39eaa662ec904302d4d77de5a54",
+                "sha256:71335e7a4e93817982f473f3507bffc2eff7a544119ab9b73e089c8ba1409ba3"
             ],
             "markers": "sys_platform == 'linux'",
-            "version": "==0.6.0"
+            "version": "==0.7.0"
         },
         "keyring": {
             "hashes": [
                 "sha256:045703609dd3fccfcdb27da201684278823b72af515aedec1a8515719a038cb8",
                 "sha256:8f607d7d1cc502c43a932a275a56fe47db50271904513a379d39df1af277ac48"
             ],
+            "markers": "python_version >= '3.6'",
             "version": "==23.0.1"
         },
-        "more-itertools": {
-            "hashes": [
-                "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d",
-                "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"
-            ],
-            "version": "==8.8.0"
-        },
         "packaging": {
             "hashes": [
-                "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
-                "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
+                "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
+                "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
             ],
-            "version": "==20.9"
+            "markers": "python_version >= '3.6'",
+            "version": "==21.0"
         },
         "pkginfo": {
             "hashes": [
-                "sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4",
-                "sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75"
+                "sha256:37ecd857b47e5f55949c41ed061eb51a0bee97a87c969219d144c0e023982779",
+                "sha256:e7432f81d08adec7297633191bbf0bd47faf13cd8724c3a13250e51d542635bd"
             ],
-            "version": "==1.7.0"
+            "version": "==1.7.1"
         },
         "pluggy": {
             "hashes": [
                 "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
                 "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==0.13.1"
         },
         "py": {
@@ -183,6 +210,7 @@
                 "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
                 "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==1.10.0"
         },
         "pycparser": {
@@ -190,6 +218,7 @@
                 "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
                 "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.20"
         },
         "pygments": {
@@ -197,6 +226,7 @@
                 "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
                 "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
             ],
+            "markers": "python_version >= '3.5'",
             "version": "==2.9.0"
         },
         "pyparsing": {
@@ -204,15 +234,16 @@
                 "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
                 "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
             ],
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
             "version": "==2.4.7"
         },
         "pytest": {
             "hashes": [
-                "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3",
-                "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"
+                "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
+                "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
             ],
             "index": "pypi",
-            "version": "==5.4.2"
+            "version": "==6.2.4"
         },
         "readme-renderer": {
             "hashes": [
@@ -223,10 +254,11 @@
         },
         "requests": {
             "hashes": [
-                "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
-                "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
+                "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
+                "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
             ],
-            "version": "==2.25.1"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+            "version": "==2.26.0"
         },
         "requests-toolbelt": {
             "hashes": [
@@ -235,6 +267,13 @@
             ],
             "version": "==0.9.1"
         },
+        "rfc3986": {
+            "hashes": [
+                "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835",
+                "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"
+            ],
+            "version": "==1.5.0"
+        },
         "secretstorage": {
             "hashes": [
                 "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f",
@@ -248,37 +287,40 @@
                 "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
                 "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
             "version": "==1.16.0"
         },
-        "tqdm": {
+        "toml": {
             "hashes": [
-                "sha256:736524215c690621b06fc89d0310a49822d75e599fcd0feb7cc742b98d692493",
-                "sha256:cd5791b5d7c3f2f1819efc81d36eb719a38e0906a7380365c556779f585ea042"
+                "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
+                "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
             ],
-            "version": "==4.61.0"
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
+            "version": "==0.10.2"
         },
-        "twine": {
+        "tqdm": {
             "hashes": [
-                "sha256:c1af8ca391e43b0a06bbc155f7f67db0bf0d19d284bfc88d1675da497a946124",
-                "sha256:d561a5e511f70275e5a485a6275ff61851c16ffcb3a95a602189161112d9f160"
+                "sha256:5aa445ea0ad8b16d82b15ab342de6b195a722d75fc1ef9934a46bba6feafbc64",
+                "sha256:8bb94db0d4468fea27d004a0f1d1c02da3cdedc00fe491c0de986b76a04d6b0a"
             ],
-            "index": "pypi",
-            "version": "==3.1.1"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==4.61.2"
         },
-        "urllib3": {
+        "twine": {
             "hashes": [
-                "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c",
-                "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"
+                "sha256:16f706f2f1687d7ce30e7effceee40ed0a09b7c33b9abb5ef6434e5551565d83",
+                "sha256:a56c985264b991dc8a8f4234eb80c5af87fa8080d0c224ad8f2cd05a2c22e83b"
             ],
             "index": "pypi",
-            "version": "==1.26.5"
+            "version": "==3.4.1"
         },
-        "wcwidth": {
+        "urllib3": {
             "hashes": [
-                "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784",
-                "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"
+                "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
+                "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
             ],
-            "version": "==0.2.5"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
+            "version": "==1.26.6"
         },
         "webencodings": {
             "hashes": [
@@ -289,18 +331,19 @@
         },
         "wheel": {
             "hashes": [
-                "sha256:8788e9155fe14f54164c1b9eb0a319d98ef02c160725587ad60f14ddc57b6f96",
-                "sha256:df277cb51e61359aba502208d680f90c0493adec6f0e848af94948778aed386e"
+                "sha256:78b5b185f0e5763c26ca1e324373aadd49182ca90e825f7853f4b2509215dc0e",
+                "sha256:e11eefd162658ea59a60a0f6c7d493a7190ea4b9a85e335b33489d9f17e0245e"
             ],
             "index": "pypi",
-            "version": "==0.34.2"
+            "version": "==0.36.2"
         },
         "zipp": {
             "hashes": [
-                "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76",
-                "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"
+                "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3",
+                "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"
             ],
-            "version": "==3.4.1"
+            "markers": "python_version >= '3.6'",
+            "version": "==3.5.0"
         }
     }
 }
diff --git a/setup.py b/setup.py
index 951cf1c..b8a0055 100644
--- a/setup.py
+++ b/setup.py
@@ -20,6 +20,9 @@
         'Topic :: Software Development :: Build Tools',
         'Topic :: System :: Filesystems'
     ],
+    install_requires=[
+        'tarsafe>=0.0.2',
+    ],
     keywords='untiy unity3d unitypackage extract tar extractor',
     packages=['unitypackage_extractor']
 )
diff --git a/unitypackage_extractor/extractor.py b/unitypackage_extractor/extractor.py
index 6d1567b..0d6add6 100644
--- a/unitypackage_extractor/extractor.py
+++ b/unitypackage_extractor/extractor.py
@@ -1,9 +1,10 @@
-import tarfile
+import tarsafe
 import tempfile
 import sys
 import os
 import time
 import shutil
+from pathlib import Path
 
 def extractPackage(packagePath, outputPath="", encoding='utf-8'):
   """
@@ -13,7 +14,7 @@ def extractPackage(packagePath, outputPath="", encoding='utf-8'):
   """
   with tempfile.TemporaryDirectory() as tmpDir:
     # Unpack the whole thing in one go (faster than traversing the tar)
-    with tarfile.open(name=packagePath, encoding=encoding) as upkg:
+    with tarsafe.open(name=packagePath, encoding=encoding) as upkg:
       upkg.extractall(tmpDir)
 
     # Extract each file in tmpDir to final destination
@@ -29,9 +30,14 @@ def extractPackage(packagePath, outputPath="", encoding='utf-8'):
         pathname = f.readline()
         pathname = pathname[:-1] if pathname[-1] == '\n' else pathname #Remove newline
 
+      # Figure out final path, make sure that it's inside the write directory
+      assetOutPath = os.path.join(outputPath, pathname)
+      if Path(outputPath).resolve() not in Path(assetOutPath).resolve().parents:
+        print(f"WARNING: Skipping '{dirEntry.name}' as '{assetOutPath}' is outside of '{outputPath}'.")
+        continue
+
       #Extract to the pathname
       print(f"Extracting '{dirEntry.name}' as '{pathname}'")
-      assetOutPath = os.path.join(outputPath, pathname)
       os.makedirs(os.path.dirname(assetOutPath), exist_ok=True) #Make the dirs up to the given folder
       shutil.move(f"{assetEntryDir}/asset", assetOutPath)
 
@@ -40,7 +46,7 @@ def cli(args):
   CLI entrypoint, takes CLI arguments array
   """
   if not args:
-    raise TypeError("No .unitypackage path was given. \n\nUSAGE: unitypackage_extractor [XXX.unitypackage]")
+    raise TypeError("No .unitypackage path was given. \n\nUSAGE: unitypackage_extractor [XXX.unitypackage] (optional/output/path)")
   startTime = time.time()
   extractPackage(args[0], args[1] if len(args) > 1 else "")
   print("--- Finished in %s seconds ---" % (time.time() - startTime))


Test patch:
diff --git a/tests/README.md b/tests/README.md
index 124261c..6491433 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -20,4 +20,21 @@ This one has unicode in one of the path names
 
 Expected output of package:
 
-* `Assets/テスト.txt` - contents "テスト, but with katakana!"
\ No newline at end of file
+* `Assets/テスト.txt` - contents "テスト, but with katakana!"
+
+### `testEscape.unitypackage`
+
+This one has a path with .. in it, which should cause a failure
+
+No expected output, but contains:
+
+* `../escape.txt`
+
+### `testEscape2.unitypackage`
+
+This one has an absolute path, which should cause a failure. `/tmp` because it's
+globally writable on most systems.
+
+No expected output, but contains:
+
+* `/tmp/escape.txt`
\ No newline at end of file
diff --git a/tests/testEscape.unitypackage b/tests/testEscape.unitypackage
new file mode 100644
index 0000000..63bb214
Binary files /dev/null and b/tests/testEscape.unitypackage differ
diff --git a/tests/testEscape2.unitypackage b/tests/testEscape2.unitypackage
new file mode 100644
index 0000000..00ae4a6
Binary files /dev/null and b/tests/testEscape2.unitypackage differ
diff --git a/tests/test_testPackage.py b/tests/test_testPackage.py
index 44dcd64..e623761 100644
--- a/tests/test_testPackage.py
+++ b/tests/test_testPackage.py
@@ -1,62 +1,113 @@
 '''
 Tests for package extracting
 '''
+import io
+import sys
+import re
 import os
-import unittest
 import tempfile
 
 from unitypackage_extractor.extractor import extractPackage
 
-class TestTestPackageExtract(unittest.TestCase):
-  '''
-  Tests the package extracting functionality
-  '''
-  def test_packageExtract(self):
-    '''should be able to extract a simple unity pckage'''
-    #arrange
-    with tempfile.TemporaryDirectory() as tmp:
-      #test.unitypackage - Should contain one file named test.txt with the contents "testing"
-
-      #act
-      print(f"Extracting to {tmp}...")
-      extractPackage("./tests/test.unitypackage", outputPath=tmp)
-
-      #assert
-      self.assertTrue(os.path.isdir(tmp))
-      self.assertTrue(os.path.isdir(f"{tmp}/Assets"))
-      self.assertTrue(os.path.isfile(f"{tmp}/Assets/test.txt"))
-      self.assertEqual(open(f"{tmp}/Assets/test.txt").read(), "testing")
-
-  def test_packageExtractWithLeadingDots(self):
-    '''should be able to extract a unity package that contains ./ in every path in the tar'''
-    #arrange
-    with tempfile.TemporaryDirectory() as tmp:
-      #testLeadingDots.unitypackage - Same as test.unitypackage but archived with `tar -zrf archive.unitypackage .`
-      #to get the specific `./` before every path
-
-      #act
-      print(f"Extracting to {tmp}...")
-      extractPackage("./tests/testLeadingDots.unitypackage", outputPath=tmp)
-
-      #assert
-      self.assertTrue(os.path.isdir(tmp))
-      self.assertTrue(os.path.isdir(f"{tmp}/Assets"))
-      self.assertTrue(os.path.isfile(f"{tmp}/Assets/test.txt"))
-      self.assertEqual(open(f"{tmp}/Assets/test.txt").read(), "testing")
-
-  def test_packageExtractWithUnicodePath(self):
-    '''should be able to extract a unity package that has a unicode pathname'''
-    #arrange
-    with tempfile.TemporaryDirectory() as tmp:
-      # testo.unitypackage - Should just contain one file at Assets/テスト.txt with
-      # contents 'testing, but with katakana!'
-
-      #act
-      print(f"Extracting to {tmp}...")
-      extractPackage("./tests/testo.unitypackage", outputPath=tmp)
-
-      #assert
-      self.assertTrue(os.path.isdir(tmp))
-      self.assertTrue(os.path.isdir(f"{tmp}/Assets"))
-      self.assertTrue(os.path.isfile(f"{tmp}/Assets/テスト.txt"))
-      self.assertEqual(open(f"{tmp}/Assets/テスト.txt").read(), "テスト, but with katakana!")
+def test_packageExtract():
+  '''should be able to extract a simple unity pckage'''
+  #arrange
+  with tempfile.TemporaryDirectory() as tmp:
+    #test.unitypackage - Should contain one file named test.txt with the contents "testing"
+
+    #act
+    print(f"Extracting to {tmp}...")
+    extractPackage("./tests/test.unitypackage", outputPath=tmp)
+
+    #assert
+    assert os.path.isdir(tmp)
+    assert os.path.isdir(f"{tmp}/Assets")
+    assert os.path.isfile(f"{tmp}/Assets/test.txt")
+    assert open(f"{tmp}/Assets/test.txt").read() == "testing"
+
+def test_packageExtractRelative():
+  '''should be able to extract to a relative path'''
+  #arrange
+  with tempfile.TemporaryDirectory() as tmp:
+    #test.unitypackage - Should contain one file named test.txt with the contents "testing"
+    relTmp = os.path.relpath(tmp, start=".")
+
+    #act
+    print(f"Extracting to {tmp} as {relTmp}...")
+    extractPackage("./tests/test.unitypackage", outputPath=os.path.relpath(relTmp))
+
+    #assert
+    assert os.path.isdir(tmp)
+    assert os.path.isdir(f"{tmp}/Assets")
+    assert os.path.isfile(f"{tmp}/Assets/test.txt")
+    assert open(f"{tmp}/Assets/test.txt").read() == "testing"
+
+def test_packageExtractWithLeadingDots():
+  '''should be able to extract a unity package that contains ./ in every path in the tar'''
+  #arrange
+  with tempfile.TemporaryDirectory() as tmp:
+    #testLeadingDots.unitypackage - Same as test.unitypackage but archived with `tar -zrf archive.unitypackage .`
+    #to get the specific `./` before every path
+
+    #act
+    print(f"Extracting to {tmp}...")
+    extractPackage("./tests/testLeadingDots.unitypackage", outputPath=tmp)
+
+    #assert
+    assert os.path.isdir(tmp)
+    assert os.path.isdir(f"{tmp}/Assets")
+    assert os.path.isfile(f"{tmp}/Assets/test.txt")
+    assert open(f"{tmp}/Assets/test.txt").read() == "testing"
+
+def test_packageExtractWithUnicodePath():
+  '''should be able to extract a unity package that has a unicode pathname'''
+  #arrange
+  with tempfile.TemporaryDirectory() as tmp:
+    # testo.unitypackage - Should just contain one file at Assets/テスト.txt with
+    # contents 'testing, but with katakana!'
+
+    #act
+    print(f"Extracting to {tmp}...")
+    extractPackage("./tests/testo.unitypackage", outputPath=tmp)
+
+    #assert
+    assert os.path.isdir(tmp)
+    assert os.path.isdir(f"{tmp}/Assets")
+    assert os.path.isfile(f"{tmp}/Assets/テスト.txt")
+    assert open(f"{tmp}/Assets/テスト.txt").read(), "テスト == but with katakana!"
+
+def test_packageExtractEscape(capsys):
+  '''should skip relative paths that are outside'''
+  #arrange
+  with tempfile.TemporaryDirectory() as tmp:
+    # testEscape.unitypackage - Should skip relative paths
+
+    #act
+    print(f"Extracting to {tmp}...")
+    extractPackage("./tests/testEscape.unitypackage", outputPath=tmp)
+    out, err = capsys.readouterr()
+    sys.stdout.write(out)
+    sys.stderr.write(err)
+
+    #assert
+    assert os.path.isdir(tmp)
+    assert not os.path.isfile(f"{tmp}/../escape.txt")
+    assert re.search(r"outside", out, flags=re.IGNORECASE)
+
+def test_packageExtractEscape2(capsys):
+  '''should skip absolute paths'''
+  #arrange
+  with tempfile.TemporaryDirectory() as tmp:
+    # testEscape2.unitypackage - Should skip absolute paths
+
+    #act
+    print(f"Extracting to {tmp}...")
+    extractPackage("./tests/testEscape2.unitypackage", outputPath=tmp)
+    out, err = capsys.readouterr()
+    sys.stdout.write(out)
+    sys.stderr.write(err)
+
+    #assert
+    assert os.path.isdir(tmp)
+    assert not os.path.isfile(f"/tmp/escape.txt")
+    assert re.search(r"outside", out, flags=re.IGNORECASE)