Fix path traversal vulnerability with hooks execution Ensure that Package: and SourcePackage: fields loaded from reports do not contain directories. Until now, an attacker could trick a user into opening a malicious .crash file containing Package: ../../../../some/dir/foo which would execute /some/dir/foo.py with arbitrary code. Note: This did not work for SourcePackage: as this would require a /usr/share/apport/*-hooks/source_/ directory -- but sanity-check the value anyway as a second line of defence. Thanks to Donncha O'Cearbhaill for discovering this! CVE-2016-XXXX LP: #1648806 === modified file 'apport/report.py' --- old/apport/report.py 2016-12-10 11:28:27 +0000 +++ new/apport/report.py 2016-12-10 16:47:06 +0000 @@ -828,14 +828,21 @@ False otherwise. ''' # determine package names, unless already given as arguments + # avoid path traversal if not package: package = self.get('Package') if package: package = package.split()[0] + if '/' in package: + self['UnreportableReason'] = 'invalid Package: %s' % package + return if not srcpackage: srcpackage = self.get('SourcePackage') if srcpackage: srcpackage = srcpackage.split()[0] + if '/' in srcpackage: + self['UnreportableReason'] = 'invalid SourcePackage: %s' % package + return hook_dirs = [_hook_dir] # also search hooks in /opt, when program is from there === modified file 'test/test_ui.py' --- old/test/test_ui.py 2016-12-10 14:05:18 +0000 +++ new/test/test_ui.py 2016-12-10 16:47:06 +0000 @@ -951,6 +951,26 @@ self.assertFalse(os.path.exists('/tmp/pwned')) self.assertIn('invalid crash database definition', self.ui.msg_text) + def test_run_crash_malicious_package(self): + '''Package: path traversal''' + + bad_hook = tempfile.NamedTemporaryFile(suffix='.py') + bad_hook.write(b"def add_info(r, u):\n open('/tmp/pwned', 'w').close()") + bad_hook.flush() + + self.report['ExecutablePath'] = '/bin/bash' + self.report['Package'] = '../' * 20 + os.path.splitext(bad_hook.name)[0] + self.update_report_file() + self.ui.present_details_response = {'report': True, + 'blacklist': False, + 'examine': False, + 'restart': False} + + self.ui.run_crash(self.report_file.name) + + self.assertFalse(os.path.exists('/tmp/pwned')) + self.assertIn('invalid Package:', self.ui.msg_text) + def test_run_crash_ignore(self): '''run_crash() on a crash with the Ignore field''' self.report['Ignore'] = 'True'