Ansible จะป้องกันการเรียกใช้ 'rm -rf /' ในเชลล์สคริปต์หรือไม่


23

นี่ขึ้นอยู่กับคำถามหลอกลวงนี้ที่นี่ ปัญหาที่อธิบายคือการมีสคริปต์ทุบตีซึ่งมีบางสิ่งที่มีผลต่อ:

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

ข่าวดีทุกคน! มันเริ่มพยายามลบไฟล์ทั้งหมดของฉัน! แต่น่าเสียดายที่มันเกิดข้อผิดพลาด ฉันจะปล่อยให้แก้ไขและรับ playbook เพื่อทำลายทุกสิ่งโดยใช้fileโมดูลเป็นแบบฝึกหัดให้ผู้อ่าน


อย่าเล่น playbooks ใด ๆ ที่คุณเห็นเกินจุดนี้! คุณจะเห็นว่าทำไมในอีกสักครู่

ในที่สุดสำหรับการทำรัฐประหาร ...

[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มันเป็นเพียงแค่พิมพ์คำเตือนเดียวกันเกี่ยวกับการใช้กับfilestate=absent

ฉันจะบอกว่าปรากฏว่าถ้าคุณไม่ได้ใช้rawมันจะมีการป้องกันจากrmอาละวาดไป คุณไม่ควรเชื่อถือในสิ่งนี้ ฉันดูรหัสผ่านของ Ansible อย่างรวดเร็วและในขณะที่ฉันพบคำเตือนฉันไม่พบสิ่งใดที่จะระงับการเรียกใช้rmคำสั่ง


10
+1 สำหรับวิทยาศาสตร์ ฉันต้องการชื่อโฮสต์เพิ่มอีก +1 แต่มันอาจเป็นการฉ้อโกง p /
Journeyman Geek

ดูเหมือนว่าคุณอาจติดตั้งระบบไฟล์/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ส่งคืนค่าว่าง/และดังนั้น rm จึงล้มเหลว

วิธีเดียวที่จะหลีกเลี่ยงการบล็อกโค้ดแรก (ด้วยการเรียกซ้ำ) คือการมี--no-preserve-rootและไม่ใช้ตัวแปรสภาพแวดล้อมเพื่อแทนที่

ผมเชื่อว่านี่เป็นข้อพิสูจน์ว่าถ้าเบิ้ลอย่างชัดเจนผ่าน--no-preserve-rootไปrmก็จะไม่ทำเช่นนี้

ข้อสรุป

ฉันไม่เชื่อว่า Ansible ป้องกันอย่างชัดเจนrm -rf /เพราะrmป้องกันตัวเอง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.