From aadcbb74e30a50890ef0475a5153f8298c981d7e Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:13:32 -0600 Subject: [PATCH 1/3] Add CODEOWNERS file (#403) Co-authored-by: Jacob Fuss --- CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..2f93beb1a --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,3 @@ +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +* @aws/serverless-application-experience-sbt \ No newline at end of file From 1191c96abce3ef971fb8e1a6b69a4c66a24e03a7 Mon Sep 17 00:00:00 2001 From: TrellixVulnTeam <112716341+TrellixVulnTeam@users.noreply.github.com> Date: Tue, 22 Nov 2022 17:15:43 -0600 Subject: [PATCH 2/3] CVE-2007-4559 Patch (#408) * Adding tarfile member sanitization to extractall() * black reformat * Refactor: move extract_tarfile to shared module * Add test * Remove redundant code/tests * Add test for safe-guarding path traversal * add type hint Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> --- .gitignore | 4 ++- aws_lambda_builders/utils.py | 26 ++++++++++++++++++ .../workflows/nodejs_npm/actions.py | 3 +- .../workflows/nodejs_npm/utils.py | 4 --- .../workflows/python_pip/packager.py | 3 +- .../workflows/python_pip/utils.py | 4 --- .../workflows/ruby_bundler/utils.py | 4 --- tests/functional/test_utils.py | 22 ++++++++++++++- .../testdata/path_reversal_uxix.tgz | Bin 0 -> 800 bytes .../functional/testdata/path_reversal_win.tgz | Bin 0 -> 799 bytes tests/functional/testdata/test.tgz | Bin 0 -> 769 bytes .../workflows/nodejs_npm/test_utils.py | 14 ---------- .../workflows/ruby_bundler/test_ruby_utils.py | 8 ------ .../unit/workflows/nodejs_npm/test_actions.py | 7 +++-- 14 files changed, 58 insertions(+), 41 deletions(-) create mode 100644 tests/functional/testdata/path_reversal_uxix.tgz create mode 100644 tests/functional/testdata/path_reversal_win.tgz create mode 100644 tests/functional/testdata/test.tgz diff --git a/.gitignore b/.gitignore index 50664d49e..fc6c4ff82 100644 --- a/.gitignore +++ b/.gitignore @@ -163,7 +163,9 @@ typings/ *.tgz # Except test file -!tests/functional/workflows/ruby_bundler/test_data/test.tgz +!tests/functional/testdata/test.tgz +!tests/functional/testdata/path_reversal_uxix.tgz +!tests/functional/testdata/path_reversal_win.tgz # Yarn Integrity file .yarn-integrity diff --git a/aws_lambda_builders/utils.py b/aws_lambda_builders/utils.py index 0e5e7447b..201e8f460 100644 --- a/aws_lambda_builders/utils.py +++ b/aws_lambda_builders/utils.py @@ -7,6 +7,7 @@ import os import logging from pathlib import Path +from typing import Union from aws_lambda_builders.architecture import X86_64, ARM64 @@ -196,3 +197,28 @@ def create_symlink_or_copy(source: str, destination: str) -> None: exc_info=ex if LOG.isEnabledFor(logging.DEBUG) else None, ) copytree(source, destination) + + +def _is_within_directory(directory: Union[str, os.PathLike], target: Union[str, os.PathLike]) -> bool: + """Checks if target is located under directory""" + abs_directory = os.path.abspath(directory) + abs_target = os.path.abspath(target) + + prefix = os.path.commonprefix([abs_directory, abs_target]) + + return prefix == abs_directory + + +def extract_tarfile(tarfile_path: Union[str, os.PathLike], unpack_dir: Union[str, os.PathLike]) -> None: + """Extracts a tarfile""" + import tarfile + + with tarfile.open(tarfile_path, "r:*") as tar: + # Makes sure the tar file is sanitized and is free of directory traversal vulnerability + # See: https://github.com/advisories/GHSA-gw9q-c7gh-j9vm + for member in tar.getmembers(): + member_path = os.path.join(unpack_dir, member.name) + if not _is_within_directory(unpack_dir, member_path): + raise tarfile.ExtractError("Attempted Path Traversal in Tar File") + + tar.extractall(unpack_dir) diff --git a/aws_lambda_builders/workflows/nodejs_npm/actions.py b/aws_lambda_builders/workflows/nodejs_npm/actions.py index d74e7088e..9aefcc079 100644 --- a/aws_lambda_builders/workflows/nodejs_npm/actions.py +++ b/aws_lambda_builders/workflows/nodejs_npm/actions.py @@ -5,6 +5,7 @@ import logging from aws_lambda_builders.actions import BaseAction, Purpose, ActionFailedError +from aws_lambda_builders.utils import extract_tarfile from .npm import NpmExecutionError LOG = logging.getLogger(__name__) @@ -64,7 +65,7 @@ def execute(self): LOG.debug("NODEJS extracting to %s", self.artifacts_dir) - self.osutils.extract_tarfile(tarfile_path, self.artifacts_dir) + extract_tarfile(tarfile_path, self.artifacts_dir) except NpmExecutionError as ex: raise ActionFailedError(str(ex)) diff --git a/aws_lambda_builders/workflows/nodejs_npm/utils.py b/aws_lambda_builders/workflows/nodejs_npm/utils.py index c06469983..529e6fac5 100644 --- a/aws_lambda_builders/workflows/nodejs_npm/utils.py +++ b/aws_lambda_builders/workflows/nodejs_npm/utils.py @@ -20,10 +20,6 @@ class OSUtils(object): def copy_file(self, file_path, destination_path): return shutil.copy2(file_path, destination_path) - def extract_tarfile(self, tarfile_path, unpack_dir): - with tarfile.open(tarfile_path, "r:*") as tar: - tar.extractall(unpack_dir) - def file_exists(self, filename): return os.path.isfile(filename) diff --git a/aws_lambda_builders/workflows/python_pip/packager.py b/aws_lambda_builders/workflows/python_pip/packager.py index e67d91a4c..f43b0c274 100644 --- a/aws_lambda_builders/workflows/python_pip/packager.py +++ b/aws_lambda_builders/workflows/python_pip/packager.py @@ -9,6 +9,7 @@ from email.parser import FeedParser from aws_lambda_builders.architecture import ARM64, X86_64 +from aws_lambda_builders.utils import extract_tarfile from .compat import pip_import_string from .compat import pip_no_compile_c_env_vars from .compat import pip_no_compile_c_shim @@ -619,7 +620,7 @@ def _unpack_sdist_into_dir(self, sdist_path, unpack_dir): if sdist_path.endswith(".zip"): self._osutils.extract_zipfile(sdist_path, unpack_dir) elif sdist_path.endswith((".tar.gz", ".tar.bz2")): - self._osutils.extract_tarfile(sdist_path, unpack_dir) + extract_tarfile(sdist_path, unpack_dir) else: raise InvalidSourceDistributionNameError(sdist_path) # There should only be one directory unpacked. diff --git a/aws_lambda_builders/workflows/python_pip/utils.py b/aws_lambda_builders/workflows/python_pip/utils.py index f5eea5882..25d77bfc7 100644 --- a/aws_lambda_builders/workflows/python_pip/utils.py +++ b/aws_lambda_builders/workflows/python_pip/utils.py @@ -56,10 +56,6 @@ def extract_zipfile(self, zipfile_path, unpack_dir): with zipfile.ZipFile(zipfile_path, "r") as z: z.extractall(unpack_dir) - def extract_tarfile(self, tarfile_path, unpack_dir): - with tarfile.open(tarfile_path, "r:*") as tar: - tar.extractall(unpack_dir) - def directory_exists(self, path): return os.path.isdir(path) diff --git a/aws_lambda_builders/workflows/ruby_bundler/utils.py b/aws_lambda_builders/workflows/ruby_bundler/utils.py index a3f36439e..ad8e0282b 100644 --- a/aws_lambda_builders/workflows/ruby_bundler/utils.py +++ b/aws_lambda_builders/workflows/ruby_bundler/utils.py @@ -16,10 +16,6 @@ class OSUtils(object): unit test actions in memory """ - def extract_tarfile(self, tarfile_path, unpack_dir): - with tarfile.open(tarfile_path, "r:*") as tar: - tar.extractall(unpack_dir) - def popen(self, command, stdout=None, stderr=None, env=None, cwd=None): p = subprocess.Popen(command, stdout=stdout, stderr=stderr, env=env, cwd=cwd) return p diff --git a/tests/functional/test_utils.py b/tests/functional/test_utils.py index d4b80b9ad..a89790f95 100644 --- a/tests/functional/test_utils.py +++ b/tests/functional/test_utils.py @@ -1,10 +1,12 @@ import os import tempfile import shutil +import platform +from tarfile import ExtractError from unittest import TestCase -from aws_lambda_builders.utils import copytree, get_goarch +from aws_lambda_builders.utils import copytree, get_goarch, extract_tarfile class TestCopyTree(TestCase): @@ -63,6 +65,24 @@ def test_must_return_valid_go_architecture(self): self.assertEqual(get_goarch(""), "amd64") +class TestExtractTarFile(TestCase): + def test_extract_tarfile_unpacks_a_tar(self): + test_tar = os.path.join(os.path.dirname(__file__), "testdata", "test.tgz") + test_dir = tempfile.mkdtemp() + extract_tarfile(test_tar, test_dir) + output_files = set(os.listdir(test_dir)) + shutil.rmtree(test_dir) + self.assertEqual({"test_utils.py"}, output_files) + + def test_raise_exception_for_unsafe_tarfile(self): + tar_filename = "path_reversal_win.tgz" if platform.system().lower() == "windows" else "path_reversal_uxix.tgz" + test_tar = os.path.join(os.path.dirname(__file__), "testdata", tar_filename) + test_dir = tempfile.mkdtemp() + self.assertRaisesRegexp( + ExtractError, "Attempted Path Traversal in Tar File", extract_tarfile, test_tar, test_dir + ) + + def file(*args): path = os.path.join(*args) basedir = os.path.dirname(path) diff --git a/tests/functional/testdata/path_reversal_uxix.tgz b/tests/functional/testdata/path_reversal_uxix.tgz new file mode 100644 index 0000000000000000000000000000000000000000..876b0b7b2105ed19c14fd05319e1d13d15679a85 GIT binary patch literal 800 zcmV+*1K<1~iwFp=9DQQ~|8QY+XkT(=c4cyNVQgP@cxiYpbZ2@1?NnWB+b|UMv!C`8 zO!i;}f|j&S8H2!H_q2y?Ph$whzHZ(6L+FDvjKO}%e(SCrTea&nT^U^&=sYBnbNkwYTf|9^s_5{=Z6*xN1$!)~hf%8i@5p}Mkd z;-(rZkryR|VJ@30;ycAkUTrhZwx%dEkc#dad6SpTPSLu_;99X-R8dE7TP_U4_lM1C zUfgOUAw$)sm;0 zq1RS1GqvF9jbhw>AqvWT(eH`WmTS>+ZGUWVl?tw}=yg-nq@QGkta$~5u{JB4FQQq? z$++gTZbC7^Y<@2(GBIgiL0a8pc1#9gog8{kI_WhnT%sY#Xkm1#Mqz#h+G;Her7?~; z!hrF*bCt?UO8{~tsgOmDc?PK`i@m7h3*&U?7@+;%XJ`1{%zi|h%}91QcCITiJG6kZ zbM&{7dzLqnSr;bu^&556*1SV4d%v*1v96FB&9@~?wK?XU?T}*5VVczGwq4x{w*%FxWod)=w1!%%CkXEo zk(kGeDBjwiH2xEH?ZrCAaw0_|pk~t8x-ub~6cmo?%vBrP2t~uga?@SL#Qq7lIi@sr zzgC33*sZ!5){RZ6~&du7pW|83zoQ{WDGij7ND2zVqMq#T8xh#o%_iwLPG zT2L%nW$Gg38pzZtK@as!@ddYrEvF4>G|j-Sli29|Z9Jz+%L1RX&frVXcy@B!(3IDV zrzr@{Y|zQa*p|P0JO9qYry1AV{mj;#3P%N-VFg~y%h6LZRup)6czAetczAetczAet eczAetczAetczAetczAfc6n+EbNc@lhC;$NK36ZM+ literal 0 HcmV?d00001 diff --git a/tests/functional/testdata/path_reversal_win.tgz b/tests/functional/testdata/path_reversal_win.tgz new file mode 100644 index 0000000000000000000000000000000000000000..428bb48d0aa2b926ad0d61aadc1ba7fe59638753 GIT binary patch literal 799 zcmV+)1K|80iwFqj9DQQ~|8QY+XkT(=c4cyNVQgP_X>KlbXL z_Fx5!NZO=~L13?Y+QYV|Z3xA_Zr%Dr=z}zj!G6hp>#h{rwd*up8C@CZJS38J&(+aA zSEBT*2Bn$NMX9LR2hV^7;$pQ5e&YD{*ZWs?zBpUC?}*iMxek`+tHpA4zP>nH2aDx; zdA}_ zq@QZ2L|&8-hPiC2i0>3Dd9}?r+nS=xKq|UxNSX&hWjN{fIW3k?e5nTw7vxXaQyC z=x-zUEN>*UE==s}HfpKPyhANJzp%ftu8kD?o9;3u_D{IYF{N?; zYem?L?bPiu?B3zQ#GndJDfu$)m89MNTheo;z#Z@un?6w?;E`~Uauj|ddi+o%5mHgK zpjfoZ)J4iQkf~LI9_pLo3vLZtOdHZ@nt@#>vC;XPcutj;1wLo3!Iz-%?BuwiDX$q% zQxKZjpp%cWEr0uV{+)$SGp@7ynXNk&jtVxz3cQ$?qo-u7DDd#`@bK{P@bK{P@bK{P d@bK{P@bK{P@bK{P@bGvk{07zAt^)ul006{`|rE*hicbZMj54y={(p{bkEh%J=Y=zv|)`YN=@bI>UEGL$?bYg?ECI^)4r4I zZf>z&tu|zJvrbm)o6YU@hTve8++C66mGn%|Xu}msxflC`xDbb7d46HNI}35|e*{G( zg)&5*3^qa#^izG(ZY-XL`oVUJn;NKOUX&07xe^uON6kuJ?J~}GO;Ki`G(8G+$V+k5 zv=$lcHLGPsdV1S+K@fcGHm7;r+e2!4o=s(QGk7$Lr-q@|RxxX8$o-2M`2DD!yO6R9oNqUB-)+2Sgd+&s{| zDC%gKWQD4E1rcLyRlWeyW*}>GgzQn9+0d42#uTUqJ3&pGpGy8^(dTMK4SIfaK>~E|qq>bUb66V?* z^U3#%RHv5jw2gLZaW2Znzd_kf!}Ejwb3sSJ|v1l9>yfx*++Wj40CnuhjotSL`or{W~#AuWm1V0G>+=b zRU6v~MZ@pqrpJuQ(+j@lkkZgStqA*Jx9W}=cJKILa#V#@Y4mN{D{;U5_t24)J4h-M02Yo?dqH24Q>rf<_*~xnt@%9!p7w9 z!zI;56?mQX2H%6hvq$F*O?l0Dnu0XU23@?2ZTb7V^Y1KtnQ?>NuWa3=a8$4zSK!UO zoLnVSMQ{CQ#>2zI!^6YF!^6YF!^6YF!^6YF!^6YF!^6YF<3GkP9 Date: Tue, 22 Nov 2022 16:06:57 -0800 Subject: [PATCH 3/3] chore: Bump version (#409) --- aws_lambda_builders/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws_lambda_builders/__init__.py b/aws_lambda_builders/__init__.py index 2b30f53b8..7c9dbd4cb 100644 --- a/aws_lambda_builders/__init__.py +++ b/aws_lambda_builders/__init__.py @@ -4,5 +4,5 @@ # Changing version will trigger a new release! # Please make the version change as the last step of your development. -__version__ = "1.23.0" +__version__ = "1.23.1" RPC_PROTOCOL_VERSION = "0.3"