Сказ о том, как я сервер с WP лечил, или 700 юзеров онлайн на 2х ядрах

в 20:34, , рубрики: cache, cache-control, fastcgi_cache, mariadb, nginx, php-7.0, php-fpm, wordpress, безопасность сайтов, Серверная оптимизация

День добрый, пятница ясная, бравый молодец иль девица красная!

Можешь мне верить, можешь мне не верить, но начался сей сказ с пары весточек на мою почту электронную и вот такой вот картины, красоты неписанной:

Сказ о том, как я сервер с WP лечил, или 700 юзеров онлайн на 2х ядрах - 1

Это 500 бравых молодцев онлайн (по депеше от гугла) на движке заморском, wordpress именуемом, на сервере Intel Xeon E3 1245v2 (soyoustart, E3-SSD-3). К полотну была приложена рукописъ, помочь в оптимизации сего хозяйства.

Disclaimer: В данном материале показана быстрая диагностика глючного сервера и приведены конфиги, которые можно счесть полезными. Если вам есть что дополнить или критиковать, не стесняйтесь писать об этом в комментариях, ибо одна голова хуже две, две хуже чем три, а n-1 хуже чем n. Заразки, выдранные с сервера залиты на gist с соотв. комментариями как private, ясен пень, что не стоит их запускать на системе, для этого есть debug и ideone.com .

Я все еще лениво висел в скайпе и хотел снять порчу по телефону, как правило это получается, ибо копипаста не врет, пока пробелы после паролей не копируют.

netstat -ntu | awk '{print $5}'| cut -d: -f1 | sort | uniq -c | sort -nr | more

выдал

Сказ о том, как я сервер с WP лечил, или 700 юзеров онлайн на 2х ядрах - 2После команды

iptables -I INPUT -s 188.138.89.112 -j DROP

стало красиво

Сказ о том, как я сервер с WP лечил, или 700 юзеров онлайн на 2х ядрах - 3

Но весь php встал на дыбы, в том числе и залитый phpinfo().

Все еще подгоняемый желанием решить удаленно и нежеланием подключаться я попросил запустить

tcpdump -i eth0 dst host 188.138.89.112 -v -XX

и следом разбанить

iptables -D INPUT -s 188.138.89.112 -j DROP

После чего мне отправили лог:

        Host: broin.top
        Accept: */*

        0x0000:  0007 b400 0102 3860 7713 ffbe 0800 4500  ......8`w.....E.
        0x0010:  006f 900d 4000 4006 0592 2e69 6086 bc8a  .o..@.@....i`...
        0x0020:  5970 d8f5 0050 3be0 6f97 6bd8 b0e4 8018  Yp...P;.o.k.....
        0x0030:  00e5 a54b 0000 0101 080a 03dd ffa0 4221  ...K..........B!
        0x0040:  da17 4745 5420 2f6c 6e6b 2f69 6e6a 2e70  ..GET./lnk/inj.p
        0x0050:  6870 2048 5454 502f 312e 310d 0a48 6f73  hp.HTTP/1.1..Hos
        0x0060:  743a 2062 726f 696e 2e74 6f70 0d0a 4163  t:.broin.top..Ac
        0x0070:  6365 7074 3a20 2a2f 2a0d 0a0d 0a         cept:.*/*....
20:33:17.765232 IP (tos 0x0, ttl 64, id 50803, offset 0, flags [DF], proto TCP (6), length 52)
    server.s.55540 > xray874.dedicatedpanel.com.http: Flags [.], cksum 0xa510 (incorrect -> 0x6231), ack 15929, win 477, options [nop,nop,TS val 64880546 ecr 1109514777], length 0
        0x0000:  0007 b400 0102 3860 7713 ffbe 0800 4500  ......8`w.....E.
        0x0010:  0034 c673 4000 4006 cf66 2e69 6086 bc8a  .4.s@.@..f.i`...
        0x0020:  5970 d8f4 0050 8d4b 3a3b a61a 0724 8010  Yp...P.K:;...$..
        0x0030:  01dd a510 0000 0101 080a 03dd ffa2 4221  ..............B!
        0x0040:  da19                                     ..
