• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Исследование безопасности протокола erlang-distribution

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265
ОРИГИНАЛЬНАЯ СТАТЬЯ
ПЕРЕВЕДЕНО СПЕЦИАЛЬНО ДЛЯ xss.pro
$600 на SSD для Solidity hacking by Jolah Milovsky---> 0x5B1f2Ac9cF5616D9d7F1819d1519912e85eb5C09

предисловие


Поскольку некоторые из бизнесов компании в настоящее время реализованы с использованием erlang, в китайском Интернете мало исследований по вопросам безопасности erlang.Чтобы понять проблемы безопасности приложений erlang, я провел некоторое исследование кода и общедоступной информации. Эта статья представляет собой исследование коммуникационного протокола дистрибутива erlang в исследовательском проекте безопасности erlang, целью которого является решение проблемы раскрытия общедоступной сети приложений erlang. Пакет pcap, документация и код в этой статье хранятся в https://github.com/lxraa/erl-matter/tree/master/otp25

1 установка операционной среды erlang

Загружаем -> Erlang/OTP

2 установка управления пакетами erlang-rebar3

Код:
git clone https://github.com/erlang/rebar3.git
cd rebar3
./bootstrap

3. Настройка среды отладки Erlang (vscode)


Конфигурация проекта VSCode Debug Erlang

Код:
При установке плагина erlang в vscode может появиться следующее сообщение
# no such file or directory pgourlain... _build...
# Причина в том, что расширение vscode erlang (pgourlain) не компилируется.
# Вам нужно вручную перейти в каталог расширения и скомпилировать его с помощью rebar3 compile для создания папки _build

1663892576772.png


Демонстрация взаимодействия с кластером Erlang


1. Особенности языка erlang​


  • интерпретируемый язык
  • функциональный
  • нет отражения
  • Хорошо справляется с параллельной обработкой
    • Поддерживается набор потоков ring3, поэтому планирование потоков не зависит от системного вызова, накладные расходы невелики, и можно легко создать большое количество потоков.
  • самораспределяемый
    • Нижний слой вызывается через rpc.
    • Поскольку нет отражения, нет и десериализации rce для связи кластера (суть десериализации в том, чтобы обойти занесенный в черный список метод.invoke), но все же могут быть другие проблемы с безопасностью.

2. Принципиальная схема связи кластера​

01415554-e57f-46e4-bbfe-28c2a9a1ffb3.png-w331s.translated.jpg


1. Когда машина1 открывает службу для внешнего мира, она сначала открывает службу epmd на порту 4369. Эту службу можно понимать как реестр, который используется для сохранения (имя, порт) службы машины1.
2. Когда машина2 хочет вызвать службу машины1, ей нужно сначала найти epmd, чтобы получить список (имя, порт) машины1.
3. Машина2 напрямую подключена к порту машины1, и вызывается RPC

3 демонстрация связи

Запустите виртуальную машину Linux и используйте Windows для удаленного вызова узла Linux.
  • Откройте в режиме отладки, для облегчения подключения укажите имя хоста для машины
Код:
# linux
epmd -d
hostname localcentos2

1663892818095.png


  • Используйте -sname для указания имени, erl автоматически откроет процесс во внешний мир и зарегистрирует его в epmd (если epmd нет, он также автоматически откроет epmd)
Код:
erl -sname test


1663892838213.png


1663892844709.png


установиv куки

Код:
%% Обратите внимание, что одинарные кавычки в erlang представляют типы атомов, а не строк.

atom %% можно понимать как глобально уникальный идентификатор, аналогичный символу js's Symbol

auth:set_cookie('123456').

  • Windows открывает оболочку erlang и настраивает тот же файл cookie, что и узел Linux.

файлы хоста добавляют записи разрешения DNS друг к другу

Код:
erlang -sname test
>> auth:set_cookie('123456').

Подключитесь к узлу и посмотрите, успешно ли установлено соединение

Код:
%% Connect Не забудьте выключить брандмауэр linux systemctl stop firewalld
net_adm:ping('test@localcentos2').
%% Проверка наличия подключенных узлов
nodes().

1663892973457.png

На данный момент test@PPC2LXR и test@localcentos2 успешно подключены

Код:
rpc:call('test@localcentos2','os','cmd',["touch /tmp/connect_success.txt"]).

1663893026684.png


Видно, что процесс защищен куки, а получение куки эквивалентно разрешению на выполнение произвольного кода.Следующее решает две проблемы

1. Аутентификация связывается с epmd или с процессом?
2. Есть ли проблемы с безопасностью в процессе аутентификации?

Aнализ протокола epmd


epmd — это служба регистрации узла erlang, которая обеспечивает разрешение имени узла, что можно понимать как центр регистрации, который используется для сообщения внешнему подключению к узлу информации об этом узле. Когда внешний хост запрашивает службу epmd, epmd возвращает всю информацию о прослушивающем порте узла и имя узла на текущем узле.

Код:
erl
1> net_adm:names("localcentos2").
{ok,[{"test",36612}]}

Обратите внимание, что epmd не аутентифицирован, что означает, что epmd будет предоставлять всю информацию о процессах, запущенных по sname или имени на хосте, и epmd поддерживает только запросы для нелокальных операций.Код находится в otp_src/erts/epmd/src/epmd_src. c: строка 799: void do_request(g, fd, s, buf, bsize)

Код:
case EPMD_ALIVE2_REQ:
    //只允许local调用
    dbg_printf(g, 1, "** got ALIVE2_REQ");
    if (!s->local_peer)
    {
      dbg_printf(g, 0, "ALIVE2_REQ from non local address");
      return;
    }


  case EPMD_PORT2_REQ:
    dbg_printf(g, 1, "** got PORT2_REQ");

    if (buf[bsize - 1] == '\000') /* Skip null termination */
      bsize--;

    if (bsize <= 1)
    {
      dbg_printf(g, 0, "packet too small for request PORT2_REQ (%d)", bsize);
      return;
    }

    for (i = 1; i < bsize; i++)
      if (buf[i] == '\000')
      {
        dbg_printf(g, 0, "node name contains ascii 0 in PORT2_REQ");
        return;
      }

    {
      char *name = &buf[1]; /* Points to node name */
      int nsz;
      Node *node;

      nsz = verify_utf8(name, bsize, 0);
      if (nsz < 1 || 255 < nsz)
      {
        dbg_printf(g, 0, "invalid node name in PORT2_REQ");
        return;
      }

      wbuf[0] = EPMD_PORT2_RESP;
      for (node = g->nodes.reg; node; node = node->next)
      {
        int offset;
        if (is_same_str(node->symname, name))
        {
          wbuf[1] = 0; /* ok */
          put_int16(node->port, wbuf + 2);
          wbuf[4] = node->nodetype;
          wbuf[5] = node->protocol;
          put_int16(node->highvsn, wbuf + 6);
          put_int16(node->lowvsn, wbuf + 8);
          put_int16(length_str(node->symname), wbuf + 10);
          offset = 12;
          offset += copy_str(wbuf + offset, node->symname);
          put_int16(node->extralen, wbuf + offset);
          offset += 2;
          memcpy(wbuf + offset, node->extra, node->extralen);
          offset += node->extralen;
          if (!reply(g, fd, wbuf, offset))
          {
            dbg_tty_printf(g, 1, "** failed to send PORT2_RESP (ok) for \"%s\"", name);
            return;
          }
          dbg_tty_printf(g, 1, "** sent PORT2_RESP (ok) for \"%s\"", name);
          return;
        }
      }
      wbuf[1] = 1; /* error */
      if (!reply(g, fd, wbuf, 2))
      {
        dbg_tty_printf(g, 1, "** failed to send PORT2_RESP (error) for \"%s\"", name);
        return;
      }
      dbg_tty_printf(g, 1, "** sent PORT2_RESP (error) for \"%s\"", name);
      return;
    }
    break;

  case EPMD_NAMES_REQ:
    dbg_printf(g, 1, "** got NAMES_REQ");
   ...
   break;
  case EPMD_DUMP_REQ:
    dbg_printf(g, 1, "** got DUMP_REQ");
    if (!s->local_peer)
    {
      dbg_printf(g, 0, "DUMP_REQ from non local address");
      return;
    }
    // 只允许local调用
    ...
    break;

  case EPMD_KILL_REQ:
    if (!s->local_peer)
    {
      dbg_printf(g, 0, "KILL_REQ from non local address");
      return;
    }
    dbg_printf(g, 1, "** got KILL_REQ");

   // 只允许local调用

  case EPMD_STOP_REQ:
    dbg_printf(g, 1, "** got STOP_REQ");
    if (!s->local_peer)
    {
      dbg_printf(g, 0, "STOP_REQ from non local address");
      return;
    }
    // 只允许local调用
    break;

  default:
    dbg_printf(g, 0, "got garbage ");
  }

EPMD_NAMES_REQ, по-видимому, используется для ответа на net_adm:names(). Следующий отладочный EPMD_PORT2_REQ


Измените код epmd, распечатайте содержимое пакета tcp перед do_request и make&&make install, запустите режим отладки epmd через epmd -d на хосте A:

Код:
// epmd_srv.c - print16:
...
print16(s->buf,s->got);
do_request(g, s->fd, s, s->buf + 2, s->got - 2);
...
static int print16(char * s,unsigned int size){
  int i = 0;
  int count = 0;
  for(i = 0;i < size;i++){
    if(count > 16){
      count = 0;
    }
    printf("%x ",s[i]);
    count++;
  }
  printf("\n");
  return 0;
}

Использовать erl -sname test Перезапустите процесс на хосте A и получите отладочную информацию:

Код:
invoke do_request
0 11 78 ffffffa8 d 4d 0 0 6 0 5 0 4 74 65 73 74 0 0
epmd: Mon Sep  5 15:50:22 2022: ** got ALIVE2_REQ
epmd: Mon Sep  5 15:50:22 2022: registering 'test:1662364223', port 43021
epmd: Mon Sep  5 15:50:22 2022: type 77 proto 0 highvsn 6 lowvsn 5
epmd: Mon Sep  5 15:50:22 2022: ** sent ALIVE2_RESP for "test"

Инициировать запрос на соединение с ошибкой файла cookie с хоста B:

Код:
%% Host A This sentence does not get debugging information, i.e. node auth information does not inform epmd
auth:set_cookie("654321").
%% Host B
erl -sname test2
auth:set_cookie("123456").
net_adm:ping("test@192.168.245.128").

Получите отладочную информацию, вы можете видеть, что пакет запроса не содержит информации об аутентификации, то есть аутентификация выполняется непосредственно между процессами, и epmd не отвечает за аутентификацию.
Код:
invoke do_request
0 5 7a 74 65 73 74   
epmd: Mon Sep  5 15:55:14 2022: ** got PORT2_REQ
epmd: Mon Sep  5 15:55:14 2022: ** sent PORT2_RESP (ok) for "test"

0 5 Первые два байта — это длина
7a 74 65 73 74 — ztest, z — управляющий символ, запрос информации о процессе, имя которого — test


анализ протокола рукопожатия erlang-distribution


Проблемы безопасности процесса связи изучались ранее: https://github.com/gteissier/erl-matter

Вывод
1. Файл cookie, сгенерированный erl по умолчанию, является псевдослучайным и может быть удален.
2. Рукопожатие протокола распределения erl защищено файлами cookie, процесс связи не аутентифицируется, и по умолчанию отсутствует TLS, который может быть атакован посредником.

Поскольку протокол связи erlang otp (стандартная библиотека, содержащая распределенный коммуникационный код) меняется, процесс OTP более высокой версии не может обмениваться данными с более ранней версией, а тестовый код проекта erl-matter не был успешно протестирован под otp 25.


Экспериментальная машина:


hostnameipsystem_type别名
PPC2LXR192.168.245.1WINDOWSmachine1
localcentos1192.168.245.128linuxmachine2



1. После того, как Windows и Linux перезапустят процесс, выполните следующую команду и используйте wireshark для захвата пакета рукопожатия.

Код:
net_adm:ping('test@localcentos1').

1663893592222.png


Первый шаг рукопожатия, машина1 отправляет машине2:


Имя поляДлиннаметод записиОтображение
Length 2bytes big endian length of data
Tag 1byte opcode, 'N' for handshake
Flags 8bytes see documentation
Creation 4bytes big endian Node A marks the identifier of its own pid, ports and references. Since it is an identifier, a 4bytes long unsigned integer can be randomly generated when writing the code.
NameLength 2bytes big endian length of name
Name NameLength the name of the machine1 node



1663893811768.png


название полядлиннамотод храненияотображение
Length 2bytes big endian length of data
Tag 1byte opcode, value 's' on success
Status 2bytes The value is ok on success

1663893901296.png


На втором этапе машина2 посылает машине1:

Название поляДлиннаметод храненияотображение
Length 2bytes big endian length of data
Tag 1byte value is 'N'
Flags 8bytes see documentation
challenge 4bytes big endian 32-bit random number generated by machine2
Creation 4bytes big endian identifier
NameLength 2bytes big endian length of name
Name NameLength the name of the machine2 node

1663894004574.png


Ответ

