امن‌سازی SSH در سرور لینوکس: تغییر پورت، بستن ورود root و محدود کردن کاربران

امن‌سازی SSH در سرور لینوکس: تغییر پورت، بستن ورود root و محدود کردن کاربران

حدود ۱۴ دقیقه مطالعه

یک سرور تازه که به اینترنت وصل می‌شود، ظرف چند ساعت شروع می‌کند به دریافت تلاش‌های ورود ناموفق روی پورت ۲۲. کافی است یک بار lastb یا لاگ sshd را ببینید تا صف بلند تلاش‌ها برای ورود با کاربر root و پسوردهای حدسی را مشاهده کنید. این ترافیک هدفمند نیست؛ ربات‌ها کل رنج‌های IP را اسکن می‌کنند و هر پورت ۲۲ بازی را امتحان می‌کنند. کاری که باید بکنید هم پیچیده نیست: چند خط در sshd_config عوض می‌شود و بخش بزرگی از این سطح حمله بسته می‌شود.

این راهنما مسیر کامل امن‌سازی SSH را به همان ترتیبی که در عمل باید انجام دهید می‌آورد: اول کاربر و کلید را آماده می‌کنیم، بعد سراغ بستن درها می‌رویم. ترتیب مهم است، چون اگر برعکس عمل کنید احتمال زیادی هست که خودتان را از سرور بیرون بیندازید.

قدم صفر: سرور را به‌روز کنید

پیش از هر تغییری بسته‌ها را به‌روز کنید تا خود openssh-server آخرین وصله‌های امنیتی را داشته باشد:

# AlmaLinux / Rocky Linux / RHEL 8 و بالاتر
dnf update -y

# دبیان / اوبونتو
apt update && apt upgrade -y

دستور yum update -y که در راهنماهای قدیمی می‌بینید مربوط به نسل CentOS 6 و ۷ است. CentOS 7 در تیر ۱۴۰۳ (ژوئن ۲۰۲۴) به پایان پشتیبانی رسید و دیگر وصله‌ی امنیتی نمی‌گیرد؛ اگر هنوز روی آن هستید، مهاجرت به AlmaLinux یا Rocky Linux مهم‌تر از هر تنظیمی در این مقاله است. روی نسخه‌های ۸ به بعد yum فقط یک نام مستعار برای dnf است و همان کار را می‌کند.

قدم اول: یک کاربر غیر root با دسترسی sudo بسازید

وقتی ورود مستقیم root را ببندید، باید از پیش راهی برای رسیدن به دسترسی روت داشته باشید. پس این کار را قبل از تغییر PermitRootLogin انجام دهید.

اول یک گروه بسازید تا بعداً بتوانید دسترسی SSH را فقط به اعضای همین گروه بدهید:

groupadd developers

حالا کاربر جدید را بسازید و همان موقع عضو گروه کنید:

useradd -m -G developers newuser
passwd newuser

سوئیچ -m پوشه‌ی خانگی کاربر را می‌سازد؛ روی دبیان و اوبونتو بدون آن پوشه‌ی خانگی ساخته نمی‌شود و بعداً برای گذاشتن کلید عمومی به مشکل می‌خورید (روی خانواده‌ی ردهت ساخت پوشه‌ی خانگی پیش‌فرض است، ولی نوشتن -m ضرری ندارد). دستور passwd را هم فراموش نکنید: کاربری که تازه ساخته شده پسورد ندارد و تا وقتی پسورد نگذارید نه می‌تواند با پسورد وارد شود و نه می‌تواند sudo بزند، چون sudo پسورد خودِ کاربر را می‌پرسد.

اگر کاربر از قبل وجود دارد و فقط می‌خواهید به گروه اضافه‌اش کنید:

usermod -aG developers newuser

حرف a در -aG حیاتی است. اگر آن را جا بیندازید و فقط -G بنویسید، کاربر از تمام گروه‌های فعلی‌اش حذف و فقط عضو گروه جدید می‌شود.

در آخر به کاربر اجازه‌ی sudo بدهید:

# AlmaLinux / Rocky / RHEL
usermod -aG wheel newuser

# دبیان / اوبونتو
usermod -aG sudo newuser

عضویت گروه فقط در نشست بعدی اعمال می‌شود؛ اگر کاربر همین حالا لاگین است باید یک بار خارج و دوباره وارد شود. حالا از یک ترمینال دوم با همین کاربر وارد شوید و sudo -i را امتحان کنید. تا وقتی این تست جواب نداده، سراغ قدم‌های بعدی نروید.

قدم دوم: ورود با کلید را راه‌اندازی کنید

پسورد را می‌شود حدس زد؛ کلید را نه. این مهم‌ترین کاری است که برای امنیت SSH انجام می‌دهید و بقیه‌ی تنظیمات در مقایسه با آن فرعی‌اند.

روی کامپیوتر خودتان (نه روی سرور) یک جفت کلید بسازید:

ssh-keygen -t ed25519 -C "admin@onlineserver"

الگوریتم ed25519 امروز انتخاب پیش‌فرض است؛ کلیدش کوتاه‌تر و سریع‌تر از RSA است و امنیت بسیار خوبی می‌دهد. اگر با کلاینت یا سروری سر و کار دارید که آن‌قدر قدیمی است که از آن پشتیبانی نمی‌کند، از ssh-keygen -t rsa -b 4096 استفاده کنید. موقع ساخت کلید حتماً یک passphrase بگذارید تا اگر فایل کلید خصوصی از لپ‌تاپتان بیرون رفت، بلافاصله قابل استفاده نباشد.

بعد کلید عمومی را روی سرور بگذارید:

ssh-copy-id newuser@SERVER_IP

اگر پورت SSH را از قبل عوض کرده‌اید، با سوئیچ -p پورت را بدهید: ssh-copy-id -p 1423 newuser@SERVER_IP. اگر ssh-copy-id در دسترس نبود، همین کار را دستی انجام دهید؛ محتوای فایل id_ed25519.pub را در سرور در فایل زیر بچسبانید:

mkdir -p /home/newuser/.ssh
nano /home/newuser/.ssh/authorized_keys
chmod 700 /home/newuser/.ssh
chmod 600 /home/newuser/.ssh/authorized_keys
chown -R newuser:newuser /home/newuser/.ssh

پرمیژن‌ها را جدی بگیرید. گزینه‌ی StrictModes در sshd به‌صورت پیش‌فرض روشن است و پیش از پذیرفتن کلید، مالکیت و پرمیژن فایل‌ها را چک می‌کند. اگر پوشه‌ی .ssh، فایل authorized_keys یا حتی خودِ پوشه‌ی خانگی کاربر برای گروه یا سایرین قابل نوشتن باشد، کلید به‌کلی نادیده گرفته می‌شود و شما با پیام مبهم «Permission denied (publickey)» روبه‌رو می‌شوید بدون اینکه بدانید مشکل از پرمیژن است. پوشه‌ی خانگی باید حداکثر 755 باشد و مالکش همان کاربر.

حالا با کلید وارد شوید و مطمئن شوید بدون پرسیدن پسورد سرور کار می‌کند. تا این تست موفق نشده، بند بعدی را اجرا نکنید.

قدم سوم: تغییر پورت SSH

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

nano /etc/ssh/sshd_config

خط زیر را پیدا کنید:

#Port 22

علامت # را بردارید و پورت دلخواهتان را بگذارید:

Port 1423

عددی بالای ۱۰۲۴ انتخاب کنید که سرویس دیگری روی سرور از آن استفاده نکند. با ss -tlnp (با کاربر root) می‌توانید پورت‌های در حال شنود را ببینید. یک نکته که کمتر گفته می‌شود: پورت را از بازه‌ی پورت‌های موقت سیستم انتخاب نکنید، وگرنه ممکن است بعد از یک ری‌بوت، پورت را برنامه‌ی دیگری گرفته باشد و sshd بالا نیاید. این بازه را با دستور زیر ببینید (روی لینوکس معمولاً از ۳۲۷۶۸ شروع می‌شود):

cat /proc/sys/net/ipv4/ip_local_port_range

تا وقتی از درستی همه چیز مطمئن نشده‌اید، می‌توانید هر دو خط Port 22 و Port 1423 را کنار هم نگه دارید — Port از معدود گزینه‌هایی است که چند بار نوشتنش مجاز است و هر دو پورت شنود می‌شوند. بعد از تست موفق، خط ۲۲ را حذف کنید.

پورت جدید را در فایروال باز کنید

این مرحله فراموش‌شدنی‌ترین قدم کل ماجراست و دقیقاً همان‌جایی است که آدم‌ها خودشان را از سرور بیرون می‌اندازند. پیش از ری‌استارت sshd، پورت را در فایروال باز کنید.

روی سرورهایی که CSF دارند، فایل /etc/csf/csf.conf را باز کنید و پورت جدید را به فهرست TCP_IN (و در صورت نیاز TCP_OUT) اضافه کنید:

nano /etc/csf/csf.conf
TCP_IN = "20,21,22,25,53,80,110,143,443,465,587,993,995,1423"

این خط را عیناً کپی نکنید. فقط شماره‌ی پورت جدید را به انتهای فهرستی که در فایل خودتان هست اضافه کنید. اگر فهرست بالا را جایگزین کنید، پورت‌های کنترل‌پنل (مثلاً ۲۰۸۳ و ۲۰۸۷ در cPanel یا ۲۲۲ در دایرکت‌ادمین) و هر سرویس دیگری که به آن وابسته‌اید بسته می‌شود.

سپس قوانین را بارگذاری مجدد کنید:

csf -r

اگر به‌جای CSF از firewalld یا ufw استفاده می‌کنید:

# firewalld (AlmaLinux / Rocky / RHEL)
firewall-cmd --permanent --add-port=1423/tcp
firewall-cmd --reload

# ufw (اوبونتو / دبیان)
ufw allow 1423/tcp
ufw status

در مورد ufw حواستان باشد که اگر بعداً بخواهید آن را با ufw enable فعال کنید، سیاست پیش‌فرضش «رد کردن ورودی» است؛ پس پیش از فعال کردن، حتماً قانون پورت SSH را اضافه کرده باشید.

اگر SELinux فعال است

روی خانواده‌ی ردهت، SELinux به‌صورت پیش‌فرض فقط اجازه‌ی شنود sshd روی پورت‌های شناخته‌شده را می‌دهد. اگر این کار را نکنید، سرویس با وجود پیکربندی درست بالا نمی‌آید. اول وضعیت را ببینید:

getenforce

اگر خروجی Enforcing بود، پورت جدید را به SELinux معرفی کنید (اگر Permissive یا Disabled بود این مرحله لازم نیست):

dnf install -y policycoreutils-python-utils
semanage port -a -t ssh_port_t -p tcp 1423

اگر پیام Port tcp/1423 already defined گرفتید یعنی آن پورت از قبل به نوع دیگری اختصاص دارد؛ در آن حالت به‌جای -a از -m استفاده کنید. برای دیدن پورت‌های مجاز sshd هم:

semanage port -l | grep ssh_port_t

یک نکته درباره‌ی اوبونتوهای جدید

در نسخه‌های اخیر اوبونتو (از جمله ۲۴.۰۴) سرویس SSH می‌تواند به‌صورت socket activation بالا بیاید و در آن حالت گزینه‌ی Port داخل sshd_config نادیده گرفته می‌شود، چون شنود روی پورت را systemd انجام می‌دهد. برای بررسی:

systemctl is-enabled ssh.socket

اگر فعال بود، پورت را باید در همان یونیت عوض کنید. با systemctl edit ssh.socket این چند خط را اضافه کنید:

[Socket]
ListenStream=
ListenStream=1423

خط خالی اول عمداً آنجاست و مقدار پیش‌فرض (پورت ۲۲) را پاک می‌کند؛ اگر آن را ننویسید، پورت ۲۲ هم باز می‌ماند. سپس:

systemctl daemon-reload
systemctl restart ssh.socket

قدم چهارم: بستن ورود مستقیم root

در فایل sshd_config خط زیر را پیدا کنید:

PermitRootLogin yes

و به این شکل درش بیاورید:

PermitRootLogin no

از این پس ورود مستقیم با root از طریق SSH ممکن نیست؛ با کاربر عادی وارد می‌شوید و با sudo -i به روت می‌رسید. (دستور su - پسورد خودِ root را می‌خواهد و روی اوبونتو که پسورد root معمولاً تنظیم نشده کار نمی‌کند؛ پس روی مسیر sudo حساب کنید.) اگر به هر دلیلی به ورود مستقیم root نیاز دارید، دست‌کم آن را به ورود با کلید محدود کنید:

PermitRootLogin prohibit-password

در این حالت root فقط با کلید می‌تواند وارد شود و ورود با پسورد برایش بسته است. مقدار قدیمی without-password هم دقیقاً همین معنا را دارد و هنوز به‌عنوان نام مستعار پذیرفته می‌شود، اما نام درست و امروزی‌اش prohibit-password است.

قدم پنجم: محدود کردن کاربران مجاز SSH

به‌صورت پیش‌فرض هر کاربری که روی سیستم حساب دارد و شل معتبر دارد می‌تواند SSH بزند. با AllowGroups این را به یک گروه مشخص محدود کنید. در sshd_config بنویسید:

AllowGroups developers

از این لحظه فقط اعضای گروه developers اجازه‌ی ورود دارند. اگر ترجیح می‌دهید به‌جای گروه، کاربران را تک‌تک نام ببرید:

AllowUsers newuser

سه نکته که در راهنماهای قدیمی جا می‌افتد و دردسر می‌سازد:

  • یکی از این دو را انتخاب کنید، نه هر دو را. sshd این فهرست‌ها را جداگانه و پشت سر هم بررسی می‌کند (اول DenyUsers و AllowUsers، بعد DenyGroups و AllowGroups). یعنی اگر هر دو را بنویسید، کاربر باید هر دو شرط را همزمان داشته باشد: هم نامش در فهرست باشد و هم عضو گروه. در عمل معمولاً همین باعث می‌شود کاربری که فکر می‌کردید مجاز است رد شود. برای مدیریت ساده‌تر فقط AllowGroups را نگه دارید.
  • این محدودیت شامل root هم می‌شود. اگر همین حالا با کاربر root وصل هستید و root عضو گروه developers نیست، بعد از ری‌استارت دیگر نمی‌توانید با root وارد شوید — حتی اگر PermitRootLogin yes باشد. پیش از ری‌استارت مطمئن شوید کاربر جایگزینتان تست شده است.
  • مراقب بلوک‌های Match باشید. اگر فایل شما بخش Match دارد، خطوط جدید را بالاتر از اولین Match بگذارید. هر چیزی که بعد از یک Match بنویسید فقط برای همان شرط اعمال می‌شود، نه کل سرور. به همین دلیل توصیه‌ی «این خطوط را به انتهای فایل اضافه کنید» که در آموزش‌های قدیمی می‌بینید همیشه درست نیست.

قدم ششم: بستن احراز هویت پسوردی و GSSAPI

وقتی مطمئن شدید ورود با کلید کار می‌کند، پسورد را کامل ببندید. با این کار حمله‌های brute force عملاً بی‌اثر می‌شوند:

PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no

گزینه‌ی KbdInteractiveAuthentication مسیر دومی برای پرسیدن پسورد است و پیش‌فرضش yes است؛ اگر آن را باز بگذارید، بستن PasswordAuthentication ممکن است از طریق PAM دور زده شود. در فایل‌های قدیمی همین گزینه با نام ChallengeResponseAuthentication نوشته شده؛ آن نام از OpenSSH 8.7 به بعد فقط یک نام مستعار منسوخ است و نام درست همان است که بالا آمد.

گزینه‌ی بعدی GSSAPIAuthentication است که به احراز هویت مبتنی بر Kerberos مربوط می‌شود. پیش‌فرض خود OpenSSH برای این گزینه no است، اما فایل پیکربندی پیش‌فرض روی خانواده‌ی ردهت آن را yes می‌گذارد. روی یک سرور وب معمولی که Kerberos ندارد این فقط یک مسیر احراز هویت اضافی و بی‌استفاده است؛ ببندیدش:

GSSAPIAuthentication no

مراقب فایل‌های Include باشید

روی توزیع‌های امروزی (اوبونتو ۲۲.۰۴ به بعد، دبیان ۱۱ به بعد، RHEL 9 و مشتقاتش مثل AlmaLinux و Rocky) معمولاً در ابتدای sshd_config این خط وجود دارد — فایل خودتان را نگاه کنید:

Include /etc/ssh/sshd_config.d/*.conf

در sshd_config برای هر گزینه اولین مقداری که خوانده شود برنده است. چون این خط در ابتدای فایل است، هر چیزی که داخل پوشه‌ی sshd_config.d باشد بر تنظیمات شما در ادامه‌ی فایل غلبه می‌کند. روی سرورهای ابری خیلی وقت‌ها فایلی مثل 50-cloud-init.conf آنجا هست که PasswordAuthentication yes را برمی‌گرداند. پس قبل از هر چیز محتوای این پوشه را ببینید:

ls -la /etc/ssh/sshd_config.d/

و برای دیدن تنظیمات واقعاً اعمال‌شده پس از احتساب همه‌ی این فایل‌ها (این دستور را باید با کاربر root اجرا کنید):

sshd -T | grep -Ei 'port|permitrootlogin|passwordauthentication|kbdinteractive|allowgroups|allowusers|pubkey|gssapi'

تکلیف گزینه‌ی Protocol چیست؟

در آموزش‌های قدیمی — از جمله مطلبی که پیش‌تر در همین وبلاگ داشتیم — توصیه می‌شد خط #Protocol 2,1 را از حالت کامنت خارج کنید و به Protocol 2 تغییر دهید تا نسخه‌ی ناامن ۱ پروتکل SSH غیرفعال شود. آن توصیه در زمان خودش درست بود، اما امروز موضوعیت ندارد.

پشتیبانی از SSH نسخه‌ی ۱ در OpenSSH 7.6 (سال ۲۰۱۷) به‌کلی از کد حذف شد و همراه آن خود گزینه‌ی Protocol هم از مستندات و از کار افتاد. یعنی هر نسخه‌ای که امروز روی سرورتان نصب است فقط و فقط با نسخه‌ی ۲ کار می‌کند و نوشتن این خط هیچ اثری ندارد؛ sshd آن را بی‌سروصدا نادیده می‌گیرد (نه خطا می‌دهد و نه سرویس را زمین می‌زند). نتیجه‌ی عملی ساده است: اگر چنین خطی در فایل شما هست حذفش کنید تا کسی گمراه نشود، و اگر نیست هیچ کاری لازم نیست. نسخه‌ی نصب‌شده را می‌توانید ببینید:

ssh -V

قدم آخر: تست پیکربندی و ری‌استارت

پیش از ری‌استارت، صحت نحو فایل را بررسی کنید:

sshd -t

اگر هیچ خروجی‌ای نداد یعنی فایل سالم است. اگر دستور پیدا نشد، مسیر کامل را بدهید: /usr/sbin/sshd -t.

و مهم‌ترین توصیه‌ی کل این مقاله: نشست SSH فعلی خود را باز نگه دارید. ری‌استارت sshd اتصال‌های برقرار را قطع نمی‌کند، پس پنجره‌ی فعلی شما زنده می‌ماند. با یک پنجره‌ی دوم روی پورت و کاربر جدید وارد شوید و فقط وقتی مطمئن شدید کار می‌کند، پنجره‌ی اول را ببندید. اگر جایی از پیکربندی اشتباه باشد، همین نشست باز تنها راه برگرداندن آن است؛ در غیر این صورت پشت در می‌مانید و باید از کنسول (KVM/VNC) دیتاسنتر یا پشتیبانی سرور کمک بگیرید.

حالا سرویس را ری‌استارت کنید:

# AlmaLinux / Rocky / RHEL
systemctl restart sshd

# دبیان / اوبونتو
systemctl restart ssh

روی همه‌ی توزیع‌های امروزی، دستورهای قدیمی service sshd restart و /etc/rc.d/init.d/sshd restart (یا /etc/init.d/sshd restart) منسوخ‌اند. اولی فقط یک لایه‌ی سازگاری است که درخواست را به systemd پاس می‌دهد و دومی روی بیشتر سرورها اصلاً وجود ندارد. شکل درست همان systemctl است. توجه کنید که نام یونیت روی خانواده‌ی ردهت sshd و روی دبیان و اوبونتو ssh است؛ در دستورهای بعدی هم همین نام را جایگزین کنید.

وضعیت سرویس را ببینید:

# خانواده‌ی ردهت
systemctl status sshd

# دبیان / اوبونتو
systemctl status ssh

و اگر بالا نیامد، دلیلش را در لاگ پیدا کنید:

# خانواده‌ی ردهت
journalctl -u sshd -n 50 --no-pager

# دبیان / اوبونتو
journalctl -u ssh -n 50 --no-pager

حالا از پنجره‌ی دوم تست کنید:

ssh -p 1423 newuser@SERVER_IP

وقتی ورود موفق شد و sudo -i هم کار کرد، برگردید و پورت ۲۲ را از sshd_config و از فایروال حذف کنید و یک بار دیگر سرویس را ری‌استارت کنید.

یک لایه‌ی آخر: جلوی تلاش‌های مکرر را بگیرید

حتی با پورت غیراستاندارد و ورود کلیدی، بد نیست IPهایی که پشت سر هم ورود ناموفق دارند به‌صورت خودکار بلاک شوند. اگر CSF نصب دارید، سرویس lfd همراه آن دقیقاً همین کار را می‌کند و تنظیمات مربوط به ورود ناموفق SSH در /etc/csf/csf.conf با کلیدواژه‌ی LF_SSHD قابل تغییر است. روی سرورهای بدون CSF، fail2ban جایگزین رایج و رایگان همین کار است. اگر پورت SSH را عوض کرده‌اید، یادتان باشد پورت جدید را در پیکربندی این ابزار هم اعلام کنید، وگرنه چیزی را پایش نمی‌کند. ضمناً IP ثابت خودتان را در فهرست سفید (در CSF فایل /etc/csf/csf.allow) بگذارید تا یک اشتباه تایپی شما را بلاک نکند.

چک‌لیست نهایی

  • سرور به‌روز است و روی توزیعی با پشتیبانی فعال اجرا می‌شود.
  • یک کاربر غیر root با پسورد و دسترسی sudo ساخته شده و تست شده است.
  • ورود با کلید ed25519 کار می‌کند و پرمیژن ~/.ssh برابر ۷۰۰، authorized_keys برابر ۶۰۰ و پوشه‌ی خانگی حداکثر ۷۵۵ است.
  • Port عوض شده و پورت جدید در CSF یا firewalld/ufw و در صورت نیاز در SELinux باز شده است.
  • PermitRootLogin no تنظیم شده است.
  • AllowGroups فقط گروه مجاز را اجازه می‌دهد و کاربر شما عضو آن گروه است.
  • PasswordAuthentication no، KbdInteractiveAuthentication no و GSSAPIAuthentication no تنظیم شده‌اند.
  • پوشه‌ی sshd_config.d بررسی شده و خروجی sshd -T با چیزی که انتظار دارید جور در می‌آید.
  • گزینه‌ی بی‌اثر Protocol از فایل حذف شده است.
  • sshd -t بدون خطا اجرا شده و سرویس با systemctl ری‌استارت شده است.
  • ورود از یک نشست دوم پیش از بستن نشست اول تست شده است.

آموزش‌های مرتبط