Izinkan setuid pada skrip shell

185

The setuidbit izin memberitahu Linux untuk menjalankan program dengan user id efektif dari pemilik bukan eksekutor:

> cat setuid-test.c

#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv) {
    printf("%d", geteuid());
    return 0;
}

> gcc -o setuid-test setuid-test.c
> ./setuid-test

1000

> sudo chown nobody ./setuid-test; sudo chmod +s ./setuid-test
> ./setuid-test

65534

Namun, ini hanya berlaku untuk executable; skrip shell mengabaikan bit setuid:

> cat setuid-test2

#!/bin/bash
id -u

> ./setuid-test2

1000

> sudo chown nobody ./setuid-test2; sudo chmod +s ./setuid-test2
> ./setuid-test2

1000

Wikipedia mengatakan :

Karena meningkatnya kemungkinan kelemahan keamanan, banyak sistem operasi mengabaikan atribut setuid ketika diterapkan pada skrip shell yang dapat dieksekusi.

Dengan asumsi saya bersedia menerima risiko itu, apakah ada cara untuk memberitahu Linux untuk memperlakukan setuid sedikit sama pada skrip shell seperti halnya pada executable?

Jika tidak, apakah ada solusi umum untuk masalah ini? Solusi saya saat ini adalah menambahkan sudoersentri untuk memungkinkan ALLmenjalankan skrip yang diberikan sebagai pengguna yang saya inginkan dijalankan, dengan NOPASSWDmenghindari prompt kata sandi. Kerugian utama untuk itu adalah kebutuhan untuk sudoersentri setiap kali saya ingin melakukan ini, dan kebutuhan untuk penelepon untuk sudo some-scriptbukan hanyasome-script

Michael Mrozek
sumber

Jawaban:

203

