بسیاری از نفوذها به سرورهای لینوکس نه از یک آسیب‌پذیری پیچیده، بلکه از یک تنظیم دسترسی سهل‌انگارانه شروع می‌شوند: کاربر اپلیکیشنی که عضو گروه root است، فایل sudoers با NOPASSWD: ALL برای همهٔ کاربران، یا پوشهٔ وب با مجوز 777. مدل دسترسی لینوکس (کاربر، گروه، مجوزهای فایل و sudo) ساده اما دقیق است و اگر درست به کار گرفته شود، حتی پس از لو رفتن رمز یک کاربر، دامنهٔ خسارت را محدود می‌کند. در این مقاله اصول عملی مدیریت کاربران و گروه‌ها، تنظیم امن sudo، و الگوی «حداقل دسترسی» را برای سرورهای چندکاربره مرور می‌کنیم.

کاربران و گروه‌ها در لینوکس چگونه تعریف می‌شوند

هر کاربر با یک شناسهٔ عددی (UID) و هر گروه با GID شناخته می‌شود. اطلاعات کاربران در /etc/passwd، گروه‌ها در /etc/group و هش رمزها در /etc/shadow نگهداری می‌شود که فقط root حق خواندن آن را دارد. کاربر root همواره UID صفر دارد و هر کاربر دیگری با UID صفر، صرف‌نظر از نامش، root است.

کاربران سیستمی در برابر کاربران واقعی

به‌عنوان قاعده، UIDهای زیر 1000 به کاربران سیستمی (مثل www-data، mysql، nginx) تعلق دارد که سرویس‌ها با هویت آن‌ها اجرا می‌شوند و نباید shell ورود داشته باشند. کاربران واقعی از 1000 به بعد ساخته می‌شوند. دستور زیر کاربرانی را که shell قابل ورود دارند فهرست می‌کند:

awk -F: '$7 !~ /(nologin|false)$/ {print $1, $3, $7}' /etc/passwd
awk -F: '$3 == 0 {print $1}' /etc/passwd

خط دوم هر حسابی با UID صفر را نشان می‌دهد؛ خروجی باید فقط root باشد. وجود حساب دوم با UID صفر یکی از رایج‌ترین نشانه‌های دسترسی پنهان مهاجم است.

گروه اصلی و گروه‌های تکمیلی

هر کاربر یک گروه اصلی دارد که فایل‌های جدیدش به آن تعلق می‌گیرد، و می‌تواند عضو چند گروه تکمیلی باشد. در توزیع‌های دبیان‌محور گروه sudo و در خانوادهٔ RHEL گروه wheel دسترسی مدیریتی می‌دهد؛ عضویت در این گروه‌ها را با getent group sudo wheel مرتب بازبینی کنید.

ساخت، تغییر و غیرفعال کردن کاربران

دو ابزار برای ساخت کاربر وجود دارد: useradd که سطح پایین و در همهٔ توزیع‌ها یکسان است، و adduser که در دبیان و اوبونتو پوسته‌ای تعاملی روی آن است و پوشهٔ خانگی و رمز را هم می‌سازد. برای اسکریپت‌ها از useradd با گزینه‌های صریح استفاده کنید:

useradd -m -s /bin/bash -G developers ali
passwd ali
chage -M 90 -W 7 ali
usermod -aG docker ali

گزینهٔ -a در usermod -aG حیاتی است؛ بدون آن، کاربر از همهٔ گروه‌های قبلی خارج می‌شود و تنها عضو گروه جدید می‌ماند؛ اشتباهی که می‌تواند دسترسی sudo شما را از بین ببرد.

قفل کردن به‌جای حذف

وقتی همکاری تیم را ترک می‌کند، حساب را بلافاصله قفل کنید اما حذف نکنید: usermod -L -e 1 ali هم رمز را قفل می‌کند و هم تاریخ انقضا را به گذشته می‌برد تا ورود با کلید SSH هم مسدود شود. حذف کاربر با userdel -r فایل‌هایی را که مالکشان بود بی‌صاحب می‌کند و اگر UID بعداً به کاربر جدیدی داده شود، آن فایل‌ها به‌طور ناخواسته به او می‌رسند.

مجوزهای فایل و مدیریت پوشه‌های مشترک

هر فایل سه دسته مجوز برای مالک، گروه و سایرین دارد. جدول زیر مقادیر عددی رایج و کاربرد درست آن‌ها را در سرور وب نشان می‌دهد:

مقدارمعنیکاربرد مناسب
644مالک می‌نویسد، همه می‌خوانندفایل‌های PHP، HTML و CSS
755مالک می‌نویسد، همه اجرا و فهرست می‌کنندپوشه‌های وب و اسکریپت‌ها
600فقط مالک می‌خواند و می‌نویسد.env، کلیدهای خصوصی، wp-config.php
2775مثل 775 با بیت setgidپوشهٔ مشترک تیم
700فقط مالک به پوشه وارد می‌شود~/.ssh
777همه می‌نویسند و اجرا می‌کنندهرگز روی سرور

پوشهٔ مشترک با setgid و umask

وقتی چند توسعه‌دهنده روی یک پروژه کار می‌کنند، مشکل رایج این است که فایل جدید هر نفر با گروه اصلی خودش ساخته می‌شود و دیگران نمی‌توانند آن را ویرایش کنند. راه حل، بیت setgid روی پوشه است که باعث می‌شود همهٔ فایل‌های جدید گروه پوشه را به ارث ببرند:

groupadd developers
chown -R :developers /var/www/project
chmod -R g+rwX /var/www/project
find /var/www/project -type d -exec chmod g+s {} +

حرف بزرگ X در g+rwX مجوز اجرا را فقط به پوشه‌ها (و فایل‌هایی که از قبل اجرایی بودند) می‌دهد، نه به همهٔ فایل‌ها. برای اینکه فایل‌های تازه برای گروه قابل نوشتن باشند، umask 002 را در پروفایل کاربران تیم تنظیم کنید؛ umask پیش‌فرض 022 نوشتن گروه را حذف می‌کند.

پیکربندی امن sudo

sudo به کاربر اجازه می‌دهد دستور مشخصی را با هویت کاربر دیگر (معمولاً root) اجرا کند، در حالی که هر اجرا ثبت می‌شود و رمز root هرگز به اشتراک گذاشته نمی‌شود. فایل اصلی تنظیمات /etc/sudoers است، اما هرگز آن را مستقیم ویرایش نکنید؛ از visudo استفاده کنید که پیش از ذخیره نحو را بررسی می‌کند، چون یک خطای نحوی در این فایل همهٔ دسترسی‌های sudo را از کار می‌اندازد.

فایل‌های جداگانه در sudoers.d

به‌جای ویرایش فایل اصلی، برای هر نقش یک فایل در /etc/sudoers.d/ بسازید و با visudo -f /etc/sudoers.d/deployers ویرایش کنید. نمونه‌ای برای تیمی که فقط باید سرویس‌های وب را راه‌اندازی مجدد کند:

Cmnd_Alias WEBCTL = /usr/bin/systemctl restart nginx, \
    /usr/bin/systemctl restart php8.4-fpm, \
    /usr/bin/systemctl status nginx
%deployers ALL=(root) NOPASSWD: WEBCTL
Defaults:%deployers !lecture, timestamp_timeout=5

مسیر کامل دستورها الزامی است و دستورهایی که خودشان shell باز می‌کنند (vim، less، find با -exec، bash) نباید در فهرست باشند؛ کاربر از داخل آن‌ها به root کامل می‌رسد. با sudo -l هر کاربر می‌تواند ببیند دقیقاً چه چیزی به او داده شده است.

NOPASSWD: کجا قابل قبول است

حذف رمز برای مجموعهٔ محدودی از دستورهای بی‌خطر (مثل راه‌اندازی مجدد یک سرویس توسط اسکریپت استقرار) معقول است، اما NOPASSWD: ALL به این معناست که لو رفتن کلید SSH آن کاربر معادل لو رفتن root است. برای کاربران انسانی رمز را نگه دارید و timestamp_timeout را کوتاه کنید تا جلسهٔ تأییدشده پس از چند دقیقه منقضی شود.

اصل حداقل دسترسی برای سرویس‌ها و سایت‌ها

همان منطق برای پروسه‌ها هم صدق می‌کند. اگر همهٔ سایت‌های یک سرور با کاربر www-data اجرا شوند، یک افزونهٔ آسیب‌پذیر وردپرس در سایت اول می‌تواند فایل‌های سایت دوم را بخواند و تغییر دهد. راه حل، یک کاربر سیستمی جداگانه برای هر سایت و یک pool مجزای PHP-FPM است:

useradd -r -s /usr/sbin/nologin -d /var/www/shop shop
# /etc/php/8.4/fpm/pool.d/shop.conf
[shop]
user = shop
group = shop
listen = /run/php/shop.sock
listen.owner = www-data

کاربر وب‌سرور (www-data) فقط باید به فایل‌های استاتیک دسترسی خواندن داشته باشد و مالک هیچ فایل PHP نباشد. برای سرویس‌هایی که با systemd اجرا می‌شوند، گزینه‌های User=، ProtectSystem=strict و NoNewPrivileges=yes در فایل unit همین اصل را بدون نیاز به تغییر کد اعمال می‌کنند. در هاست اشتراکی این ایزوله‌سازی وظیفهٔ ارائه‌دهنده است؛ برای نمونه در هاست لینوکس با CloudLinux، هر اکانت در CageFS جدا از دیگران اجرا می‌شود.

ممیزی دسترسی‌ها و لاگ‌های sudo

هر اجرای sudo، موفق یا ناموفق، با نام کاربر، دستور و پوشهٔ کاری در لاگ احراز هویت ثبت می‌شود. روی سیستم‌های مبتنی بر journald می‌توان آن‌ها را مستقیم فیلتر کرد:

journalctl _COMM=sudo --since today
grep 'COMMAND' /var/log/auth.log | tail -20
lastlog | grep -v 'Never'

خط سوم آخرین ورود هر کاربر را نشان می‌دهد. یک بازبینی ماهانه با سه پرسش کافی است: چه کسانی در گروه sudo یا wheel هستند؟ کدام فایل‌های sudoers.d قاعدهٔ NOPASSWD دارند؟ کدام حساب‌ها بیش از ۹۰ روز وارد نشده‌اند؟

اشتباهات رایج و ترتیب اصلاح آن‌ها

  1. کار روزمره با root: حتی روی سرور شخصی، کاربر عادی با sudo بسازید تا دستورهای اشتباه (مثل rm -rf در مسیر غلط) پیش از اجرا یک مانع داشته باشند.
  2. chmod 777 برای رفع خطای Permission denied: به‌جای آن مالک درست را با chown تعیین کنید؛ 777 به هر پروسهٔ سرور اجازهٔ نوشتن می‌دهد.
  3. ویرایش مستقیم /etc/sudoers: همواره از visudo استفاده کنید و پیش از بستن نشست، در ترمینال دوم sudo را تست کنید.
  4. اشتراک یک حساب بین چند نفر: لاگ‌ها بی‌معنا می‌شوند و با رفتن یک نفر باید رمز برای همه عوض شود.
  5. عضویت در گروه docker بدون آگاهی: هر عضو گروه docker عملاً root است، چون می‌تواند پوشهٔ ریشه را داخل کانتینر mount کند.

جمع‌بندی

مدیریت کاربران و گروه‌ها و پیکربندی sudo، لایهٔ اول امنیت هر سرور لینوکس است و برخلاف فایروال یا WAF، هزینه‌ای جز دقت ندارد. کاربر جداگانه برای هر نفر و هر سرویس، گروه‌های setgid برای کار تیمی، فایل‌های sudoers.d با فهرست دستورهای مشخص، و بازبینی ماهانهٔ لاگ‌ها، دامنهٔ خسارت هر نفوذ احتمالی را به یک حساب محدود می‌کند. قاعدهٔ راهنما ساده است: هر هویت فقط به چیزی دسترسی داشته باشد که برای کارش لازم است.

سوالات متداول

تفاوت sudo -i و sudo su چیست؟

sudo -i یک shell ورود کامل با محیط root (پروفایل، PATH و پوشهٔ خانگی root) باز می‌کند و در لاگ به‌صورت یک فرمان مشخص ثبت می‌شود. sudo su ابتدا sudo و سپس su را اجرا می‌کند و محیط را ناقص منتقل می‌کند. برای کار مدیریتی طولانی sudo -i روش درست است.

چرا بعد از افزودن کاربر به گروه، دسترسی جدید کار نمی‌کند؟

عضویت گروه در لحظهٔ ورود خوانده می‌شود. کاربر باید یک بار خارج و دوباره وارد شود یا با newgrp developers یک shell جدید با گروه تازه باز کند.

آیا باید کاربر root را کاملاً غیرفعال کنم؟

ورود مستقیم root از طریق SSH را ببندید، اما حساب را قفل نکنید؛ در حالت recovery یا کنسول ارائه‌دهنده به آن نیاز خواهید داشت. در اوبونتو رمز root به‌طور پیش‌فرض تنظیم نشده و دسترسی فقط از طریق sudo است که ترکیب مناسبی است.

sudo از من رمز کدام کاربر را می‌خواهد؟

به‌طور پیش‌فرض رمز خود کاربر فراخواننده را می‌خواهد، نه رمز root. این رفتار با Defaults targetpw یا rootpw قابل تغییر است، اما پیشنهاد نمی‌شود چون به اشتراک گذاشتن رمز root را دوباره ضروری می‌کند.

چگونه بفهمم کدام فایل‌ها بیت setuid دارند؟

دستور find / -perm -4000 -type f 2>/dev/null همهٔ باینری‌های setuid را فهرست می‌کند. موارد استاندارد مثل passwd، sudo و mount طبیعی هستند؛ هر فایل ناشناخته در این فهرست، به‌ویژه در پوشه‌های وب یا /tmp، نشانهٔ احتمالی نفوذ است و باید بررسی شود.