Название поляДлиннаМетод храненияОтображение
Length 2bytes big endian length of data-2
Tag 1byte value is 'r'
Challenge 4bytes big endian 32-bit random number generated by machine1
Digest 16bytes md5(cookie+machine2_challenge)

1663894080368.png


otp_src/lib/kernel/src/dist_util.erl, обратите внимание на способ преобразования в код python (см. код в конце)

машина2 отправляет машине1

ИмяДлиннаМетод храненияОтображение
Length 2bytes big endian length of data-2
Tag 1byte value is 'a'
Digest 16bytes md5(cookie+machine1_challenge), communicate with each other, so they need to check each other

1663894177991.png


Вот что по итогу из этого вышло:

Код:
class Erldp:
    def __init__(self,host:string,port:int,cookie:bytes,cmd:string):
        self.host = host
        self.port = port
        self.cookie = cookie
        self.cmd = cmd
    def setCookie(self,cookie:bytes):
        self.cookie = cookie

    def _connect(self):
        self.sock = socket(AF_INET,SOCK_STREAM,0)
        self.sock.settimeout(1)
        assert(self.sock)
        self.sock.connect((self.host,self.port))

    def rand_id(self,n=6):
        return ''.join([choice(ascii_uppercase) for c in range(n)]) + '@nowhere'

    # Note that the challenge here is str.encode(str(int.from_bytes(challenge,"big")))
    def getDigest(self,cookie:bytes,challenge:int):
        challenge = str.encode(str(challenge))
        m = md5()
        m.update(cookie)
        m.update(challenge)
        return m.digest()
    def getRandom(self):
        r = int(random() * (2**32))
        return int.to_bytes(r,4,"big")
    def isErlDp(self):
        try:
            self._connect()
        except:
            print("[!]%s:%s tcp Connection failure" % (self.host,self.port))
            return False
        try:
            self._handshake_step1()
        except:
            print("[!]%s:%sNOerldp" % (self.host,self.port))
            return False
        print("[*]%s:%sYeserldp" % (self.host,self.port))
        return True

    def _handshake_step1(self):

        self.name = self.rand_id()
        packet = pack('!Hc8s4sH', 1+8+4+2+len(self.name), b'N', b"\x00\x00\x00\x01\x03\xdf\x7f\xbd",b"\x63\x15\x95\x8c", len(self.name)) + str.encode(self.name)
        self.sock.sendall(packet)
        (res_packet_len,) = unpack(">H",self.sock.recv(2))
        (tag,status) = unpack("1s2s",self.sock.recv(res_packet_len))
        assert(tag == b"s")
        assert(status == b"ok")
        print("step1 end:Send node1 name successfully)

    def _handshake_step2(self):
        (res_packet_len,) = unpack(">H",self.sock.recv(2))
        data = self.sock.recv(res_packet_len)
        tag = data[0:1]
        flags = data[1:9]
        self.node2_challenge = int.from_bytes(data[9:13],"big")
        node2_creation = data[13:17]
        node2_name_len = int.from_bytes(data[17:19],"big")
        self.node2_name = data[19:]
        assert(tag == b"N")
        print("step2 end:Receive node2 name successfully")

    def _handshake_step3(self):
        node1_digest = self.getDigest(self.cookie,self.node2_challenge)
        self.node1_challenge = self.getRandom()
        packet2 = pack("!H1s4s16s",21,b"r",self.node1_challenge,node1_digest)
        self.sock.sendall(packet2)
        (res_packet_len,) = unpack(">H",self.sock.recv(2))
        (tag,node2_digest) = unpack("1s16s",self.sock.recv(res_packet_len))

        assert(tag == b"a")

        print("step3 end:Verify that md5 is successful and handshake is over ")


    def handshake(self):
        self._connect()
        self._handshake_step1()
        self._handshake_step2()
        self._handshake_step3()
        print("handshake done")

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

Ознакомьтесь с принципами псевдослучайной атаки «человек посередине» и инструкциями по управлению паролями по умолчанию. github.com/gteissier/erl-matter, если для написания кода для OTP25 требуется настройка кода, например. rpc:call('test@localcentos1','os','cmd',["touch /tmp/tttt"])29-й ctrl SPAWN_REQUEST (см. пакет и документацию pcap), недавно добавленный otp23, используется под otp25, а тот, что в erl-материи send_cmdИспользуется 6-я команда REG_SEND, которую нельзя запустить на otp25.
 

Вложения

  • index.png
    index.png
    12.7 КБ · Просмотры: 7


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх