JS
CV

SQL Injection e a Next.js Conf

out 28, 2023 4 min - tempo de leitura

Na última semana, ocorreu a Next.js Conf, onde foi apresentado o Server Actions como uma feature estável. Durante essa apresentação, houve um exemplo que acabou causando agitação na comunidade.

Print da nextjs.conf, mostra um codigo de um component SQL utilizando server actions

Várias pessoas acabaram criticando ou zombando dessa abordagem, alegando que esse exemplo é uma falha grave de SQL injection. O objetivo deste artigo é demonstrar que essa abordagem é segura e não causa SQL injection.

O que é SQL injection ?

Um ataque de SQL Injection consiste em passar uma query maliciosa para ser executada em seu banco de dados. Essa query pode alterar ou até mesmo apagar seus dados.

Segue um exemplo:

const { rows } = await client.query(
`SELECT * from USERS where user_id='${params.userId}'`
);

Essa query é suscetível a um ataque de SQL Injection, pois a entrada do usuário é inserida diretamente na query SQL sem realizar nenhuma validação.

Por exemplo, se passarmos o seguinte código como parâmetro no userID: "'; DROP TABLE USERS; --"; a query que seria executada ficaria assim:

SELECT * from USERS where user_id=''; DROP TABLE USERS; --'

Resultado na exclusão da nossa tabela de users.

Para evitarmos isso, devemos utilizar parameterized-query:

client.query(query, [parameter])

A função client.query() recebe dois parâmetros: o primeiro é a nossa query SQL e o segundo é um array com nossos parâmetros, ficando assim:

postgres.query("SELECT * from USERS where user_id = $1", [params.user_id])

Desta forma, o próprio Postgres cuida da validação dos nossos parâmetros, garantindo nossa segurança contra ataques de SQL injection.

No entanto, como escrever SQL seguido de template strings como demonstrado na Next.js Conf não é vulnerável a uma injeção? A razão para isso é que foi utilizado um template string combinado com a tag function.

Como funciona tag functions ?

Tag functions são funções que modificam a saída do nosso template string. Vejamos um exemplo abaixo:

let name = "Juliano"
function greeting(strings, name){
	console.log(strings) // []
	return `Hello, ${name}`
}
greeting`${name}` // "Hello, Jane"
 

Veja, a string hello foi adicionada pela nossa função. Outro exemplo de tag function que muitos de nós já utilizamos é escrevendo CSS com styled-components.

Agora que vimos brevemente como que funciona tag functions, vamos demonstrar como funciona a função sql do pacote @vercel-postgres que utiliza o mesmo conceito.

Veja o exemplo abaixo:

 
// custom tag function
export function sqlTemplate(strings, ...values) {
  // Isso garante que a função seja chamada corretamente
  if (!isTemplateStringsArray(strings) || !Array.isArray(values)) {
    throw new VercelPostgresError(
      'incorrect_tagged_template_call',
      "It looks like you tried to call `sql` as a function. Make sure to use it as a tagged template.\n\tExample: sql`SELECT * FROM users`, not sql('SELECT * FROM users')",
    );
  }
  let result = strings[0] ?? '';
  for (let i = 1; i < strings.length; i++) {
    result += `$${i}${strings[i] ?? ''}`;
  }
  return [result, values]; // retorna a  template literal e os valores que será interpolado como um array.
}
 
 
// a função `sql` utiliza da função `sqlTemplate`. Isso retorna uma consulta parametrizada, que está protegida contra ataques de injeção de SQL.
async function sql(strings, ...values) {
  const [query, params] = sqlTemplate(strings, ...values);
  return this.query(query, params);
}

A primeira função sqlTemplate retorna dois dados: a query e os parâmetros que serão utilizados por ela. Essa função é utilizada dentro da função sql, que executa a query seguindo a abordagem de consulta parametrizada, prevenindo ataques de injeção de SQL.

Conclusão

Como vimos acima, o exemplo apresentado na Next.js Conf é seguro e não causa SQL injection, pois a função sql utiliza um conceito chamado de tag functions, que consiste em modificar a saída das template strings.

Internamente, a função sql formata as strings passadas como parâmetros e executa client.query() utilizando parameterized-query, tornando-o totalmente seguro contra SQL injection.

Existe também outra discussão na comunidade sobre se devemos misturar conceitos de back-end com os de front-end, mas acredito que isso seja conteúdo para outro artigo.

Até mais e obrigado pelos peixes.

Refs: