Bug 998223 (CVE-2013-4259)

Summary: CVE-2013-4259 ansible: insecure location for ssh ControlMaster socket
Product: [Other] Security Response Reporter: Michael S. <misc>
Component: vulnerabilityAssignee: Red Hat Product Security <security-response-team>
Status: CLOSED ERRATA QA Contact:
Severity: medium Docs Contact:
Priority: medium    
Version: unspecifiedCC: athmanem, jrusnack, kevin, maxim, security-response-team, vdanen, vkrizan
Target Milestone: ---Keywords: Security
Target Release: ---   
Hardware: Unspecified   
OS: Unspecified   
Fixed In Version: Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2017-05-12 07:04:19 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Bug Depends On: 999621, 1001454    
Bug Blocks: 998712    
Description Flags
better patch none

Description Michael S. 2013-08-18 11:40:22 UTC
by default, ansible try to create a ControlMaster file in a predictible location in /tmp. This is vulnerable to a ssh socket injection attack like this :

~ $ sudo ln -s /tmp/ansible-ssh-elspeth.example.org-22-misc /tmp/ansible-ssh-sisay.example.org-22-misc

~ $ ansible -i 'elspeth.example.org,sisay.example.org' all -m shell -u misc -a hostname
elspeth.example.org | success | rc=0 >>

sisay.example.org | success | rc=0 >>

I also did a test without using root, that's the same.
Based on this attack, someone could divert the ssh connexion to another server, make it connect to a server under the control of attacker, and steal configuration file ( with passwords ), or steal password with a fake sudo ( since ansible can also use sudo )

Please note that you need to :
- disable selinux
# setenforce 0

- disable latest protection from the kernel 

# sysctl -w fs.protected_symlinks=0
# sysctl -w fs.protected_hardlinks=0

to make sure this work.
I didn't found how/where ssh control the socket file for suitability, maybe it should

I am not sure what could be a good fix. I do have a patch that put the socket in $XDG_RUNTIME_DIR but it is a very weak mitigation technique that do not work on older platform such as RHEL 6. 

Another solution would be to make sure the socket is created in specific temporary directory, but this could make the software much slower.

And checking if the socket exist first is prone to race condition.

Upstream was not contacted yet, and plan to release 1.3 around 2 weeks. Issue is not public ( but quite easy to spot )

Comment 1 Michael S. 2013-08-18 11:45:04 UTC
The issue is in https://github.com/ansible/ansible/blob/devel/lib/ansible/runner/connection_plugins/ssh.py#L59

Possible fix :

commit d06eaae5fa32ae24e8076f846bdf3f04e6090384
Author: Michael Scherer <misc@zarb.org>
Date:   Sun Aug 18 13:19:01 2013 +0200

    Try to mitigate symlink attacks on newer platform
    A attacker could pre create a socket in /tmp and so divert
    the ssh connexion to another server. However, newer platform
    are protected against this since kernel 3.10 and surely with
    a proper selinux policy.

diff --git a/lib/ansible/runner/connection_plugins/ssh.py b/lib/ansible/runner/connection_plugins/ssh.py
index abbffcf..be8289e 100644
--- a/lib/ansible/runner/connection_plugins/ssh.py
+++ b/lib/ansible/runner/connection_plugins/ssh.py
@@ -51,12 +51,15 @@ class Connection(object):
         self.common_args = []
         extra_args = C.ANSIBLE_SSH_ARGS
+        control_path_dir = '/tmp'
+        if 'XDG_RUNTIME_DIR' in os.environ:
+            control_path_dir = os.environ['XDG_RUNTIME_DIR']
         if extra_args is not None:
             self.common_args += shlex.split(extra_args)
             self.common_args += ["-o", "ControlMaster=auto",
                                  "-o", "ControlPersist=60s",
-                                 "-o", "ControlPath=/tmp/ansible-ssh-%h-%p-%r"]
+                                 "-o", "ControlPath=%s/ansible-ssh-%%h-%%p-%%r" % control_path_dir]
         if not C.HOST_KEY_CHECKING:
             self.common_args += ["-o", "StrictHostKeyChecking=no"]

Comment 2 Michael S. 2013-08-18 14:24:40 UTC
Created attachment 787788 [details]
better patch

So here is a proper patch, using a temporary directory shared during the playbook run. 

However, it could be written more cleanly without using global, and using proper object lifecycle, but I consider that being minor when compared to the security fix.

Comment 3 Vincent Danen 2013-08-19 20:12:46 UTC
Hi, Michael.  The best thing to do here would be to alert upstream to this.  We can assign a CVE for this that you can pass along as well, and then we can open this bug up and get fixes into Fedora and EPEL6 once the issue is fixed upstream.

Comment 4 Vincent Danen 2013-08-19 20:28:31 UTC

Red Hat would like to thank Michael Scherer for reporting this issue.

Comment 5 Vincent Danen 2013-08-19 20:29:09 UTC
Michael, when you report this to upstream can you also note that it was assigned CVE-2013-4259?


Comment 6 Michael S. 2013-08-19 21:35:39 UTC
Yep, will do. I keep you in CC to see how we organize the embargo, if upstream want one.

Comment 7 Vincent Danen 2013-08-21 17:58:33 UTC
Created ansible tracking bugs for this issue:

Affects: fedora-all [bug 999621]

Comment 8 Vincent Danen 2013-08-21 18:01:10 UTC
This is public now:


Comment 9 Vincent Danen 2013-08-27 05:51:32 UTC
Created ansible tracking bugs for this issue:

Affects: epel-6 [bug 1001454]

Comment 10 Fedora Update System 2013-08-28 16:43:55 UTC
ansible-1.2.3-2.el6 has been pushed to the Fedora EPEL 6 stable repository.  If problems still persist, please make note of it in this bug report.

Comment 11 Fedora Update System 2013-08-30 22:58:23 UTC
ansible-1.2.3-2.fc18 has been pushed to the Fedora 18 stable repository.  If problems still persist, please make note of it in this bug report.

Comment 12 Fedora Update System 2013-08-30 23:03:27 UTC
ansible-1.2.3-2.fc19 has been pushed to the Fedora 19 stable repository.  If problems still persist, please make note of it in this bug report.