쉘 스크립트에서 'rm -rf /'의 실행을 막을 수 있습니까?


23

이것은이 사기 문제에 대한 것입니다. 설명 된 문제는 다음과 같은 효과가있는 bash 스크립트가 있습니다.

rm -rf {pattern1}/{pattern2}

... 두 패턴에 하나 이상의 빈 요소가 포함 된 경우 rm -rf /원래 명령이 올바르게 기록되고 OP가 매개 변수 확장이 아닌 중괄호 확장 을 수행한다고 가정하면 하나 이상의 빈 요소가 하나 이상의 인스턴스로 확장 됩니다.

OP 에 대한 사기에 대한 설명 에서 그는 다음과 같이 말합니다.

명령 [...]은 무해하지만 아무도 눈치 채지 못한 것 같습니다.

Ansible 도구는 이러한 오류를 방지합니다. [...] 그러나 [...] 아무도 그것을 알지 못하는 것 같았습니다. 그렇지 않으면 내가 설명한 내용이 발생할 수 없음을 알 것입니다.

따라서 rm -rf /중괄호 확장 또는 매개 변수 확장을 통해 명령을 내보내는 셸 스크립트가 있다고 가정하면 Ansible 을 사용 하면 해당 명령이 실행되지 못하게되고 어떻게됩니까?

rm -rf /Ansible을 사용 하는 한 루트 권한으로 실행 하는 것이 실제로 "무해"합니까?


4
나는이 질문으로 무엇을 해야할지 토론했지만 궁극적으로 나는 그것을 불쾌하게하고 대답하기로 결정했다.
Michael Hampton

나는 그 대답이 실제로 rm소스 에 있다고 생각하며 , 아래에서 분석했습니다.
Aaron Hall

답변:


54

나는 가상 머신을 가지고 있습니다. 과학을 위해.

[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.

물론, 그것은 또한 내가 사용 시도해야 나에게 경고 모듈을 하고 . 그것이 작동하는지 봅시다.filestate=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모듈을 독자에게 연습으로 사용하여 모든 것을 파괴하기 위해 플레이 북을 얻는다 .


이 시점 이후에 보이는 플레이 북을 실행하지 마십시오! 잠시 후 이유를 알 수 있습니다.

마지막으로 쿠데타를 위해 ...

[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

이 VM은 전 앵무새입니다 !

흥미롭게도 위의 command대신에 아무것도하지 못했습니다 raw. filewith 사용 에 대한 동일한 경고가 인쇄 되었습니다 state=absent.

나는 그것을 사용하지 않는 경우에 나타납니다 말할거야 raw에서 몇 가지 보호가 있음을 rm사라 미친 듯이 날 뛰어. 그러나 이것에 의존해서는 안됩니다. Ansible의 코드를 간략히 살펴 보았고 경고를 발견 한 동안 실제로 rm명령 실행을 억제하는 것을 찾지 못했습니다 .


10
과학 +1 나는 호스트 이름을 하나 더 싶지만, 그것은 사기가 될 것이다 P /
저니 긱

에 파일 시스템이 마운트 된 것 같습니다 /boot.
84104

1
@ 84104 재미 있네요. 순전히 우연의 일치 boot는의 첫 번째 디렉토리 항목입니다 /. 따라서 파일이 손실되지 않았습니다.
Michael Hampton

5
@aroth 정확하게! 그러나, 과학에 대한 시도 rm -rf {{x}}/{{y}}할 때y 로 설정되어"*" . 이 --no-preserve-root검사는 그것이 무엇인지에 유용하지만 가능한 모든 상황에서 벗어날 수는 없습니다. 우회하기 쉽습니다. 그렇기 때문에 그 질문이 즉시 사기로 잡히지 않는 이유가 있습니다. 나쁜 영어와 명백한 구문 오류를 고려하면 그럴듯 합니다.
Michael Hampton

1
게다가 raw 나쁜 cron것은 시스템을 망칠 수있는 또 다른 방법 일 수 있습니다.
84104

3

Ansible rm -rf /은 쉘 스크립트에서 의 실행을 방해 합니까?

coreutils rm source를 검사 했는데 다음이 있습니다.

  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 on을 반환 /하므로 rm이 실패 한다는 것을 의미하는 것으로 해석 합니다.

첫 번째 코드 블록을 우회하는 유일한 방법은 재귀를 --no-preserve-root사용하는 것이며 환경 변수를 재정의하는 데 사용하지 않으므로 명시 적으로 rm에 전달해야합니다.

나는이 Ansible 명시 적으로 전달하지 않는 것을 증명 생각 --no-preserve-rootrm, 그것은이 작업을 수행하지 않습니다.

결론

나는 Ansible이 그것을 막기 rm -rf /때문에 명시 적으로 막지 않는다고 믿지 않습니다 rm.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.