EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / Security / อ่าน exim mainlog หา stuck client — Mail Forensics ภาคแรก
Security — Mail Forensics [1]

อ่าน exim mainlog หา stuck client — Mail Forensics ภาคแรก

โดย Ton-Or ·23 พ.ค. 2026·อ่าน 12 นาที
อ่าน exim mainlog หา stuck client — Mail Forensics ภาคแรก

ลูกค้าโทรเข้ามาเรื่องส่งเมลไม่ได้แทบทุกวัน บ้าง IP โดน block บ้างบอกรหัสรั่ว ผมไล่บ่อยจนทำเป็นท่ามาตรฐานในหัว เลยรวบคำสั่งกับวิธีคิดมาเขียนไว้

สรุปใน 1 นาที

อาการ/ปัญหา
ลูกค้าส่งเมลไม่ได้ และต้องแยกเครื่องในออฟฟิศที่ใส่รหัสผิดออกจาก attacker brute force
ใครควรอ่าน
แอดมินเมล exim ที่ต้องตอบลูกค้าให้เร็ว
ทางแก้แบบสั้น
ไล่อ่าน /var/log/exim/mainlog ด้วยชุดคำสั่งที่ใช้จริง เพื่อแยกเครื่องรหัสผิดออกจาก attacker
ข้ามไปขั้นตอน

ช่วงนี้ลูกค้าโทรเข้ามาบ่อยมาก เรื่องส่งเมลไม่ได้ บางรายโดน IP block บางรายบอกว่ารหัสรั่ว บางรายตามหาเครื่องในออฟฟิศที่ใส่รหัสผิดจนทำให้ทั้ง IP โดน firewall block พร้อมกันหมด ผมไล่เคสแบบนี้บ่อยจนทำเป็นลำดับขั้นมาตรฐานในหัวไปแล้ว เลยถือโอกาสรวบรวมคำสั่งและวิธีคิดที่ใช้ประจำมาเขียนไว้ เผื่อใครเจอเคสคล้ายกันจะได้หยิบไปใช้ได้เลย

ระบบที่ผมดูแลเป็น DirectAdmin บน Debian, mail server เป็น exim, firewall เป็น CSF/LFD ใครใช้ stack ใกล้เคียงน่าจะนำไป apply ได้ตรงๆ ส่วนคนที่ใช้ postfix หรือ iptables ตรงๆ ก็ใช้แนวคิดเดียวกันได้ แต่ regex ต้องปรับให้เข้ากับรูปแบบ log ของตัวเอง

เป้าหมายของบทความชุดนี้คือทำให้คนที่ต้องดูแล mail server หลายโดเมนมีคู่มืออ้างอิงเร็วๆ ตอนลูกค้าเร่งคำตอบ ไม่ต้องมานั่งนึก regex ใหม่ทุกครั้ง

ข้อตกลงเรื่อง placeholder ในบทความ

ทุกตัวอย่างในนี้ผมแทนค่าจริงด้วย placeholder เพื่อความปลอดภัย เวลานำไปใช้ให้แทนด้วยค่าจริงของคุณเอง

  • customer.com = โดเมนลูกค้าตัวอย่าง
  • 203.0.113.x, 198.51.100.x = public IP ตัวอย่าง (X.X.X.X / Y.Y.Y.Y)
  • 192.168.1.x = LAN IP (RFC1918)
  • [email protected] = mail account ตัวอย่าง
  • /home/<user>/... = home directory ของแต่ละบัญชี
ภาพรวม workflow ในหัวผม

ก่อนลงรายละเอียด ลองดูแผนผังการตัดสินใจตามอาการที่ลูกค้าโทรเข้ามาก่อน แต่ละกล่องตรงกับหัวข้อด้านล่าง

ไล่ตามอาการส่งเมลไม่ได้เจาะ account->IP โดน block ซ้ำเจาะ IP->หาเครื่องใน LANNAT fingerprint->พิสูจน์ stuck client->ปลดบล็อก + whitelist

1. รู้จัก log ก่อน — สองตัวที่ใช้บ่อยที่สุด

ก่อนจะ grep อะไรได้ ต้องรู้ว่าข้อมูลอยู่ที่ไหนและหน้าตาเป็นอย่างไร ผมเปิดสองไฟล์นี้บ่อยที่สุด

  • /var/log/exim/mainlog — รวมทั้ง success, fail, queue, delivery (logrotate รายวัน)
  • /var/log/lfd.log — เหตุการณ์ block จาก CSF/LFD (รูปแบบ syslog แบบ May 23 ...)

ส่วนสถานะของ CSF เก็บไว้ในไฟล์เหล่านี้

  • /var/lib/csf/csf.tempban — temp ban ที่ auto-expire ได้เอง
  • /etc/csf/csf.deny — permanent deny (มาจาก BFM และที่เติมเอง)

รูปแบบบรรทัด SUCCESS (auth ผ่าน)

2026-05-23 10:05:38 1wPAm1-... <= [email protected] H=ppp-203-0-113-x.isp.example ([192.168.1.20]) [203.0.113.x] P=esmtpsa A=plain:[email protected] S=1234

จุดที่ต้องอ่านให้ออก

  • <= คือ inbound แปลว่า server กำลังรับเมลจาก client
  • H=hostname ([internal]) คือ EHLO ที่ client ประกาศตัวเอง พร้อม LAN IP ถ้ามี
  • [203.0.113.x] คือ public IP จริงของ client ตัวนี้
  • A=plain:[email protected] คือ authenticator บวกกับ account ที่ login ผ่าน นี่คือ marker สำคัญที่สุดของบรรทัด success

รูปแบบบรรทัด FAIL (รหัสผิด)

2026-05-23 08:07:19 plain authenticator failed for H=ppp-203-0-113-x.isp.example ([IPv6:::ffff:192.168.1.56]) [203.0.113.x]: 535 Incorrect authentication data ([email protected])

จุดที่ต้องอ่านให้ออก

  • authenticator failed คือ marker สำคัญของบรรทัด fail
  • [IPv6:::ffff:192.168.1.56] คือ LAN IP ของ client มีเฉพาะ client บางตัวที่ส่งมา
  • [email protected] คือ account ที่กำลังถูกเดารหัสอยู่
นับ fail ให้ระวัง 2 mechanism

ผมสังเกตว่า client ตัวเดียวกันมักจะ fail ด้วย plain แล้วตามด้วย login ในวินาทีเดียวกัน เพราะ mail client ลองทั้งสอง mechanism (PLAIN กับ LOGIN) มันจะถูกนับเป็น 2 fail ทั้งที่จริงเป็นการลองครั้งเดียว เวลาดู threshold ของ LFD ต้องเผื่อตรงนี้ ไม่งั้นจะตีความ traffic ผิด

2. เจาะ 1 account — รหัสผิดมาจากไหน

เคสตั้งต้น ลูกค้าโทรมาบอกว่า [email protected] ส่งเมลไม่ออก โดน block ซ้ำๆ ขั้นแรกผมจะดูว่ามี IP ไหน login เข้ามาบ้าง ผ่านหรือไม่ผ่านกี่ครั้ง

สรุปยอดรวมก่อน

[email protected]
LOG=/var/log/exim/mainlog

ok=$(grep -c "A=[a-z_]*:$ACCT" $LOG)
fail=$(grep "set_id=$ACCT" $LOG | grep -c 'authenticator failed')
echo "success=$ok  fail=$fail"

ตาราง Per-IP ที่แสดง OK / FAIL / ประเทศ / HELO / rDNS ตัวนี้ผมใช้บ่อยที่สุดในการแกะเคส

printf '%-16s %-5s %-5s %-4s %-30s %s\n' 'IP' 'OK' 'FAIL' 'CC' 'HELO (top)' 'rDNS'
echo '------------------------------------------------------------------------------------------'
( grep "A=[a-z_]*:$ACCT" $LOG | sed -nE 's/.*\[([0-9.]+)\].*/\1/p'
  grep "set_id=$ACCT" $LOG | grep 'authenticator failed' | sed -nE 's/.*\[([0-9.]+)\].*/\1/p'
) | sort -u | while read ip; do
    [ -z "$ip" ] && continue
    ok=$(grep -F "$ip" $LOG | grep -c "A=[a-z_]*:$ACCT")
    fail=$(grep -F "$ip" $LOG | grep "set_id=$ACCT" | grep -c 'authenticator failed')
    cc=$(geoiplookup $ip 2>/dev/null | grep -oE '[A-Z]{2},' | head -1 | tr -d ',')
    helo=$(grep -F "$ip" $LOG | grep -oE 'H=\([^)]+\)' | sort | uniq -c | sort -rn | head -1 \
           | sed -E 's/^[ ]*[0-9]+[ ]+H=\(//; s/\)$//')
    rdns=$(getent hosts $ip 2>/dev/null | awk '{print $2}' | head -1)
    printf '%-16s %-5s %-5s %-4s %-30s %s\n' "$ip" "$ok" "$fail" "${cc:-??}" "${helo:--}" "${rdns:--}"
done
Regex pitfall ที่ผมเคยพลาด

geoiplookup ส่งคืนข้อความแบบ GeoIP Country Edition: TH, Thailand ซึ่งคำว่า IP ก็เป็นตัวพิมพ์ใหญ่สองตัวที่อยู่ก่อน TH ถ้าใช้ grep -oE '[A-Z]{2}' เฉยๆ จะจับคำว่า IP มาก่อน ต้องใช้ grep -oE '[A-Z]{2},' ที่มี comma แล้วค่อย tr -d ',' เพื่อให้ได้ country code อย่างเดียว

ดูว่าเป็น bot หรือมนุษย์

แยกการกระจายตัวของ fail ตามชั่วโมง จะบอกได้คร่าวๆ ว่าใครเป็นคนกด

grep "set_id=$ACCT" $LOG | grep 'authenticator failed' \
  | awk '{print substr($2,1,2)":00"}' | sort | uniq -c

ตีความ

  • peak ช่วง 08:00 / 13:00 / 16:00 = เครื่อง office จริง (Outlook auto-poll ตามรอบเวลาทำงาน)
  • กระจายเท่าๆ กันทุกชั่วโมง = bot หรือ cron retry ตายตัว
  • burst หนักช่วงเดียว เช่น 10 ครั้งขึ้นใน 1 นาที = brute force ของจริง

10 fail ล่าสุด

grep "set_id=$ACCT" $LOG | grep 'authenticator failed' | tail -10

ใน 10 บรรทัดนี้ผมจะมองหา

  • H=(hostname) ซ้ำกันไหม ถ้าซ้ำแปลว่าเครื่องเดียวกันลองซ้ำๆ
  • [IPv6:::ffff:192.168.1.x] ตัวไหนโผล่บ่อย นั่นคือ LAN IP ของเครื่องที่รหัสผิด
  • timestamp กระชั้น น้อยกว่า 5 วินาที แปลว่าเป็น mail client retry อัตโนมัติ

3. เจาะ 1 IP — ใช้ account ไหนบ้าง

ใช้ตอนเห็น IP โผล่ใน csf.deny หรือ lfd.log แล้วอยากรู้ว่าเป็น attacker จริง หรือเป็น office ที่รหัสผิด

Account ที่ IP นี้พยายาม login แล้ว fail

IP=203.0.113.x
LOG=/var/log/exim/mainlog
grep -F "$IP" $LOG | grep 'authenticator failed' \
  | grep -oE 'set_id=[^ )]+' | sort | uniq -c | sort -rn

ตีความ

  • 1 account แต่ fail หลายร้อยครั้ง = stuck client (cached password ผิด)
  • หลาย account fail กระจาย = credential stuffing (attacker ไล่ลิสต์ leaked creds)
  • 1 account แต่มี success อื่นมาก่อน = ผู้ใช้จริงที่เพิ่งเปลี่ยนรหัส หรือ device ค้าง

HELO hostname ที่ IP นี้ประกาศ

grep -F "$IP" $LOG | grep -oE 'H=\([^)]+\)' | sort | uniq -c | sort -rn | head

H=(DESKTOP-AB1234) ตามด้วย Outlook คือ Windows, H=(iPhone) คือ device ส่วนตัว, H=(localhost) หรือ H=(WIN-XXXXXX) มักเป็น bot หรือ VPS

LAN IP ที่ IP นี้ประกาศ เห็นได้เฉพาะกรณีอยู่หลัง NAT

grep -F "$IP" $LOG | grep -oE 'IPv6:::ffff:[0-9.]+' | sort | uniq -c

Success ที่ IP นี้เคยมีไหม เอาไว้ sanity check

grep -F "$IP" $LOG | grep -oE 'A=[a-z_]*:[^ ]+' | sort | uniq -c
กฎตัดสินใจที่ผมใช้ประจำ

ถ้า IP นี้เคยมี A= success ในอดีต ด้วย account เดียวกับที่ fail อยู่ตอนนี้ ให้ถือว่าเป็น stuck client แล้ว whitelist ไว้ พร้อมแจ้งลูกค้าให้ไปแก้ที่ device แต่ถ้าไม่เคยมี success เลย ให้ถือว่าเป็น attacker แล้วปล่อย block ไว้ตามเดิม แค่นี้ตัดสินใจได้แล้วในเคสส่วนใหญ่

4. หาเครื่องใน LAN — ใช้ internal NAT IP เป็น fingerprint

เคสที่เจอบ่อย ลูกค้าบอกว่า office มีหลายเครื่อง แล้วเครื่องไหนรหัสผิด ผมใช้ LAN IP ที่อยู่ใน [IPv6:::ffff:192.168.1.x] เป็น signature เฉพาะตัวของแต่ละเครื่อง

ดูว่า LAN IP นี้ลอง login account ไหน

INT=192.168.1.56
LOG=/var/log/exim/mainlog
grep "IPv6:::ffff:$INT" $LOG | grep 'authenticator failed' \
  | grep -oE 'set_id=[^ )]+' | sort | uniq -c | sort -rn

LAN IP นี้เคย login สำเร็จด้วย account ไหน เผื่อมันเปลี่ยน password แล้วแต่ยังค้างของเก่า

grep "IPv6:::ffff:$INT" $LOG | grep -oE 'A=[a-z_]*:[^ ]+' | sort | uniq -c

รายการ LAN IP ใน office ที่ login เข้ามาวันนี้ทั้งหมด

TODAY=$(date +%F)
LOG=/var/log/exim/mainlog
awk -v d="$TODAY" '$1==d' $LOG \
  | grep -oE 'IPv6:::ffff:(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)[0-9.]+' \
  | sed 's/IPv6:::ffff://' | sort | uniq -c | sort -rn
ไม่ใช่ทุก client ที่ส่ง LAN IP

ผมเจอบ่อยว่าไม่ใช่ทุก mail client จะส่ง LAN IP มาใน EHLO โดยทั่วไป Outlook บน Desktop ส่ง แต่ iPhone, Mac Mail และ webmail ไม่ส่ง ถ้าหาไม่เจอ LAN IP เลย แปลว่าลูกค้าน่าจะใช้ iPhone หรือ webmail นั่นเอง อย่าเพิ่งสรุปว่าไม่มีเครื่องในออฟฟิศ

5. หลักฐาน stuck client = IP office เดียวกัน

ใช้ตอนลูกค้ายืนยันว่าไม่มี device ไหนรหัสผิด เราต้องพิสูจน์ให้เห็นว่า IP ที่ fail คือ IP เดียวกับที่ office กำลัง success อยู่ตอนนี้

DOM=customer.com
LOG=/var/log/exim/mainlog

echo "== Office IPs (มี success อย่างน้อย 1 ครั้ง) =="
grep "A=[a-z_]*:.*@$DOM" $LOG | sed -nE 's/.*\[([0-9.]+)\].*/\1/p' \
  | sort | uniq -c | sort -rn

echo ""
echo "== OK vs FAIL ต่อแต่ละ IP =="
for ip in $(grep "A=[a-z_]*:.*@$DOM" $LOG | sed -nE 's/.*\[([0-9.]+)\].*/\1/p' | sort -u); do
  ok=$(grep "A=[a-z_]*:.*@$DOM" $LOG | grep -c "\[$ip\]")
  fail=$(grep "set_id=.*@$DOM" $LOG | grep "\[$ip\]" | grep -c 'authenticator failed')
  printf '%-16s OK=%-4s FAIL=%s\n' "$ip" "$ok" "$fail"