20:33:17.765486 IP (tos 0x0, ttl 64, id 50804, offset 0, flags [DF], proto TCP (6), length 52)
    server.s.55540 > xray874.dedicatedpanel.com.http: Flags [.], cksum 0xa510 (incorrect -> 0x5c72), ack 17377, win 500, options [nop,nop,TS val 64880546 ecr 1109514777], length 0
        0x0000:  0007 b400 0102 3860 7713 ffbe 0800 4500  ......8`w.....E.
        0x0010:  0034 c674 4000 4006 cf65 2e69 6086 bc8a  .4.t@.@..e.i`...
        0x0020:  5970 d8f4 0050 8d4b 3a3b a61a 0ccc 8010  Yp...P.K:;......
        0x0030:  01f4 a510 0000 0101 080a 03dd ffa2 4221  ..............B!

Видно, что дергается http://broin.top/lnk/inj.php, вот ссылка на gist и на декодированный.
Моя лень подсказала несчастному админу, мол найти все моменты влючения этого безобразия. Файлы были найдены, пути относительно «корня сайта»:

Следом я попросил скинуть мне index.php из WP и получил вот такое чудо

@include_once('./wp-includes/wp-mod.php');

В итоге под раздачу попала пачка файлов в /wp-includes/:

  • 9f120c79d956d543a1cd44902f0f50056b3338cc1136effb6c208bf46fcf74fd wp-010617.php
  • d0eb12533eed5d316504573976e611a71fd066f8fe5591422f6f49cdcaa8ff5e wp-0bf.php
  • cfb91fbc45eb7a74791a7845e7b18c2b8771b217ee27150a6f8422a1a073cc6d wp-accuracy.php
  • d67051bc4d20782f770dac771cfdf68e876156a0b012be2462f8a436dad07ceb wp-file.php
  • 5503f88744374570f9168216ee174ced87c80ef17d8f17accb2001f985862267 wp-fiscal.php
  • ee53973940016c403f457762e0c270d9729a101337bcdf3d6e4667b050549316 wp-mod.php
  • 5361bdbd75b368e79d17a47282f0ce6cd2759ccfdef8833ff38fe2a5dee95170 wp-obf.php
  • 89e96f69b8cb9d54959b300cc03b1312a0500c24ddffb6659e6acb688a1cfd8d wp-pas.php
  • 6c752d54255ead4b7db10e00f7b53ba9132b0c143c5bb3f667e2a81eb98801cc wp-wso.php

 

В общем больше нельзя было оставаться в стороне, и я забрал ssh доступ на скромный свеже установленный (февраль 2017) debian 7, php 5.4, демо ISP панель, приправленный proftpd… апачем до кучи (сайтами же рулил php-fpm)…

После поиска php файлов в разных директориях я нарвался на /wp-content/uploads/:

  • cfb91fbc45eb7a74791a7845e7b18c2b8771b217ee27150a6f8422a1a073cc6d wp-accuracy.php
  • 5503f88744374570f9168216ee174ced87c80ef17d8f17accb2001f985862267 wp-fiscal.php
  • 9b53f3e7243b78e6d0978817aab98c58e4598aa6a3c012ed10621b6e59c9283e wp-uso.php

Затем в папке /wp-content/plugins/, схожие файлы загружать не стал:

  • cfb91fbc45eb7a74791a7845e7b18c2b8771b217ee27150a6f8422a1a073cc6d wp-accuracy.php
  • 510785b58d10fc7687a63548adffc4cee82b68974fe1c349824d90cbfbfe39e7 wp-dojika.php
  • d67051bc4d20782f770dac771cfdf68e876156a0b012be2462f8a436dad07ceb wp-file.php
  • 5503f88744374570f9168216ee174ced87c80ef17d8f17accb2001f985862267 wp-fiscal.php
  • 86a4d9ef20d506eab64ece2ef1c6555bc2404c4ded0aac6a846d64837e1a509b wp-jojiro.php
  • 5361bdbd75b368e79d17a47282f0ce6cd2759ccfdef8833ff38fe2a5dee95170 wp-obf.php
  • 5361bdbd75b368e79d17a47282f0ce6cd2759ccfdef8833ff38fe2a5dee95170 wp-orekio.php
  • 89e96f69b8cb9d54959b300cc03b1312a0500c24ddffb6659e6acb688a1cfd8d wp-pas.php
  • 6c752d54255ead4b7db10e00f7b53ba9132b0c143c5bb3f667e2a81eb98801cc wp-wso.php

