Давным-давно в далеком-далеком git-репозитории Брайаном Акером был сделан коммит, внедряющий замечательную фичу прослушивания UDP трафика в установке memcached по умолчанию.

А между 23 и 27 февраля 2018 года по всей Европе прокатилась волна memcached-амплифицированных DDoS атак.

Проблема с безопасностью memcached известна как минимум с 2014 года, когда Иван Новиков — директор и основатель Wallarm, со сцены Black Hat U.S. рассказал про “memcached injections”.

И хотя инъекции могут позволить атакующему достичь самых разнообразных целей, прошлой ночью мы видели волну амплифицированных DDoS-атак по всему интернету, включая крупнейшие в России сетевые ресурсы, с портом 11211 в источнике. Все атаки на прошедших выходных идентифицируются по данному признаку именно как memcached-амплифицированные. В 2017 году группа исследователей из китайской OKee Team рассказала о возможности организации подобных атак, указав на их потенциально разрушительную мощность.

За прошедшие несколько дней множество источников подтвердили факт атаки амплифицированными ответами от memcached-ресурсов, с небольшими вкраплениями DNS и NTP. Посредниками этих атак стал крупный провайдер OVH и большое количество меньших интернет-провайдеров и хостеров.

Множество раз специалисты Qrator Labs говорили о том, что в сетях, несмотря на общее падение количества, все еще остается множество амплификаторов. В прошлом году исследователи рассказали нам, а прошедшие выходные продемонстрировали наглядно, как подобные атаки могут достигать фантастической силы. Один из клиентов компании Qrator Labs — платежная система QIWI, подтверждает факт успешно нейтрализованной атаки полосой в 480 Гбит/сек UDP трафика по собственным ресурсам от скомпрометированных memcached амплификаторов.

По умолчанию memcache прослушивает весь UDP трафик на порте 11211 в последней версии. Ubuntu 16.04 с дефолтными настройками запускает memcached только на localhost, в отличие от CentOS 7.4, где конфигурация memcached по умолчанию прослушивает UDP-запросы на всех интерфейсах.

Подобных, уязвимых, memcached-ресурсов в интернете огромное количество. В случае корректной настройки такого не может произойти, но корректная настройка все еще остается редким случаем. Технические специалисты устанавливают memcached и забывают о его настройках по умолчанию, также забывая о том, что он прослушивает весь UDP трафик отправляемый на сервер и отвечает, в среднем, амплифицированными в 9000-10000 раз ответами.

Нейтрализация такой атаки может быть достигнута несколькими способами, наиболее простые из которых: установка межсетевого экрана на udp трафик из порта 11211 (маловероятно появление легитимного трафика из данного порта) или установки входящего rate-limit’а. Это подразумевает, что у вас будет достаточно полосы для обработки такой атаки перед сетевым экраном. Для провайдера крупнее есть две известные опции:
1. Фильтрация по flowspec;
2. Использование extended community.

Берегите порт 11211.

UPDATE:


