debops.pki does not validate CSRs allowing certificate mis-issuance by compromised remote host
by Robin Schneider
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
Dear DebOps folks
The vulnerability goes in the same direction as other vulns on
https://www.ansible.com/security which are compromised remote hosts exploding
the Ansible controller.
Steps to reproduce:
```YAML
pki_ca_domain: 'example.org'
pki_ca_organization: 'example'
pki_default_authority: 'example-issuing-ca'
pki_authorities:
- name: 'example-root-ca'
domain: '{{ network__public_dns_fqdn }}'
subdomain: 'root-ca'
subject: [ 'o=example Internal Root CA' ]
key_size: '{{ pki_ca_root_key_size }}'
- name: 'example-issuing-ca'
domain: '{{ network__public_dns_fqdn }}'
subdomain: 'issuing-ca'
subject: [ 'o=example', 'ou=example Internal Issuing CA' ]
issuer_name: 'example-root-ca'
key_size: '{{ pki_ca_domain_key_size }}'
# What the Ansible controller expects:
pki_host_realms:
- name: 'good.{{ pki_ca_domain }}'
# That is what the remote host will give us (the Ansible controller):
pki_evil_host_realms:
- name: 'good.{{ pki_ca_domain }}'
domains:
- 'evil.{{ pki_ca_domain }}'
```
Then simply replace all:
```YAML
with_flattened:
- '{{ pki_realms }}'
- '{{ pki_group_realms }}'
- '{{ pki_host_realms }}'
- '{{ pki_default_realms }}'
- '{{ pki_dependent_realms }}'
```
with:
```YAML
with_flattened:
- '{{ pki_evil_host_realms }}'
```
in [tasks](https://github.com/debops/ansible-pki/blob/master/tasks/main.yml)
where `pki-realm` is called on the remote host (which can be spoofed by the
remote host to their liking).
Then run the role. The remote host gets this cert issued:
```
Certificate:
Data:
Version: 3 (0x2)
Signature Algorithm: sha256WithRSAEncryption
Subject: CN=good.example.org
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:evil.example.org
```
The above is just my way of demonstrating/reproducing it.
Basically, the pki-realms script is always executed by the remote host, so the
input and output of the script could be controlled by an attacker if the
remote host got compromised. That is basically what I simulated here. I
defined a second variable in the inventory on the controller
`pki_evil_host_realms` representing the input by which pki-realms gets called
despite the fact that the controller sends `pki_host_realms`. I could have
done it manually on the remote host but, oh well, Automation
`pki-authority` must validate `pki_*realms` and check if the admin actually
authorized the CSR generated by that particular (untrusted) remote host.
Unfortunately, DebOps does not have a patch for this and it is currently not
clear when one will be available as it might end in a rewrite of
`pki-authority`. For transparency reasons, we therefore decided to make this
public to not leave users in the dark. Technically, DebOps is not yet ready
for production.
@drybjed Already wrote how this can be solved. It is probably best when he
pastes that in himself. Tracked in
https://github.com/debops/ansible-pki/issues/106
My idea was:
`pki-authority` could just get all `pki_*realms` of the current Ansible run,
encoded as JSON, build Subject and SANs for each realm. Then subject and SANs
(and possibly other interesting parts of the CSR) get extracted from the CSR
the remote host provided, sorted and compared. Python would make that a bit
easier.
Timeline:
* 2017-04-17: Internal discussion with @drybjed
* 2017-04-23: Decision that the vulnerability can/should be made public.
See also: https://github.com/debops/ansible-pki/issues/106
- --
Live long and prosper
Robin `ypid` Schneider -- https://me.ypid.de/
-----BEGIN PGP SIGNATURE-----
iQIcBAEBCgAGBQJY/S/FAAoJEIb9mAu/GkD4AaMP/3woWIZMWbBJB7aiZsXAevCQ
P2IRe8O/0yX5LlQ2XZfNrvSQa0Ik00ffIfDHl3a7ujanyFUw+x2tkdf3wUrUyr0A
VmQa8wOV3KPyQ5n8gNGJC64hUjPK9iqbZASOs8q7/vQXJEOk6cwrA0VnoAByDBoC
PMPQyH0Opkk846Q4tVYpEGP/qEkpz43K2DbtyVM0xUo8qOK6odRNBjXgZjpexyQh
WXgGOFpt8Ex9LzyyGqr5seWUOlXdbL3Mbig3vbYQFagMRA6tJ+XaqVED2W9tqi9N
ZnAdmIgJsXRgq6yBWHSHsdWRIz3q5uBNs9T2JwgwkNG6D72KutdWvNIctgmsIqOw
zp3nqoG5ndr/XyqSo1D5mM1J9pSucSUTwUfFMir0KK5u67yATMaY5P4YGjkarSVj
YX08dhlRXH3+hXJrDe8xG4S+l1OtWDr1xcoT3gpLaa62Zg5Az8+S4/qLWCEltUQh
g7erb5kbQQxHgjtw5Fcr28aFSK24qJktMukdEI5YZrD5duGBCUO1RSi7GgXpN3az
feH2waipTqHDclyHZHg4XAJGaE0jihXvDnwa7B57928uuqygp6y1kMk2lE6clOsQ
C9CMn5MDEPaUOtYm1raZtGNnqmBH7PpibhYOIfePVamfjSc2linYhO5ha21D6LEZ
ai4GxjEGi2c2QRxJg7xS
=tRxq
-----END PGP SIGNATURE-----
7 years, 8 months