Remote Code Execution in Ansible dynamic inventory plugins

I was wondering if this was raised at a GitHub issue, if it was I’d be interested in reading the discussion that followed:

Does the same thing apply to scripts in the /etc/ansible/facts.d directory?

It doesn’t appear to:

$ cat /etc/ansible/facts.d/j2.fact
#!/bin/sh

echo "\"{{ lookup('pipe', 'touch /tmp/hacked' ) }}\""

$ ssh node /etc/ansible/facts.d/j2.fact | jq .
"{{ lookup('pipe', 'touch /tmp/hacked' ) }}"
- hosts: node
  gather_facts: true
  tasks:
   - copy: content="{{ ansible_local.j2 }}" dest=/tmp/bla
$ ssh node cat /tmp/bla
{{ lookup('pipe', 'touch /tmp/hacked' ) }}

$ ls -l /tmp/hac*
ls: cannot access '/tmp/hac*': No such file or directory
1 Like

My guess is that the original discussion happened outside GitHub. I can kind of understand why this doesn’t count as a vulnerability in ansible-core, but I would definitely consider it a vulnerability in almost all inventory plugins.

IMO the problem is that the API makes the common thing (set something that should NOT be interpreted as a template) hard (you basically need to wrap strings as unsafe yourself), and something dangerous easy. Unfortunately fixing the API isn’t trivial (you don’t want to break valid use-cases, and if you add new functions they won’t work with older ansible-core releases).

1 Like

One interesting find: if you make the value of ansible_connection unsafe, some older versions of ansible-core will crash, at least when using the raw module. (Found while working on Prevent RCE via inventory plugins by felixfontein · Pull Request #815 · ansible-collections/community.docker · GitHub)

1 Like

The unsafe plugin name issue was recently discovered with the unsafe persistence fixes: plugin names derived from unsafe sources result in `ValueError: unmarshallable object` · Issue #82708 · ansible/ansible · GitHub

A fix has been merged to the stable-2.16 branch, but no releases have been cut yet. The rc1 releases for next week will include the fix.

2 Likes