TCP/IP: SYN flood атака на сервер RTFM, та “Hacker News hug of death”
0 (0)

Автор |  02/01/2026
Click to rate this post!
[Total: 0 Average: 0]

Прилітає сьогодні зранку алерт від моніторингу, що блог впав:

Ну, думаю – знов якийсь DDoS, не перший раз.

Investigating the issue

Йду в Cloudflare, вмикаю Under Attack Mode, і починаю розбиратись.

Дивлюсь запити:

Ага, думаю, ващє фігня – з одного IP запити, зараз його забаню, і готово.

Додаю нове правило з Action = Block в Cloudflare Security Rules, і пішов глянути – що з IP такий?

Whois каже, що якийсь хост з DigitalOcean:

$ whois 46.101.201.123
...
inetnum:        46.101.128.0 - 46.101.255.255
abuse-c:        AD10778-RIPE
netname:        DIGITALOCEAN
...

Там часто всякі боти запускаються, нічого незвичного.

Далі, вирішив з nmap глянути що за сервіси є на тому атакуючому хості:

# nmap -sS 46.101.201.123
...
PORT     STATE    SERVICE
22/tcp   open     ssh
80/tcp   open     http
443/tcp  open     https
...

Хм, думаю – дивно, що за бот такий, що має 80 і 443 порти?

Відкриваю https://46.101.201.123 в браузері, і… попадаю на власний блог 🙂

Шта?…

Перевіряю, які IP в мене в DigitalOcean, і:

Тобто – да, 46.101.201.123 – це Droplet IP сервера, на якому хоститься RTFM.

Хоча взагалі на DNS в IN A для rtfm.co.ua використовується DigitalOcean Reserved IP, який можна переключати між дроплетами:

Тобто:

  • NS для rtfm.co.ua – Cloudflare
  • на них IN A 67.207.75.157
  • Droplet IP 46.101.201.123 не вказаний ніде
  • але запити йдуть на нього

Окей…

Тут ще буде окреме питання – чому в CloudFlare показувались запити від 46.101.201.123 – але про це в кінці.

SYN flood та підключення в SYN_RECV 

Пішов глянути що взагалі на сервері в нетворкінгу, які активні конекти?

А там…

Купа підключень в статусі SYN_RECV – класичний SYN flood: клієнт нам відправляє TCP-пакет з флагом SYN, ми йому відповіли з SYN-ACK, і чекаємо на ACK від нього – але він не приходить, а ресурси CPU/RAM сервера на очікування зайняті (див. TCP handshake, нещодавно писав).

Mitigating the issue

Так як підключення йдуть не через CloudFalre – то і його Security Rules нам не допоможуть.

А Network Firewall в Digital Ocean, як і Security Groups в AWS вміють тільки в Allow правила – але не в Deny (в AWS можна зробити Deny через правила у VPC NACL – Network Access Control List).

Linux Kernel TCP tuning

В першу черги тюнимо параметри TCP-стеку ядра:

# sysctl -w net.ipv4.tcp_syncookies=1
net.ipv4.tcp_syncookies = 1
# sysctl -w net.ipv4.tcp_max_syn_backlog=4096
net.ipv4.tcp_max_syn_backlog = 4096
# sysctl -w net.ipv4.tcp_synack_retries=2
net.ipv4.tcp_synack_retries = 2

Тут:

  • net.ipv4.tcp_syncookies: вмикаємо SYN cookies – ядро може не тримати стан TCP-підключення, коли backlog переповнений
  • net.ipv4.tcp_max_syn_backlog: збільшуємо розмір беклогу для SYN/SYN-ACK, аби реальні клієнти не відвалювались
    • дефолт 256
  • net.ipv4.tcp_synack_retries: обмежуємо кількість спроб ядра відповісти на SYN – скільки раз шлемо SYN-ACK, якщо клієнт не повернув ACK
    • дефолт 5

Вже стало краще:

 

Далі можна на DigitalOcean firewall дозволити доступ тільки з мереж Cloudflare – але вони змінюються, а робити якусь авторизацію зараз влом.

Iptables та DROP by Source Address

Можна, звісно, банити атакуючі мережі – на момент перевірки була одна 177.36.16.0/20:

# netstat -anp | grep 46.101.201.123 | grep SYN_RECV
tcp        0      0 46.101.201.123:443      177.36.16.214:15795     SYN_RECV    -                   
tcp        0      0 46.101.201.123:443      177.36.16.152:43548     SYN_RECV    -                   
tcp        0      0 46.101.201.123:443      177.36.17.0:43309       SYN_RECV    -                   
tcp        0      0 46.101.201.123:443      177.36.17.237:47283     SYN_RECV    -

Додаємо правило з DROP і логуванням для перевірки:

# iptables -I INPUT -s 177.36.16.0/20 -j LOG --log-prefix "DROP 177.36.16.0/20 "

Дивимось, чи працює правило:

# journalctl -k | grep "DROP 177.36.16.0/20" | head
Jan 02 08:34:35 setevoy-do-2023-09-02 kernel: DROP 177.36.16.0/20 IN=eth0 OUT= MAC=de:71:8d:d9:82:55:fe:00:00:00:01:01:08:00 SRC=177.36.16.242 DST=46.101.201.123 LEN=52 TOS=0x00 PREC=0x00 TTL=54 ID=40235 DF PROTO=TCP SPT=17587 DPT=443 WINDOW=65535 RES=0x00 SYN URGP=0 
Jan 02 08:34:37 setevoy-do-2023-09-02 kernel: DROP 177.36.16.0/20 IN=eth0 OUT= MAC=de:71:8d:d9:82:55:fe:00:00:00:01:01:08:00 SRC=177.36.16.193 DST=46.101.201.123 LEN=52 TOS=0x00 PREC=0x00 TTL=56 ID=8752 DF PROTO=TCP SPT=45940 DPT=443 WINDOW=65535 RES=0x00 SYN URGP=0 
...