Linux mengabaikan bit setuid¹ pada semua executable yang ditafsirkan (yaitu executable yang dimulai dengan sebuah #!baris). The comp.unix.questions FAQ menjelaskan masalah keamanan dengan script shell setuid. Masalah-masalah ini ada dua jenis: terkait shebang dan terkait shell; Saya masuk ke detail lebih lanjut di bawah ini.

Jika Anda tidak peduli dengan keamanan dan ingin mengizinkan skrip setuid, di Linux, Anda harus menambal kernel. Pada kernel 3.x, saya pikir Anda perlu menambahkan panggilan ke install_exec_credsdalam load_scriptfungsi, sebelum panggilan ke open_exec, tapi saya belum menguji.


Setuid shebang

Ada kondisi ras yang melekat pada cara shebang ( #!) biasanya diterapkan:

  1. Kernel membuka executable, dan menemukan bahwa itu dimulai dengan #!.
  2. Kernel menutup executable dan membuka interpreter sebagai gantinya.
  3. Kernel menyisipkan path ke skrip ke daftar argumen (as argv[1]), dan mengeksekusi interpreter.

Jika skrip setuid diizinkan dengan implementasi ini, penyerang dapat memanggil skrip arbitrer dengan membuat tautan simbolis ke skrip setuid yang ada, menjalankannya, dan mengatur untuk mengubah tautan setelah kernel melakukan langkah 1 dan sebelum penerjemah berkeliling untuk membuka argumen pertamanya. Untuk alasan ini, sebagian besar unices mengabaikan bit setuid ketika mereka mendeteksi shebang.

Salah satu cara untuk mengamankan implementasi ini adalah bagi kernel untuk mengunci file skrip sampai interpreter membukanya (perhatikan bahwa ini harus mencegah tidak hanya memutus tautan atau menimpa file, tetapi juga mengganti nama direktori di path). Tetapi sistem unix cenderung menghindar dari kunci wajib, dan tautan simbolik akan membuat fitur kunci yang benar terutama sulit dan invasif. Saya tidak berpikir ada yang melakukannya dengan cara ini.

Beberapa sistem unix (terutama OpenBSD, NetBSD dan Mac OS X, yang semuanya memerlukan pengaturan kernel untuk diaktifkan) mengimplementasikan setuid shebang aman menggunakan fitur tambahan: path merujuk ke file yang sudah dibuka pada deskriptor file N (jadi pembukaannya adalah kira-kira setara dengan ). Banyak sistem unix (termasuk Linux) memiliki tetapi tidak memiliki skrip setuid./dev/fd/N/dev/fd/Ndup(N)/dev/fd

  1. Kernel membuka executable, dan menemukan bahwa itu dimulai dengan #!. Katakanlah file descriptor untuk executable adalah 3.
  2. Kernel membuka interpreter.
  3. Kernel memasukkan /dev/fd/3daftar argumen (as argv[1]), dan mengeksekusi penerjemah.

Halaman shebang Sven Mascheck memiliki banyak informasi tentang shebang di seluruh unit, termasuk dukungan setuid .


Penerjemah setuid

Mari kita asumsikan Anda telah berhasil membuat program Anda berjalan sebagai root, baik karena OS Anda mendukung setuid shebang atau karena Anda telah menggunakan pembungkus biner asli (seperti sudo). Sudahkah Anda membuka lubang keamanan? Mungkin . Masalahnya di sini bukan tentang program yang ditafsirkan vs dikompilasi. Masalahnya adalah apakah sistem runtime Anda berperilaku aman jika dijalankan dengan hak istimewa.

  • Setiap biner yang dapat dieksekusi secara dinamis terhubung dengan cara diinterpretasikan oleh loader dinamis (misalnya /lib/ld.so), yang memuat pustaka dinamis yang diperlukan oleh program. Pada banyak unices, Anda dapat mengonfigurasi jalur pencarian untuk pustaka dinamis melalui lingkungan ( LD_LIBRARY_PATHadalah nama umum untuk variabel lingkungan), dan bahkan memuat pustaka tambahan ke semua binari yang dijalankan ( LD_PRELOAD). Penyerang program dapat mengeksekusi kode arbitrer dalam konteks program tersebut dengan menempatkan keahlian khusus ( libc.sodi $LD_LIBRARY_PATHantara taktik lain). Semua sistem waras mengabaikan LD_*variabel dalam executable setuid.

  • Dalam shell seperti sh, csh dan turunannya, variabel lingkungan secara otomatis menjadi parameter shell. Melalui parameter seperti PATH, IFS, dan banyak lagi, Invoker script memiliki banyak kesempatan untuk mengeksekusi kode arbitrary dalam konteks skrip shell. Beberapa shell mengatur variabel-variabel ini ke default waras jika mereka mendeteksi bahwa script telah dipanggil dengan hak istimewa, tapi saya tidak tahu bahwa ada implementasi tertentu yang akan saya percayai.

  • Sebagian besar lingkungan runtime (apakah asli, bytecode atau ditafsirkan) memiliki fitur serupa. Beberapa mengambil tindakan pencegahan khusus dalam executable setuid, meskipun yang menjalankan kode asli sering tidak melakukan hal yang lebih baik daripada menghubungkan dinamis (yang memang mengambil tindakan pencegahan).

  • Perl adalah pengecualian penting. Ini secara eksplisit mendukung skrip setuid dengan cara yang aman. Bahkan, skrip Anda dapat menjalankan setuid bahkan jika OS Anda mengabaikan bit setuid pada skrip. Ini karena perl mengirim dengan setuid root helper yang melakukan pemeriksaan yang diperlukan dan menginstal ulang juru bahasa pada skrip yang diinginkan dengan hak istimewa yang diinginkan. Ini dijelaskan dalam manual perlsec . Dulu skrip perl setuid diperlukan #!/usr/bin/suidperl -wTalih-alih #!/usr/bin/perl -wT, tetapi pada sebagian besar sistem modern, #!/usr/bin/perl -wTsudah cukup.

Perhatikan bahwa menggunakan pembungkus biner asli tidak melakukan apa pun untuk mencegah masalah ini . Bahkan, ini dapat membuat situasi menjadi lebih buruk , karena dapat mencegah lingkungan runtime Anda mendeteksi bahwa ia dipanggil dengan hak istimewa dan memintas konfigurasi runtime-nya.

Pembungkus biner asli dapat membuat skrip shell aman jika pembungkus membersihkan lingkungan . Script harus berhati-hati untuk tidak membuat terlalu banyak asumsi (misalnya tentang direktori saat ini) tetapi ini berjalan. Anda dapat menggunakan sudo untuk ini asalkan sudah diatur untuk membersihkan lingkungan. Variabel daftar hitam rentan kesalahan, jadi selalu daftar putih. Dengan sudo, pastikan env_resetopsi dihidupkan, setenvdimatikan, dan itu env_filedan env_keephanya berisi variabel tidak berbahaya.


TL, DR:

  • Setuid shebang tidak aman tetapi biasanya diabaikan.
  • Jika Anda menjalankan program dengan hak istimewa (baik melalui sudo atau setuid), tulis kode asli atau perl, atau mulai program dengan pembungkus yang membersihkan lingkungan (seperti sudo dengan env_resetopsi).

¹ Diskusi ini berlaku sama jika Anda mengganti "setgid" dengan "setuid"; keduanya diabaikan oleh kernel Linux pada skrip

Gilles
sumber
2
@Josh: Amankan skrip shell setuid dimungkinkan, tetapi hanya jika baik shell implementer dan penulis skrip sangat berhati-hati. Daripada kode asli, saya sarankan Perl, di mana pelaksana telah berhati-hati bahwa skrip setuid harus aman dengan sedikit usaha pada bagian penulis skrip.
Gilles
3
rupanya suidperlbarang - barang itu telah ditinggalkan dan ditandai untuk dihapus selama bertahun-tahun (tetapi tetap tidak-kurang)
jmtd
7
Sebenarnya suidperltelah dihapus pada perl 5.11 (5.12 stable): perl5110delta:> "suidperl" telah dihapus. Ini digunakan untuk menyediakan mekanisme untuk meniru bit setuid permisi pada sistem yang tidak mendukungnya dengan benar. perl5120delta:> "suidperl" tidak lagi menjadi bagian dari Perl. Ini digunakan untuk menyediakan mekanisme untuk meniru bit setuid permisi pada sistem yang tidak mendukungnya dengan benar.
Randy Stauner
5
Perhatikan juga baris ini dari perl 5.6.1 docs (hampir satu dekade yang lalu) ... perl561delta:> Perhatikan bahwa suidperl tidak dibangun atau diinstal secara default dalam versi perl terbaru. Penggunaan suidperl sangat tidak dianjurkan . Jika Anda merasa memerlukannya, cobalah alternatif seperti sudo terlebih dahulu. Lihat courtesan.com/sudo .
Randy Stauner
3
Saya tidak mengerti: ini tampaknya merupakan penjelasan tentang alasan masalah, tetapi apakah ada jawaban aktual untuk pertanyaan OP di sini? Apakah ada cara untuk memberitahu OS saya untuk menjalankan skrip setuid shell?
Tom
55

Salah satu cara untuk memecahkan masalah ini adalah dengan memanggil skrip shell dari program yang dapat menggunakan bit setuid.
sesuatu seperti sudo. Sebagai contoh, berikut adalah bagaimana Anda akan mencapai ini dalam program C:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    setuid( 0 );   // you can set it at run time also
    system( "/home/pubuntu/setuid-test2.sh" );
    return 0;
 }

Simpan sebagai setuid-test2.c.
kompilasi
Sekarang lakukan setuid pada biner program ini:

su - nobody   
[enter password]  
chown nobody:nobody a.out  
chmod 4755 a.out  

Sekarang, Anda harus dapat menjalankannya, dan Anda akan melihat skrip Anda dieksekusi tanpa izin siapa pun.
Tapi di sini Anda juga perlu membuat hardcode pada jalur skrip atau meneruskannya sebagai baris perintah arg ke exe di atas.

Hemant
sumber
25
Saya akan menyarankan terhadap saran untuk memungkinkan lewat skrip sebagai argumen baris perintah, karena pada dasarnya memberikan siapa pun yang dapat menjalankan program kemampuan untuk menjalankan skrip apa pun sebagai pengguna yang ditetapkan.
dsp
40
Perhatikan bahwa INI ADALAH KEAMANAN bahkan jika path lengkap ke skrip di-hardcode. Shell akan mewarisi variabel dari lingkungan, dan banyak dari mereka memungkinkan penyerang untuk menyuntikkan kode arbitrer. PATHdan LD_LIBRARY_PATHmerupakan vektor yang jelas. Beberapa kerang mengeksekusi $ENVatau $BASHENVatau ~/.zshenvbahkan sebelum mereka mulai mengeksekusi script yang tepat, sehingga Anda tidak dapat melindungi dari ini sama sekali dari dalam script. Satu-satunya cara aman untuk menjalankan skrip shell dengan hak istimewa adalah dengan membersihkan lingkungan . Sudo tahu bagaimana melakukannya dengan aman. Jadi jangan menulis bungkus sendiri, gunakan sudo .
Gilles
28
Saya merasa sedih karena dia tiba-tiba dipecat karena hal ini - saya memang secara spesifik mengatakan saya ingin mendengar versi tidak aman juga, dan saya membayangkan seorang executable yang mengambil argumen skrip shell ketika saya mengatakannya. Jelas itu tidak aman secara besar-besaran, tetapi saya ingin tahu kemungkinan apa yang ada
Michael Mrozek
8
@Gilles: FYI, Linux tidak terhapus di LD_LIBRARY_PATHantara hal-hal lain ketika bertemu bit setuid.
grawity
3
Alih-alih menggunakan system, Anda mungkin merasa lebih mudah (dan lebih efisien) untuk menggunakan salah satu execkeluarga - kemungkinan besar execve. Dengan begitu, Anda tidak membuat proses baru atau memulai shell, dan Anda dapat meneruskan argumen (dengan asumsi bahwa skrip istimewa Anda dapat menangani argumen dengan aman).
Toby Speight
23

Saya awali beberapa skrip yang ada di kapal ini sebagai berikut:

#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"

Perhatikan bahwa ini tidak menggunakan setuidtetapi hanya menjalankan file saat ini dengan sudo.

rcrowley
sumber
5
Ini tidak menggunakan setuid tetapi hanya memberi Anda sudoprompt. (Bagi saya, seluruh titik setuid memungkinkan hal-hal berjalan sebagai root tanpa perlu sudo.)
Luc
12

Jika Anda ingin menghindari panggilan, sudo some_scriptAnda bisa melakukan:

  #!/ust/bin/env sh

  sudo /usr/local/scripts/your_script

Program SETUID perlu dirancang dengan sangat hati-hati karena dijalankan dengan hak akses root dan pengguna memiliki kontrol besar terhadapnya. Mereka perlu memeriksa semuanya. Anda tidak dapat melakukannya dengan skrip karena:

  • Shell adalah perangkat lunak berukuran besar yang sangat berinteraksi dengan pengguna. Hampir tidak mungkin untuk memeriksa semua kewarasan - terutama karena sebagian besar kode tidak dimaksudkan untuk berjalan dalam mode seperti itu.
  • Script sebagian besar merupakan solusi cepat dan biasanya tidak disiapkan dengan hati-hati sehingga mereka memungkinkan setuid. Mereka memiliki banyak fitur yang berpotensi berbahaya.
  • Mereka sangat bergantung pada program lain. Tidak cukup bahwa shell sudah diperiksa. sed,, awkdll. perlu diperiksa juga

Harap dicatat bahwa sudomenyediakan beberapa pemeriksaan kewarasan tetapi tidak cukup - periksa setiap baris dalam kode Anda sendiri.

Sebagai catatan terakhir: pertimbangkan untuk menggunakan kemampuan. Mereka memungkinkan Anda untuk memberikan proses yang berjalan sebagai hak istimewa khusus pengguna yang biasanya membutuhkan hak akses root. Namun misalnya, ketika pingperlu memanipulasi jaringan, ia tidak perlu memiliki akses ke file. Namun saya tidak yakin apakah itu diwariskan.

Maciej Piechotka
sumber
2
Saya kira /ust/bin/envseharusnya /usr/bin/env.
Bob
4

Anda dapat membuat alias untuk sudo + nama skrip. Tentu saja, itu lebih sulit untuk diatur, karena Anda kemudian harus mengatur alias juga, tetapi itu menyelamatkan Anda dari keharusan mengetikkan sudo.

Tetapi jika Anda tidak keberatan dengan risiko keamanan yang mengerikan, gunakan setuid shell sebagai penerjemah untuk skrip shell. Tidak tahu apakah itu akan berhasil untuk Anda, tapi saya kira itu mungkin.

Biarkan saya menyatakan bahwa saya menyarankan agar tidak melakukan ini. Saya hanya menyebutkannya untuk tujuan pendidikan ;-)

