17-bo‘lim

Foydalanuvchilar, huquqlar va xavfsizlik

UID va GID, haqiqiy va amaldagi identifikator, setuid dasturlar, imkoniyatlar (capabilities), sudo, SELinux va AppArmor.

🕑 26 daqiqa o‘qish 📄 1 101 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Kim kim?
  2. Uch xil UID
  3. setuid dasturlar
  4. Huquqni tushirish
  5. Imkoniyatlar (capabilities)
  6. Muhit o'zgaruvchilari xavfi
  7. Parol saqlanishi
  8. Majburiy kirish nazorati
  9. Xavfsiz dastur yozish qoidalari
  10. Xulosa

Operatsion tizim uchun foydalanuvchi - bu son. Barcha tekshiruvlar shu sonni taqqoslashga borib taqaladi.

Kim kim? #

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

int main(void) {
    uid_t haqiqiy = getuid();
    uid_t amaldagi = geteuid();
    gid_t guruh = getgid();
    gid_t amaldagi_guruh = getegid();

    printf("Haqiqiy UID (real)      : %s\n",
           haqiqiy == 0 ? "0 - root" : "0 emas - oddiy foydalanuvchi");
    printf("Haqiqiy va amaldagi teng? %s\n",
           haqiqiy == amaldagi ? "ha" : "yo'q - setuid dastur");
    printf("Guruh va amaldagi teng?  %s\n",
           guruh == amaldagi_guruh ? "ha" : "yo'q");

    /* Qo'shimcha guruhlar */
    gid_t guruhlar[64];
    int soni = getgroups(64, guruhlar);

    printf("Qo'shimcha guruhlar bormi? %s\n", soni > 0 ? "ha" : "yo'q");

    return 0;
}
Natija
Haqiqiy UID (real)      : 0 emas - oddiy foydalanuvchi
Haqiqiy va amaldagi teng? ha
Guruh va amaldagi teng?  ha
Qo'shimcha guruhlar bormi? ha
Terminal
id
Natija
uid=1000(husanboy) gid=1000(husanboy) groups=1000(husanboy),27(sudo),100(users)

Uch xil UID #

Har bir jarayonda uchta UID Haqiqiy (real) UID Dasturni kim ishga tushirdi Signal yuborishda tekshiriladi getuid() Amaldagi (effective) UID Huquqlar SHU bo'yicha tekshiriladi Eng muhimi geteuid() Saqlangan (saved) UID Vaqtincha tushirilgan huquqni tiklash uchun zaxira nusxa getresuid() Oddiy dasturda uchalasi bir xil Farq faqat setuid dasturlarda paydo bo'ladi Masalan: passwd, sudo, ping
Bu ajratish huquqlarni vaqtincha tushirish imkonini beradi
C
/* getresuid POSIX emas - GNU kengaytmasi */
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>

int main(void) {
    uid_t haqiqiy, amaldagi, saqlangan;

    getresuid(&haqiqiy, &amaldagi, &saqlangan);

    printf("Uchala UID teng bo'lishi kerak (oddiy dastur):\n");
    printf("  Haqiqiy = amaldagi ? %s\n", haqiqiy == amaldagi ? "ha" : "yo'q");
    printf("  Amaldagi = saqlangan ? %s\n",
           amaldagi == saqlangan ? "ha" : "yo'q");

    gid_t hg, ag, sg;
    getresgid(&hg, &ag, &sg);

    printf("\nGuruhlar uchun ham shunday:\n");
    printf("  Uchalasi teng ? %s\n",
           hg == ag && ag == sg ? "ha" : "yo'q");

    return 0;
}
Natija
Uchala UID teng bo'lishi kerak (oddiy dastur):
  Haqiqiy = amaldagi ? ha
  Amaldagi = saqlangan ? ha

Guruhlar uchun ham shunday:
  Uchalasi teng ? ha

setuid dasturlar #

Terminal
ls -l /usr/bin/passwd /usr/bin/sudo
Natija
-rwsr-xr-x 1 root root  68208 may 30  2024 /usr/bin/passwd
-rwsr-xr-x 1 root root 277936 apr  4  2024 /usr/bin/sudo

Egasining x o'rnida s turibdi - bu setuid biti.

