Frage Will Ansible verhindert die Ausführung von 'rm -rf /' in einem Shell-Skript


Dies basiert darauf Scherzfrage Hier. Das beschriebene Problem hat ein Bash-Skript, das etwas enthält mit:

rm -rf {pattern1}/{pattern2}

... welche, wenn beide Muster ein oder mehrere leere Elemente enthalten, auf mindestens eine Instanz erweitert werden rm -rf /, angenommen, dass der ursprüngliche Befehl korrekt transkribiert wurde und der OP dies tat Klammererweiterung statt Parametererweiterung.

In den OPs Erklärung des Betrugs, Er legt fest:

Der Befehl [...] ist harmlos, aber es scheint so   das hat fast niemand bemerkt.

Das Ansible-Tool verhindert diese Fehler, [...] aber niemand schien es zu tun   Wisse das, sonst wüssten sie, dass das, was ich beschrieben habe, könnte   nicht passieren.

Angenommen, Sie haben ein Shell-Skript, das ein rm -rf / Befehl entweder durch geschweifte Erweiterung oder Parametererweiterung, ist es wahr, dass mit Ansible verhindert, dass dieser Befehl ausgeführt wird, und wenn ja, wie geht das?

Wird ausgeführt rm -rf / mit Root-Privilegien wirklich "harmlos", solange Sie Ansible verwenden, um es zu tun?


22
2018-04-20 04:48


Ursprung


Ich habe darüber debattiert, was ich mit dieser Frage anfangen soll, aber letztendlich habe ich beschlossen, es zu verbessern und zu beantworten, um endlich dieses ganze traurige, lächerliche Durcheinander in der Vergangenheit zu schaffen, wo es hingehört. - Michael Hampton♦
Ich denke, die Antwort liegt wirklich in der rm Quelle, die ich unten analysiert habe. - Aaron Hall


Antworten:


Ich habe virtuelle Maschinen, lasst uns einen Haufen davon blasen! Für die Wissenschaft.

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

Erster Versuch:

[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, also command Leitet einfach die Literale mit, und nichts passiert.

Wie wäre es mit unserer bevorzugten Sicherheitsumgehung, 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

Nein, geh wieder! Wie schwer kann es sein, alle deine Dateien zu löschen?

Oh, aber was, wenn sie undefinierte Variablen oder etwas waren?

[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

Nun, das hat nicht funktioniert.

Was aber, wenn die Variablen definiert, aber leer sind?

[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

Endlich, ein bisschen Fortschritt! Aber es beschwert sich immer noch, dass ich es nicht benutzt habe --no-preserve-root.

Natürlich warnt es mich auch, dass ich versuchen sollte zu verwenden das file Modul und state=absent. Mal sehen, ob das funktioniert.

[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

Gute Nachrichten, Leute! Es begann versuchen um alle meine Dateien zu löschen! Aber leider ist ein Fehler aufgetreten. Ich werde das reparieren lassen und das Spielbuch dazu bringen, alles zu zerstören file Modul als Übung für den Leser.


Lassen Sie keine Spielbücher laufen, die Sie über diesen Punkt hinaus sehen! Du wirst sehen, warum in einem Moment.

Schließlich für die Gnadenstoß...

[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

Diese VM ist ein Ex-Papagei!

Interessanterweise konnte das Obige nichts damit anfangen command anstatt raw. Es hat nur die gleiche Warnung über die Verwendung gedruckt file mit state=absent.

Ich werde sagen, dass es scheint, dass wenn Sie nicht verwenden raw dass es etwas Schutz vor gibt rm Amok gegangen. Sie sollten sich jedoch nicht darauf verlassen. Ich habe einen kurzen Blick durch den Code von Ansible geworfen, und während ich die Warnung gefunden habe, habe ich nichts gefunden, was die Ausführung des rm Befehl.


53
2018-04-20 05:18



+1 für die Wissenschaft. Ich würde mehr für den Hostnamen +1 geben, aber es wäre Betrug; p / - Journeyman Geek
Sieht so aus, als ob du ein Dateisystem angehängt hast /boot. - 84104
@ 84104 Lustig, das. Durch Zufall, boot ist der erste Verzeichniseintrag in /. So sind keine Dateien verloren gegangen. - Michael Hampton♦
@aroth Genau! Aber, für die Wissenschaft, versuche es rm -rf {{x}}/{{y}} wann y ist eingestellt auf "*". Das --no-preserve-root Check ist nützlich für das, was es ist, aber es wird dich nicht aus jeder möglichen Situation herausholen; es ist einfach genug, um zu umgehen. Deshalb wurde diese Frage nicht sofort als Betrug aufgefasst: In Anbetracht des schlechten Englisch und der scheinbaren Syntaxfehler, es ist plausibel. - Michael Hampton♦
Außerdem raw, ein schlechter cron könnte eine andere Möglichkeit sein, ein System zu zerstören. - 84104


Will Ansible verhindert die Ausführung von rm -rf / in einem Shell-Skript?

Ich habe die Coreutils untersucht rm Quellemit folgendem Inhalt:

  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 ("/"));
    }

Die einzige Möglichkeit zu wischen von der Wurzel ist es, diesen Codeblock zu umgehen. Von diese Quelle:

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;
}

Ich interpretiere das so, dass das die Funktion ist get_root_dev_ino Gibt null an /und somit versagt rm.

Die einzige Möglichkeit den ersten Codeblock (mit Rekursion) zu umgehen ist es zu haben --no-preserve-root und es wird keine Umgebungsvariable zum Überschreiben verwendet, daher müsste es explizit an rm übergeben werden.

Ich glaube, das beweist das, es sei denn, Ansible geht ausdrücklich darauf ein --no-preserve-root zu rmEs wird das nicht tun.

Fazit

Ich glaube nicht, dass Ansible dies explizit verhindert rm -rf / da rm selbst verhindert es.


3
2018-04-21 12:56