CVE-2019-3828
Description
Ansible fetch module before versions 2.5.15, 2.6.14, 2.7.8 has a path traversal vulnerability which allows copying and overwriting files outside of the specified destination in the local ansible controller host, by not restricting an absolute path.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
ansiblePyPI | < 2.5.15 | 2.5.15 |
ansiblePyPI | >= 2.6.0a1, < 2.6.14 | 2.6.14 |
ansiblePyPI | >= 2.7.0a1, < 2.7.8 | 2.7.8 |
Affected products
1Patches
3f3edc091523f[stable-2.5] Disallow use of remote home directories containing .. in their path (CVE-2019-3828) (#52133) (#52175)
3 files changed · +44 −23
changelogs/fragments/disallow-relative-homedir.yaml+3 −0 added@@ -0,0 +1,3 @@ +bugfixes: +- remote home directory - Disallow use of remote home directories that include + relative pathing by means of `..` (CVE-2019-3828) (https://github.com/ansible/ansible/pull/52133)
lib/ansible/plugins/action/__init__.py+3 −0 modified@@ -609,6 +609,9 @@ def _remote_expand_user(self, path, sudoable=True, pathsep=None): else: expanded = initial_fragment + if '..' in os.path.dirname(expanded).split('/'): + raise AnsibleError("'%s' returned an invalid relative home directory path containing '..'" % self._play_context.remote_addr) + return expanded def _strip_success_message(self, data):
test/units/plugins/action/test_action.py+38 −23 modified@@ -56,6 +56,28 @@ """ +def _action_base(): + fake_loader = DictDataLoader({ + }) + mock_module_loader = MagicMock() + mock_shared_loader_obj = MagicMock() + mock_shared_loader_obj.module_loader = mock_module_loader + mock_connection_loader = MagicMock() + + mock_shared_loader_obj.connection_loader = mock_connection_loader + mock_connection = MagicMock() + + play_context = MagicMock() + + action_base = DerivedActionBase(task=None, + connection=mock_connection, + play_context=play_context, + loader=fake_loader, + templar=None, + shared_loader_obj=mock_shared_loader_obj) + return action_base + + class DerivedActionBase(ActionBase): TRANSFERS_FILES = False @@ -522,6 +544,18 @@ def test_action_base_sudo_only_if_user_differs(self): finally: C.BECOME_ALLOW_SAME_USER = become_allow_same_user + def test__remote_expand_user_relative_pathing(self): + action_base = _action_base() + action_base._play_context.remote_addr = 'bar' + action_base._low_level_execute_command = MagicMock(return_value={'stdout': b'../home/user'}) + action_base._connection._shell.join_path.return_value = '../home/user/foo' + with self.assertRaises(AnsibleError) as cm: + action_base._remote_expand_user('~/foo') + self.assertEqual( + cm.exception.message, + "'bar' returned an invalid relative home directory path containing '..'" + ) + class TestActionBaseCleanReturnedData(unittest.TestCase): def test(self): @@ -573,27 +607,8 @@ def fake_all(path_only=None): class TestActionBaseParseReturnedData(unittest.TestCase): - def _action_base(self): - fake_loader = DictDataLoader({ - }) - mock_module_loader = MagicMock() - mock_shared_loader_obj = MagicMock() - mock_shared_loader_obj.module_loader = mock_module_loader - mock_connection_loader = MagicMock() - - mock_shared_loader_obj.connection_loader = mock_connection_loader - mock_connection = MagicMock() - - action_base = DerivedActionBase(task=None, - connection=mock_connection, - play_context=None, - loader=fake_loader, - templar=None, - shared_loader_obj=mock_shared_loader_obj) - return action_base - def test_fail_no_json(self): - action_base = self._action_base() + action_base = _action_base() rc = 0 stdout = 'foo\nbar\n' err = 'oopsy' @@ -607,7 +622,7 @@ def test_fail_no_json(self): self.assertEqual(res['module_stderr'], err) def test_json_empty(self): - action_base = self._action_base() + action_base = _action_base() rc = 0 stdout = '{}\n' err = '' @@ -621,7 +636,7 @@ def test_json_empty(self): self.assertFalse(res) def test_json_facts(self): - action_base = self._action_base() + action_base = _action_base() rc = 0 stdout = '{"ansible_facts": {"foo": "bar", "ansible_blip": "blip_value"}}\n' err = '' @@ -637,7 +652,7 @@ def test_json_facts(self): # self.assertIsInstance(res['ansible_facts'], AnsibleUnsafe) def test_json_facts_add_host(self): - action_base = self._action_base() + action_base = _action_base() rc = 0 stdout = '''{"ansible_facts": {"foo": "bar", "ansible_blip": "blip_value"}, "add_host": {"host_vars": {"some_key": ["whatever the add_host object is"]}
396a2f747174[stable-2.7] Disallow use of remote home directories containing .. in their path (CVE-2019-3828) (#52133) (#52173)
3 files changed · +44 −23
changelogs/fragments/disallow-relative-homedir.yaml+3 −0 added@@ -0,0 +1,3 @@ +bugfixes: +- remote home directory - Disallow use of remote home directories that include + relative pathing by means of `..` (CVE-2019-3828) (https://github.com/ansible/ansible/pull/52133)
lib/ansible/plugins/action/__init__.py+3 −0 modified@@ -614,6 +614,9 @@ def _remote_expand_user(self, path, sudoable=True, pathsep=None): else: expanded = initial_fragment + if '..' in os.path.dirname(expanded).split('/'): + raise AnsibleError("'%s' returned an invalid relative home directory path containing '..'" % self._play_context.remote_addr) + return expanded def _strip_success_message(self, data):
test/units/plugins/action/test_action.py+38 −23 modified@@ -56,6 +56,28 @@ """ +def _action_base(): + fake_loader = DictDataLoader({ + }) + mock_module_loader = MagicMock() + mock_shared_loader_obj = MagicMock() + mock_shared_loader_obj.module_loader = mock_module_loader + mock_connection_loader = MagicMock() + + mock_shared_loader_obj.connection_loader = mock_connection_loader + mock_connection = MagicMock() + + play_context = MagicMock() + + action_base = DerivedActionBase(task=None, + connection=mock_connection, + play_context=play_context, + loader=fake_loader, + templar=None, + shared_loader_obj=mock_shared_loader_obj) + return action_base + + class DerivedActionBase(ActionBase): TRANSFERS_FILES = False @@ -526,6 +548,18 @@ def test_action_base_sudo_only_if_user_differs(self): finally: C.BECOME_ALLOW_SAME_USER = become_allow_same_user + def test__remote_expand_user_relative_pathing(self): + action_base = _action_base() + action_base._play_context.remote_addr = 'bar' + action_base._low_level_execute_command = MagicMock(return_value={'stdout': b'../home/user'}) + action_base._connection._shell.join_path.return_value = '../home/user/foo' + with self.assertRaises(AnsibleError) as cm: + action_base._remote_expand_user('~/foo') + self.assertEqual( + cm.exception.message, + "'bar' returned an invalid relative home directory path containing '..'" + ) + class TestActionBaseCleanReturnedData(unittest.TestCase): def test(self): @@ -577,27 +611,8 @@ def fake_all(path_only=None): class TestActionBaseParseReturnedData(unittest.TestCase): - def _action_base(self): - fake_loader = DictDataLoader({ - }) - mock_module_loader = MagicMock() - mock_shared_loader_obj = MagicMock() - mock_shared_loader_obj.module_loader = mock_module_loader - mock_connection_loader = MagicMock() - - mock_shared_loader_obj.connection_loader = mock_connection_loader - mock_connection = MagicMock() - - action_base = DerivedActionBase(task=None, - connection=mock_connection, - play_context=None, - loader=fake_loader, - templar=None, - shared_loader_obj=mock_shared_loader_obj) - return action_base - def test_fail_no_json(self): - action_base = self._action_base() + action_base = _action_base() rc = 0 stdout = 'foo\nbar\n' err = 'oopsy' @@ -611,7 +626,7 @@ def test_fail_no_json(self): self.assertEqual(res['module_stderr'], err) def test_json_empty(self): - action_base = self._action_base() + action_base = _action_base() rc = 0 stdout = '{}\n' err = '' @@ -625,7 +640,7 @@ def test_json_empty(self): self.assertFalse(res) def test_json_facts(self): - action_base = self._action_base() + action_base = _action_base() rc = 0 stdout = '{"ansible_facts": {"foo": "bar", "ansible_blip": "blip_value"}}\n' err = '' @@ -641,7 +656,7 @@ def test_json_facts(self): # self.assertIsInstance(res['ansible_facts'], AnsibleUnsafe) def test_json_facts_add_host(self): - action_base = self._action_base() + action_base = _action_base() rc = 0 stdout = '''{"ansible_facts": {"foo": "bar", "ansible_blip": "blip_value"}, "add_host": {"host_vars": {"some_key": ["whatever the add_host object is"]}
4be3215d2f9f[stable-2.6] Disallow use of remote home directories containing .. in their path (CVE-2019-3828) (#52133)
3 files changed · +44 −23
changelogs/fragments/disallow-relative-homedir.yaml+3 −0 added@@ -0,0 +1,3 @@ +bugfixes: +- remote home directory - Disallow use of remote home directories that include + relative pathing by means of `..` (CVE-2019-3828) (https://github.com/ansible/ansible/pull/52133)
lib/ansible/plugins/action/__init__.py+3 −0 modified@@ -628,6 +628,9 @@ def _remote_expand_user(self, path, sudoable=True, pathsep=None): else: expanded = initial_fragment + if '..' in os.path.dirname(expanded).split('/'): + raise AnsibleError("'%s' returned an invalid relative home directory path containing '..'" % self._play_context.remote_addr) + return expanded def _strip_success_message(self, data):
test/units/plugins/action/test_action.py+38 −23 modified@@ -56,6 +56,28 @@ """ +def _action_base(): + fake_loader = DictDataLoader({ + }) + mock_module_loader = MagicMock() + mock_shared_loader_obj = MagicMock() + mock_shared_loader_obj.module_loader = mock_module_loader + mock_connection_loader = MagicMock() + + mock_shared_loader_obj.connection_loader = mock_connection_loader + mock_connection = MagicMock() + + play_context = MagicMock() + + action_base = DerivedActionBase(task=None, + connection=mock_connection, + play_context=play_context, + loader=fake_loader, + templar=None, + shared_loader_obj=mock_shared_loader_obj) + return action_base + + class DerivedActionBase(ActionBase): TRANSFERS_FILES = False @@ -526,6 +548,18 @@ def test_action_base_sudo_only_if_user_differs(self): finally: C.BECOME_ALLOW_SAME_USER = become_allow_same_user + def test__remote_expand_user_relative_pathing(self): + action_base = _action_base() + action_base._play_context.remote_addr = 'bar' + action_base._low_level_execute_command = MagicMock(return_value={'stdout': b'../home/user'}) + action_base._connection._shell.join_path.return_value = '../home/user/foo' + with self.assertRaises(AnsibleError) as cm: + action_base._remote_expand_user('~/foo') + self.assertEqual( + cm.exception.message, + "'bar' returned an invalid relative home directory path containing '..'" + ) + class TestActionBaseCleanReturnedData(unittest.TestCase): def test(self): @@ -577,27 +611,8 @@ def fake_all(path_only=None): class TestActionBaseParseReturnedData(unittest.TestCase): - def _action_base(self): - fake_loader = DictDataLoader({ - }) - mock_module_loader = MagicMock() - mock_shared_loader_obj = MagicMock() - mock_shared_loader_obj.module_loader = mock_module_loader - mock_connection_loader = MagicMock() - - mock_shared_loader_obj.connection_loader = mock_connection_loader - mock_connection = MagicMock() - - action_base = DerivedActionBase(task=None, - connection=mock_connection, - play_context=None, - loader=fake_loader, - templar=None, - shared_loader_obj=mock_shared_loader_obj) - return action_base - def test_fail_no_json(self): - action_base = self._action_base() + action_base = _action_base() rc = 0 stdout = 'foo\nbar\n' err = 'oopsy' @@ -611,7 +626,7 @@ def test_fail_no_json(self): self.assertEqual(res['module_stderr'], err) def test_json_empty(self): - action_base = self._action_base() + action_base = _action_base() rc = 0 stdout = '{}\n' err = '' @@ -625,7 +640,7 @@ def test_json_empty(self): self.assertFalse(res) def test_json_facts(self): - action_base = self._action_base() + action_base = _action_base() rc = 0 stdout = '{"ansible_facts": {"foo": "bar", "ansible_blip": "blip_value"}}\n' err = '' @@ -641,7 +656,7 @@ def test_json_facts(self): # self.assertIsInstance(res['ansible_facts'], AnsibleUnsafe) def test_json_facts_add_host(self): - action_base = self._action_base() + action_base = _action_base() rc = 0 stdout = '''{"ansible_facts": {"foo": "bar", "ansible_blip": "blip_value"}, "add_host": {"host_vars": {"some_key": ["whatever the add_host object is"]}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
16- lists.opensuse.org/opensuse-security-announce/2019-04/msg00021.htmlghsavendor-advisoryWEB
- lists.opensuse.org/opensuse-security-announce/2019-06/msg00077.htmlghsavendor-advisoryWEB
- lists.opensuse.org/opensuse-security-announce/2019-08/msg00020.htmlghsavendor-advisoryWEB
- access.redhat.com/errata/RHSA-2019:3744ghsavendor-advisoryWEB
- access.redhat.com/errata/RHSA-2019:3789ghsavendor-advisoryWEB
- github.com/advisories/GHSA-74vq-h4q8-x6jvghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-3828ghsaADVISORY
- usn.ubuntu.com/4072-1/mitrevendor-advisory
- packetstormsecurity.com/files/172837/Ansible-Fetch-Path-Traversal.htmlghsaWEB
- bugzilla.redhat.com/show_bug.cgighsaWEB
- github.com/ansible/ansible/commit/396a2f74717477d80600450e2b7e45349d7b5110ghsaWEB
- github.com/ansible/ansible/commit/4be3215d2f9f84ca283895879f0c6ce1ed7dd333ghsaWEB
- github.com/ansible/ansible/commit/f3edc091523fbe301926b7a0db25fbbd96940d93ghsaWEB
- github.com/ansible/ansible/pull/52133ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/ansible/PYSEC-2019-5.yamlghsaWEB
- usn.ubuntu.com/4072-1ghsaWEB
News mentions
0No linked articles in our index yet.