Correlacionando origens Tor em eventos do SIEM ArcSight usando Python

Isaque Profeta
14 min readOct 13, 2020

--

English version here.

Introdução

A anonimização, apesar de ser atualmente um recurso importante na internet, acaba sendo abusada como ferramenta para vetor de muitas explorações e ataques de segurança. Assim, integrar as informações de uso da rede Tor (atualmente a principal/maior rede de anonimização) para classificar eventos de acesso a um ambiente de Hosting/Serviços Web em um SIEM, para avaliação da motivação desses acessos, é uma tática muito importante.

Esse artigo apresenta um Caso de Uso usando Python e a biblioteca requests, usando um ambiente Linux, para integração da API do SIEM ArcSight ESM com Active Lists para classificar eventos oriundos da rede Tor.

Revisão de conceitos

SIEM

Um log é um registro de evento de sistema computacional. Abaixo, por exemplo, há três logs do sistema de instalação de pacotes do Ubuntu Linux:

$ tail -3 /var/log/dpkg.log 
2020-10-04 17:24:52 status unpacked gcc:amd64 4:7.4.0-1ubuntu2.3
2020-10-10 13:25:09 startup archives unpack
2020–10–10 13:25:13 status installed mime-support:all 3.60ubuntu1

Uma ferramenta de Syslog é um software que agrega as informações de logs oriundos de diversos hardwares e/ou softwares dentro de um ambiente. No exemplo abaixo, o Linux Syslogd agrega logs das aplicações CRON, systemd e anacron de um sistema operacional Ubuntu Linux:

