Meu primeiro hack: um site que permite definir qualquer senha de usuário

Recentemente, encontrei uma vulnerabilidade interessante que permite a qualquer usuário definir um site específico para definir qualquer senha. Legal, né?

Foi engraçado e achei que poderia escrever um artigo interessante.

Você tropeçou nele.



Nota: o autor do artigo traduzido não é especialista em segurança da informação e esta é sua primeira excursão ao mundo da injeção de SQL. Ele pede para ser "condescendente com sua ingenuidade".

Aviso: o autor do artigo traduzido não divulgará o site com esta vulnerabilidade. Não porque ele informou o proprietário sobre isso e é obrigado pelo silêncio, mas porque ele quer preservar a vulnerabilidade para si mesmo. Se você descobrir este site, mantenha a boca fechada (tsyts).

Você sabe, é assim que às vezes você abre um site no kit de ferramentas para um desenvolvedor, examina solicitações de código e de rede minificadas sem nenhum objetivo. E de repente você percebe que algo está errado aqui. Nem um pouco assim. Então, fiz algo semelhante com a página de perfil de usuário em um dos sites e notei que, quando você ativa e desativa a notificação de recebimento, a página envia uma solicitação de rede:

/api/users?email=no 

E pensei: Será que eles permitiram alguma estupidez? Talvez eu deva tentar a injeção de SQL?

Eu procurei na net por “xkcd little bobby tables” para refrescar minha memória sobre como fazer injeções de SQL - eu não gosto delas - e comecei a trabalhar.

Na guia Rede do Chrome, copiei a solicitação (Copiar> Copiar como busca) e colei o resultado em um fragmento para que a solicitação possa ser reproduzida:

 fetch('https://blah.com/api/users', { credentials: 'include', headers: { authorization: 'Bearer blah', 'content-type': 'application/x-www-form-urlencoded', 'sec-fetch-mode': 'cors', 'x-csrf-token': 'blah', }, referrer: 'https://blah.com/blah', referrerPolicy: 'no-referrer-when-downgrade', body: 'email=no', // < -- The bit we're interested in method: 'POST', mode: 'cors', }); 

O restante do artigo é dedicado à confusão com a linha do body - é um transporte para o envio de instruções ao servidor.

Primeiro, tentei alterar meu sobrenome, definindo o valor na coluna lastName , concentrando-me simplesmente em seu nome:

 { // ... body: `email=no', lastName='testing` } 

Nada de interessante aconteceu. Depois fiz o mesmo com last_name , depois tentei a sorte com o surname - e opa! - a página substituiu meu sobrenome por "testing".

Foi muito emocionante. Sempre considerei as injeções de SQL uma lenda do livro. O fato de ele realmente não abrir o mundo para o código que insere a entrada do usuário diretamente nas expressões SQL.

Um pouco de filosofia
Recentemente, abordo muitas questões da minha vida do ponto de vista da lei de Sturgeon: "90% de tudo que há por aí é lixo". Percebi que, se você presume que tudo é feito corretamente, perde muitas oportunidades. Penso que essa nova descrença na humanidade me deu confiança suficiente para levar adiante esse experimento.

Para todos os não iniciados, explicarei o significado do resultado que descobri.
Acredito que algo semelhante acontece no servidor:

 const userId = someSessionStore.userId; const email = request.body.email; const sql = `UPDATE users SET email = '${email}' WHERE id = '${userId}'`; 

Tenho certeza de que o servidor deles está escrito em PHP, mas eu não falo essa linguagem, então escreverei exemplos em JavaScript. Além disso, não sou particularmente bom em consultas SQL. Não faço ideia se a tabela é chamada de user ou users ou user_table users e isso não importa.

Se meu ID de usuário for 1234 e eu enviar email = não, o SQL ficará assim:

 UPDATE users SET email = 'no' WHERE id = '1234' 

E se você substituir no pela string no', surname = 'testing , o SQL será válido, mas complicado:

 UPDATE users SET email = 'no', surname = 'testing' WHERE id = '1234' 

Como você se lembra, eu envio solicitações de um snippet de código nas ferramentas do desenvolvedor, enquanto estou na página de perfil. Portanto, a partir de agora, você pode pensar no campo de sobrenome nesta página (o elemento HTML <input>) como um pequeno stdout no qual você pode gravar informações, definindo o valor da minha conta de usuário na coluna de surname no banco de dados.

Então me perguntei se poderia copiar dados de outra coluna para a coluna surname ?

Não entendi o que fazer, o que fazer com SQL e, além disso, não sabia qual banco de dados é usado no servidor. Então, após cada etapa, passei 20 minutos pesquisando na rede e depois coçando a cabeça por mais 20 minutos, porque inseria regularmente minhas aspas na direção errada. É estranho que eu não destrua todo o banco de dados.

Copiar dados de uma coluna para outra acabou sendo um pouco mais difícil, porque eu queria enviar uma solicitação (supunha-se que deveria haver uma coluna de password ):

 UPDATE users SET email = 'no', surname = password WHERE id = '1234' 

Observe que não há aspas no código em torno da password . Como você se lembra, um designer de consulta super-moderno deve ficar assim ...

 const sql = `UPDATE users SET email = '${email}' WHERE id = '${userId}'`; 

... ou seja, quando você tenta passar no', surname = password sequência resultante não será uma consulta SQL válida. Em vez disso, eu precisava que toda a cadeia injetada se tornasse a segunda parte da solicitação e tudo o que vem depois dela deveria ser ignorado. Em particular, eu precisava passar WHERE e; no final da instrução SQL, bem como o comentário # para que as informações à direita sejam ignoradas. Sim, eu explico terrivelmente.

Em resumo, enviei uma nova linha:

 { // ... body: `email=no', surname = password WHERE username = 'me@email.com'; #` } 

E a seguinte linha será enviada ao banco de dados:

 UPDATE users SET email = 'no', surname = password WHERE username = 'me@email.com'; # WHERE id = '1234' 

Observe que o banco de dados ignorará WHERE id = '1234' , pois essa parte vem após o comentário # (proibir comentários em consultas SQL parece ser uma boa maneira de se proteger contra códigos desleixados).

Esperava que minha senha P @ ssword1 aparecesse em forma de texto no campo sobrenome, mas recebi 00fcdde26dd77af7858a52e3913e6f3330a32b31.

Isso me decepcionou, embora não tenha me surpreendido, e continuei tentando copiar o hash da minha senha na coluna de senhas de outro usuário.

Deixe-me explicar para iniciantes: quando você cria uma conta em algum lugar e envia uma nova senha P @ ssword1, ela se transforma em um hash como 00fcdde26dd77af7858a52e3913e6f3330a32b31 e é armazenada no banco de dados. Observando esse hash, ninguém será capaz de determinar sua senha (ou é o que dizem).

Na próxima vez que você fizer login e digitar a senha Senha @ 1, o servidor fará o hash novamente e a comparará com o hash armazenado no banco de dados. Isso confirmará a conformidade sem mesmo salvar sua senha.

Isso significa que, se eu quiser fornecer a alguém a senha P @ ssword1, devo definir a coluna de senha deste usuário como 00fcdde26dd77af7858a52e3913e6f3330a32b31.

Peso leve.

Abri outro navegador, criei um novo usuário com correio diferente e verifiquei primeiro se eu podia definir os dados para ele. Atualizado a propriedade do body para ele:

 { // ... body: `email=no', surname = 'WOOT!!' WHERE username = 'user-two@email.com'; #` } 

Eu executei o código, atualizei a página deste usuário e, ofiget, funcionou! Agora ele tinha o sobrenome "WOOT !!" (nome de solteira da minha avó).

Então eu tentei definir uma senha para este usuário:

  // ... body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b31' WHERE username = 'user-two@email.com'; #` } 

E você sabe o que?!?!?

Não deu certo. Agora eu não tinha acesso à segunda conta.
Aconteceu que eu cometi dois erros, cujo cálculo levou várias horas. Os especialistas em segurança da informação que estão lendo este artigo já entenderam o que estão falando e provavelmente estão rindo do tolo que escreve suas "façanhas" listadas na primeira página de Hacking for the Youngest.

No entanto, no final, procurei na rede por “hash da senha” e notei que muitos hashes são maiores que o meu 00fcdde26dd77af7858a52e3913e6f3330a32b31. Parece que ele está cortando em algum lugar.
Tentei inserir um pedaço de texto no campo sobrenome e encontrei um limite de 40 caracteres (é bom que eles definam o atributo maxlength para <input> para corresponder à restrição do banco de dados).

Agora, eu estava interessado apenas nos primeiros 40 caracteres do hash, que poderiam ser muito mais longos. Eu procurei por "sql substring" e logo enviei a seguinte solicitação ao servidor:

 { // ... body: `email=no', surname = SUBSTRING(password, 30, 1000) WHERE username = 'me@email.com'; #` } 

Começou com 30 para garantir que os 10 primeiros caracteres se sobreponham aos últimos 10 caracteres 00fcdde26dd77af7858a52e3913e6f3330a32b31. Ou os últimos 9. Ou 11.

Digressão lírica
Acho que quando eu morrer e for para o inferno, eles me forçarão a assistir todos os meus erros para sempre em câmera lenta, um após o outro. Um close mostrando meu rosto enquanto eu, repetidamente, percebo minha estupidez sem fim.

Voltando às realidades: os caracteres se sobrepuseram e, combinando as linhas, obtive um hash de 64 caracteres. Mais uma vez tentei copiá-lo para o segundo usuário:

  { // ... body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b3121a61bce915cc6145fc44453' WHERE username = 'user-two@email.com'; #` } 

E você sabe o que?!?!
Bem, você adivinhou, porque eu mencionei dois erros.

Ainda não consegui acessar a segunda conta, mas já estava perto disso (seria bom saber sobre isso naquele momento).

Eu procurei por “senha de banco de dados de melhores práticas” e descobri / lembrei de algo como “salt”.

Usar salt significa que, se você criar um hash para P @ ssword1 para um usuário, para o outro usuário a mesma senha fornecerá um hash diferente (outro salt será usado). Obviamente, um hash de senha não funcionará para dois usuários, os sais são diferentes.

Parece ser inteligente, mas ao mesmo tempo estúpido. Em todos os exemplos da tabela, havia simplesmente outra coluna chamada salt. Isso não significa que eu preciso copiar dados de duas colunas, não uma? Não parece um segundo cadeado, no qual a mesma chave se encaixa?

Alterei a consulta na esperança de copiar o valor de uma coluna que poderia ser chamada de salt,
para a coluna sobrenome:

  { // ... body: `email=no', surname = salt WHERE username = 'myemail@email.com'; #` } 

Um conjunto aleatório de caracteres apareceu no campo de sobrenome, um bom sinal. Para obter o que era um sal de 64 caracteres, usei SUBSTRING novamente.

Tudo estava pronto. Eu tenho um hash de senha e o salt que foi usado para criá-lo, você só precisa copiá-los para outro usuário. E enviei meu último pedido de rede naquela noite:

 fetch('https://blah.com/api/users', { credentials: 'include', headers: { authorization: 'Bearer blah', 'content-type': 'application/x-www-form-urlencoded', 'sec-fetch-mode': 'cors', 'x-csrf-token': 'blah', }, referrer: 'https://blah.com/blah', referrerPolicy: 'no-referrer-when-downgrade', body: `email=no', password = '00fcdde26dd77af7858a52e3913e6f3330a32b3121a61bce915cc6145fc44453', salt = '8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52' WHERE username = 'user-two@gmail.com'; #`, method: 'POST', mode: 'cors', }); 

Funcionou! Agora eu poderia fazer login na segunda conta com uma senha da primeira conta.
Isso não é louco?

* * *

Houve muitas tentativas e erros, mas quando seleciono um usuário real, primeiro recebo o sal e o hash dele e o mantenho comigo. Depois, substituirei o hash salgado pelo meu, conforme descrito no artigo, efetue login e substituirei instantaneamente o salt and hash pelos valores originais. Eu só preciso alterar a senha de outra pessoa por uma fração de segundo enquanto eu faço login, para que eles quase certamente não me encontrem.

Em teoria, é claro. Eu nunca faria isso.

* * *

Você pode estar se perguntando se isso é uma história fictícia. Não inventado. Alterei alguns pequenos detalhes para me proteger das acusações, mas tudo o mais era como descrito. E, é claro, na verdade, relatei a vulnerabilidade aos proprietários do site.

Mas não consigo deixar de me perguntar se isso foi apenas uma sorte para iniciantes. Este é literalmente o primeiro site em que tentei a injeção de SQL, e tudo estava preparado para mim, como se eu tivesse passado no exame de hackers para crianças.

O site que descrevi é pequeno, possui poucos usuários (34.718). Este é um serviço pago, portanto, para hackers experientes, isso não é de interesse. E, no entanto, me ocorreu que isso era possível.

Em suma, agora estou viciado em todo esse tópico com segurança da informação. Para mim, duas atividades favoritas foram combinadas: escrever um código e hooliganismo. Então, pesquisando “salários de segurança da informação na Austrália”, acho que encontrei um novo emprego.
Obrigado pela leitura!

PS: a tradução do artigo tenta preservar o estilo do autor o máximo possível :)

Source: https://habr.com/ru/post/pt468695/


All Articles