Kemungkinan akan mencegah eksekusi 'rm -rf /' di skrip shell

23

Ini didasarkan pada pertanyaan bohong ini di sini. Masalah yang dijelaskan adalah memiliki skrip bash yang berisi sesuatu dengan efek:

rm -rf {pattern1}/{pattern2}

... yang jika kedua pola menyertakan satu atau lebih elemen kosong akan diperluas ke setidaknya satu instance rm -rf /, dengan asumsi bahwa perintah asli ditranskripsikan dengan benar dan OP melakukan ekspansi brace daripada ekspansi parameter .

Dalam penjelasan OP tentang tipuan itu , ia menyatakan:

Perintah [...] tidak berbahaya tetapi tampaknya hampir tidak ada yang memperhatikan.

Alat Ansible mencegah kesalahan ini, [...] tetapi [...] sepertinya tidak ada yang tahu itu, kalau tidak mereka akan tahu bahwa apa yang saya jelaskan tidak dapat terjadi.

Jadi dengan asumsi Anda memiliki skrip shell yang memancarkan rm -rf /perintah melalui ekspansi brace atau ekspansi parameter, apakah benar bahwa menggunakan Ansible akan mencegah perintah itu dieksekusi, dan jika demikian, bagaimana cara melakukannya?

Apakah mengeksekusi rm -rf /dengan hak akses root benar-benar "tidak berbahaya" selama Anda menggunakan Kemungkinan untuk melakukannya?

aroth
sumber
4
Saya berdebat apa yang harus dilakukan dengan pertanyaan ini, tetapi pada akhirnya saya memutuskan untuk mengangkat dan menjawabnya, sehingga untuk bergerak ke arah akhirnya menempatkan seluruh kekacauan yang menyedihkan ini di masa lalu di mana tempatnya.
Michael Hampton
Saya pikir jawabannya benar-benar terletak pada rmsumbernya, yang saya analisis di bawah.
Aaron Hall

Jawaban:

54

Saya punya mesin virtual, mari kita meledakkannya! Untuk sains.

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

Percobaan pertama:

[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

Oke, jadi commandlewati saja literalnya, dan tidak ada yang terjadi.

Bagaimana dengan bypass keselamatan favorit kita 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

Jangan pergi lagi! Seberapa sulit mungkin untuk menghapus semua file Anda?

Oh, tetapi bagaimana jika mereka adalah variabel yang tidak ditentukan atau sesuatu?

[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

Ya, itu tidak berhasil.

Tetapi bagaimana jika variabel didefinisikan, tetapi kosong?

[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

Akhirnya, beberapa kemajuan! Tetapi masih mengeluh bahwa saya tidak menggunakan --no-preserve-root.

Tentu saja, itu juga memperingatkan saya bahwa saya harus mencoba menggunakan yang filemodul dan state=absent. Mari kita lihat apakah itu berhasil.

[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

Berita bagus, semuanya! Itu mulai mencoba menghapus semua file saya! Namun sayangnya itu mengalami kesalahan. Saya akan meninggalkan memperbaikinya dan membuat buku pedoman untuk menghancurkan semuanya menggunakan filemodul sebagai latihan untuk pembaca.


JANGAN menjalankan buku pedoman apa pun yang Anda lihat di luar titik ini! Anda akan melihat mengapa suatu saat.

Akhirnya, untuk kudeta ...

[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 ini adalah mantan burung beo !

Menariknya, hal di atas gagal melakukan apa pun dengan commandalih - alih raw. Itu hanya mencetak peringatan yang sama tentang penggunaan filedengan state=absent.

Saya akan mengatakan bahwa tampaknya jika Anda tidak menggunakan rawitu ada perlindungan dari rmamuk yang hilang. Anda seharusnya tidak mengandalkan ini. Saya melihat sekilas kode Ansible, dan ketika saya menemukan peringatan itu, saya tidak menemukan apa pun yang benar-benar akan menekan menjalankan rmperintah.

Michael Hampton
sumber
10
+1 untuk sains. Saya akan memberi +1 lebih banyak untuk nama host, tetapi itu adalah penipuan; p /
Journeyman Geek
Sepertinya Anda mungkin memasang sistem file di /boot.
84104
1
@ 84104 Lucu, itu. Dengan kebetulan belaka, bootadalah entri direktori pertama di /. Jadi tidak ada file yang hilang.
Michael Hampton
5
@aroth Tepat! Tapi, untuk sains, cobalah rm -rf {{x}}/{{y}}kapan ydiatur "*". The --no-preserve-rootcek berguna untuk apa itu, tapi itu tidak akan membuat Anda keluar dari setiap situasi yang mungkin; cukup mudah untuk memotong. Itulah sebabnya pertanyaan itu tidak langsung dianggap sebagai tipuan: Mempertimbangkan bahasa Inggris yang buruk dan kesalahan sintaksis yang jelas, itu masuk akal .
Michael Hampton
1
Selain itu raw, yang buruk cronbisa menjadi cara lain untuk menghancurkan suatu sistem.
84104
3

Apakah Ansible akan mencegah eksekusi rm -rf /dalam skrip shell?

Saya telah memeriksa sumber rm coreutils , yang memiliki yang berikut:

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

Satu-satunya cara untuk menghapus dari root adalah melewati blok kode ini. Dari sumber ini :

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

Saya menafsirkan ini berarti bahwa fungsi get_root_dev_inomengembalikan nol /, dan dengan demikian rm gagal.

Satu-satunya cara untuk mem-bypass blok kode pertama (dengan rekursi) adalah memiliki --no-preserve-rootdan tidak menggunakan variabel lingkungan untuk menimpanya, sehingga harus diteruskan secara eksplisit ke rm.

Saya percaya ini membuktikan bahwa kecuali Ansible secara eksplisit beralih --no-preserve-rootke rm, itu tidak akan melakukan ini.

Kesimpulan

Saya tidak percaya bahwa Ansible secara eksplisit mencegah rm -rf /karena ia rmsendiri mencegahnya.

Aaron Hall
sumber