Уже после публикации данной заметки Хабрахабру попытались налить почти 300 Гбит/с memcached amplificated UDP.

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


  1. sebres
    27.02.2018 14:19

    По-умолчанию memcache прослушивает весь UDP трафик на порте 11211 в последней версии.

    На даже минимально нормально настроенном сервере (с портами наружу) совершенно по барабану кто где что слушает.
    Т.е. тупо не хватало чего-нибудь типа белого списка по accept на входящие, например простейшего:


    # NTP
    iptables -A INPUT -i $device -m state --state NEW -p udp --dport 123 -j ACCEPT
    ...
    # memcached
    iptables -A INPUT -i $device -m state --state NEW -p tcp --dport 11211 -j ACCEPT
    ...

    А ещё лучше чего-нибудь следующего вида (хотя бы для udp, если вообще нужен):


    ...
    # memcached from my-subnet only:
    iptables -A INPUT -i $device -m state --state NEW -s $my_sub_net -p tcp --dport 11211 -j ACCEPT
    iptables -A INPUT -i $device -m state --state NEW -s $my_sub_net -p udp --dport 11211 -j ACCEPT
    ...

    Детский сад, честное слово.
    С connectionless протоколами нужно быть особенно осторожным. Ударение на всегда.
    Если сегодня какой-нибудь сервис не слушает UDP, то завтра каким-нибудь коммитом оно всё может измениться.
    Так то ведь все читают changelog после каждого обновления для каждой софтины. Ага.


    1. inkvizitor68sl
      27.02.2018 15:23

      Вы же в курсе, что такая конструкция (-m state --state NEW) сильно снижает производительности сетевой подсистемы?


      1. sebres
        27.02.2018 16:21

        Чего это?


        То значит что совершенно новые (brand NEW) пакеты на порту (в общем случае самый первый пакет в передаче) пробросится в ACCEPT chain.
        Каким местом как раз это должно "снижать производительности сетевой подсистемы"?


        Это же не ESTABLISHED, RELATED и т.д. Ну и соответственно keep-alive и иже с ним вообще не затронуты.


        Хотя даже на NEW оно не просаживается (зависит от количества правил на самом деле).


        Чтобы не быть голословным, вот вам короткий коннект (2048 payload size), на локали для несильно быстрой машинки с 10Gbit, сильно multi-threaded:


        - # 500 правил -m state --state NEW (для разных портов)
        + # нет правил -m state --state NEW (пустая iptables)
        - connect 516.796 µs/# - 1935.0 #/sec
        + connect 518.054 µs/# - 1930.3 #/sec
        - payload 18.3720 µs/# - 54430.7 #/sec
        + payload 18.3589 µs/# - 54469.6 #/sec

        Т.е. никак не влияет (небольшое отличие — то разброс и погрешности измерения).
        При этом и макс. задержка 24µs на payload пакете в обоих случаях.
        Я думаю задержка на свиче там большую роль играет (ибо бюджетный).


        Ну или если мне не верите см. Netfilter Performance Testing / Figure 15...


        Т.е. тут вопрос-то лишь — для скольких правил (в одной "топологии")…
        Ну если вы например банить так будете, то нужно ipset оборачивать и т.д.


        Ну и во вторых, то был пример. Т.е. юзайте что-нить другое вместо iptables (или в связке) если нужно.


        1. inkvizitor68sl
          28.02.2018 11:51

          А conntrack разве не включается из-за этого правила? Или только когда ESTABLISHED/RELATED используешь?


          1. sebres
            28.02.2018 13:37

            Встречный вопрос — а разве оно тогда не `-m conntrack --ctstate NEW` должно быть?
            Или вы про statefull connection tracking?

            Я не сисадмин и точно не скажу, но statefull инфо насколько я понимаю в любом случае используется при разборе/дефрагментации пакетов даже для stateless (т.е. как минимум чтобы раскидать по портам/соединениям на layer-4, дропнуть INVALID пакеты, закрыть соединение по inactivity-timeout и т.д.)…
            При том что дважды оно конечно не должно (да и врядли будет) осуществляться, т. е. если есть какой-то dedicated firewal на передней линии, осуществляющий проверку plausibility и разбор пакетов, то собственно на сервере оно или выключаемо или уже обладает необходимой информацией, чтобы пометить тот же state пакета как NEW.
            Со stateless протоколами все несколько сложнее, но не суть.

            Как бы объяснить — ну вот у вас на порт 80 прилетело 250 пакетов от 5 соединений, как совсем без connection tracking можно раскидать их на 5 сокетов (соответствующих собственно каждому соединению).
            Т.е. грубо говоря это то, чем acceptor от listener отличаются на «сетевом» уровне в ядре.

            Соответственно в идеале мы говорим про единственный условный переход типа
            `if (packet->state & NEW) goto jmp_chain`.

            Хотя, возможно что когда-то это и было не очень перфомантно реализовано в каком-нибудь kernel 2.2, но уже например очень даже ничего в kernel 2.4 (я уже молчу про последние).


      1. mlhi
        28.02.2018 14:01

        Если не затруднит, то пожалуйста можно поподробней почему так?


    1. VulvarisMagistralis
      27.02.2018 19:41
      +1

      Детский сад, честное слово.

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


      1. sebres
        27.02.2018 20:14

        Да, мы поняли, что вы умный.

        Спасибо. А мы — это кто? Вас несколько? Скажите остальным, чтобы другого спикера выбрали.


        Видите ли, детский не детский, — это не важно.

        Вижу… это действительно не важно.


        Важно, что такое существует массово.

        Это не так, ну а если всё таки, то нужно с этим бороться. В том числе пропагандой "серверной" гигиены.


        Видимо, подавляющее большинство админов существенно глупее вас.

        Нет — подавляющее большинство админов как раз таки много умнее меня в этой области (ибо я не админ). А то меньшинство (вы не из них случаем?) — или пофигисты и/или личный VPS (что хочу то и делаю) и/или опыта мало… Ну или допускаю возможно не совсем компетентны в этом конкретном случае.


        Вы это хотели сказать своим постом?

        Вам лично я ничего не хотел сказать извиняйте если всё-таки макнул, я не нарочно.


    1. vesper-bot
      28.02.2018 10:27

      Скорее всего, там был iptables -P INPUT ACCEPT и вот это всё.


  1. SergeyMax
    27.02.2018 14:52

    Т.е. тупо не хватало чего-нибудь типа белого списка по accept на входящие, например простейшего:
    # memcached
    iptables -A INPUT -i $device -m state --state NEW -p tcp --dport 11211 -j ACCEPT


    А где сам белый список?


    1. sebres
      27.02.2018 15:11

      Спрятан под троеточием :) То же просто пример был.
      Грубо говоря, все входящие соединения с изнанки запрещены, кроме source+порт+протокол+итд (к тем службам) которые вами **явно** разрешены.
      Всё остальное от лукавого.


      1. SergeyMax
        27.02.2018 16:08

        Я думаю, что в этом и проблема. Когда вроде бы и надо написать нормальное правило, но поздно, далеко, да и незачем уже: ведь всё работает.


        1. sebres
          27.02.2018 16:25

          Дык как раз в случае белого списка и нет проблемы, — вам как раз НУЖНО его писать, ибо по умолчанию всё остальное (т.е. явно не разрешенное правилом) просто дропнется — т.е. коннекта к службе не будет.


  1. lexore
    27.02.2018 14:57
    -1

    Имхо, чем настраивать iptables, проще настроить сервисы слушать только на 127.0.0.1.
    И в конце не полениться проверить выхлоп netstat -nl


    1. mickvav
      27.02.2018 16:31

      На мой вкус, одно другому не третье. Оставить в фаерволе только то, что нужно И объяснить сервисам, чтобы слушали только там, где нужно.


    1. sebres
      27.02.2018 18:18

      чем настраивать iptables, проще настроить сервисы слушать только loopback.

      Ну если так, то чем тогда вообще unix-socket не угодил (я про /var/run/memcached/memcached.sock).


      Вопрос-то не про проще, а как правильно...


      1. Система из одной машины? Всегда ли порты службы за NAT (не торчат наружу)?


      2. Если завтра что-то поставится/расширится на другой порт/протокол (вы видимо всегда читаете changelog-и всего чего апдейтите)?


      3. Вы разницу между открыть слушающее соединение и изменить правило в firewall (netfiltering), в контексте "делегирования привелегий" представляете?
        Кто может создать listener? (кто угодно), а кто может открыть порт? (рут/судоер)

      И т.д. и т.п… На самом деле есть куча всего, почему то, что вы написали (без firewall) — как минимум не комильфо.


      1. lexore
        27.02.2018 18:46

        Ну если так, то чем тогда вообще unix-socket не угодил

        Не все слушает unix socket.
        dns/ntp/почта для локальных нужд, или какой-то свой сервис, который просто не умеет в unix socket.


        Если завтра что-то поставится/расширится на другой порт/протокол (вы видимо всегда читаете changelog-и всего чего апдейтите)?

        Да в том-то и дело, что не очень читаю и апдейчу.
        Мне проще новый сервис один раз настроить на localhost и больше не думать об этом.
        Но это наверное применимо, только когда машина одна.
        Если машин несколько и они ходят друг к другу на какие-то внутренние службы, проще будет уже вариант с iptables (там есть разные способы, но они уже более сложные).


        1. sebres
          27.02.2018 19:26

          Мне проще новый сервис один раз настроить на localhost и больше не думать об этом.

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


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


          Как пример:
          допустим у вас есть сервис XYZ-1.0 слушающий порт 1234. Вы его настроили в xyz.conf, например:


          [XYZ]
          LocalHostOnly = true

          А завтра по обновлению на версию 1.1 случилось:


          • опцию убрали и заменили на ListenerAddr = 127.0.0.1, ...(т.е. при апдейте вы проигнорили/проглядели N варнингов подряд, что LocalHostOnly is obsolete — т.е. есть устаревшая штука и закончится с версией 1.1);
          • оно было всегда localhostonly, а конфиг xyz.conf вдруг стал case-sesitive (и ваша LocalHostOnly теперь просто переменная INI-файла без смысловой нагрузки);
          • что-то (апдейтом) или кто-то (у вас несколько "поваров" у одной кастрюли, а шеф-повару следить лень) затер весь этот конфиг и/или инклюд оригинальным-стоковым, где теперь ListenerAddr = any;
          • если то был инклюд, он вдруг потерялся (и после рестарта действует стоковый конфиг с listen from anywhere).
          • у версии 1.1 был баг, который внезапно игнорит LocalHostOnly-опцию;
          • у совтины появился плагин (и он активирован по умолчанию ибо "необходим как вода"), слушающий порт 1235 с пробросом JSON-а развернутого в формат XYZ на listener к порту 1234;
          • и т.д. и т.п.

          Смысл понятен, надеюсь...


          1. lexore
            27.02.2018 19:31

            Вы сейчас привели экзотические примеры, но я с вами согласен.


            1. saege5b
              28.02.2018 22:41

              У меня в кубунте, как-то тор при обновлении выстраданный конфиг затёр.
              А потом оказалось что забыл сделать последний бекап и долго вспоминал что я там такого наваял.


  1. lehnh
    27.02.2018 16:57

    Подобные белые списки в iptables не всегда достаточны.
    Многие сервисы по-дефолту биндятся еще и на ipv6, который по-дефолту опять же, часто включен. И получаем мы открытый сервис. Так что либо бинд на локалхост, либо отключение ipv6 либо (зачем?) iptables6


    1. Pas
      27.02.2018 18:27
      +1

      Просто надо не забывать, что кроме iptables есть ip6tables. Просто раскладывать по всем тачкам свои типовые rules.v4 и rules.v6. Default to deny на входе — просто вопрос гигиены.


  1. ugenk
    27.02.2018 18:04

    А вы не планируете добавить memcached amplification в radar?


    1. flx
      27.02.2018 18:33

      кино в 9.


    1. Shapelez Автор
      27.02.2018 18:38
      +1

      Уже в процессе.


      1. ugenk
        27.02.2018 18:44

        А еще нету ли у вас в планах сделать какой-то сервис, который бы позволил например API-запросом владельцу AS проверять наличие IP в списках усилителей? Хочется сделать для своих клиентов нотификации и возможно автобан, но очень неудобно делать автоматизацию через ручную выгрузку csv.


  1. sebres
    27.02.2018 18:39

    Хабрахабру попытались налить почти 300 Гбит/с memcached amplificated UDP.

    Чисто в качестве интереса, а там видно (можно собрать статистику) сколькимя хостами оно там разлилось на 300 гиг/с?


    1. Pas
      27.02.2018 20:39
      +1

      Где-то 1300 хостов:


      1. sebres
        27.02.2018 20:45
        +1

        Спасибо. Интересная там география… :)


        1. akamensky
          28.02.2018 05:40

          У такой географии есть простое объяснение — толпы «DevOps» инженеров, которые не знают основ администрирования и оставляют тысячи и более сервисов светить портами в интернет, сканировали, знаем ;) А, например, там нет Китая (где тоже все очень печально в этом плане), так как GFW намертво отсекает UDP, так как многие пытаются делать VPN via UDP.


          1. sebres
            28.02.2018 14:38

            так как GFW намертво отсекает UDP

            А можно по подробнее...


            Просто как тогда объяснить что я их очень часто вижу с UDP.
            Например вырезка из статистики IDS, нескольких первых попавшихся с UDP из Китая (я обрезал, их сотни если что):


            Скрытый текст
            {
              "116.224.143.250" : {
                "country" : "CN",
                "bad-rank" : 4.49,
                "ports" : [4970],
                "protos" : ["UDP", "TCP"],
                "reason" : [
                  "REJECT UDP IN=net0 OUT= MAC= SRC=116.224.143.250 DST=... LEN=58 TOS=0x00 PREC=0x00 TTL=45 ID=15969 PROTO=UDP SPT=1024 DPT=4970 LEN=38",
                  "REJECT TCP IN=net0 OUT= MAC= SRC=116.224.143.250 DST=... LEN=52 TOS=0x00 PREC=0x00 TTL=45 ID=20296 DF PROTO=TCP SPT=56047 DPT=4970 WINDOW=8192 RES=0x00 SYN URGP=0"
                ]
              },
              "124.133.103.134" : {
                "country" : "CN",
                "bad-rank" : 3.54,
                "ports" : [4970],
                "protos" : ["UDP", "TCP"],
                "reason" : [
                  "REJECT UDP IN=net0 OUT= MAC= SRC=124.133.103.134 DST=... LEN=58 TOS=0x00 PREC=0x00 TTL=111 ID=17056 PROTO=UDP SPT=20489 DPT=4970 LEN=38",
                  "REJECT TCP IN=net0 OUT= MAC= SRC=124.133.103.134 DST=... LEN=52 TOS=0x00 PREC=0x00 TTL=111 ID=16732 DF PROTO=TCP SPT=57876 DPT=4970 WINDOW=8192 RES=0x00 SYN URGP=0"
                ]
              },
              "144.12.61.238" : {
                "country" : "CN",
                "bad-rank" : 4.36,
                "ports" : [4970],
                "protos" : ["UDP", "TCP"],
                "reason" : [
                  "REJECT UDP IN=net0 OUT= MAC= SRC=144.12.61.238 DST=... LEN=58 TOS=0x00 PREC=0x00 TTL=108 ID=15447 PROTO=UDP SPT=1027 DPT=4970 LEN=38",
                  "REJECT TCP IN=net0 OUT= MAC= SRC=144.12.61.238 DST=... LEN=52 TOS=0x00 PREC=0x00 TTL=108 ID=9926 DF PROTO=TCP SPT=56111 DPT=4970 WINDOW=8192 RES=0x00 SYN URGP=0"
                ]
              },
              "180.154.225.61" : {
                "country" : "CN",
                "bad-rank" : 5.77,
                "ports" : [4970],
                "protos" : ["UDP", "TCP"],
                "reason" : [
                  "REJECT UDP IN=net0 OUT= MAC= SRC=180.154.225.61 DST=... LEN=58 TOS=0x00 PREC=0x00 TTL=109 ID=29301 PROTO=UDP SPT=11590 DPT=4970 LEN=38",
                  "REJECT TCP IN=net0 OUT= MAC= SRC=180.154.225.61 DST=... LEN=52 TOS=0x00 PREC=0x00 TTL=109 ID=445 DF PROTO=TCP SPT=56461 DPT=4970 WINDOW=8192 RES=0x00 SYN URGP=0"
                ]
              },
              "122.143.151.48" : {
                "country" : "CN",
                "bad-rank" : 6.77,
                "ports" : [4970],
                "protos" : ["UDP", "TCP"],
                "reason" : [
                  "REJECT UDP IN=net0 OUT= MAC= SRC=122.143.151.48 DST=... LEN=58 TOS=0x00 PREC=0x00 TTL=47 ID=24052 PROTO=UDP SPT=9691 DPT=4970 LEN=38",
                  "REJECT TCP IN=net0 OUT= MAC= SRC=122.143.151.48 DST=... LEN=60 TOS=0x00 PREC=0x00 TTL=47 ID=7668 DF PROTO=TCP SPT=9671 DPT=4970 WINDOW=64240 RES=0x00 SYN URGP=0"
                ]
              },
              "114.92.110.43" : {
                "country" : "CN",
                "bad-rank" : 4.58,
                "ports" : [4970],
                "protos" : ["UDP", "TCP"],
                "reason" : [
                  "REJECT UDP IN=net0 OUT= MAC= SRC=114.92.110.43 DST=... LEN=58 TOS=0x00 PREC=0x00 TTL=110 ID=24714 PROTO=UDP SPT=12852 DPT=4970 LEN=38",
                  "REJECT TCP IN=net0 OUT= MAC= SRC=114.92.110.43 DST=... LEN=52 TOS=0x00 PREC=0x00 TTL=110 ID=24716 DF PROTO=TCP SPT=62254 DPT=4970 WINDOW=64240 RES=0x00 SYN URGP=0"
                ]
              },
              "183.0.88.171" : {
                "country" : "CN",
                "bad-rank" : 3.81,
                "ports" : [4970],
                "protos" : ["UDP"],
                "reason" : [
                  "REJECT UDP IN=net0 OUT= MAC= SRC=183.0.88.171 DST=... LEN=116 TOS=0x00 PREC=0x00 TTL=109 ID=11172 PROTO=UDP SPT=7854 DPT=4970 LEN=96"
                ]
              },
              "124.78.150.69" : {
                "country" : "CN",
                "bad-rank" : 4.51,
                "ports" : [4970],
                "protos" : ["UDP"],
                "reason" : [
                  "REJECT UDP IN=net0 OUT= MAC= SRC=124.78.150.69 DST=... LEN=58 TOS=0x00 PREC=0x00 TTL=46 ID=4839 DF PROTO=UDP SPT=60001 DPT=4970 LEN=38"
                ]
              }
            }


  1. Arik
    28.02.2018 09:53

    Есть какой простой скрипт/пакет который показывает актуальные уязвимости? чтоб юный падаван мог запустить и понять что все плохо? Было бы здорово если на github лежал какой актуальный/обновляемый shell(?)-скрипт, который бы информировал подобные вещи. Да и после первоначальной настройки ВПС можно было бы убеждаться что все хорошо, что ничего не забыл. Или такое сложно реализовать?


    1. equand
      28.02.2018 10:33

      Есть, называется firewall default to deny и SELinux enforced.
      Тогда будет известно, что где открыло и куда имеет доступ.
      А еще лучший Jail by default (для Linux полагаю chroot by default). Любой процесс помещать в chroot и давать доступ только к тому, что ему нужно.

      Подобная практика защитит от 99% попыток взлома.
      Ну и конечно port scramble не повредит там где это возможно.


  1. php7
    28.02.2018 10:41

    1. Я так и не понял, в чем уязвимость.
    2. Ну так это скорее не уязвимость, а дебилы, не удосужившие настроить firewall.
    В CentOs 7 (не знаю как в 7.4) по умолчанию файервол закрывает все.
    Дебилы, налейте минусов. Посмотрим сколько вас.


    1. vesper-bot
      28.02.2018 12:51

      Уязвимость, как всегда, в людях — далеко не все умеют даже включить файрволл. Возможно, настройки по умолчанию для VPS содержат iptables -P INPUT ACCEPT, и это никто не проверяет. Настройки memcached в этих образах по умолчанию, т.е. он открыт. Вместе имеем готовую ферму для memcached-амплификации.


      1. php7
        28.02.2018 12:57

        Хотелось бы услышать как воспроизвести атаку :)


        1. vesper-bot
          28.02.2018 13:18

          UDP-протокол позволяет подменить исходящий адрес. Собственно, подменяешь на адрес цели, кладешь в пакет корректный запрос к memcached, всё.


  1. php7
    28.02.2018 14:10

    Чет мне подсказывает, что у слова «Амплифицированные» есть прекрасный русскоязычный заменитель.


  1. amarao
    28.02.2018 15:38

    Атака на хабр была очень заметна, потому что половина JS'а не могла подгрузиться.