ОРИГИНАЛЬНАЯ СТАТЬЯ
ПЕРЕВЕДЕНО СПЕЦИАЛЬНО ДЛЯ 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
2 установка управления пакетами erlang-rebar3
Конфигурация проекта VSCode Debug Erlang
1. Когда машина1 открывает службу для внешнего мира, она сначала открывает службу epmd на порту 4369. Эту службу можно понимать как реестр, который используется для сохранения (имя, порт) службы машины1.
2. Когда машина2 хочет вызвать службу машины1, ей нужно сначала найти epmd, чтобы получить список (имя, порт) машины1.
3. Машина2 напрямую подключена к порту машины1, и вызывается RPC
3 демонстрация связи
Запустите виртуальную машину Linux и используйте Windows для удаленного вызова узла Linux.
установиv куки
файлы хоста добавляют записи разрешения DNS друг к другу
Подключитесь к узлу и посмотрите, успешно ли установлено соединение
На данный момент test@PPC2LXR и test@localcentos2 успешно подключены
Видно, что процесс защищен куки, а получение куки эквивалентно разрешению на выполнение произвольного кода.Следующее решает две проблемы
1. Аутентификация связывается с epmd или с процессом?
2. Есть ли проблемы с безопасностью в процессе аутентификации?
epmd — это служба регистрации узла erlang, которая обеспечивает разрешение имени узла, что можно понимать как центр регистрации, который используется для сообщения внешнему подключению к узлу информации об этом узле. Когда внешний хост запрашивает службу epmd, epmd возвращает всю информацию о прослушивающем порте узла и имя узла на текущем узле.
Обратите внимание, что epmd не аутентифицирован, что означает, что epmd будет предоставлять всю информацию о процессах, запущенных по sname или имени на хосте, и epmd поддерживает только запросы для нелокальных операций.Код находится в otp_src/erts/epmd/src/epmd_src. c: строка 799: void do_request(g, fd, s, buf, bsize)
EPMD_NAMES_REQ, по-видимому, используется для ответа на net_adm:names(). Следующий отладочный EPMD_PORT2_REQ
Измените код epmd, распечатайте содержимое пакета tcp перед do_request и make&&make install, запустите режим отладки epmd через epmd -d на хосте A:
Использовать erl -sname test Перезапустите процесс на хосте A и получите отладочную информацию:
Инициировать запрос на соединение с ошибкой файла cookie с хоста B:
Получите отладочную информацию, вы можете видеть, что пакет запроса не содержит информации об аутентификации, то есть аутентификация выполняется непосредственно между процессами, и epmd не отвечает за аутентификацию.
0 5 Первые два байта — это длина
7a 74 65 73 74 — ztest, z — управляющий символ, запрос информации о процессе, имя которого — test
Проблемы безопасности процесса связи изучались ранее: https://github.com/gteissier/erl-matter
Вывод
1. Файл cookie, сгенерированный erl по умолчанию, является псевдослучайным и может быть удален.
2. Рукопожатие протокола распределения erl защищено файлами cookie, процесс связи не аутентифицируется, и по умолчанию отсутствует TLS, который может быть атакован посредником.
Поскольку протокол связи erlang otp (стандартная библиотека, содержащая распределенный коммуникационный код) меняется, процесс OTP более высокой версии не может обмениваться данными с более ранней версией, а тестовый код проекта erl-matter не был успешно протестирован под otp 25.
Экспериментальная машина:
1. После того, как Windows и Linux перезапустят процесс, выполните следующую команду и используйте wireshark для захвата пакета рукопожатия.
Первый шаг рукопожатия, машина1 отправляет машине2:
На втором этапе машина2 посылает машине1:
Ответ
otp_src/lib/kernel/src/dist_util.erl, обратите внимание на способ преобразования в код python (см. код в конце)
машина2 отправляет машине1
Вот что по итогу из этого вышло:
На основе приведенного выше кода уже можно реализовать взлом паролей 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.
ПЕРЕВЕДЕНО СПЕЦИАЛЬНО ДЛЯ 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/OTP2 установка управления пакетами 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
Демонстрация взаимодействия с кластером Erlang
1. Особенности языка erlang
- интерпретируемый язык
- функциональный
- нет отражения
- Хорошо справляется с параллельной обработкой
- Поддерживается набор потоков ring3, поэтому планирование потоков не зависит от системного вызова, накладные расходы невелики, и можно легко создать большое количество потоков.
- самораспределяемый
- Нижний слой вызывается через rpc.
- Поскольку нет отражения, нет и десериализации rce для связи кластера (суть десериализации в том, чтобы обойти занесенный в черный список метод.invoke), но все же могут быть другие проблемы с безопасностью.
2. Принципиальная схема связи кластера
1. Когда машина1 открывает службу для внешнего мира, она сначала открывает службу epmd на порту 4369. Эту службу можно понимать как реестр, который используется для сохранения (имя, порт) службы машины1.
2. Когда машина2 хочет вызвать службу машины1, ей нужно сначала найти epmd, чтобы получить список (имя, порт) машины1.
3. Машина2 напрямую подключена к порту машины1, и вызывается RPC
3 демонстрация связи
Запустите виртуальную машину Linux и используйте Windows для удаленного вызова узла Linux.
- Откройте в режиме отладки, для облегчения подключения укажите имя хоста для машины
Код:
# linux
epmd -d
hostname localcentos2
- Используйте -sname для указания имени, erl автоматически откроет процесс во внешний мир и зарегистрирует его в epmd (если epmd нет, он также автоматически откроет epmd)
Код:
erl -sname test
установи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().
На данный момент test@PPC2LXR и test@localcentos2 успешно подключены
Код:
rpc:call('test@localcentos2','os','cmd',["touch /tmp/connect_success.txt"]).
Видно, что процесс защищен куки, а получение куки эквивалентно разрешению на выполнение произвольного кода.Следующее решает две проблемы
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.
Экспериментальная машина:
| hostname | ip | system_type | 别名 |
|---|---|---|---|
| PPC2LXR | 192.168.245.1 | WINDOWS | machine1 |
| localcentos1 | 192.168.245.128 | linux | machine2 |
1. После того, как Windows и Linux перезапустят процесс, выполните следующую команду и используйте wireshark для захвата пакета рукопожатия.
Код:
net_adm:ping('test@localcentos1').
Первый шаг рукопожатия, машина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 |
| название поля | длинна | мотод хранения | отображение |
|---|---|---|---|
| Length | 2bytes | big endian | length of data |
| Tag | 1byte | opcode, value 's' on success | |
| Status | 2bytes | The value is ok on success |
На втором этапе машина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 |
Ответ
| Название поля | Длинна | Метод хранения | Отображение |
|---|---|---|---|
| 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) |
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 |
Вот что по итогу из этого вышло:
Код:
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.