Вопрос: Будет ли Ansible предотвращать выполнение 'rm -rf /' в сценарии оболочки


Это основано на этом мистификационный вопрос Вот. Описанная проблема имеет скрипт bash, который содержит что-то вроде:

rm -rf {pattern1}/{pattern2}

... который, если оба шаблона включают один или несколько пустых элементов, будет расширяться, по меньшей мере, до одного экземпляра rm -rf /, предполагая, что исходная команда была правильно расшифрована и OP выполнял расширение скобки, а не расширение параметра,

В ОП объяснение мистификации, он утверждает:

Команда [...] безвредна, но кажется   что почти никто не заметил.

Инструмент Ansible предотвращает эти ошибки, [...], но [...] никто не казался   знайте, что в противном случае они знали бы, что то, что я описал, могло бы   не произойдет.

Предположим, что у вас есть сценарий оболочки, который испускает rm -rf / через расширение или расширение расширений, верно ли это, что использование анзибль предотвратит выполнение этой команды, и если да, то как это сделать?

Выполняется rm -rf / с привилегиями root действительно «безвредными», если вы используете Ansible для этого?


22
2018-04-20 04:48


Источник


Я обсуждал, что делать с этим вопросом, но в конечном итоге я решил поддержать и ответить на него, чтобы двигаться в направлении, в конце концов, положить весь этот жалкий смешной беспорядок в прошлом, где он принадлежит. - Michael Hampton♦
Я думаю, что ответ действительно лежит в rm источник, который я проанализировал ниже. - Aaron Hall


Ответы:


У меня есть виртуальные машины, давайте взорвать их! Для науки.

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

Первая попытка:

[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

Итак command просто передает литералы, и ничего не происходит.

Как насчет нашего любимого байпаса безопасности, 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

Не возвращайся! Насколько сложно, возможно, удалить все ваши файлы?

О, но что, если они были неопределенными переменными или чем-то еще?

[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

Ну, это не сработало.

Но что, если переменные определены, но пусты?

[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

Наконец, некоторый прогресс! Но он все еще жалуется, что я не использовал --no-preserve-root,

Конечно, он также предупреждает меня, что я должен попытаться использовать file модуль а также state=absent, Давайте посмотрим, работает ли это.

[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

Хорошие новости, все! Началось пытаясь удалить все мои файлы! Но, к сожалению, это столкнулось с ошибкой. Я оставлю это исправление и получаю книгу, чтобы уничтожить все, используя file модуль как упражнение для читателя.


НЕ запускайте какие-либо плейбуки, которые вы видите за пределами этого пункта! Вы сразу увидите, почему.

Наконец, для coup de grâce...

[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

Эта виртуальная машина бывший попугай!

Интересно, что выше не удалось ничего сделать с command вместо raw, Он просто напечатал то же предупреждение об использовании file с state=absent,

Я скажу, что, если вы не используете raw что существует некоторая защита от rm ушел. Однако вы не должны полагаться на это. Я быстро просмотрел код Ansible, и, пока я нашел предупреждение, я не нашел ничего, что фактически подавляло бы запуск rm команда.


53
2018-04-20 05:18



+1 для науки. Я бы добавил еще +1 для имени хоста, но это было бы мошенничество, p / - Journeyman Geek
Похоже, у вас может быть файловая система, смонтированная на /boot, - 84104
@ 84104 Забавно, что. По простому совпадению, boot это первая запись в каталоге в /, Таким образом, файлы не были потеряны. - Michael Hampton♦
@aroth Точно! Но, для науки, попробуйте rm -rf {{x}}/{{y}} когда y для "*", --no-preserve-root проверка полезна для того, что она есть, но она не выведет вас из любой возможной ситуации; его достаточно легко обойти. Вот почему этот вопрос не был сразу же обнаружен как мистификация: принимая во внимание плохой английский и очевидные синтаксические ошибки, это правдоподобно, - Michael Hampton♦
Кроме raw, плохой cron может быть другим способом разрушить систему. - 84104


Будет ли Ansible предотвращать выполнение rm -rf / в сценарии оболочки?

Я проверил coreutils источник rm, который имеет следующее:

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

Единственный способ стереть от корня заключается в том, чтобы пройти этот блок кода. Из этот источник:

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

Я интерпретирую это как означающее, что функция get_root_dev_ino возвращает null /, и, следовательно, rm терпит неудачу.

Единственный способ обойти первый блок кода (с рекурсией) - это --no-preserve-root и он не использует переменную среды для переопределения, поэтому ее нужно будет явно передать в rm.

Я считаю, что это доказывает, что, если Ansible явно не пройдет --no-preserve-root в rm, он этого не сделает.

Вывод

Я не считаю, что Ansible явно предотвращает rm -rf / потому как rm сам мешает.


3
2018-04-21 12:56