دسته بندی:

چه کسی وارد سرور شد؟ راهنمای دستور last در لینوکس، lastb و chage

چه کسی وارد سرور شد؟ راهنمای دستور last در لینوکس، lastb و chage

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

یک صبح می‌بینید سرور کند شده، یا در گزارش‌ها یک تغییر ناخواسته پیدا می‌کنید و اولین سؤالی که باید جوابش را بدهید این است: چه کسی، از کدام IP و چه ساعتی وارد سرور شد؟ لینوکس جواب این سؤال را نگه می‌دارد؛ فقط باید بدانید در کدام فایل و با کدام دستور بیرونش بکشید. در این راهنما از خواندن فایل‌های ورود و خروج شروع می‌کنیم، به تلاش‌های ناموفق و سن رمز عبور کاربران می‌رسیم و در پایان یک هشدار ایمیلی برای ورود root راه‌اندازی می‌کنیم.

ورودها به سرور لینوکس کجا ثبت می‌شوند؟

بیشتر گزارش‌های سیستم زیر مسیر /var/log نگهداری می‌شوند، اما سه فایل مربوط به ورود و خروج کاربران فایل متنی نیستند؛ فایل باینری‌اند و با cat یا nano چیز خوانایی نشان نمی‌دهند. این سه فایل عبارت‌اند از:

  • utmp در مسیر /run/utmp — وضعیت همین لحظهی سیستم: چه کاربرانی الان وارد شده‌اند، روی کدام ترمینال، از کجا، به‌علاوه‌ی رکورد زمان boot سیستم که مبنای محاسبه‌ی uptime است. این فایل با هر ورود و خروج به‌روز می‌شود و تاریخچه نگه نمی‌دارد.
  • wtmp در مسیر /var/log/wtmpتاریخچهی همان رکوردها: هر ورود موفق، هر خروج، هر ری‌بوت و هر تغییر runlevel به‌ترتیب زمانی در این فایل انباشته می‌شود.
  • btmp در مسیر /var/log/btmp — فقط تلاش‌های ناموفق برای ورود. روی سروری که IP عمومی دارد، این فایل معمولاً پرترافیک‌ترین‌شان است.

نکته‌ی مسیرها: در متن‌های قدیمی‌تر آدرس فایل utmp را /var/run/utmp می‌نویسند. روی توزیع‌های امروزی /var/run فقط یک symlink به /run است، پس هر دو مسیر به یک فایل می‌رسند و هرکدام را که راحت‌ترید می‌توانید بنویسید. مسیر استاندارد امروز /run/utmp است.

همین حالا چه کسی روی سرور است؟

برای وضعیت لحظه‌ای، به‌جای اینکه فایل utmp را دستی بخوانید از ابزارهای مخصوص همین کار استفاده کنید:

w

خروجی w علاوه بر نام کاربر، ترمینال، مبدأ اتصال و زمان ورود، نشان می‌دهد هر نشست همین حالا چه دستوری را اجرا می‌کند و load average سرور چقدر است — یعنی هم می‌فهمید چه کسی وصل است و هم دارد چه می‌کند. اگر می‌خواهید به‌جای نام دامنه، IP خام را ببینید:

w -i

دستور who ساده‌تر است و فقط فهرست نشست‌های باز را می‌دهد:

who
who -a
who am i

who -a اطلاعات کامل‌تر (از جمله زمان boot و runlevel) را نشان می‌دهد و who am i فقط نشست خودتان را چاپ می‌کند — برای وقتی که چند نشست باز است و می‌خواهید مطمئن شوید کدام‌یک مال شماست.

دستور last در لینوکس: تاریخچه‌ی ورودها

ابزار اصلی این کار last است. اگر بدون هیچ آرگومانی اجرایش کنید، فایل /var/log/wtmp را از آخر به اول می‌خواند:

last

هر خط شامل نام کاربر، ترمینال (مثلاً pts/0 برای SSH و tty1 برای کنسول)، مبدأ اتصال، زمان ورود، زمان خروج و مدت نشست است. خط‌هایی که با still logged in تمام می‌شوند یعنی آن نشست هنوز باز است.

خواندن مستقیم یک فایل با گزینه‌ی -f

گزینه‌ی -f به last می‌گوید به‌جای فایل پیش‌فرض، فایل مشخصی را بخواند:

last -f /var/log/wtmp
last -f /run/utmp
last -f /var/log/btmp

دستور اول همان حالت پیش‌فرض است و بیشتر وقتی به کار می‌آید که بخواهید یک فایل wtmp چرخش‌خورده (مثل /var/log/wtmp.1) را باز کنید. دستور دوم utmp را می‌خواند و عملاً نشست‌های باز فعلی را نشان می‌دهد — اما برای این کار w یا who خروجی تمیزتر و قابل‌اعتمادتری می‌دهند. دستور سوم هم معادل دستور lastb است که در بخش بعد به آن می‌رسیم.

گزینه‌های پرکاربرد last

last -n 20
last root
last -a
last -i
last -F
last -x
last reboot
last -s yesterday
last -s 2026-08-01 -t 2026-08-05
  • -n 20 فقط ۲۰ رکورد آخر را نشان می‌دهد.
  • last root فقط ورودهای کاربر root را فیلتر می‌کند؛ به‌جای نام کاربر می‌توانید نام ترمینال (مثل pts/1) هم بدهید.
  • -a ستون مبدأ را به انتهای خط می‌برد تا آدرس‌های بلند بریده نشوند.
  • -i مبدأ را به‌صورت IP عددی چاپ می‌کند (بدون تبدیل به نام دامنه).
  • -F زمان کامل ورود و خروج را با سال و ثانیه نشان می‌دهد.
  • -x رکوردهای shutdown و تغییر runlevel را هم اضافه می‌کند؛ برای فهمیدن اینکه سرور چه زمانی خاموش یا ری‌استارت شده عالی است.
  • last reboot فهرست ری‌بوت‌های سیستم را می‌دهد.
  • -s و -t بازه‌ی زمانی تعیین می‌کنند؛ قالب تاریخ YYYY-MM-DD و کلیدواژه‌هایی مثل today، yesterday و now پذیرفته می‌شود.

آخرین ورود هر کاربر با lastlog

last تاریخچه‌ی نشست‌ها را می‌دهد، اما اگر سؤالتان این است که «هر کاربر آخرین بار کِی وارد شده و آیا اصلاً تا حالا وارد شده؟»، ابزار درست lastlog است:

lastlog
lastlog -u username
lastlog -b 90

lastlog -b 90 فقط کاربرانی را نشان می‌دهد که بیش از ۹۰ روز است وارد نشده‌اند؛ برای پیدا کردن اکانت‌های متروکه‌ای که بهتر است قفل شوند دقیقاً همین را لازم دارید. توجه کنید که در نسخه‌های جدید ابزارهای پایه‌ی لینوکس، lastlog منسوخ اعلام شده و جایش را lastlog2 می‌گیرد؛ اگر روی توزیع تازه‌ای دستور اول را نداشتید، دومی را امتحان کنید.

تلاش‌های ناموفق ورود: btmp و lastb

برای دیدن تلاش‌های ناموفق، دستور اختصاصی lastb است. این دستور فقط با کاربر root اجرا می‌شود، چون فایل btmp عمداً برای کاربران عادی خوانا نیست (مبادا رمزی که کاربر اشتباهاً در فیلد username تایپ کرده لو برود):

lastb
lastb -n 50
lastb -i -a

برای دیدن اینکه بیشترین حمله از کدام IPها می‌آید، خروجی را گروه‌بندی کنید. دو خط آخر خروجی lastb یک سطر خالی و سطر «btmp begins …» است، پس آن‌ها را کنار می‌گذاریم:

lastb -i | awk 'NF >= 4 && $1 != "btmp" {print $3}' | sort | uniq -c | sort -rn | head -20

اگر فایل btmp وجود نداشت

روی بعضی نصب‌ها فایل btmp از ابتدا ساخته نشده و lastb خطا می‌دهد. خودتان با دسترسی درست بسازیدش:

touch /var/log/btmp
chown root:utmp /var/log/btmp
chmod 600 /var/log/btmp

جزئیات بیشتر را از لاگ SSH بگیرید

فایل btmp فقط می‌گوید «ورود ناموفق بود»؛ نمی‌گوید علتش رمز اشتباه بود، کلید نامعتبر یا کاربر ناموجود. آن جزئیات در لاگ سرویس SSH است:

journalctl -u sshd --since today | grep -Ei 'failed|invalid user'
journalctl -u sshd --since today | grep 'Accepted'

روی خانواده‌ی Debian و Ubuntu نام سرویس ممکن است ssh باشد؛ اگر -u sshd خالی برگشت، journalctl -u ssh را امتحان کنید. اگر روی سرور شما rsyslog هم نصب باشد، همین رویدادها در فایل متنی هم هستند: روی AlmaLinux، Rocky و CentOS در /var/log/secure و روی Debian و Ubuntu در /var/log/auth.log. توجه کنید که در نصب‌های جدید که فقط journald دارند، این فایل‌های متنی اصلاً ساخته نمی‌شوند و باید سراغ journalctl بروید.

و یک نکته‌ی عملی: اگر btmp شما روزانه ده‌ها هزار رکورد می‌گیرد، مشکل واقعی حمله‌ی brute force است نه حجم لاگ. نصب fail2ban و بستن ورود root با رمز (تنظیم PermitRootLogin prohibit-password در /etc/ssh/sshd_config) ریشه‌ی ماجرا را می‌زند. پیش از ری‌استارت سرویس SSH مطمئن شوید کلید عمومی‌تان روی سرور کار می‌کند و نشست فعلی را نبندید، وگرنه ممکن است پشت در بمانید.

اگر خروجی last خالی بود

روی توزیع‌های خیلی جدید ممکن است last هیچ رکوردی نشان ندهد. علتش خرابی سرور نیست: پروژه‌ی systemd پشتیبانی از utmp را کنار گذاشته و بعضی توزیع‌های تازه دیگر این فایل‌ها را پر نمی‌کنند و به‌جایش سراغ ابزارهای جدیدتری مثل wtmpdb و lastlog2 رفته‌اند. روی نسخه‌های پایدار و LTS رایج امروز (خانواده‌ی AlmaLinux، Rocky، CentOS، Debian و Ubuntu) last و lastb همچنان کار می‌کنند؛ اما اگر روی توزیعی به خروجی خالی برخوردید، همان اطلاعات را از journald بگیرید:

journalctl _COMM=sshd --since "7 days ago" | grep 'Accepted'

سن رمز عبور کاربران با chage

بخش دوم ماجرای امنیت ورود، خودِ رمزهاست. ممکن است روی سرور کاربرانی داشته باشید که سال‌هاست رمزشان عوض نشده. دستور chage (مخفف change age) وضعیت انقضای رمز هر کاربر را نشان می‌دهد:

chage -l username

خروجی چیزی شبیه این است:

Last password change                                    : Jul 28, 2012
Password expires                                        : never
Password inactive                                       : never
Account expires                                         : never
Minimum number of days between password change          : 0
Maximum number of days between password change          : 99999
Number of days of warning before password expires       : 7

مقدار 99999 در خط Maximum یعنی رمز عملاً هرگز منقضی نمی‌شود — همان پیش‌فرضی که روی بیشتر سرورها دست‌نخورده باقی می‌ماند.

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

chage -l هر بار یک کاربر را نشان می‌دهد. برای اینکه تاریخ آخرین تغییر رمز همه‌ی کاربران را یک‌جا ببینید، روی فهرست کاربران حلقه بزنید (این دستور فقط می‌خواند و چیزی را تغییر نمی‌دهد):

for u in $(cut -d: -f1 /etc/passwd); do
  printf '%-20s %sn' "$u" "$(LC_ALL=C chage -l "$u" | sed -n 's/^Last password change[[:space:]]*: //p')"
done

گذاشتن LC_ALL=C لازم است تا اگر locale سرور فارسی یا زبان دیگری بود، متن خروجی ترجمه نشود و الگوی sed از کار نیفتد. اگر فقط کاربران واقعی (نه اکانت‌های سیستمی) را می‌خواهید، به‌جای cut این را بگذارید:

awk -F: '$3 >= 1000 && $1 != "nobody" {print $1}' /etc/passwd

اجبار کاربر به تغییر رمز

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

chage -d 0 username

و برای اینکه از این به بعد سیاست انقضا برقرار باشد:

chage -M 90 -m 1 -W 14 username
chage -E 2027-12-31 username
  • -M 90 حداکثر عمر رمز را ۹۰ روز می‌کند.
  • -m 1 حداقل یک روز فاصله بین دو تغییر رمز می‌گذارد تا کاربر نتواند بلافاصله رمز قبلی را برگرداند.
  • -W 14 از ۱۴ روز مانده به انقضا به کاربر هشدار می‌دهد.
  • -E تاریخ انقضای خودِ اکانت را تعیین می‌کند (مناسب برای دسترسی موقت پیمانکار). حواستان باشد تاریخ گذشته بدهید یعنی اکانت همان لحظه بسته می‌شود؛ این دستور را روی root یا اکانت خودتان اجرا نکنید.

برای اینکه این سیاست روی کاربران جدید هم به‌صورت پیش‌فرض اعمال شود، مقدار PASS_MAX_DAYS را در فایل /etc/login.defs تنظیم کنید. توجه کنید این فایل فقط روی کاربرانی که بعد از تغییر ساخته می‌شوند اثر دارد و کاربران موجود را عقب‌گرد نمی‌کند.

