Ansible impedirà l'esecuzione di 'rm -rf /' in uno script di shell


23

Questo si basa su questa domanda ingannevole qui. Il problema descritto è avere uno script bash che contiene qualcosa sull'effetto di:

rm -rf {pattern1}/{pattern2}

... che se entrambi i modelli includono uno o più elementi vuoti si espanderà in almeno un'istanza di rm -rf /, supponendo che il comando originale sia stato trascritto correttamente e che l'OP stesse eseguendo l' espansione del controvento anziché l'espansione dei parametri .

Nella spiegazione dell'OP sulla bufala , afferma:

Il comando [...] è innocuo, ma sembra che quasi nessuno se ne sia accorto.

Lo strumento Ansible previene questi errori, ma [...] nessuno sembrava saperlo, altrimenti avrebbero saputo che ciò che ho descritto non poteva accadere.

Quindi supponendo che tu abbia uno script di shell che emette un rm -rf /comando attraverso l'espansione del controvento o l'espansione dei parametri, è vero che l'uso di Ansible impedirà l'esecuzione di quel comando e, in tal caso, come lo fa?

L'esecuzione rm -rf /con i privilegi di root è davvero "innocua" fintanto che stai utilizzando Ansible per farlo?


4
Ho discusso di cosa fare con questa domanda, ma alla fine ho deciso di esprimere il mio voto e di rispondere, in modo da spostarmi verso il fine di mettere finalmente tutto questo dispiacere ridicolo pasticcio nel passato a cui appartiene.
Michael Hampton

Penso che la risposta stia davvero nella rmfonte, che ho analizzato di seguito.
Aaron Hall,

Risposte:


54

Ho macchine virtuali, facciamolo saltare in aria! Per la scienza.

[root@diaf ~]# ansible --version
ansible 2.0.1.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides

Primo tentativo:

[root@diaf ~]# cat killme.yml 
---
- hosts: localhost
  gather_facts: False
  tasks:
    - name: Die in a fire
      command: "rm -rf {x}/{y}"
[root@diaf ~]# ansible-playbook -l localhost -vvv killme.yml
Using /etc/ansible/ansible.cfg as config file
1 plays in killme.yml

PLAY ***************************************************************************

TASK [Die in a fire] ***********************************************************
task path: /root/killme.yml:5
ESTABLISH LOCAL CONNECTION FOR USER: root
localhost EXEC /bin/sh -c '( umask 22 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1461128819.56-86533871334374 `" && echo "` echo $HOME/.ansible/tmp/ansible-tmp-1461128819.56-86533871334374 `" )'
localhost PUT /tmp/tmprogfhZ TO /root/.ansible/tmp/ansible-tmp-1461128819.56-86533871334374/command
localhost EXEC /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python /root/.ansible/tmp/ansible-tmp-1461128819.56-86533871334374/command; rm -rf "/root/.ansible/tmp/ansible-tmp-1461128819.56-86533871334374/" > /dev/null 2>&1'
changed: [localhost] => {"changed": true, "cmd": ["rm", "-rf", "{x}/{y}"], "delta": "0:00:00.001844", "end": "2016-04-20 05:06:59.601868", "invocation": {"module_args": {"_raw_params": "rm -rf {x}/{y}", "_uses_shell": false, "chdir": null, "creates": null, "executable": null, "removes": null, "warn": true}, "module_name": "command"}, "rc": 0, "start": "2016-04-20 05:06:59.600024", "stderr": "", "stdout": "", "stdout_lines": [], "warnings": ["Consider using file module with state=absent rather than running rm"]}
 [WARNING]: Consider using file module with state=absent rather than running rm


PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=1    unreachable=0    failed=0

OK, quindi commandpassa solo i letterali e non succede nulla.

Come per la nostra sicurezza bypass preferito, raw?

[root@diaf ~]# cat killme.yml
---
- hosts: localhost
  gather_facts: False
  tasks:
    - name: Die in a fire
      raw: "rm -rf {x}/{y}"
[root@diaf ~]# ansible-playbook -l localhost -vvv killme.yml
Using /etc/ansible/ansible.cfg as config file
1 plays in killme.yml

PLAY ***************************************************************************

TASK [Die in a fire] ***********************************************************
task path: /root/killme.yml:5
ESTABLISH LOCAL CONNECTION FOR USER: root
localhost EXEC rm -rf {x}/{y}
ok: [localhost] => {"changed": false, "invocation": {"module_args": {"_raw_params": "rm -rf {x}/{y}"}, "module_name": "raw"}, "rc": 0, "stderr": "", "stdout": "", "stdout_lines": []}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0