Terminal
find /usr/bin -perm -4000 -type f 2>/dev/null | head
Natija
/usr/bin/passwd
/usr/bin/sudo
/usr/bin/su
/usr/bin/chsh
/usr/bin/newgrp
/usr/bin/gpasswd
setuid - eng xavfli mexanizm

setuid biti qo'yilgan dastur egasining huquqlari bilan ishlaydi. passwd root egaligida bo'lgani uchun oddiy foydalanuvchi ham /etc/shadow ni o'zgartira oladi.

Muammo: dasturda bitta xato bo'lsa, hujumchi root oladi.

Xavfli joylar:

XavfNima bo'ladi
Bufer to'lib ketishiIxtiyoriy kod root sifatida
system() chaqiruviPATH orqali hujum
Muhit o'zgaruvchilariLD_PRELOAD, IFS bilan hujum
Fayl yo'llarida TOCTOURamziy havola bilan almashtirish

Shuning uchun zamonaviy Linux imkoniyatlar (capabilities) ga o'tmoqda - keyingi bo'limchada.

O'z dasturingizga setuid qo'yish kerak bo'lsa, avval o'ylab ko'ring: haqiqatan boshqa yo'l yo'qmi? Ko'p hollarda alohida xizmat va soket orqali aloqa ancha xavfsiz yechim.

Huquqni tushirish #

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

int main(void) {
    uid_t boshlangich = geteuid();

    printf("Boshlang'ich amaldagi UID root mi? %s\n",
           boshlangich == 0 ? "ha" : "yo'q");

    /* root emasmiz - root bo'lishga urinish rad etiladi */
    errno = 0;
    int natija = setuid(0);

    if (boshlangich != 0) {
        printf("Root emasmiz, setuid(0) natijasi: %s\n",
               natija == 0 ? "o'tdi" : "rad etildi");
        printf("Bu kutilgan holat - oddiy foydalanuvchi root bo'la olmaydi\n");
    }

    printf("\nTo'g'ri tartib (root uchun):\n");
    printf("  1. setgroups(0, NULL)  - qo'shimcha guruhlarni tashlash\n");
    printf("  2. setgid(yangi_gid)   - avval guruh\n");
    printf("  3. setuid(yangi_uid)   - keyin foydalanuvchi\n");
    printf("  4. Natijani TEKSHIRISH - setuid xato qaytarishi mumkin\n");

    return 0;
}
Natija
Boshlang'ich amaldagi UID root mi? yo'q
Root emasmiz, setuid(0) natijasi: rad etildi
Bu kutilgan holat - oddiy foydalanuvchi root bo'la olmaydi

To'g'ri tartib (root uchun):
  1. setgroups(0, NULL)  - qo'shimcha guruhlarni tashlash
  2. setgid(yangi_gid)   - avval guruh
  3. setuid(yangi_uid)   - keyin foydalanuvchi
  4. Natijani TEKSHIRISH - setuid xato qaytarishi mumkin
Huquq tushirishdagi klassik xatolar

1-xato: tartibni chalkashtirish

C
setuid(1000);          /* endi root emassiz */
setgid(1000);          /* BU ISHLAMAYDI - huquq yo'q */

setgid ni avval chaqiring, chunki setuid dan keyin uni o'zgartirish huquqi yo'qoladi.

2-xato: qo'shimcha guruhlarni unutish

C
setgid(1000);
setuid(1000);
/* lekin jarayon hali "sudo" guruhida! */

setgroups(0, NULL) ni birinchi chaqiring.

3-xato: natijani tekshirmaslik

C
setuid(1000);          /* xato qaytarsa-chi? */
xavfli_ishni_bajarish();

Agar setuid muvaffaqiyatsiz bo'lsa (masalan RLIMIT_NPROC chegarasi sabab), dastur root sifatida davom etadi.

To'g'ri:

C
if (setgroups(0, NULL) != 0) { _exit(1); }
if (setgid(gid) != 0)        { _exit(1); }
if (setuid(uid) != 0)        { _exit(1); }
if (setuid(0) == 0)          { _exit(1); }   /* qayta ko'tarilmasin */