wzzrd
sumber
5
Itu akan berhasil. Mengerikan seperti yang Anda katakan. Bit SETUID memungkinkan eksekusi dengan pemilik yang benar. Shell setuid (kecuali jika dirancang untuk bekerja dengan setuid) akan berjalan sebagai root untuk setiap pengguna. Yaitu siapa pun dapat menjalankan rm -rf /(dan perintah lain dari seri JANGAN LAKUKAN DI RUMAH ).
Maciej Piechotka
9
@MaciejPiechotka oleh JANGAN MELAKUKANNYA DI RUMAH Maksud Anda Jangan ragu untuk melakukan itu di tempat kerja? :)
peterph
4

perintah super [-r reqpath] [args]

Super memungkinkan pengguna tertentu untuk mengeksekusi skrip (atau perintah lain) seolah-olah mereka root; atau dapat mengatur grup uid, gid, dan / atau tambahan berdasarkan per-perintah sebelum menjalankan perintah. Ini dimaksudkan sebagai alternatif yang aman untuk membuat skrip setuid root. Super juga memungkinkan pengguna biasa untuk memasok perintah untuk dieksekusi oleh orang lain; ini dijalankan dengan uid, gid, dan grup pengguna yang menawarkan perintah.

Super berkonsultasi file `` super.tab '' untuk melihat apakah pengguna diizinkan untuk menjalankan perintah yang diminta. Jika izin diberikan, super akan exec pgm [args], di mana pgm adalah program yang dikaitkan dengan perintah ini. (Root diizinkan untuk dieksekusi secara default, tetapi masih dapat ditolak jika aturan mengecualikan root. Pengguna biasa tidak diizinkan untuk mengeksekusi.)