wp-dojika.php — фанат «Game of trones» как и wp-jojiro.php.

Как видно — они обфусцированы. В том или иной мере, что следующим шагом я выбрал все php с

grep -A 3 -B 3 --include=*.php -rnw '/path/to/www/' -e ".*base64_decode.*"

и нашел еще один, WAIpro.php, из которого выдирается вот такой вот декодированный файлик, который дергает много разной гадости, как вот к примеру apiword.press/addadmin_1.txt (gist).

Файлы были перемещены для будущих ковыряний.

Траффик упал очень сильно. Через сутки зайдя и увидя повышенный траф я прописал

tcpdump -i eth0 dst port 25 -v -XX

я получил простыню, которая вертелась быстрее, чем армейский вентилятор из одноименного адегдота. После выключения php проблема не исчезла. Не верю, сказал я и ввел crontab -u admin -e.

… и, вишенкой на торте, стал вывод

crontab -u admin -e

*/10 * * * * /var/tmp/eumqvTiN >/dev/null 2>&1

Затем sha256sum:

694fc1f7f17d5f3c447fcdb83fa6177b736b241430e70309a4b3111ef1d0e3b9 eumqvTiN

Который некто иной, как Linux/Mumblehard.U. Как он туда попадал… честно разбираться уже было лень. К счастью у несчастного админа была его старая виртуалка (OVH VPS SSD 3), проплаченная до 21 февраля, с которое сие было перенесено другим гореадмином 4 февраля по причине, того, что она (виртуалка) не справлялась с нагрузкой. И «несчастный админ» вышел на меня 6 февраля с простой просьбой по оптимизации сего хозяйства.

В итоге, сайт (как это возможно) был очищен от налепленных на него бяк, перенесен на переставленную старую виртуалку (которая бодро рассылала спам на всю ширину канала), и теперь показывает вот такие вот скромные числа, вот значение в пике (700 юзеров онлайн по googleAnal, кеш 3 сек):

Сказ о том, как я сервер с WP лечил, или 700 юзеров онлайн на 2х ядрах - 4

Конфигурация достаточно простая: php-fpm 7.0.16 (7.1.1 не воспринимается некоторыми плагинами)

./configure --prefix=/opt/php-7.0 --with-pdo-pgsql --with-zlib-dir --with-freetype-dir --enable-mbstring --with-libxml-dir=/usr --enable-soap --enable-calendar --with-curl --with-mcrypt --with-zlib --with-gd --with-pgsql --disable-rpath --enable-inline-optimization --with-bz2 --with-zlib --enable-sockets --enable-sysvsem --enable-sysvshm --enable-pcntl --enable-mbregex --enable-exif --enable-bcmath --with-mhash --enable-zip --with-pcre-regex --with-pdo-mysql --with-mysqli --with-mysql-sock=/var/run/mysqld/mysqld.sock --with-jpeg-dir=/usr --with-png-dir=/usr --enable-gd-native-ttf --with-openssl --with-fpm-user=www-data --with-fpm-group=www-data --with-libdir=/lib/x86_64-linux-gnu --enable-ftp --with-imap --with-imap-ssl --with-kerberos --with-gettext --with-xmlrpc --with-xsl --enable-opcache --enable-fpm --enable-intl

+ memcached

fpm-fpm сидит в chroot'e,

[wwwsomeone]

listen = 127.0.0.1:9001
listen.allowed_clients = 127.0.0.1

user = wwwsomeone
group = wwwsomeone
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 10
chroot = /path/to/folder
chdir = /
catch_workers_output = yes

Nginx тоже на достаточно простой конфигурации (fpm подцеплен на порте, а не на сокете), а кеш имеет вид:

fastcgi_cache_key "$scheme:$server_name:$server_port:$request_uri";
fastcgi_cache mapcgi;
fastcgi_cache_lock on;
fastcgi_cache_lock_timeout 6s;
fastcgi_cache_valid 200 301 302 304 3s;
fastcgi_cache_valid 403 404 10s;
fastcgi_no_cache $cookie_wordpress_auth_cookie;
fastcgi_cache_methods GET HEAD;

Где $cookie_wordpress_auth_cookie проверяет наличие куки wordpress_auth_cookie и запрещает кеширование ответов. Сейчас кеш составляет, отчего нагрузка упала больше всего:

fastcgi_cache_lock_timeout 10s;
fastcgi_cache_valid 200 301 302 304 2m;
fastcgi_cache_valid 403 404 30s;

Кеш лежит на ремдиске (tmpfs) объемом 1 Гб. Если кому интересны тесты такой же ВПС (но пустой), то вот они (gist).

Динамика отдается на 12 r/sec, статика на 256 r/sec.

В принципе кеш всему голова… 470 онлайн без кеша nginx:

Сказ о том, как я сервер с WP лечил, или 700 юзеров онлайн на 2х ядрах - 5

Вот с кешем nginx:

Сказ о том, как я сервер с WP лечил, или 700 юзеров онлайн на 2х ядрах - 6

 

MySQL MariaDB отличается от дефолта кодом ниже:

[mysqld]
user		= mysql
pid-file	= /var/run/mysqld/mysqld.pid
socket		= /var/run/mysqld/mysqld.sock
port		= 3306
basedir		= /usr
datadir		= /var/lib/mysql
tmpdir		= /tmp
#init-connect='SET NAMES utf8'
lc-messages-dir	= /usr/share/mysql
language = /usr/share/mysql/english
skip-external-locking
bind-address = 127.0.0.1

collation-server = utf8_unicode_ci
character-set-server = utf8
event_scheduler = on

innodb_file_per_table = 1
innodb_flush_method=O_DIRECT
innodb_lock_wait_timeout = 50
innodb_buffer_pool_size = 1G

join_buffer_size = 32M

max_connections = 1024
max_allowed_packet = 256M
max_join_size = 4096000
myisam_sort_buffer_size = 32M
myisam-recover = BACKUP

thread_cache_size = 16
key_buffer_size=32M
net_buffer_length = 16K
read_buffer_size=64M
read_rnd_buffer_size = 8M
sort_buffer_size = 32M
bulk_insert_buffer_size = 256M
expire_logs_days = 10
max_binlog_size = 100M
#
tmp_table_size = 1G
max_heap_table_size = 1G
#
table_cache = 8192
open-files-limit = 262144
transaction-isolation = READ-COMMITTED
query_cache_type = 2
query_cache_limit = 2M
query_cache_size = 256M
connect_timeout=30
wait_timeout=300

Логи доступа и ошибок nginx пишутся активно (1.5Гб в день), 3 дня полет нормальный и сервер чистый, судя по логах и активности в целом, в том числе и в логе fpm.

Так же в логе fpm уже на свежем ВПС:

[16-Feb-2017 23:06:42] WARNING: [pool wwwюзер] child 15075 said into stderr: "NOTICE: PHP message: PHP Fatal error: Uncaught Error: Call to undefined function mysql_escape_string() in /www/wp-content/themes/одна-такая-старая-тема-которая-не-используется/functions.php:60"

  • 3622502af8083e53ced808af6ca7aca0bd3cb088d1df309964ab865f51ec90ca functions.php

После чего были поиски файлов, которые могут быть похожи на functions.php в той или иной мере (аналогичные команды), но ничего подозрительного найдено не было.

Следующим этапом будет запереть сайты за cloudflare, играя add_header Cache-Control "public, max-age=123456789"; для контента без авторизации и add_header Cache-Control "private, max-age=0"; когда кукисы присуствуют в ответе и включить их файрволл, обеспечив дополнительную защиту «несчастному админу» и его блогам.

 

Теплого вам солнца и настоящих выходных!

Автор: nikitasius

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js