No vai di nuovo! Quanto può essere difficile eliminare tutti i tuoi file?

Oh, ma se fossero variabili indefinite o qualcosa del genere?

[root@diaf ~]# cat killme.yml
---
- hosts: localhost
  gather_facts: False
  tasks:
    - name: Die in a fire
      command: "rm -rf {{x}}/{{y}}"
[root@diaf ~]# ansible-playbook -l localhost -vvv killme.yml
Using /etc/ansible/ansible.cfg as config file
1 plays in killme.yml

PLAY ***************************************************************************

TASK [Die in a fire] ***********************************************************
task path: /root/killme.yml:5
fatal: [localhost]: FAILED! => {"failed": true, "msg": "'x' is undefined"}

NO MORE HOSTS LEFT *************************************************************
        to retry, use: --limit @killme.retry

PLAY RECAP *********************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1

Bene, non ha funzionato.

Ma cosa succede se le variabili sono definite, ma vuote?

[root@diaf ~]# cat killme.yml 
---
- hosts: localhost
  gather_facts: False
  tasks:
    - name: Die in a fire
      command: "rm -rf {{x}}/{{y}}"
  vars:
    x: ""
    y: ""
[root@diaf ~]# ansible-playbook -l localhost -vvv killme.yml
Using /etc/ansible/ansible.cfg as config file
1 plays in killme.yml

PLAY ***************************************************************************

TASK [Die in a fire] ***********************************************************
task path: /root/killme.yml:5
ESTABLISH LOCAL CONNECTION FOR USER: root
localhost EXEC /bin/sh -c '( umask 22 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1461129132.63-211170666238105 `" && echo "` echo $HOME/.ansible/tmp/ansible-tmp-1461129132.63-211170666238105 `" )'
localhost PUT /tmp/tmp78m3WM TO /root/.ansible/tmp/ansible-tmp-1461129132.63-211170666238105/command
localhost EXEC /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python /root/.ansible/tmp/ansible-tmp-1461129132.63-211170666238105/command; rm -rf "/root/.ansible/tmp/ansible-tmp-1461129132.63-211170666238105/" > /dev/null 2>&1'
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["rm", "-rf", "/"], "delta": "0:00:00.001740", "end": "2016-04-20 05:12:12.668616", "failed": true, "invocation": {"module_args": {"_raw_params": "rm -rf /", "_uses_shell": false, "chdir": null, "creates": null, "executable": null, "removes": null, "warn": true}, "module_name": "command"}, "rc": 1, "start": "2016-04-20 05:12:12.666876", "stderr": "rm: it is dangerous to operate recursively on ‘/’\nrm: use --no-preserve-root to override this failsafe", "stdout": "", "stdout_lines": [], "warnings": ["Consider using file module with state=absent rather than running rm"]}

NO MORE HOSTS LEFT *************************************************************
        to retry, use: --limit @killme.retry

PLAY RECAP *********************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1

Finalmente alcuni progressi! Ma si lamenta ancora che non ho usato --no-preserve-root.

Naturalmente, mi avverte anche che dovrei provare a usare il filemodulo e state=absent. Vediamo se funziona.

[root@diaf ~]# cat killme.yml 
---
- hosts: localhost
  gather_facts: False
  tasks:
    - name: Die in a fire
      file: path="{{x}}/{{y}}" state=absent
  vars:
    x: ""
    y: ""
[root@diaf ~]# ansible-playbook -l localhost -vvv killme.yml    
Using /etc/ansible/ansible.cfg as config file
1 plays in killme.yml

PLAY ***************************************************************************

TASK [Die in a fire] ***********************************************************
task path: /root/killme.yml:5
ESTABLISH LOCAL CONNECTION FOR USER: root
localhost EXEC /bin/sh -c '( umask 22 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1461129394.62-191828952911388 `" && echo "` echo $HOME/.ansible/tmp/ansible-tmp-1461129394.62-191828952911388 `" )'
localhost PUT /tmp/tmpUqLzyd TO /root/.ansible/tmp/ansible-tmp-1461129394.62-191828952911388/file
localhost EXEC /bin/sh -c 'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8 /usr/bin/python /root/.ansible/tmp/ansible-tmp-1461129394.62-191828952911388/file; rm -rf "/root/.ansible/tmp/ansible-tmp-1461129394.62-191828952911388/" > /dev/null 2>&1'
fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "invocation": {"module_args": {"backup": null, "content": null, "delimiter": null, "diff_peek": null, "directory_mode": null, "follow": false, "force": false, "group": null, "mode": null, "original_basename": null, "owner": null, "path": "/", "recurse": false, "regexp": null, "remote_src": null, "selevel": null, "serole": null, "setype": null, "seuser": null, "src": null, "state": "absent", "validate": null}, "module_name": "file"}, "msg": "rmtree failed: [Errno 16] Device or resource busy: '/boot'"}