Jika perintah adalah tautan simbolik (atau tautan keras juga) ke program super, maka mengetik% perintah args sama dengan mengetik% super command args (Perintah tidak boleh super, atau super tidak akan mengenali bahwa itu dipanggil melalui tautan.)

http://www.ucolick.org/~will/RUE/super/README

http://manpages.ubuntu.com/manpages/utopic/en/man1/super.1.html

Nizam Mohamed
sumber
Hai dan selamat datang di situs ini! Kami berharap jawaban lebih rinci di sini. Bisakah Anda mengedit jawaban Anda dan menjelaskan apa program ini dan bagaimana itu bisa membantu menyelesaikan masalah OP?
terdon
Terima kasih Nizam, selama berjam-jam mencoba menemukan sesuatu seperti super untuk akun yang diizinkan untuk dieksekusi oleh pengguna lain sebagai pengguna file.
Chad
2

Jika karena alasan tertentu sudotidak tersedia, Anda dapat menulis skrip pembungkus tipis di C:

#include <unistd.h>
int main() {
    setuid(0);
    execle("/bin/bash","bash","/full/path/to/script",(char*) NULL,(char*) NULL);
}

Dan sekali Anda compile mengaturnya sebagai setuiddengan chmod 4511 wrapper_script.

Ini mirip dengan jawaban lain yang diposting, tetapi menjalankan skrip dengan lingkungan yang bersih dan secara eksplisit menggunakan /bin/bashalih-alih shell yang dipanggil oleh system(), dan karenanya menutup beberapa lubang keamanan potensial.

Perhatikan bahwa ini sepenuhnya membuang lingkungan. Jika Anda ingin menggunakan beberapa variabel lingkungan tanpa membuka celah, Anda benar-benar hanya perlu menggunakannya sudo.

Jelas, Anda ingin memastikan skrip itu sendiri hanya dapat ditulis oleh root.

Chris
sumber
-3

Saya pertama kali menemukan pertanyaan ini, tidak yakin dengan semua jawaban, di sini ada yang jauh lebih baik, yang juga memungkinkan Anda untuk sepenuhnya mengaburkan skrip bash Anda, jika Anda memang cenderung!

Itu harus jelas.

#include <string>
#include <unistd.h>

template <typename T, typename U>
T &replace (
          T &str, 
    const U &from, 
    const U &to)
{
    size_t pos;
    size_t offset = 0;
    const size_t increment = to.size();

    while ((pos = str.find(from, offset)) != T::npos)
    {
        str.replace(pos, from.size(), to);
        offset = pos + increment;
    }

    return str;
}

int main(int argc, char* argv[])
{
    // Set UUID to root
    setuid(0);

    std::string script = 
R"(#!/bin/bash
whoami
echo $1
)";

    // Escape single quotes.
    replace(script, std::string("'"), std::string("'\"'\"'"));

    std::string command;
    command = command + "bash -c '" + script + "'"; 

    // Append the command line arguments.
    for (int a = 0; a < argc; ++a)
    {
        command = command + " " + argv[a];
    }

    return system(command.c_str());
}

Lalu kamu lari

g++ embedded.cpp -o embedded
sudo chown root embedded
sudo chmod u+s embedded
Theodore R. Smith
sumber
Mengapa downvotes ???
Theodore R. Smith
2
Mungkin karena ini adalah solusi yang terlalu rumit yang melakukan manipulasi string dan telah memasukkan kode shell. Baik Anda membuat peluncur sederhana, atau Anda menggunakan sudo. Ini adalah yang terburuk dari semua opsi.
siride