یک نکته‌ی مهم که خیلی‌ها از قلم می‌اندازند: سیاست انقضای رمز فقط روی ورود با رمز اثر دارد. کاربری که با کلید SSH وارد می‌شود، حتی با رمز منقضی‌شده هم می‌تواند وصل شود. دقت کنید که usermod -L (یا passwd -l) هم فقط رمز را قفل می‌کند و جلوی ورود با کلید را نمی‌گیرد. اگر هدفتان بستن کامل دسترسی یک نفر است، هر سه کار را با هم انجام دهید:

usermod -L username
chage -E 0 username
mv /home/username/.ssh/authorized_keys /home/username/.ssh/authorized_keys.disabled

خط دوم اکانت را منقضی می‌کند و ماژول account در PAM جلوی ورود را می‌گیرد (روی پیکربندی پیش‌فرض SSH که UsePAM yes است)؛ خط سوم هم کلیدهای کاربر را از دور خارج می‌کند. برای غیرفعال کردن کامل، usermod -s /usr/sbin/nologin username را هم اضافه کنید. اگر کاربر همین حالا نشست باز دارد، این تغییرات نشست فعلی‌اش را نمی‌بندند؛ برای آن باید فرایندهایش را با pkill -u username ببندید.

روی سرورهای cPanel

اگر سرور شما cPanel دارد، برای تغییر رمز اکانت‌ها از خود WHM یا cPanel استفاده کنید و نه passwd در خط فرمان؛ چون cPanel رمز اکانت را با سرویس‌های وابسته (FTP، ایمیل و…) هماهنگ می‌کند و تغییر دستی در سطح سیستم‌عامل این هماهنگی را به‌هم می‌ریزد. به همین دلیل روی سرورهای cPanel سیاست انقضای رمز را هم از بخش Password Aging در WHM اعمال کنید، نه با chage.

هشدار ایمیلی هنگام ورود root

تا اینجا همه‌چیز گذشته‌نگر بود؛ حالا می‌خواهیم لحظه‌ای که کسی با root وارد می‌شود خبردار شویم.

روش قدیمی با فایل .bashrc و محدودیت‌هایش

روش رایجی که در راهنماهای قدیمی CentOS می‌بینید این است که یک خط به انتهای /root/.bashrc اضافه کنید:

nano /root/.bashrc

خطی که معمولاً پیشنهاد می‌شود چیزی شبیه این است، که هم نقل‌قول‌های تودرتویش شکننده است و هم who در آن خروجی چندخطی می‌دهد:

echo -n 'ALERT - Root Shell Access on:' `date` `who` | mail -s "Alert: Root Access" email@address.com

نسخه‌ی درست و قابل‌اتکای همان ایده این است — از $(...) به‌جای backtick استفاده می‌کند، IP مبدأ را از متغیر SSH_CONNECTION می‌گیرد و فقط برای نشست‌های تعاملی SSH ایمیل می‌فرستد:

if [ -n "$SSH_CONNECTION" ] && [[ $- == *i* ]]; then
    CLIENT_IP=$(echo "$SSH_CONNECTION" | awk '{print $1}')
    echo "ALERT: root login on $(hostname) at $(date) from $CLIENT_IP" 
      | mail -s "Alert: root login from $CLIENT_IP" email@address.com
fi

مقدار ایمیل را با آدرس خودتان عوض کنید و فایل را ذخیره کنید. برای اینکه دستور mail اصلاً کار کند، باید یک کلاینت خط فرمان و یک MTA روی سرور باشد. روی AlmaLinux و Rocky نسخه‌ی ۹ بسته‌ی s-nail و روی نسخه‌ی ۸ بسته‌ی mailx این دستور را فراهم می‌کند و روی Debian و Ubuntu بسته‌ی bsd-mailx یا mailutils:

dnf install -y s-nail postfix
systemctl enable --now postfix

اگر هنوز روی CentOS 7 هستید دستور نصب yum install -y mailx postfix است، ولی توجه داشته باشید CentOS 7 دیگر پشتیبانی نمی‌شود و مهاجرت به AlmaLinux یا Rocky Linux کار درست‌تری است. روی نسخه‌های ۸ به بعد هم yum فقط یک نام مستعار برای dnf است.

حالا محدودیت‌های این روش را بدانید، چون کم نیستند:

  • فقط وقتی اجرا می‌شود که نشست، یک شل bash باز کند. اتصال sftp (که از زیرسیستم sftp استفاده می‌کند)، پورت‌فورواردِ خالی یا نشستی که shell کاربر bash نیست، هیچ ایمیلی تولید نمی‌کند. در مقابل، برخلاف تصور رایج، ssh root@server "command" ممکن است همین خط را اجرا کند، چون bash فایل .bashrc را برای شل‌های غیرتعاملیِ راه‌دور هم می‌خواند؛ شرط [[ $- == *i* ]] بالا دقیقاً برای همین گذاشته شده است.
  • اگر shell پیش‌فرض کاربر bash نباشد، فایل .bashrc اصلاً خوانده نمی‌شود.
  • کسی که root گرفته، می‌تواند در چند ثانیه همین خط را از فایل پاک کند. پس این یک ابزار «اطلاع‌رسانی» است، نه یک کنترل امنیتی.

روش مطمئن‌تر: pam_exec

راه‌حل درست‌تر این است که هشدار را به لایه‌ی احراز هویت (PAM) وصل کنید تا مستقل از نوع shell و نوع نشست اجرا شود. ابتدا اسکریپت را بسازید:

nano /usr/local/sbin/ssh-login-alert.sh
#!/bin/bash
[ "$PAM_TYPE" = "open_session" ] || exit 0
{
  echo "User    : $PAM_USER"
  echo "From    : $PAM_RHOST"
  echo "Service : $PAM_SERVICE"
  echo "Server  : $(hostname)"
  echo "Date    : $(date)"
} | mail -s "SSH login: $PAM_USER from $PAM_RHOST" email@address.com &
exit 0

اگر فقط ورود root برایتان مهم است، بعد از خط اول این شرط را هم اضافه کنید: [ "$PAM_USER" = "root" ] || exit 0. علامت & در انتهای خط mail هم عمدی است: pam_exec اسکریپت را همگام اجرا می‌کند و اگر ارسال ایمیل کند باشد، ورود شما به سرور هم به همان اندازه معطل می‌ماند. سپس دسترسی اسکریپت را ببندید:

chown root:root /usr/local/sbin/ssh-login-alert.sh
chmod 700 /usr/local/sbin/ssh-login-alert.sh

و این خط را به انتهای /etc/pam.d/sshd اضافه کنید:

session optional pam_exec.so /usr/local/sbin/ssh-login-alert.sh

کلمه‌ی optional و exit 0 در انتهای اسکریپت هر دو عمدی‌اند: اگر روزی ارسال ایمیل خطا بدهد، نباید ورود به سرور مسدود شود. با این حال قانون همیشگی دستکاری PAM را رعایت کنید — نشست SSH فعلی را نبندید؛ یک ترمینال دوم باز کنید و ورود را تست کنید، و تنها بعد از موفقیت نشست اول را ببندید. اگر امکانش هست، پیش از دست زدن به فایل‌های /etc/pam.d یک نسخه‌ی پشتیبان بگیرید و به کنسول یا KVM سرور هم دسترسی داشته باشید.

وقتی ایمیل کافی نیست

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

  • auditd: سرویس ممیزی هسته که مستقل از bash و PAM رویدادها را ثبت می‌کند و با ausearch و aureport قابل جست‌وجوست. برای مثال aureport -au گزارش تلاش‌های احراز هویت را می‌دهد.
  • ارسال لاگ به بیرون از سرور: با rsyslog یا systemd-journal-remote لاگ‌ها را روی یک ماشین دیگر هم بنویسید. اگر لاگ فقط روی همان سروری باشد که نفوذ شده، ارزش اثباتی‌اش صفر است.

چک‌لیست عملی

  • «الان چه کسی وصل است؟» ← w
  • «چه کسانی اخیراً وارد شدند؟» ← last -a -i -n 50
  • «چه کسی سعی کرد وارد شود و نتوانست؟» ← lastb -i -a
  • «سرور کِی ری‌بوت شد؟» ← last -x reboot
  • «کدام کاربر سال‌هاست رمزش را عوض نکرده؟» ← حلقه‌ی chage -l
  • «چه کسی مدت‌هاست وارد نشده و بهتر است قفل شود؟» ← lastlog -b 90
  • «می‌خواهم لحظه‌ی ورود root خبردار شوم» ← pam_exec

اگر تازه با خط فرمان آشنا شده‌اید، چند دستور کاربردی در لینوکس نقطه‌ی شروع خوبی است. و اگر می‌خواهید یک قدم جلوتر بروید و به‌جای رصد کردن، جلوی ورود ناخواسته را بگیرید، راهنمای ایمن‌سازی SSH و مطلب تغییر رمز عبور سرور لینوکس ادامه‌ی طبیعی همین کار است.

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