Ansible sẽ ngăn việc thực thi 'rm -rf /' trong tập lệnh shell


23

Điều này dựa trên câu hỏi chơi khăm này ở đây. Vấn đề được mô tả là có một tập lệnh bash chứa nội dung nào đó có tác dụng:

rm -rf {pattern1}/{pattern2}

... mà nếu cả hai mẫu bao gồm một hoặc nhiều phần tử trống sẽ mở rộng thành ít nhất một thể hiện rm -rf /, giả sử rằng lệnh ban đầu được sao chép chính xác và OP đang thực hiện mở rộng dấu ngoặc thay vì mở rộng tham số .

Trong lời giải thích của OP về trò lừa bịp , ông nói:

Lệnh [...] vô hại nhưng dường như không ai để ý.

Công cụ Ansible ngăn chặn các lỗi này, [...] nhưng [...] dường như không ai biết điều đó, nếu không họ sẽ biết rằng những gì tôi đã mô tả không thể xảy ra.

Vì vậy, giả sử bạn có một tập lệnh shell phát ra rm -rf /lệnh thông qua mở rộng dấu ngoặc hoặc mở rộng tham số, có đúng là sử dụng Ansible sẽ ngăn lệnh đó được thực thi không, và nếu vậy, làm thế nào để thực hiện điều này?

Việc thực thi rm -rf /với các đặc quyền gốc có thực sự "vô hại" miễn là bạn đang sử dụng Ansible để làm điều đó?


4
Tôi đã tranh luận phải làm gì với câu hỏi này, nhưng cuối cùng tôi quyết định nâng cấp và trả lời nó, để cuối cùng đưa ra toàn bộ mớ hỗn độn đáng tiếc này trong quá khứ nơi nó thuộc về.
Michael Hampton

Tôi nghĩ rằng câu trả lời thực sự nằm trong rmnguồn, mà tôi đã phân tích dưới đây.
Aaron Hall

Câu trả lời:


54

Tôi có máy ảo, hãy thổi bay chúng! Đối với khoa học.

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

Nỗ lực đầu tiên:

[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, vì vậy commandchỉ cần vượt qua nghĩa đen, và không có gì xảy ra.

Làm thế nào về bỏ qua an toàn yêu thích của chúng tôi , 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

Không đi nữa! Làm thế nào khó có thể có thể xóa tất cả các tập tin của bạn?

Ồ, nhưng nếu chúng là các biến không xác định hoặc một cái gì đó thì sao?

[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

Vâng, điều đó đã không làm việc.

Nhưng nếu các biến được định nghĩa, nhưng trống rỗng thì sao?

[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

Cuối cùng, một số tiến bộ! Nhưng nó vẫn phàn nàn rằng tôi đã không sử dụng --no-preserve-root.

Tất nhiên, nó cũng cảnh báo với tôi rằng tôi nên thử sử dụng các filemô-đunstate=absent. Hãy xem nếu nó hoạt động.

[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

Tin vui, mọi người! Nó bắt đầu cố gắng xóa tất cả các tập tin của tôi! Nhưng thật không may, nó đã chạy vào một lỗi. Tôi sẽ sửa lỗi đó và lấy playbook để phá hủy mọi thứ bằng cách sử dụng filemô-đun như một bài tập cho người đọc.


KHÔNG chạy bất kỳ sách vở nào bạn thấy ngoài thời điểm này! Bạn sẽ thấy tại sao trong một khoảnh khắc.

Cuối cùng, cho cuộc đảo chính ...

[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 này là một con vẹt cũ !

Thật thú vị, ở trên không làm bất cứ điều gì với commandthay vì raw. Nó chỉ in cùng một cảnh báo về việc sử dụng filevới state=absent.

Tôi sẽ nói rằng có vẻ như nếu bạn không sử dụng rawthì có một sự bảo vệ nào rmđó đã biến mất. Bạn không nên dựa vào điều này, mặc dù. Tôi đã xem nhanh mã của Ansible và trong khi tôi tìm thấy cảnh báo, tôi không tìm thấy bất cứ thứ gì thực sự ngăn chặn việc chạy rmlệnh.


10
+1 cho khoa học. Tôi muốn thêm +1 cho tên máy chủ, nhưng đó sẽ là lừa đảo; p /
Journeyman Geek

Có vẻ như bạn có thể có một hệ thống tập tin được gắn kết tại /boot.
84104

1
@ 84104 Buồn cười, đó. Bởi sự trùng hợp ngẫu nhiên, bootlà mục nhập thư mục đầu tiên /. Vì vậy, không có tập tin bị mất.
Michael Hampton

5
@aroth Chính xác! Nhưng, đối với khoa học, hãy thử rm -rf {{x}}/{{y}}khi yđược đặt thành "*". Các --no-preserve-rootkiểm tra rất hữu ích cho những gì nó là, nhưng nó sẽ không giúp bạn có được ra khỏi mọi tình huống có thể; nó đủ dễ dàng để bỏ qua. Đó là lý do tại sao câu hỏi đó không bị phát hiện là một trò lừa bịp ngay lập tức: Có tính đến tiếng Anh không tốt và các lỗi cú pháp rõ ràng, điều đó là hợp lý .
Michael Hampton

1
Bên cạnh đó raw, một điều xấu croncó thể là một cách khác để phá hỏng một hệ thống.
84104

3

Ansible sẽ ngăn chặn việc thực thi rm -rf /trong một kịch bản shell?

Tôi đã kiểm tra nguồn rut coreutils , có những điều sau đây:

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

Cách duy nhất để xóa từ gốc là vượt qua khối mã này. Từ nguồn này :

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

Tôi giải thích điều này có nghĩa là hàm get_root_dev_inotrả về null /và do đó rm không thành công.

Cách duy nhất để bỏ qua khối mã đầu tiên (có đệ quy) là có --no-preserve-rootvà nó không sử dụng biến môi trường để ghi đè, vì vậy nó sẽ phải được chuyển rõ ràng đến rm.

Tôi tin rằng điều này chứng tỏ rằng trừ khi Ansible rõ ràng chuyển --no-preserve-rootđến rm, nó sẽ không làm điều này.

Phần kết luận

Tôi không tin rằng Ansible ngăn chặn rõ ràng rm -rf /bởi vì rmchính nó ngăn chặn nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.