Залишаємо правило, але прибираємо запис в лог, бо це зайве навантаження на диск і систему:

# iptables -R INPUT 1 -s 177.36.16.0/20 -j DROP

Вже краще – але, очікувано, пішли підключення з інших адрес:

# netstat -anp | grep 46.101.201.123 | grep SYN_RECV
tcp        0      0 46.101.201.123:443      45.94.171.239:48242     SYN_RECV    -                   
tcp        0      0 46.101.201.123:443      146.103.26.224:30654    SYN_RECV    -                   
tcp        0      0 46.101.201.123:443      91.124.63.174:45287     SYN_RECV    -                   
tcp        0      0 46.101.201.123:443      194.116.228.226:15311   SYN_RECV    -

Iptables та DROP by Destination Address

Ну і насправді тут є дуже просте рішення:

  • валідні запити мають йти тільки на Reserved IP, який вказаний в Cloudflare DNS
  • запити на Droplet IP на порт 443 взагалі не мають приходити

Тому просто банимо їх з iptables:

# iptables -A INPUT -p tcp -d 46.101.201.123 --dport 443 -j DROP

Тепер жодного SYN_RECV не залишилось.

Зберігання правил з iptables-persistent

Перевіряємо правила зараз:

# iptables -L INPUT -n --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    DROP       0    --  177.36.16.0/20       0.0.0.0/0           
2    DROP       6    --  0.0.0.0/0            46.101.201.123       tcp dpt:443

Аби зберігати їх при ребутах системи – встановлюємо iptables-persistent:

# apt install iptables-persistent

Під час установки він запропонує зберегти правила в файл /etc/iptables/rules.v4:

Перевіряємо що там:

# cat /etc/iptables/rules.v4 | grep 46.101.201.123
-A INPUT -d 46.101.201.123/32 -p tcp -m tcp --dport 443 -j DROP

Готово.

Але це буде працювати, поки не почнуть флудити на сам Reserved IP 67.207.75.157.

Тоді вже доведеться робити через дозвіл тільки з Cloudflare IPs.

Bonus: WordPress, Cloudflare та запити від Droplet IP

Але після того, як із SYN flood начебто розібрався – графіки кількості запитів в Cloudflare не зменшились.

І це логічно – бо SYN взагалі йшов напряму до сервера на IP 46.101.201.123, а не через Cloudflare, а там ці запити в Clodflare взагалі не трекались.

При цьому в логах Cloudflare від IP 46.101.201.123 всюди був один і той самий Path до файлу /wp-content/uploads/2025/11/freebsd_logo1.jpg:

Графіки Cloudflare за останні 6 годин виглядали так – в топі Source IPs зліва внизу саме 46.101.201.123:

Тут я вже напрягся:

  • SYN flood почався близько 10 ранку за Києвом
  • в цей жеж час є спайк запитів до Cloudflare від самого сервера RTFM

Тобто виглядало так, ніби на сервері якийсь код постійно звертається до одного і того самого URL на самому сервері.

Відключив Cloudflare WordPress плагін – ні, запити не спадають.

Відключив WP_CRON – теж не допомогло.

WTF is going on? – судорожно думав я, і додумався, що пора б включити і подивитись NGINX access logs і подивитись, що взагалі на сервер приходить.

А access logs побачив купу записів виду:

...
[02/Jan/2026:11:07:35 +0000] "GET /en/freebsd-home-nas-part-1-configuring-zfs-mirror-raid1/ HTTP/1.1" 200 35451 "-" "HackerNews/1536 CFNetwork/3860.200.71 Darwin/25.1.0"
[02/Jan/2026:11:07:42 +0000] "GET /en/freebsd-home-nas-part-1-configuring-zfs-mirror-raid1/ HTTP/1.1" 200 35462 "https://news.ycombinator.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36"
[02/Jan/2026:11:07:43 +0000] "GET /wp-json/pvc/v1/increase/33806 HTTP/1.1" 200 99 "https://rtfm.co.ua/en/freebsd-home-nas-part-1-configuring-zfs-mirror-raid1/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36"
...

HackerNews, ycombinator? Вау…

А друге – сама причина 46.101.201.123 в логах Clodflare: “GET /wp-json/pvc/v1/increase/” – це запит до WordPress-плагіна Page View Count, який не так давно включив. А “https://rtfm.co.ua/en/freebsd-home-nas-part-1-configuring-zfs-mirror-raid1″ – звідки запит був зроблений.

Тобто, плагін на сторінці поста робить запит, аби збільшити лічильник переглядів – при цьому підставляючи Referrer у вигляді тої сторінки, звідки він запит робить.

Cloudflare жеж бачить, що запит йде від Origin – і використовує в логах Droplet IP.

Ну а далі вже проста перевірка – що коїться взагалі з постом FreeBSD: Home NAS, part 1 – configuring ZFS mirror (RAID1):

Це при тому, що зазвичай переглядів кілька десятків, ну максимум 100-200.

І перевірка в гуглі вже показала причину такого напливу:

А трапилось те, що я сьогодні вранці перший раз запостив лінк на https://lobste.rs, звідки його перепостили на Hacker News, і я отримав “Hacker News hug of death” – див. Surviving the Hug of Death, де у людини була схожа ситуація.

Після відключення плагіну Page View Count – Droplet IP 46.101.201.123 в Cloudflare зник.

Loading