$ tail -5 /var/log/syslog.1
Oct 10 23:59:01 ubuntu-desktop CRON[3344]: (root) CMD (command -v debian-sa1 > /dev/null && debian-sa1 60 2)
Oct 11 00:02:46 ubuntu-desktop systemd[1]: Started Run anacron jobs.
Oct 11 00:02:46 ubuntu-desktop anacron[3430]: Anacron 2.3 started on 2020-10-11
Oct 11 00:02:46 ubuntu-desktop anacron[3430]: Will run job `cron.daily' in 5 min.
Oct 11 00:02:46 ubuntu-desktop anacron[3430]: Jobs will be executed sequentially

Ambientes de provedores e hospedagem de infraestrutura de TI possuem logs oriundos de vários equipamentos de toda a infraestrutura computacional, desde Roteadores e Switches, passando por Firewalls, IPS’s/IDS’s, até chegar a Servidores de Aplicação e Bancos de Dados. Um Servidor Syslog une essas informações em uma base central de todas as ocorrências de eventos desse parque.

As ferramentas de Gerenciamento de eventos e segurança da informação (Security Information and Event Management ou SIEM) atuam semelhante ao Servidor Syslog, mas com especialização em segurança. Isso significa que as logs passam por uma inteligência de classificação, gerando alertas de segurança de acordo com padrões definidos, tendo também uma interface de pesquisa e geração de relatórios. É possível ainda acionar comandos externos automatizando ações de bloqueio em equipamentos de Firewall e/ou IPS, que suportem integrações externas.

Exemplo de alerta: O SIEM pode ter uma regra que identifica uma série de logs de erro 404 em um Servidor Web em um curto espaço de tempo para diversos diretórios de aplicação inexistentes, gerando um alerta de tentativa de Scan de aplicação (usando uma ferramenta como o dirb por exemplo).

Neste artigo será usado o Micro Focus ArcSight ESM como ferramenta de SIEM.

Tor

O ambiente do Tor funciona de modo que os usuários da rede possam usar ou atuar como caminhos aleatórios e encriptados de passagem entre todos os participantes da rede. Dessa forma, do ponto de vista do destino de uma conexão de rede, a máquina de origem não é a máquina que está acessando esse destino.

Usando o exemplo oficial abaixo, o computador de Alice usa um caminho aleatório entre os “nós“ (nodes) da rede para então chegar em um “nó de saída” (exit node) e acessar o servidor de destino Bob, assim anonimizando o acesso - pois Bob não conhece Alice, ele vê apenas a solicitação do exit node.

Imagem da documentação oficial: https://2019.www.torproject.org/about/overview.html.en

O interessante para esse artigo é que o Tor disponibiliza a lista dos seus exit nodes, ou seja, a lista com todos os nós de saída para acesso aos destinos de conexão. Essa lista, frequentemente atualizada e disponibilizada no site do próprio Tor, será a fonte de dados utilizada neste trabalho para classificar o tráfego como oriundo da rede Tor.

Abaixo, para referência, há outras duas listas com mais informações sobre nodes de redes Tor que não foram usadas no escopo desse artigo:

- Exit nodes atualizados a cada 30 minutos

- Todos os nodes (Entry/Guard, Middle e Exit), classificados e atualizados a cada 30 minutos

Lógica para implementação do caso deste artigo

Visando evitar gerenciar muitos connectors do ArcSight, interagiu-se diretamente com a Interface de Programação da Aplicação (Application Programming Interface, API) da ferramenta, sendo os passos:

  • Buscar a lista de IP’s dos exit nodes do Tor usando Python com a biblioteca requests;
  • Importar esses IP’s dentro de uma Active List do ArcSight mantendo ela atualizada (uma carga agendada);
  • Criar uma Rule no ArcSight para marcar os eventos que tenham IP’s de origem que estejam nessa Active List.

Pré-requisitos

Python + PIP

Neste Caso de Uso, usou-se a linguagem Python em sua versão 3 (Linguagem) e o instalador de bibliotecas da linguagem Pip para essa versão 3.

sudo apt update && sudo apt install python3 python3-pip

Confirma-se então que o Python do sistema operacional está na versão 3, realizando o teste no terminal:

~$ python -V
Python 3.6.9
~$ pip -V
pip 20.1.1 from /home/isaque/.local/lib/python3.6/site-packages/pip (python 3.6)

Requests

O acesso aos dados do repositório de exit nodes, bem como a integração com a API do ArcSight, foi realizado com o uso da biblioteca requests do Python. A instalação da mesma pode ser feita com o auxilio do Pip.

pip install requests

Active List

Uma Active List dentro do Arcsight ESM é uma lista de dados, da mesma forma que uma tabela de banco de dados ou planilha do Excel. Essa lista de dados é usada para guardar valores a serem comparados com outros recursos do SIEM. Neste artigo ela irá guardar os IP’s dos exit nodes.

Dentro da console do Arcsight ESM, criou-se a Active List com apenas uma coluna Fields-based chamada “ip do tipo Ip Address, com expiração de uma hora e capacidade para dez mil resultados. Deve-se tomar o cuidado de anotar o Resource ID da Active List necessário ao script.

NOTA: Também foi criado um usuário específico para essa automação, configurado para ter privilégios de escrita nesta Active List.

Criação da ActiveList no ArcSight ESM 7.2

Buscar a lista de IP’s dos exit nodes do Tor

Buscando a lista de redes

Primeiramente criou-se uma pasta chamada “script_tor”, para guardar o projeto e, dentro dela, foi criado o arquivo “carga_tor.py”.

mkdir ./script_tor
touch ./script_tor/carga_tor.py

Iniciando o código do “carga_tor.py” propriamente dito, primeiro importou-se a biblioteca requests e, em seguida, executou-se um GET para buscar a base de informações dos exit nodes. Posteriormente, é possível visualizar o resultado com a função “print()”:

import requests
redestor = requests.get(
'https://check.torproject.org/torbulkexitlist'
)
print(redestor.text)

O resultado da lista de ip’s está no corpo da resposta em formato texto. Por isso é necessário usar o “redestor.text” porque o resultado do “requests.get()” é um objeto do tipo Response que tem esse atributo Response.text.

Com esse conteúdo já é possível testar o resultado no terminal:

python3 ./carga_tor.py
Primeiro teste de get dos dados no VSCode
Primeiro teste de GET dos dados no VSCode

Para a carga final, trocou-se o “print()” por uma variável de lista do Python, com o cuidado de separar a lista na quebra de linha “\n”, usando para isso, a função de string “split()”:

import requests
redestor = requests.get(
'https://check.torproject.org/torbulkexitlist'
)
registros = redestor.text.split('\n')

Com a coleta dos dados pronta, é hora de estudar o lado do ArcSight.

Importar os IP’s dentro de uma Active List do ArcSight

Interagindo com a API do ArcSight

Agora, a parte mais complexa, pois não foi possível encontrar muitos exemplos ou clareza na documentação relativa à API do ArcSight (versão 7.2 do ArcSight ESM). Aparentemente há preferência pelo modelo Java com XML/SOAP, o que dificulta o entendimento para o uso dos métodos e atributos em outras linguagens com JSON/REST.

Após estudo e pesquisa, chegou-se aos seguintes resultados oficiais considerados úteis:

Mesmo assim, as documentações oficiais deixam muitas dúvidas, e as respostas de fórum ainda são vagas. Fazendo engenharia reversa em alguns projetos já existentes no GitHub chegou-se na seguinte solução para usar a API com JSON/REST:

1: Identificar no listServices da instalação do ArcSight ESM quais os recursos de API precisam ser usados (o Service e o Operation). Esse listServices fica na URL abaixo (exceto para operações administrativas e de login, pois nessas situações o manager-services na URL é trocado pelo core-services).

https://url-arcsight-esm:8443/www/manager-service/services/listServices

2: Acessar nessa página o WSDL (XML Desciptor) do Service, dentro do link que tem o texto: “See Web Services Description Language (WSDL) here

listServices da instalação do ESM

3: Pesquisar dentro do XML Desciptor a Operation escolhida e identificar os parâmetros de pesquisa

Element com os Sequences que são os parâmetros de pesquisa

Para o JSON, uma das coisas não encontradas nas documentações foi a descrição das abreviações dos recursos, necessária para o formato das chamadas. Assim, encontrou-se uma lista funcional na descrição do projeto pyaesm (que também faz interação com Active Lists) constante em referência no quarto passo.

4: Selecionar a classe abreviada de Service conforme a lista abaixo:

"act" = "resource.manager/activeListService/"
"arc" = "resource.manager/archiveReportService/"
"cas" = "resource.manager/caseService/"
"cap" = "resource.manager/conAppService/"
"con" = "resource.manager/connectorService/"
"das" = "resource.manager/dashboardService/"
"dmq" = "resource.manager/dataMonitorQoSService/"
"dat" = "resource.manager/dataMonitorService/"
"drl" = "resource.manager/drilldownListService/"
"dri" = "resource.manager/drilldownService/"
"fie" = "resource.manager/fieldSetService/"
"fil" = "resource.manager/fileResourceService/"
"gra" = "resource.manager/graphService/"
"gro" = "resource.manager/groupService/"
"int" = "resource.manager/internalService/"
"man" = "resource.manager/managerAuthenticationService/"
"net" = "resource.manager/networkService/"
"por" = "resource.manager/portletService/"
"que" = "resource.manager/queryService/"
"qvs" = "resource.manager/queryViewerService/"
"rep" = "resource.manager/reportService/"
"res" = "resource.manager/resourceService/"
"sei" = "resource.manager/securityEventIntrospectorService/"
"sev" = "resource.manager/securityEventService/"
"ser" = "resource.manager/serverConfigurationService/"
"use" = "resource.manager/userResourceService/"
"vie" = "resource.manager/viewerConfigurationService/"
"inf" = "manager/infoService/"
"mss" = "manager/managerSearchService/"

5: Definir a URL do endpoint de consumo da API com a estrutura abaixo, trocando o Service e o Operation pelos escolhidos acima:

https://url-arcsight-esm:8443/www/manager-service/rest/’Service’/’Operation’

EXEMPLO:

Limpar a ActiveLists via API:

1: Procurou-se no listServices e encontrou-se o ServiceActiveListService” e, estudando as Operations do mesmo, optou-se por testar o “clearEntries”;

2: Acessou-se o link do WSDL para verificar os atributos necessários;

3: Encontrou-se no XML Descriptor os atributos necessários para a Operation de “clearEntries”, que são o “authToken” e o “resourceId”;

4: Identificou-se na tabela de referência que o “ActiveListService” é “act”;

JSON final para a chamada:

{
"act.clearEntries": {
"act.authToken": TOKEN_DE_AUTENTICACAO,
"act.resourceId": RESOURCE_ID_DA_ACTIVE_LIST
}
}

5: O endpoint de consumo é: https://url-arcsight-esm:8443/www/manager-service/rest/ActiveListService/clearEntries

É importante saber essa estrutura para facilitar a navegação e entender quais são as chamadas e parâmetros possíveis para usar os mesmos.

Organização do código de apoio ao script

Primeiro é necessário criar uma função no Python com a requests para realizar o login e ter um token, para então realizar as interações com os métodos da API usando o mesmo.

Optou-se por separar essa função em outro arquivo (uma biblioteca local) para evitar repetição de código. E, para não deixar credenciais dentro de arquivos de código, montou-se um arquivo de configuração na mesma pasta do script.

touch __init__.py
touch config.ini
touch api_arcsight.py

O arquivo “__init__.py” permanece vazio facilitando o processo de import no projeto. Já o “config.ini” guarda as credenciais que serão usadas para acessar o ArcSight com o módulo Python configparser, sendo esse arquivo escrito no seguinte formato:

[arcsight]
user
=usuario_arcsight
password=senha_arcsight
server=https://url-do-arcsight-esm:8443

Esse arquivo tem as credenciais de um usuário específico para a automação, credenciais essas que nunca devem ser guardadas em ferramentas de controle de versão, no máximo um exemplo de modelo, denominado por exemplo de “config.ini.example”.

Agora, no arquivo “api_arcsight.py” monta-se o acesso às credenciais, criando o objeto do configparser e usando os métodos “read()” e “get()”, que lerão os dados do “config.ini”:

import requests
import configparser
config = configparser.ConfigParser()
config.read("config.ini")
USER = config.get('arcsight', 'user')
PASSWORD = config.get('arcsight', 'password')
SERVER = config.get('arcsight', 'server')

Função de Login

No mesmo arquivo montou-se a função de “logar”. Nesse caso, primeiro definiu-se o endpoint de login no core-services do ArcSight, customizou-se um header HTTP específico do formato de JSON e, por fim, definiu-se um payload com usuário e senha no formato esperado pelo endpoint de login da API.

def logar():
"""
Conecta ao arcsight e retorna um token de API
"""
endpoint_login = (
SERVER + '/www/core-service/rest/LoginService/login'
)
headers = {
'accept': 'application/json',
'content-type': 'application/json'
}
payload = {
"log.login": {
"log.login": USER,
"log.password": PASSWORD
}
}

Após esse preparo, realizou-se um POST com a requests para o endpoint definido do core-services passando o payload e os headers. Depois extraiu-se o token que está no caminho do dicionário “Response.json()” e retornou-se o mesmo no termino da função:

  try:
resp = requests.post(
endpoint_login,
json=payload,
headers=headers,
verify=False
)
token = resp.json()['log.loginResponse']['log.return']
except Exception as e:
print("Erro de login: {}".format(e))
return token

A opção do “requests.post” de “verify=False” é útil em instalações do ArcSight com certificados auto-assinados, funcionando como um “Aceitar risco e continuar” de browser acessando o mesmo site.

Assim, é possível executar a função de login usando duas linhas:

import api_arcsight
TOKEN_AUTENTICACAO = api_arcsight.logar()

Função de Wrapper para executar pesquisas

Em seguida montou-se uma função do tipo wrapper chamada “executar” para evitar repetição de código ao realizar consultas no ArcSight.

Para essa função, definiu-se o header HTTP especificando o formato JSON, o endpoint, que nesse caso é do manage-service e uma f-string completada pelo segundo parâmetro que a função receber.

def executar(payload, endpoint_servico):
headers = {
'accept': 'application/json'
}

endpoint = (
SERVER + f'/www/manager-service/rest/{endpoint_servico}'
)

Em seguida, realizou-se a execução do POST usando a requests com o endpoint e os headers definidos, junto com o payload que é passado pelo primeiro parâmetro da função. Por fim retornou-se o objeto Response da resposta da API.

  try:
resposta = requests.post(
endpoint,
json=payload,
headers=headers,
verify=False
)
except Exception as e:
print("Erro de pesquisa: {}".format(e))
return resposta

Assim, é possível executar pesquisas com a função de login usando o seguinte formato:

api_arcsight.executar(
payload={
"recurso.função": {
"recurso.parametro_um": VALOR_PARAMETRO_UM,
"recurso.parametro_dois": VALOR_PARAMETRO_DOIS
}
},
endpoint_servico='Recurso/funcaoDoRecurso'
)

Função de Logout

Por fim, neste arquivo criou-se uma última função de “deslogar”, que recebe o token a ser “deslogado” do sistema.

Para isso, definiu-se o endpoint do core-services, o header para o formato JSON e o payload de logout.

def deslogar(authtoken):
endpoint_login = (
SERVER + '/www/core-service/rest/LoginService/logout'
)
headers = {
'accept': 'application/json',
'content-type': 'application/json'
}
payload = {
"log.logout": {
"log.authToken": authtoken
}
}

Finalmente executou-se mais um POST com a requests com os dados definidos:

  try:
requests.post(
endpoint_login,
json=payload,
headers=headers,
verify=False
)
except Exception as e:
print("Erro de logout: {}".format(e))
return True

Assim, é possível executar a função de login usando a linha:

api_arcsight.deslogar(TOKEN_AUTENTICACAO)

Com essa biblioteca local pronta, é possível voltar para o script principal e adicionar a lógica que falta.

Importando a biblioteca de apoio

Dentro do arquivo “carga_tor.py”, no topo, já é possível importar o módulo criado anteriormente da api_arcsight:

import requests
import api_arcsight # <--- Adição da biblioteca local no inicío

Agora, usando a função logar recuperou-se o token de login para executar as consultas, e definiu-se o Resource ID da Active List para manipulação.

TOKEN_AUTENTICACAO = api_arcsight.logar()
ACTIVE_LIST = "Ha9ZKBHUBABCAGTV5ykX2XQ=="

Limpando a Active List

Com a necessidade de manter a Active List atualizada, decidiu-se por limpar a lista completamente e depois importar todos os novos registros pesquisados. Então, primeiro fez-se uma limpeza da Active List com a função executar passando-se o endpoint e o payload corretos:

api_arcsight.executar(
payload={
"act.clearEntries": {
"act.authToken": TOKEN_AUTENTICACAO,
"act.resourceId": ACTIVE_LIST
}
},
endpoint_servico='ActiveListService/clearEntries'
)

Inserindo os dados na Active List

Uma vez que a Active List está vazia, é possível inserir os novos dados coletados usando novamente a função executar.

Para isso, o payload da operação escolhida de addEntries espera dois parâmetros: um com uma lista das colunas (columns) e uma entryList que é a lista de entradas, cada uma formatada com um objeto JSON chamado de {‘entry’: []}, que tem a lista de dados na ordem das colunas.

Aqui, a Active List tem apenas uma coluna ip, sendo mais simples fazer uma list-comprehension do Python para alimentar essa entrada:

api_arcsight.executar(
payload={
"act.addEntries": {
"act.authToken": TOKEN_AUTENTICACAO,
"act.resourceId": ACTIVE_LIST,
"act.entryList": {
"columns": ['ip'],
"entryList": [ {'entry': [reg]} for reg in registros ]
}
}
},
endpoint_servico='ActiveListService/addEntries'
)

Logout

Para fazer o logout utilizou-se a função “deslogar” para não sobrecarregar o banco de dados com sessões órfãs:

api_arcsight.deslogar(TOKEN_AUTENTICACAO)

Agendamento

Para finalizar, é possível agendar o script usando o crontab para buscar e atualizar as informações a cada hora:

Outras opções seriam o systemctl timers do systemd ou o projeto isaqueprofeta/pylineup que usa Python + Celery

0 * * * * cd /caminho/script/script_tor; /usr/bin/python3 /caminho/script/script_tor/carga_tor.py

Acessando a console do ArcSight ESM, após rodar a primeira carga, já é possível ver os resultados na Active List:

ActiveList carregada via API com os “exit nodes” da rede Tor

Integrando a Active List com uma Rule para classificar eventos

Criação da Rule

Com a Active List carregada, criou-se uma Rule do tipo Pre-persistence que é uma regra para marcar os eventos com informações extras com base em condições para depois filtrar os mesmos para análise:

Criação básica da Rule

Nas Conditions adicionou-se uma regra de AND com uma condição de InActiveList comparando o campo Attacker Address (IP de origem) do evento com a coluna ip da Active List.

Criação da condição na Rule

Depois de criada a condição de correlação, criou-se uma Action na Rule para que todo evento que passe na condição receba 3 informações: um Name e mais duas Categories, sendo essas últimas uma para o comportamento (/Acesso) e uma para a técnica (/Acesso/Anonimizado):

Adição da ação de classificação dos eventos

Visualização dos dados em Active Channel

Por último, criou-se um Active Channel (que é um canal de visualização dentro do ArcSight ESM) com filtro para uma uma das informações de etiqueta que tenha sido inseridas na Action da Rule. Dessa forma, é possível visualizar os eventos e confirmar que eles estão marcados:

Visualização dos eventos marcados como acesso Anonimizado

Conclusão

Este Caso de Uso permite, por exemplo, analisar quais os tipos de tráfego que as aplicações recebem de forma anônima, permitindo uma tomada de decisão mais assertiva no tratamento de incidentes ou na sua prevenção. Isso foi possível através da integração com Python e a biblioteca requests para buscar as informações dos exit nodes da rede Tor, em ambiente Linux, inserindo-as em Active Lists via API do SIEM ArcSight ESM para correlação.

Nos casos onde os acessos não indicam atividade maliciosa, não caindo em outras correlações em seu SIEM, pode-se concluir que a origem deseja apenas acessar aquele conteúdo de forma anônima, enquadrando-se no cenário legítimo.

E, caso as aplicações recebam atividades maliciosas por meio de origem Tor, é necessário considerar o bloqueio ou redirecionamento para soluções de desafio para acesso, como CAPTCHA.

Outro exemplo interessante nesses casos de tráfego anônimo são as atividades de abuso de fóruns: com essas informações de tráfego nas mãos é possível bloquear a inclusão de comentários com origens anônimas, para evitar mensagens ofensivas e comportamentos de SPAM.

--

--

Isaque Profeta

Security operations automation, monitoring tools specialist and Python/JavaScript enthusiast, but sometimes I can play with OKR and ITIL too.