done | sort -k3 -t= -rn

echo ""
echo "== LAN IP ที่ login OK (เครื่อง office จริง) =="
grep "A=[a-z_]*:.*@$DOM" $LOG | grep -oE '\(\[192\.168\.[0-9.]+\]\)' \
  | tr -d '()[]' | sort | uniq -c

echo ""
echo "== LAN IP ที่ fail =="
grep "set_id=.*@$DOM" $LOG | grep 'authenticator failed' \
  | grep -oE 'IPv6:::ffff:192\.168\.[0-9.]+' \
  | sed 's/IPv6:::ffff://' | sort | uniq -c
การส่งงานต่อให้ลูกค้า

ถ้า IP ที่ fail ตรงกับ IP ที่ office success ให้ส่งข้อความสั้นๆ ทำนองว่า "LAN IP 192.168.1.y ของ office รหัสผิด account=user1 fail 116 ครั้งวันนี้" แล้วให้ลูกค้าเดินไปแก้ password ที่เครื่องนั้นเอง เพราะถ้าเราไปแก้ให้ มันจะวนไม่จบ ต้นตอคือ device ค้าง ไม่ใช่ฝั่ง server

6. Audit domain — account ไหน active หรือ inactive

ก่อนจะลบ mail account เก่า ผมจะดูก่อนว่ามันมี success ใน N วันที่ผ่านมาหรือไม่ จะได้ไม่ลบของที่ยังใช้อยู่

DOM=customer.com
SINCE='2026-05-18'              # วันเริ่มต้นที่จะนับ
LOG=/var/log/exim/mainlog

# Mail accounts ที่ configure ไว้
cat /etc/virtual/$DOM/passwd | awk -F: '{print $1"@'$DOM'"}' | sort > /tmp/conf

# Accounts ที่มี success AUTH ตั้งแต่ $SINCE
awk -v s="$SINCE" '$1 >= s' $LOG \
  | grep -oE 'A=[a-z_]*:[^ ]+@'$DOM \
  | sed 's/^A=[a-z_]*://' | sort -u > /tmp/act

echo "-- INACTIVE accounts (ไม่ login เลยตั้งแต่ $SINCE) --"
comm -23 /tmp/conf /tmp/act

echo ""
echo "-- ACTIVE accounts --"
cat /tmp/act
ก่อนลบให้เช็ค alias ด้วย

ตรวจ /etc/virtual/$DOM/aliases ก่อนเสมอ ถ้า account อยู่ในรูป catch-all หรือ forwarding ต้องเก็บไว้ อย่าดูแค่ว่าไม่มี login แล้วลบทิ้ง เพราะบางบัญชีมีไว้รับ forward อย่างเดียวไม่เคย login

7. Server-wide stuck client scan

อันนี้ผมใช้ตอนเช้าก่อน office เปิดเครื่อง เพื่อดูทั้ง server ว่ามีกี่ account ที่มี stuck client ค้างอยู่ จะได้แก้เชิงรุกก่อนลูกค้าโทรมา

LOG=/var/log/exim/mainlog
tmpok=$(mktemp); tmpfail=$(mktemp)

# ดึงคู่ (IP, account) จาก success ทั้งหมด
grep -a -E 'A=[a-z_]*:[^ ]+@[a-z0-9.-]+' $LOG \
  | sed -nE 's/.*\[([0-9.]+)\].*A=[a-z_]*:([^ ]+).*/\1|\2/p' \
  | sort -u > $tmpok

# ดึงคู่ (IP, account) จาก fail
grep -a 'authenticator failed' $LOG \
  | sed -nE 's/.*\[([0-9.]+)\].*set_id=([^ )]+).*/\1|\2/p' \
  | sort -u > $tmpfail

# จับ IP ที่: success บาง account + fail account อื่นใน domain เดียวกัน = stuck mixed
for ip in $(awk -F'|' '{print $1}' $tmpok | sort -u); do
  for acct in $(grep "^$ip|" $tmpfail | awk -F'|' '{print $2}'); do
    dom=${acct#*@}
    [ "$(grep -c "^$ip|[^@]*@$dom$" $tmpok)" -eq 0 ] && continue
    ok=$(grep -F "$ip" $LOG | grep -c "A=[a-z_]*:$acct")
    fail=$(grep -F "$ip" $LOG | grep 'authenticator failed' | grep -c "set_id=$acct")
    [ "$fail" -eq 0 ] && continue
    printf '%-35s %-16s OK=%-4s FAIL=%s\n' "$acct" "$ip" "$ok" "$fail"
  done
done | sort -k4 -t= -rn

rm -f $tmpok $tmpfail

ผลลัพธ์ที่ได้คือรายการ (account, IP, ok, fail) ที่ IP นั้นเคย login account อื่นใน domain เดียวกันได้สำเร็จ ซึ่งเป็นสัญญาณชัดเจนว่ามี stuck client ใน office นั้น

8. False-positive check — IP ถูก block ผิดหรือเปล่า

ลูกค้าโทรมาบ่นว่าส่งเมลไม่ได้ทั้งสำนักงาน ก่อนตอบ ผมจะดูก่อนว่า IP ที่ถูก block เป็น customer จริงหรือ attacker

TODAY='May 23'                                  # syslog format ไม่ใช่ ISO
LOG=/var/log/exim/mainlog
grep "^$TODAY " /var/log/lfd.log \
  | grep 'Blocked in csf' \
  | grep -oE '([0-9]+\.){3}[0-9]+' | sort -u | while read ip; do
    cc=$(geoiplookup $ip 2>/dev/null | grep -oE '[A-Z]{2},' | head -1 | tr -d ',')
    ok=$(grep -ac "A=[a-z_]*:.*\[$ip\]" $LOG)
    trig=$(grep "$ip" /var/log/lfd.log | tail -1 \
           | grep -oE '\((smtpauth|credscan|distattack|sshd|modsec)\)' | tr -d '()')
    note=''
    [ "$cc" = "TH" ] && note='[!]TH'
    [ "$ok" -gt 0 ] && note="$note success=$ok"
    printf '%-16s %-4s ok=%-3s %-12s %s\n' "$ip" "${cc:-??}" "$ok" "${trig:--}" "$note"
done
Date format trap

mainlog ใช้รูปแบบ ISO เช่น 2026-05-23 แต่ lfd.log ใช้ syslog เช่น May 23 ถ้าใช้ format ผิดข้ามไฟล์ จะ grep ได้ผลเปล่าๆ โดยไม่มี error ให้เห็น เป็นจุดที่หลายคนพลาด

ตีความ

  • [!]TH + success มากกว่า 0 = ลูกค้าจริง ให้ปลดบล็อกแล้ว whitelist 24 ชั่วโมง
  • ประเทศไม่ใช่ TH + ok=0 + trigger เป็น smtpauth = attacker brute force ปล่อย block ไว้
  • ประเทศไม่ใช่ TH + ok=0 + trigger เป็น credscan = scanner หรือ bot ปล่อย block ไว้

ปลดบล็อกแล้ว whitelist 24 ชั่วโมง เมื่อยืนยันแล้วว่าเป็น customer

IP=203.0.113.x
csf -dr "$IP"                                                # ลบจาก csf.deny
csf -tr "$IP"                                                # ลบจาก csf.tempban
csf -ta "$IP" 86400 "customer office — manual"               # whitelist 24h
ทำไมต้อง csf -ta ไม่ใช่แค่ -dr

ถ้ายังมี ghost device ใน LAN เดารหัสอยู่ การลบออกจาก deny อย่างเดียวจะทำให้มันถูก block ใหม่ภายในไม่กี่นาที การ csf -ta ใส่ allow ชั่วคราวมีอายุ ทำให้ลูกค้าทำงานต่อได้ระหว่างที่เราตามไปแก้ device เคสนี้ผมเจอบ่อยมาก

9. Whitelist behavior — ทำไม log fail ยังขึ้นแม้ใส่ whitelist แล้ว

คำถามคลาสสิกจากลูกค้า ใส่ whitelist แล้ว ทำไม log ยังเตือนรหัสผิด คำตอบคือ whitelist ของ CSF (csf -a, csf -ta, csf.allow, csf.ignore) ทำงานอยู่ที่ชั้น iptables กับ lfd เท่านั้น การ AUTH ที่ผิดยังคงเดินทางต่อ

  • ยังถึง exim
  • ยังได้ response 535 Incorrect authentication data
  • ยังเขียน entry ลง mainlog
  • lfd ยังนับเข้า threshold แต่ไม่สั่ง csf -d

สรุปคือถ้าอยากให้ log หยุดจริงๆ ต้องไปแก้ที่ฝั่ง client เปลี่ยนรหัสบนเครื่อง หรือลบ account ออก เพราะ whitelist เป็นแค่ firewall layer ไม่ใช่ auth layer

whitelist ทำงานแค่ชั้นนี้ชั้น auth ไม่ถูกแตะ ยังตอบ 535clientรหัสผิดค้างชั้น firewallcsf.allow ปล่อยผ่านeximตอบ 535 ต่อmainlogเขียน entrylfdนับเข้า threshold ไม่สั่ง csf -d
whitelist ของ CSF อยู่ที่ชั้น firewall กับ lfd เท่านั้น การ AUTH ที่ผิดยังเดินต่อถึง exim และถูกเขียนลง mainlog

ความต่างระหว่าง csf.allow กับ csf.ignore

csf.allow

iptables + lfd

  • ปล่อยทุก packet ผ่าน
  • lfd ไม่ block IP นี้
  • เทียบเท่า csf -ta แต่ถาวร
csf.ignore

lfd เท่านั้น

  • iptables ยัง drop ได้ถ้ามี rule อื่น
  • แค่ lfd ไม่สั่ง csf -d
  • ไม่เปิด packet ทั้งหมดเหมือน allow

ส่วน csf -ta IP TTL เทียบเท่า csf.allow แต่มี TTL ให้ auto-expire ตามเวลา

ดู whitelist ทั้งหมดที่ใช้อยู่

csf -t | grep -i allow                                       # temp allow (csf -ta)
grep -v '^#' /etc/csf/csf.allow | grep -v '^$'               # permanent
grep -v '^#' /etc/csf/csf.ignore | grep -v '^$'              # lfd-only ignore

10. Pattern reference — ดูอะไรแล้วบอกได้ว่าเครื่องอะไร

ส่วนนี้ผมเก็บไว้เป็น cheat sheet ของตัวเอง อ่านบรรทัดเดียวก็เดาประเภทเครื่องได้คร่าวๆ

HELO hostname patterns

  • H=(DESKTOP-XXXXX) หรือ H=(LAPTOP-XXXXX) = Windows + Outlook
  • H=(iPhone) / H=(iPad) = iOS Mail
  • H=(Macbook-Pro.local) = macOS Mail
  • H=(android-XXXX) = Android K-9 หรือ Gmail app
  • H=ppp-203-0-113-x.isp.example = rDNS default ของ ISP (ไม่ได้ตั้ง HELO ในเครื่อง)
  • H=(localhost) / H=(WIN-XXXX) = VPS, bot, spammer เป็นส่วนใหญ่
  • H=(mail-XXX.google.com) = Gmail SMTP relay (legit)

Authenticator patterns

  • A=plain: = client ส่วนใหญ่ (AUTH PLAIN)
  • A=login: = Outlook (AUTH LOGIN)
  • A=cram_md5: = client เก่ามาก น่าสงสัย
  • fail ทั้ง plain และ login ในวินาทีเดียว = client ลอง 2 mechanism นับเป็น 2 fail

LFD trigger reasons (จาก /var/log/lfd.log)

  • (smtpauth) = SMTP auth fail ครบ N ครั้งใน X วินาที (default LF_SMTPAUTH=10/300s)
  • (distattack) = account เดียวกัน fail จากหลาย IP (default LF_DISTATTACK=2 IPs)
  • (credscan) = HTTP probe ไฟล์อย่าง wp-config, .env, .git/config (ban ทันที 24 ชั่วโมง)
  • (modsec) = ModSecurity rule fired N ครั้ง (default LF_MODSEC=10/300s)
  • BFM:dovecot1=N = DirectAdmin Brute Force Monitor ฝั่ง IMAP/POP
  • BFM:exim1=N = DA BFM ฝั่ง SMTP
ข้อควรระวังเรื่อง BFM

DirectAdmin BFM เรียก csf -d ตรงๆ ไม่ผ่าน LFD ดังนั้น hook unblock อัตโนมัติที่ผูกไว้กับ LFD จะไม่ทำงานกับ block ของ BFM ผมเลยใส่ sweep cron เป็น safety net เพิ่มอีกชั้น กันลูกค้าค้าง block ข้ามคืนโดยไม่มีใครรู้

11. One-liners ที่ใช้บ่อย — เก็บไว้ในกระเป๋า

Top 10 IP ที่ fail วันนี้

awk -v d="$(date +%F)" '$1==d' /var/log/exim/mainlog \
  | grep 'authenticator failed' \
  | sed -nE 's/.*\[([0-9.]+)\].*/\1/p' | sort | uniq -c | sort -rn | head

Top 10 account ที่ถูกเดารหัสวันนี้

awk -v d="$(date +%F)" '$1==d' /var/log/exim/mainlog \
  | grep 'authenticator failed' \
  | grep -oE 'set_id=[^ )]+' | sort | uniq -c | sort -rn | head

รวม success เทียบกับ fail วันนี้

LOG=/var/log/exim/mainlog
TODAY=$(date +%F)
ok=$(awk -v d="$TODAY" '$1==d' $LOG | grep -c 'A=[a-z_]*:')
fail=$(awk -v d="$TODAY" '$1==d' $LOG | grep -c 'authenticator failed')
echo "today: success=$ok  fail=$fail"

ตรวจว่า IP อยู่ใน whitelist ครบทุกประเภทหรือยัง

IP=203.0.113.x
echo "-- csf.allow (permanent) --"; grep -F "$IP" /etc/csf/csf.allow
echo "-- csf -t ALLOW (temp)    --"; csf -t | grep -i allow | grep -F "$IP"
echo "-- csf.ignore (lfd-only)  --"; grep -F "$IP" /etc/csf/csf.ignore

สรุป workflow ตามอาการ

ผมใช้ลำดับนี้เป็น flowchart ในหัวเวลาลูกค้าโทรเข้ามา

  • ลูกค้าบ่นว่าส่งเมลไม่ได้ ไปที่หัวข้อ 2 Per-account
  • IP โดน block ซ้ำๆ ไปที่หัวข้อ 3 Per-IP
  • สงสัย device ใน office รหัสผิด ไปที่หัวข้อ 4 และ 5 เรื่อง LAN กับ stuck client
  • ลูกค้าถามว่า account ไหนใช้หรือไม่ใช้ ไปที่หัวข้อ 6 Domain audit
  • อยาก audit ทั้ง server ไปที่หัวข้อ 7 Server-wide stuck scan
  • ลูกค้าบ่นว่า block ผิด ไปที่หัวข้อ 8 False-positive check
  • ลูกค้าถามว่าใส่ whitelist แล้วทำไม log ยังขึ้น ไปที่หัวข้อ 9 Whitelist behavior

ทั้งหมดนี้ผมก็ลองผิดลองถูกมาเรื่อยๆ จนตกผลึกเป็นท่ามาตรฐานในหัว เห็นว่าน่าจะเป็นประโยชน์เลยเอามาเขียนรวมไว้ ใครมีท่าเพิ่ม regex ที่ดีกว่านี้ script เก็บ stat อัตโนมัติ หรือทำเป็น dashboard เลย ก็ลงคอมเมนต์บอกได้ จะลองเอาไปปรับใช้ ใครถนัด postfix หรือ iptables ก็ปรับ regex ตามระบบตัวเองได้ ตามสะดวก หลักการเหมือนกันหมด

อยากให้เราทำให้ไหม?

ให้เราช่วยติดตั้ง ตั้งค่า ย้ายระบบ หรือแก้ปัญหาเซิร์ฟเวอร์ เพื่อให้คุณมีเวลาไปทำงานที่สำคัญกว่า

คุยเรื่องงานกับเรา

เคยเจองานแบบนี้ไหม? ติดตรงไหน?

มาแลกวิธีกันต่อในบอร์ด LinuxThai

→ คุยต่อในบอร์ด

ความคิดเห็น 0

ยังไม่มีความคิดเห็น — เป็นคนแรกเลย