Oxirgi qator muhim: setuid(0) muvaffaqiyat qaytarsa, demak huquq to'liq tushmagan.

Imkoniyatlar (capabilities) #

Linux root huquqlarini ~40 ta mustaqil imkoniyatga ajratgan:

ImkoniyatNimaga ruxsat
CAP_NET_BIND_SERVICE1024 dan kichik portni ochish
CAP_NET_RAWXom soket (ping uchun)
CAP_CHOWNFayl egasini o'zgartirish
CAP_KILLBoshqa foydalanuvchi jarayoniga signal
CAP_SYS_TIMETizim vaqtini o'zgartirish
CAP_DAC_OVERRIDEHuquq tekshiruvini chetlab o'tish
CAP_SYS_ADMINJuda ko'p narsa - "yangi root"
Terminal
getcap /usr/bin/ping
Natija
/usr/bin/ping cap_net_raw=ep
Terminal
sudo setcap cap_net_bind_service=+ep /usr/local/bin/mening_serverim
Terminal
grep Cap /proc/self/status
Natija
CapInh:    0000000000000000
CapPrm:    0000000000000000
CapEff:    0000000000000000
CapBnd:    000001ffffffffff
CapAmb:    0000000000000000
Terminal
capsh --decode=000001ffffffffff | head -3
Natija
0x000001ffffffffff=cap_chown,cap_dac_override,cap_dac_read_search,
cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,
cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,...
setuid o'rniga capabilities

Ilgari ping setuid root edi - unga xom soket kerak. Bu esa ping dagi har qanday xato to'liq root berishini anglatardi.

Hozir:

Terminal
getcap /usr/bin/ping
Natija
/usr/bin/ping cap_net_raw=ep

Endi ping buzilsa ham hujumchi faqat xom soket oladi - fayl o'chira olmaydi, foydalanuvchi qo'sha olmaydi.

Bu eng kam imtiyoz (least privilege) prinsipining amaliy qo'llanishi.

O'z serveringiz 80-portni ochishi kerak bo'lsa, uni root ostida ishlatish shart emas:

Terminal
sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/server

Yoki systemd orqali:

INI
[Service]
User=www-data
AmbientCapabilities=CAP_NET_BIND_SERVICE

CAP_SYS_ADMIN esa istisno - u shunchalik keng qamrovliki, uni berish deyarli root berish bilan teng.

Muhit o'zgaruvchilari xavfi #

C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
    /* Xavfli: PATH ga tayanish */
    setenv("PATH", "/tmp/yolg'on:/usr/bin", 1);

    const char *yol = getenv("PATH");

    printf("PATH qiymati: %s\n", yol);
    printf("Birinchi katalog /tmp da mi? %s\n",
           strncmp(yol, "/tmp", 4) == 0 ? "ha - XAVFLI" : "yo'q");

    printf("\nAgar dastur system(\"ls\") chaqirsa:\n");
    printf("  /tmp/yolg'on/ls topilsa - o'sha bajariladi\n");
    printf("  setuid dasturda bu to'liq root demakdir\n");

    printf("\nYechim: to'liq yo'l ishlatish\n");
    printf("  execv(\"/bin/ls\", argv) - PATH ishlatilmaydi\n");

    return 0;
}
Natija
PATH qiymati: /tmp/yolg'on:/usr/bin
Birinchi katalog /tmp da mi? ha - XAVFLI

Agar dastur system("ls") chaqirsa:
  /tmp/yolg'on/ls topilsa - o'sha bajariladi
  setuid dasturda bu to'liq root demakdir

Yechim: to'liq yo'l ishlatish
  execv("/bin/ls", argv) - PATH ishlatilmaydi
setuid dasturda system() ishlatmang

system("ls") ichida /bin/sh -c "ls" bajariladi. Qobiq esa:

  • PATH ni o'qiydi;
  • IFS (maydon ajratuvchi) ni o'qiydi;
  • ENV, BASH_ENV orqali skript yuklashi mumkin.

Hujumchi bularning istalganini o'zgartirib, o'z kodini root sifatida ishga tushira oladi.

Qoida:

YomonYaxshi
system("ls /tmp")execv("/bin/ls", argv)
popen("grep ...")pipe + fork + execv
execvp("ls", argv)execv("/bin/ls", argv)

Yadro setuid dasturlarda LD_PRELOAD va LD_LIBRARY_PATH ni avtomatik tozalaydi, lekin PATH ni tozalamaydi - bu sizning mas'uliyatingiz.

Eng ishonchli yondashuv - imtiyozli dasturda butun muhitni qayta qurish:

C
extern char **environ;
environ = NULL;
setenv("PATH", "/usr/bin:/bin", 1);

Parol saqlanishi #

Terminal
sudo cat /etc/shadow | head -2
Natija
root:$6$xyzABC$1kLm...:19845:0:99999:7:::
husanboy:$y$j9T$Qs8p...:19902:0:99999:7:::
PrefiksAlgoritm
$1$MD5 - ishlatmang
$5$SHA-256
$6$SHA-512
$y$yescrypt - zamonaviy odatiy
$2b$bcrypt
Terminal
ls -l /etc/passwd /etc/shadow
Natija
-rw-r--r-- 1 root root  2841 sen  1 09:12 /etc/passwd
-rw-r----- 1 root shadow 1524 sen  1 09:12 /etc/shadow
Nima uchun ikki fayl?

/etc/passwd hamma o'qiy oladi - dasturlar UID dan nom topishi kerak (ls -l shuni qiladi).

Ilgari parol xeshlari ham shu yerda edi. Hujumchi faylni o'qib, xeshlarni oflayn buzardi.

Yechim: xeshlarni /etc/shadow ga ko'chirish - u faqat root va shadow guruhiga o'qiladi.

Muhim tamoyillar:

  1. Parol hech qachon ochiq saqlanmaydi - faqat xeshi;
  2. Har bir xeshda tuz (salt) bor - bir xil parollar turli xesh beradi;
  3. Algoritm ataylab sekin - bcrypt, yescrypt minglab iteratsiya bajaradi.

Uchinchi nuqta muhim: SHA-256 o'zi juda tez - GPU sekundiga milliardlab xesh hisoblaydi. Parol uchun maxsus sekin algoritm kerak.

Veb-ilovangizda ham xuddi shu qoida: md5(parol) yoki sha256(parol) hech qachon yaramaydi. bcrypt, argon2 yoki scrypt ishlating.

Majburiy kirish nazorati #

MexanizmQayerdaG'oyasi
DACHamma joydaFayl egasi huquq beradi
SELinuxRHEL, FedoraHar obyektga yorliq, siyosat qat'iy
AppArmorUbuntu, SUSEDastur yo'li bo'yicha profil
Terminal
getenforce
Natija
Enforcing
Terminal
sudo aa-status | head -5
Natija
apparmor module is loaded.
42 profiles are loaded.
30 profiles are in enforce mode.
   /usr/bin/man
   /usr/sbin/cupsd
Terminal
ls -Z /etc/passwd
Natija
system_u:object_r:passwd_file_t:s0 /etc/passwd
DAC va MAC farqi

DAC (ixtiyoriy nazorat): fayl egasi o'zi hal qiladi:

Terminal
chmod 777 maxfiy.txt      # egasi shunday qila oladi

MAC (majburiy nazorat): siyosatni administrator belgilaydi, egasi uni buza olmaydi.

Amaliy misol: nginx buzilgan bo'lsin. DAC bo'yicha u www-data huquqlari bilan istalgan o'qiladigan faylni ko'ra oladi - shu jumladan boshqa saytlarning ma'lumotlarini.

AppArmor profili bilan esa:

Natija
/usr/sbin/nginx {
  /var/www/** r,
  /var/log/nginx/*.log w,
  # boshqa hech nima
}

Endi buzilgan nginx /home ni ham, /etc/shadow ni ham o'qiy olmaydi - hatto huquqlar ruxsat bersa ham.

Bu "chuqurlikda himoya" (defense in depth) prinsipi: bir qatlam buzilsa, ikkinchisi ushlab qoladi.

Xavfsiz dastur yozish qoidalari #

Amaliy tekshiruv ro'yxati
QoidaSabab
Eng kam imtiyoz bilan ishlangBuzilsa zarar kam
Huquqni imkon qadar erta tushiringXato oynasi qisqaradi
Barcha tizim chaqiruvi natijasini tekshiringSokin xato eng xavflisi
system/popen o'rniga execvQobiq hujumlari yo'qoladi
Kirish ma'lumotini uzunligini tekshiringBufer to'lishi
Vaqtinchalik faylga mkstempTOCTOU
Fayl ochishda O_NOFOLLOWRamziy havola hujumi
umask ga tayanmangRejimni aniq bering
Xato xabarida ichki yo'llarni ko'rsatmangMa'lumot sizishi
Kutubxonalarni yangilab turingMa'lum zaifliklar

Kodni tekshirish vositalari:

Terminal
gcc -Wall -Wextra -Wformat-security -fstack-protector-strong -D_FORTIFY_SOURCE=2
Terminal
valgrind --leak-check=full ./dastur
Terminal
gcc -fsanitize=address,undefined -g dastur.c -o dastur
C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void) {
    /* Xavfsiz vaqtinchalik fayl */
    char shablon[] = "/tmp/xavfsiz_XXXXXX";

    int fd = mkstemp(shablon);

    if (fd < 0) {
        perror("mkstemp");
        return 1;
    }

    printf("Vaqtinchalik fayl yaratildi\n");
    printf("Nomi tasodifiymi? %s\n",
           strcmp(shablon, "/tmp/xavfsiz_XXXXXX") != 0 ? "ha" : "yo'q");

    write(fd, "maxfiy", 6);
    close(fd);

    /* Darhol o'chiramiz - fayl deskriptori orqali ishlash mumkin edi */
    unlink(shablon);

    printf("Fayl o'chirildi   : ha\n");
    printf("\nNima uchun mkstemp:\n");
    printf("  tmpnam/tempnam nomni qaytaradi, faylni yaratmaydi\n");
    printf("  Oradagi vaqtda hujumchi o'sha nomda havola yaratadi\n");
    printf("  mkstemp esa nomni tanlash va yaratishni birga bajaradi\n");

    return 0;
}
Natija
Vaqtinchalik fayl yaratildi
Nomi tasodifiymi? ha
Fayl o'chirildi   : ha

Nima uchun mkstemp:
  tmpnam/tempnam nomni qaytaradi, faylni yaratmaydi
  Oradagi vaqtda hujumchi o'sha nomda havola yaratadi
  mkstemp esa nomni tanlash va yaratishni birga bajaradi
Amaliy topshiriq
  1. getuid va geteuid qiymatlarini chiqaring va farqini ayting.
  2. id buyrug'i chiqishini tahlil qiling.
  3. find /usr/bin -perm -4000 bilan setuid dasturlarni toping.
  4. getresuid bilan uchala UID ni ko'ring.
  5. Huquq tushirishning to'g'ri tartibini yozing.
  6. getcap /usr/bin/ping natijasini tushuntiring.
  7. setuid va capabilities yondashuvini solishtiring.
  8. system() ning PATH orqali qanday hujum qilinishini tushuntiring.
  9. /etc/passwd va /etc/shadow huquqlarini taqqoslang.
  10. mkstemp bilan xavfsiz vaqtinchalik fayl yarating.

Xulosa #

  • Foydalanuvchi OT uchun - son (UID), nom faqat qulaylik uchun.
  • Har jarayonda uchta UID: haqiqiy, amaldagi, saqlangan.
  • Huquqlar amaldagi UID bo'yicha tekshiriladi.
  • setuid dastur egasining huquqlari bilan ishlaydi - juda xavfli.
  • Huquq tushirish tartibi: setgroups → setgid → setuid.
  • setuid natijasini albatta tekshiring.
  • Imkoniyatlar root huquqini mayda bo'laklarga ajratadi.
  • setuid o'rniga setcap ishlating - eng kam imtiyoz.
  • setuid dasturda system/popen ishlatmang.
  • Parollar tuzlangan va sekin algoritm bilan xeshlanadi.
  • SELinux/AppArmor egasining xohishidan qat'i nazar cheklaydi.
  • Vaqtinchalik fayl uchun mkstemp - TOCTOU dan himoya.

Keyingi bo'limda tizimning yuklanish jarayonini ko'ramiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.