NO MORE HOSTS LEFT *************************************************************
        to retry, use: --limit @killme.retry

PLAY RECAP *********************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1

Buone notizie a tutti! Ha iniziato a provare a eliminare tutti i miei file! Ma sfortunatamente si è verificato un errore. Lascerò risolverlo e far distruggere tutto il playbook usando il filemodulo come esercizio per il lettore.


NON eseguire alcun playbook che vedi oltre questo punto! Vedrai perché tra un momento.

Infine, per il colpo di grazia ...

[root@diaf ~]# cat killme.yml
---
- hosts: localhost
  gather_facts: False
  tasks:
    - name: Die in a fire
      raw: "rm -rf {{x}}/{{y}}"
  vars:
    x: ""
    y: "*"
[root@diaf ~]# ansible-playbook -l localhost -vvv killme.yml
Using /etc/ansible/ansible.cfg as config file
1 plays in killme.yml

PLAY ***************************************************************************

TASK [Die in a fire] ***********************************************************
task path: /root/killme.yml:5
ESTABLISH LOCAL CONNECTION FOR USER: root
localhost EXEC rm -rf /*
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/ansible/executor/process/result.py", line 102, in run
  File "/usr/lib/python2.7/site-packages/ansible/executor/process/result.py", line 76, in _read_worker_result
  File "/usr/lib64/python2.7/multiprocessing/queues.py", line 117, in get
ImportError: No module named task_result

Questa VM è un ex pappagallo !

È interessante notare che quanto sopra non è riuscito a fare nulla con commandinvece di raw. Ha appena stampato lo stesso avvertimento sull'uso filedi state=absent.

Sto per dire che sembra che se non lo si utilizza rawc'è una protezione da rmimpazzire. Non dovresti fare affidamento su questo, però. Ho dato una rapida occhiata al codice di Ansible e, mentre ho trovato l'avvertimento, non ho trovato nulla che potesse effettivamente eliminare l'esecuzione del rmcomando.


10
+1 per la scienza. Farei +1 in più per il nome host, ma sarebbe una frode; p /
Journeyman Geek,

Sembra che potresti avere un filesystem montato su /boot.
84104

1
@ 84104 Divertente, quello. Per pura coincidenza, bootè la prima voce della directory in /. Quindi nessun file è stato perso.
Michael Hampton

5
@aroth Exactly! Ma, per la scienza, prova rm -rf {{x}}/{{y}}quando yè impostato su "*". Il --no-preserve-rootcontrollo è utile per quello che è, ma non ti farà uscire da ogni possibile situazione; è abbastanza facile da bypassare. Questo è il motivo per cui quella domanda non è stata colta immediatamente come una bufala: tenendo conto del cattivo inglese e degli apparenti errori di sintassi, è plausibile .
Michael Hampton

1
Inoltre raw, un cattivo cronpotrebbe essere un altro modo per distruggere un sistema.
84104

3

Ansible impedirà l'esecuzione di rm -rf /in uno script di shell?

Ho ispezionato la fonte coreutils rm , che ha il seguente:

  if (x.recursive && preserve_root)
    {
      static struct dev_ino dev_ino_buf;
      x.root_dev_ino = get_root_dev_ino (&dev_ino_buf);
      if (x.root_dev_ino == NULL)
        error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
               quoteaf ("/"));
    }

L'unico modo per cancellare dalla radice è superare questo blocco di codice. Da questa fonte :

struct dev_ino *
get_root_dev_ino (struct dev_ino *root_d_i)
{
  struct stat statbuf;
  if (lstat ("/", &statbuf))
    return NULL;
  root_d_i->st_ino = statbuf.st_ino;
  root_d_i->st_dev = statbuf.st_dev;
  return root_d_i;
}

Interpreto questo per indicare che la funzione get_root_dev_inorestituisce null su /, e quindi rm fallisce.

L'unico modo per bypassare il primo blocco di codice (con ricorsione) è avere --no-preserve-roote non usa una variabile d'ambiente per sovrascrivere, quindi dovrebbe essere passato esplicitamente a rm.

Credo che ciò dimostri che se Ansible non passa esplicitamente --no-preserve-roota rmciò, non lo farà.

Conclusione

Non credo che Ansible prevenga esplicitamente rm -rf /perché rmesso stesso lo impedisce.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.