0% acharam este documento útil (0 voto)
49 visualizações

2 Secure API Development - API Security in Action

Este capítulo apresenta uma API de exemplo para uma rede social chamada Natter. A API é dividida em dois endpoints, um para usuários normais e outro para moderadores. O capítulo descreve a estrutura geral da API e começa a implementar a operação para criar um novo espaço social.

Enviado por

Marcus Passos
Direitos autorais
© © All Rights Reserved
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0% acharam este documento útil (0 voto)
49 visualizações

2 Secure API Development - API Security in Action

Este capítulo apresenta uma API de exemplo para uma rede social chamada Natter. A API é dividida em dois endpoints, um para usuários normais e outro para moderadores. O capítulo descreve a estrutura geral da API e começa a implementar a operação para criar um novo espaço social.

Enviado por

Marcus Passos
Direitos autorais
© © All Rights Reserved
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
Você está na página 1/ 66

Tópicos Comece a aprender Pesquise mais de 50.

000 cursos, even… O que há de novo

2 Desenvolvimento seguro de API


Este capítulocapas

Configurando um projeto de API de exemplo


Entendendo os princípios de desenvolvimento seguro
Identificando ataques comuns contra APIs
Validando a entrada e produzindo uma saída segura

Até agora, falei sobre a segurança da API de forma abstrata, mas neste capítulo,
você se aprofundará e examinará os detalhes básicos do desenvolvimento de uma
API de exemplo. Eu escrevi muitas APIs em minha carreira e agora passo meus
dias revisando a segurança das APIs usadas para operações críticas de segurança
em grandes corporações, bancos e organizações de mídia multinacionais. Embora
as tecnologias e técnicas variem de situação para situação e de ano para ano, os
fundamentos permanecem os mesmos. Neste capítulo, você aprenderá como apli-
car os princípios básicos de desenvolvimento seguro ao desenvolvimento de API,
para que possa criar medidas de segurança mais avançadas sobre uma base
sólida.

2.1 A API do Natter


você temteve a ideia de negócio perfeita. O que o mundo precisa é de uma nova
rede social. Você tem o nome e o conceito: Natter - a rede social para cafés da ma-
nhã, grupos de livros e outras pequenas reuniões. Você definiu seu produto mí-
nimo viável, de alguma forma recebeu algum financiamento e agora precisa mon-
tar uma API e um cliente web simples. Em breve você será o novo Mark Zucker-
berg, rico além de seus sonhos e considerando concorrer à presidência.

Apenas um pequeno problema: seus investidores estão preocupados com a segu-


rança. Agora você deve convencê-los de que você tem tudo sob controle e que eles
não serão motivo de chacota na noite de lançamento ou enfrentarão pesadas res-
ponsabilidades legais mais tarde. Por onde você começa?

Embora este cenário possa não ser muito parecido com qualquer coisa em que
você esteja trabalhando, se você está lendo este livro, é provável que em algum
momento você tenha que pensar sobre a segurança de uma API que você proje-
tou, construiu ou solicitado a manter. Neste capítulo, você criará um exemplo de
API de brinquedo, verá exemplos de ataques contra essa API e aprenderá como
aplicar princípios básicos de desenvolvimento seguro para eliminar esses
ataques.

2.1.1 Visão geral da API do Natter

oA API do Natter é dividida em dois endpoints REST, um para usuários normais e


outro para moderadores que têm privilégios especiais para lidar com comporta-
mento abusivo. As interações entre os usuários são construídas em torno de um
conceito de espaços sociais, que são grupos apenas para convidados. Qualquer
pessoa pode se inscrever e criar um espaço social e depois convidar seus amigos
para participar. Qualquer usuário do grupo pode postar uma mensagem no grupo
e ela pode ser lida por qualquer outro membro do grupo. O criador de um espaço
torna-se o primeiro moderador desse espaço.

A implantação geral da API é mostrada na figura 2.1. As duas APIs são expostas
por HTTP e usam JSON para o conteúdo da mensagem, tanto para clientes móveis
quanto para web. As conexões com o banco de dados compartilhado usam SQL
padrão sobre a API JDBC do Java.

A Figura 2.1 Natter expõe duas APIs - uma para usuários normais e outra para
moderadores. Para simplificar, ambos compartilham o mesmo banco de dados. Os
clientes móveis e da web se comunicam com a API usando JSON sobre HTTP, em-
bora as APIs se comuniquem com o banco de dados usando SQL sobre JDBC.

A API do Natter oferece as seguintes operações:

Uma solicitação HTTP POST para o /spaces URI cria um novo espaço social. O
usuário que executa esta operação POST torna-se o proprietário do novo es-
paço. Um identificador exclusivo para o espaço é retornado na resposta.
Os usuários podem adicionar mensagens a um espaço social enviando uma soli-
citação POST para /spaces/ <spaceId>/messages onde <spaceId> está o
identificador exclusivo do espaço.
As mensagens em um espaço podem ser consultadas usando uma solicitação
GET para /spaces/ <spaceId>/messages . Um since=<timestamp> parâ-
metro de consulta pode ser usado para limitar as mensagens retornadas a um
período recente.
Por fim, os detalhes de mensagens individuais podem ser obtidos usando uma
solicitação GET para /spaces/<spaceId>/messages/<messageId> .

A API do moderador contém uma única operação para excluir uma mensagem en-
viando uma solicitação DELETE para o URI da mensagem. Uma coleção do Post-
man para ajudá-lo a usar a API está disponível em
https://www.getpostman.com/collections/ef49c7f5cba0737ecdfd . Para importar a
coleção no Postman, vá para Arquivo, Importar e selecione a guia Link. Em se-
guida, insira o link e clique em Continuar.

TIP Postman ( https://www.postman.com ) é uma ferramenta amplamente usada


para explorar e documentar APIs HTTP. Você pode usá-lo para testar exemplos
das APIs desenvolvidas neste livro, mas também forneço comandos equivalentes
usando ferramentas simples ao longo do livro.

Neste capítulo, você implementará apenas a operação para criar um novo espaço
social. As operações de postagem de mensagens em um espaço e leitura de mensa-
gens são deixadas como exercício. O repositório GitHub que acompanha o livro (
https://github.com/NeilMadden/ apisecurityinaction ) contém exemplos de imple-
mentações das operações restantes no capítulo 02-endramo.
2.1.2 Visão geral da implementação

oA API do Natter foi escrita em Java 11 usando a estrutura Spark Java (


http://sparkjava.com ) (não confundir com a plataforma de análise de dados Apa-
che Spark). Para tornar os exemplos tão claros quanto possível para desenvolve-
dores não-Java, eles são escritos em um estilo simples, evitando muitos idiomas
específicos de Java. O código também foi escrito para maior clareza e simplici-
dade, em vez de prontidão para produção. O Maven é usado para criar os exem-
plos de código e um banco de dados H2 na memória ( https://h2database.com ) é
usado para armazenamento de dados. A biblioteca de abstração de banco de da-
dos Dalesbred ( https://dalesbred.org ) é usada para fornecer uma interface mais
conveniente para o banco de dados do que a interface JDBC do Java, sem trazer a
complexidade de uma estrutura completa de mapeamento objeto-relacional.

Instruções detalhadas sobre como instalar essas dependências para Mac, Win-
dows e Linux estão no apêndice A. Se você não tiver todos ou algum deles insta-
lado, certifique-se de tê-los prontos antes deProsseguir.

DICA Para uma melhor experiência de aprendizado, é uma boa ideia digitar as
listagens deste livro à mão, para ter certeza de que entendeu cada linha. Mas se
você quiser ir mais rápido, o código-fonte completo de cada capítulo está disponí-
vel no GitHub em https://github.com/NeilMadden/apisecurityinaction . Siga as ins-
truções no arquivo README.md para configurar.

2.1.3 Configurando o projeto

UsarMaven para gerar a estrutura básica do projeto, executando o seguinte co-


mando na pasta onde deseja criar o projeto:
mvn archetype:generate \
➥ -DgroupId=com.manning.apisecurityinaction \
➥ -DartifactId=natter-api \
➥ -DarchetypeArtifactId=maven-archetype-quickstart \
➥ -DarchetypeVersion=1.4 -DinteractiveMode=false

Se esta é a primeira vez que você usa o Maven, pode levar algum tempo para bai-
xar as dependências necessárias. Depois de concluído, você terá a seguinte estru-
tura de projeto, contendo o arquivo de projeto Maven inicial (pom.xml) e uma
App classee AppTest classe de teste de unidade na estrutura de pasta do pacote
Java necessária.

natter-api
├── pom.xml ❶
└── origem
├── principal
│ └── java
│ └── com
│ └── tripulação
│ └── apisecurityinaction
│ └── App.java ❷
└── teste
└── java
└── com
└── tripulação
└── apisecurityinaction
└── AppTest.java ❸

❶ O arquivo de projeto Maven


❷ A classe Java de amostra gerada pelo Maven

❸ Um arquivo de amostra de teste de unidade

Primeiro, você precisa substituir o arquivo de projeto Maven gerado por um que
liste as dependências que você usará. Localize o arquivo pom.xml e abra-o em seu
editor ou IDE favorito. Selecione todo o conteúdo do arquivo e exclua-o, cole o
conteúdo da listagem 2.1 no editor e salve o novo arquivo. Isso garante que o Ma-
ven esteja configurado para Java 11, configura a classe principal para apontar
para a Main classe(a ser escrito em breve) e configura todas as dependências que
você precisa.

NOTA No momento da escrita, a última versão do banco de dados H2 é 1.4.200,


mas esta versão causa alguns erros com os exemplos deste livro. Por favor, use a
versão 1.4.197 conforme mostrado na listagem.

Listagem 2.1 pom.xml

<?xml versão="1.0" codificação="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.manning.api-security-in-action</groupId>
<artifactId>natter-api</artifactId>
<version>1.0.0-SNAPSHOT</version>

<propriedades>
<maven.compiler.source>11</maven.compiler.source> ❶
<maven.compiler.target>11</maven.compiler.target> ❶
<exec.mainClass>
com.manning.apisecurityinaction.Main ❷
</exec.mainClass>
</propriedades>

<dependências>
<dependência>
<groupId>com.h2database</groupId> ❸
<artifactId>h2</artifactId> ❸
<version>1.4.197</version> ❸
</dependency> ❸
<dependency> ❸
<groupId>com.sparkjava</groupId> ❸
<artifactId>spark-core</artifactId> ❸
<version>2.9.2</version> ❸
</dependency> ❸
<dependency> ❸
<groupId>org.json</groupId> ❸
<artifactId>json</artifactId> ❸
<version>20200518</version> ❸
</dependency> ❸
<dependency> ❸
<groupId>org.dalesbred</groupId> ❸
< artefatoId>dalesbred</artifactId> ❸
<version>1.3.2</version> ❸
</dependency> ❸
<dependência>
<groupId>org.slf4j</groupId> ❹
<artifactId>slf4j-simple</artifactId> ❹
<version>1.7.30</version> ❹
</dependência>
</dependencies>
</projeto>

❶ Configure o Maven para Java 11.

❷ Defina a classe principal para executar o código de exemplo.

❸ Inclua as últimas versões estáveis ​de H2, Spark, Dalesbred e JSON.org.

❹ Inclua slf4j para habilitar o log de depuração para o Spark.

Agora você pode excluir os arquivos App.java e AppTest.java, pois estará escre-
vendo novas versões deles enquantovai.

2.1.4 Inicializando o banco de dados

ParaPara colocar a API em funcionamento, você precisará de um banco de dados


para armazenar as mensagens que os usuários enviam uns aos outros em um es-
paço social, bem como os metadados sobre cada espaço social, como quem o criou
e como é chamado. Embora um banco de dados não seja essencial para este exem-
plo, a maioria das APIs do mundo real usará um para armazenar dados e, por-
tanto, usaremos um aqui para demonstrar o desenvolvimento seguro ao interagir
com um banco de dados. O esquema é muito simples e mostrado na figura 2.2.
Consiste em apenas duas entidades: espaços sociais e mensagens. Os espaços são
armazenados no spaces tabela do banco de dados, juntamente com o nome do
espaço e o nome do proprietário que o criou. As mensagens são armazenadas na
messages tabela, com referência ao espaço em que se encontram, bem como o
conteúdo da mensagem (como texto), o nome do usuário que postou a mensagem
e a hora em que ela foi criada.

Figura 2.2 O esquema do banco de dados Natter consiste em espaços sociais e


mensagens dentro desses espaços. Os espaços têm um proprietário e um nome,
enquanto as mensagens têm um autor, o texto da mensagem e a hora em que a
mensagem foi enviada. IDs exclusivos para mensagens e espaços são gerados au-
tomaticamente usando sequências SQL.

Usando seu editor ou IDE favorito, crie um arquivo schema.sql em natter-


api/src/main/resources e copie o conteúdo da listagem 2.2 para ele. Inclui uma ta-
bela chamada spaces para acompanhar os espaços sociais e seus proprietários.
Uma sequência é usada para alocar IDs exclusivos para espaços. Se você nunca
usou uma sequência antes, é um pouco como uma tabela especial que retorna um
novo valor toda vez que você a lê.
Outra mesa, messages , controla as mensagens individuais enviadas para um es-
paço, junto com quem era o autor, quando foi enviado e assim por diante. Nós in-
dexamos esta tabela por tempo, para que você possa pesquisar rapidamente por
novas mensagens que foram postadas em um espaço desde o último login de um
usuário.

Listagem 2.2 O esquema do banco de dados: schema.sql

CRIAR espaços TABLE( ❶


space_id INT PRIMARY KEY,
nome VARCHAR(255) NÃO NULO,
proprietário VARCHAR(30) NÃO NULO
);
CRIAR SEQUÊNCIA space_id_seq; ❷
Mensagens CREATE TABLE( ❸
space_id INT NOT NULL REFERENCES spaces(space_id),
msg_id INT PRIMARY KEY,
autor VARCHAR(30) NOT NULL,
msg_time TIMESTAMP NÃO NULL PADRÃO CURRENT_TIMESTAMP,
msg_text VARCHAR(1024) NÃO NULO
);
CRIAR SEQUÊNCIA msg_id_seq;
CREATE INDEX msg_timestamp_idx ON messages(msg_time); ❹
CRIAR ÍNDICE UNIQUE space_name_idx ON spaces(name);

❶ A tabela de espaços descreve quem possui quais espaços sociais.

❷ Usamos sequências para garantir a exclusividade das chaves primárias.

❸ A tabela de mensagens contém as mensagens reais.


❹ Indexamos as mensagens por carimbo de data/hora para permitir o acompanha-
mento das mensagens recentes.

Abra seu editor novamente e crie o arquivo Main.java em natter-


api/src/main/java/com/manning/apisecurityinaction (onde Maven gerou o
App.java para você anteriormente). A listagem a seguir mostra o conteúdo desse
arquivo. No método principal, você primeiro cria um novo JdbcConnectionPo-
ol objeto. Esta é uma classe H2 que implementa a DataSource interface JDBC pa-
drão, enquanto fornece agrupamento simples de conexões internamente. Você
pode então envolver isso em um Database objeto Dalesbredusando o
Database.forDataSource() método. Depois de criar o pool de conexões, você
pode carregar o esquema do banco de dados do arquivo schema.sql criado anteri-
ormente. Quando você constrói o projeto, o Maven copia todos os arquivos do ar-
quivo src/main/resources para o arquivo .jar que ele cria. Portanto, você pode
usar o Class.getResource() métodopara localizar o arquivo do caminho de
classe Java, conforme mostradodentroListagem 2.3.

Listagem 2.3 Configurando o pool de conexões do banco de dados

pacote com.manning.apisecurityinaction;

importar java.nio.file.*;

import org.dalesbred.*;
import org.h2.jdbcx.*;
import org.json.*;

public class Principal {

public static void main(String... args) lança Exception {


var datasource = JdbcConnectionPool.create( ❶
"jdbc:h2:mem:natter", "natter", "password"); ❶
var database = Database.forDataSource(datasource);
criarTabelas(banco de dados);
}

private static void createTables (banco de dados do banco de dados)


lança exceção {
var path = Paths.get( ❷
Main.class.getResource("/schema.sql").toURI()); ❷
database.update(Files.readString(caminho)); ❷
}
}

❶ Crie umObjeto JDBC DataSource para o banco de dados na memória.

❷ Carregar definições de tabela de schema.sql.

2.2 Desenvolvendo a API REST

Agoraque você tem o banco de dados no lugar, você pode começar a escrever as
APIs REST reais que o utilizam. Você aprofundará os detalhes da implementação à
medida que avançamos no capítulo, aprendendo os princípios de desenvolvi-
mento seguro à medida que avança.

Em vez de implementar toda a lógica do seu aplicativo diretamente na


Main classe, você extrairá as operações principais em vários objetos controlado-
res. A Main classe então definirá mapeamentos entre solicitações e métodos HTTP
nesses objetos controladores. No capítulo 3, você adicionará vários mecanismos
de segurança para proteger sua API, e estes serão implementados como filtros
dentro da Main classe sem alterar os objetos controladores. Esse é um padrão co-
mum ao desenvolver APIs REST e torna o código um pouco mais fácil de ler, pois
os detalhes específicos do HTTP são separados da lógica central da API. Embora
você possa escrever um código seguro sem implementar essa separação, é muito
mais fácil revisar os mecanismos de segurança se eles estiverem claramente sepa-
rados em vez de misturados à lógica central.

DEFINIÇÃO Um controlador é um trecho de código em sua API que responde às


solicitações dos usuários. O termo vem do popularmodel-view-controller(MVC)
padrão para construir interfaces de usuário. O modelo é uma exibição estrutu-
rada de dados relevantes para uma solicitação, enquanto a exibição é a interface
do usuário que exibe esses dados para o usuário. O controlador então processa as
solicitações feitas pelo usuário e atualiza o modelo adequadamente. Em uma API
REST típica, não há componente de exibição além da formatação JSON simples,
mas ainda é útil estruturar seu código em termos de objetos controladores.

2.2.1 Criando um novo espaço

oA primeira operação que você implementará é permitir que um usuário crie um


novo espaço social, que ele poderá reivindicar como proprietário. Você criará
uma nova SpaceController classeque irá lidar com todas as operações relacio-
nadas com a criação e interação com os espaços sociais. O controlador será inicia-
lizado com o Database objeto Dalesbredque você criou na listagem 2.3. o crea-
teSpace métodoserá chamado quando um usuário criar um novo espaço social, e
o Spark passará em um Request e um Response objetoque você pode usar para
implementar a operação e produzir uma resposta.

O código segue o padrão geral de muitas operações de API.

1. Primeiro, analisamos a entrada e extraímos as variáveis ​de interesse.


2. Em seguida, iniciamos uma transação de banco de dados e executamos quais-
quer ações ou consultas solicitadas.
3. Por fim, preparamos uma resposta, conforme mostra a figura 2.3.

Figura 2.3 Uma operação de API geralmente pode ser separada em três fases: pri-
meiro analisamos a entrada e extraímos as variáveis ​de interesse, depois executa-
mos a operação real e, finalmente, preparamos alguma saída que indica o status
da operação.

Nesse caso, você usará a biblioteca json.org para analisar o corpo da solicitação
como JSON e extrair o nome e o proprietário do novo espaço. Você então usará
Dalesbred para iniciar uma transação no banco de dados e criar o novo espaço in-
serindo uma nova linha no spaces banco de dadostabela. Por fim, se tudo foi
bem-sucedido, você criará uma resposta 201 Created com algum JSON descre-
vendo o espaço recém-criado. Como é necessário para uma resposta HTTP 201,
você definirá o URI do espaço recém-criado no cabeçalho Location da resposta.

Navegue até o projeto Natter API que você criou e localize a pasta
src/main/java/com/manning/apisecurityinaction. Crie uma nova subpasta cha-
mada “controller” neste local. Em seguida, abra seu editor de texto e crie um novo
arquivo chamado SpaceController.java nesta nova pasta. A estrutura do arquivo
resultante deve ter a seguinte aparência, com os novos itens destacados em
negrito:
natter-api
├── pom.xml
└── origem
├── principal
│ └── java
│ └── com
│ └── tripulação
│ └── apisecurityinaction
│ ├── Main.java
│ └── controlador
│ └── SpaceController.java
└── teste
└── ...

Abra o arquivo SpaceController.java em seu editor novamente e digite o conteúdo


da listagem 2.4 ecliqueSalvar.

AVISO O código escrito contém uma vulnerabilidade de segurança grave, conhe-


cida como vulnerabilidade de injeção de SQL. Você corrigirá isso na seção 2.4.
Marquei a linha de código quebrada com um comentário para garantir que você
não a copie acidentalmente em um aplicativo real.

Listagem 2.4 Criando um novo espaço social

pacote com.manning.apisecurityinaction.controller;
import org.dalesbred.Database;
import org.json.*;
importar faísca.*;
public class SpaceController {
banco de dados de banco de dados final privado;
public SpaceController(banco de dados do banco de dados) {
this.database = banco de dados;
}
public JSONObject createSpace(solicitação de solicitação, resposta de resposta)
lança SQLException {
var json = new JSONObject(request.body()); ❶
var spaceName = json.getString("nome");
var proprietário = json.getString("proprietário");
return database.withTransaction(tx -> { ❷
var spaceId = database.findUniqueLong( ❸
"SELECT PRÓXIMO VALOR PARA space_id_seq;"); ❸
// AVISO: esta próxima linha de código contém um
// vulnerabilidade de segurança!
database.updateUnique(
"INSERT INTO espaços(space_id, nome, proprietário)" +
"VALUES(" + spaceId + ", '" + spaceName +
"', '" + proprietário + "');");
resposta.status(201); ❹
response.header("Localização", "/espaços/" + spaceId); ❹
retornar novo JSONObject()
.put("nome", espaçoNome)
.put("uri", "/espaços/" + spaceId);
});
}
}

❶ Analise a carga útil da solicitação e extraia detalhes do JSON.

❷ Iniciar uma transação de banco de dados.

❸ Gere um novo ID para o espaço social.


❹ Retorne um código de status 201 Created com o URI do espaço no cabeçalho
Location.

2.3 Conectando os endpoints REST

Agoraque você criou o controlador, você precisa conectá-lo para que seja cha-
mado quando um usuário fizer uma solicitação HTTP para criar um espaço. Para
fazer isso, você precisará criar uma nova rota do Sparkque descreve como combi-
nar solicitações HTTP recebidas com métodos em nossos objetos controladores.

DEFINIÇÃO Uma rotadefine como converter uma solicitação HTTP em uma


chamada de método para um de seus objetos controladores. Por exemplo, um mé-
todo HTTP POST para o /spaces URI pode resultar em um createSpace método
sendo chamado no SpaceController objeto.

Na listagem 2.5, você usará importações estáticas para acessar a API do Spark.
Isso não é estritamente necessário, mas é recomendado pelos desenvolvedores do
Spark porque pode tornar o código mais legível. Então você precisa criar uma ins-
tância do seu SpaceController objetoque você criou na última seção, passando
no Dalesbred Database objeto para que ele possa acessar o banco de dados. Você
pode então configurar as rotas Spark para chamar métodos no objeto controlador
em resposta a solicitações HTTP. Por exemplo, a linha de código a seguir organiza
a chamada do método createSpace quando uma solicitação HTTP POST é recebida
para o URI /spaces:

post("/spaces", spaceController::createSpace);

Por fim, como todas as suas respostas de API serão JSON, adicionamos um af-
ter filtro Sparkpara definir o cabeçalho Content-Type na resposta
a application/json em todos os casos, qual é o tipo de conteúdo correto para
JSON. Como veremos mais tarde, é importante definir cabeçalhos de tipo correto
em todas as respostas para garantir que os dados sejam processados ​conforme
pretendido pelo cliente. Também adicionamos alguns manipuladores de erro para
produzir respostas JSON corretas para erros internos do servidor e erros não en-
contrados (quando um usuário solicita um URI que não possui uma rota definida).

DICA O Spark possui três tipos de filtros (figura 2.4). Os filtros anteriores são exe-
cutados antes que a solicitação seja tratada e são úteis para validação e definição
de padrões. Os filtros posteriores são executados depois que a solicitação foi tra-
tada, mas antes de qualquer manipulador de exceção (se o processamento da soli-
citação gerou uma exceção). Existem também pós-filtros, que são executados após
todos os outros processamentos, incluindo manipuladores de exceção e, portanto,
são úteis para definir os cabeçalhos que você deseja apresentar em todas as
respostas.

 
Figura 2.4 Os filtros anteriores do Spark são executados antes que a solicitação
seja processada por seu manipulador de solicitações. Se o manipulador for con-
cluído normalmente, o Spark executará qualquer filtro posterior. Se o manipula-
dor lançar uma exceção, o Spark executará o manipulador de exceção correspon-
dente em vez dos filtros posteriores. Por fim, os filtros afterAfter são sempre exe-
cutados após o processamento de cada solicitação.

Localize o arquivo Main.java no projeto e abra-o em seu editor de texto. Digite o


código da listagem 2.5 e salve o novo arquivo.

Listagem 2.5 Os endpoints da API REST do Natter

pacote com.manning.apisecurityinaction;

import com.manning.apisecurityinaction.controller.*;
import org.dalesbred.Database;
import org.h2.jdbcx.JdbcConnectionPool;
import org.json.*;
importar java.nio.file.*;

importar spark.Spark.* estático; ❶

public class Principal {

public static void main(String... args) lança Exception {


var fonte de dados = JdbcConnectionPool.create(
"jdbc:h2:mem:natter", "natter", "senha");
var database = Database.forDataSource(datasource);
criarTabelas(banco de dados);

var espaçoController =
novo SpaceController(banco de dados); ❷
post("/spaces", ❸
spaceController::createSpace); ❸

after((pedido, resposta) -> { ❹


resposta.type("aplicativo/json"); ❹
});

internalServerError(novo JSONObject()
.put("erro", "erro interno do servidor").toString());
notFound(novo JSONObject()
.put("erro", "não encontrado").toString());
}

private static void createTables(Banco de dados do banco de dados) {


// Como antes
}
}

❶ Use importações estáticas para usar a API Spark.

❷ Construa o SpaceController e passe para ele o objeto Banco de Dados.

❸ Isso lida com solicitações POST para o endpoint /spaces chamando o método crea-
teSpace em seu objeto controlador.

❹ Adicionamos alguns filtros básicos para garantir que toda saída seja sempre tra-
tada como JSON.

2.3.1 Experimentando

Agora que temos uma operação de API escrita, podemos iniciar o servidor e testá-
lo. A maneira mais simples de começar a funcionar é abrindo um terminal na
pasta do projeto e usando o Maven:

mvn clean compile exec:java

Você deve ver a saída de log para indicar que o Spark iniciou um servidor Jetty in-
corporado na porta 4567. Você pode usar curl para chamar sua operação de API,
como no seguinteexemplo:

$ curl -i -d '{"name": "test space", "owner": "demo"}'


➥ http://localhost:4567/spaces
HTTP/1.1 201 Criado
Data: quarta-feira, 30 de janeiro de 2019 15:13:19 GMT
Localização: /espaços/4
Tipo de conteúdo: aplicativo/json
Codificação de transferência: em partes
Servidor: Jetty(9.4.8.v20171121)

{"nome":"espaço de teste","uri":"/espaços/1"}

EXPERIMENTE Tente criar alguns espaços diferentes com nomes e proprietá-


rios diferentes, ou com o mesmo nome. O que acontece quando você envia entra-
das incomuns, como um nome de usuário do proprietário com mais de 30 caracte-
res? E os nomes que contêm caracteres especiais, como aspas simples?

2.4 Ataques de injeção

Infelizmente,o código que você acabou de escrever tem uma grave vulnerabili-
dade de segurança, conhecida como ataque de injeção de SQL. Os ataques de inje-
ção são uma das vulnerabilidades mais difundidas e graves em qualquer aplica-
tivo de software. Atualmente, a injeção é a entrada número um no Top 10 da
OWASP (veja a barra lateral).

Os 10 melhores do OWASP

O OWASP Top 10 é uma lista das 10 principais vulnerabilidades encontradas em


muitos aplicativos da Web e é considerada a linha de base oficial para um aplica-
tivo da Web seguro. Produzido pelaProjeto de segurança de aplicativos da Web
abertos(OWASP) a cada poucos anos, a última edição foi publicada em 2017 e está
disponível em https://owasp.org/www-project-top-ten/ . O Top 10 é compilado a
partir de feedback de profissionais de segurança e uma pesquisa de vulnerabili-
dades relatadas. Enquanto este livro estava sendo escrito, eles também publica-
ram um top 10 específico de segurança de API ( https://owasp.org/www-project-
api-security/ ). As versões atuais listam as seguintes vulnerabilidades, a maioria
das quais são abordadas neste livro:
Os 10 melhores aplicativos da Web API de segurança top 10

A1:2017 - Injeção API1:2019 - Autorização em nível de


objeto quebrado

A2:2017 - Autenticação quebrada API2:2019 - Autenticação de usuário


quebrada

A3:2017 - Exposição de dados API3:2019 - Exposição excessiva de


confidenciais dados

A4:2017 - Entidades Externas XML API4:2019 - Falta de recursos e limi-


(XXE) tação de taxa

A5:2017 - Controle de acesso API5:2019 - Autorização de nível de


quebrado função quebrada

A6:2017 - Configuração incorreta de API6:2019 - Atribuição em massa


segurança

A7:2017 - Cross-Site Scripting (XSS) API7:2019 - Configuração incorreta


de segurança

A8:2017 - Desserialização insegura API8:2019 - Injeção

A9:2017 - Usando Componentes com API9:2019 - Gerenciamento inade-


Vulnerabilidades Conhecidas quado de ativos
A10:2017 - Registro e monitoramento API10:2019 - Registro e monitora-
insuficientes mento insuficientes

É importante observar que, embora valha a pena aprender sobre cada vulnerabi-
lidade no Top 10, evitar o Top 10 não tornará seu aplicativo seguro por si só. Não
existe uma lista de verificação simples de vulnerabilidades a serem evitadas. Em
vez disso, este livro ensinará os princípios gerais para evitar classes inteiras de
vulnerabilidades.
Um ataque de injeção pode ocorrer em qualquer lugar em que você execute có-
digo dinâmico em resposta à entrada do usuário, como consultas SQL e LDAP, e ao
executar comandos do sistema operacional.

DEFINIÇÃO Um ataque de injeção ocorre quando uma entrada de usuário não


validada é incluída diretamente em um comando dinâmico ou consulta que é exe-
cutada pelo aplicativo, permitindo que um invasor controle o código que é
executado.

eval() Se você implementar sua API em uma linguagem dinâmica, sua lingua-
gem pode ter uma função integradaavaliar uma string como código e passar uma
entrada de usuário não validada para tal função seria uma coisa muito perigosa
de se fazer, porque pode permitir que o usuário execute código arbitrário com to-
das as permissões de seu aplicativo. Mas há muitos casos em que você está avali-
ando um código que pode não ser tão óbvio quanto chamar uma eval função ex-
plícita, como:

Construindo um comando ou consulta SQL para enviar a um banco de dados


Executando um comando do sistema operacional
Executando uma pesquisa em um diretório LDAP
Enviando uma solicitação HTTP para outra API
Gerando uma página HTML para enviar a um navegador da web

Se a entrada do usuário for incluída em qualquer um desses casos de maneira


descontrolada, o usuário poderá influenciar o comando ou a consulta para ter
efeitos indesejados. Esse tipo de vulnerabilidade é conhecido como ataque de inje-
ção e geralmente é qualificado com o tipo de código que está sendo injetado: inje-
ção de SQL(ou SQLi), injeção de LDAP e assim por diante.

Cabeçalho e injeção de log

Existem exemplos de vulnerabilidades de injeção que não envolvem a execução


de código. Por exemplo, cabeçalhos HTTP são linhas de texto separadas por re-
torno de carro e caracteres de nova linha (" \r\n " em Java). Se você incluir uma
entrada de usuário não validada em um cabeçalho HTTP, um invasor poderá adi-
cionar uma \r\n sequência de caracteres " " e injetar seus próprios cabeçalhos
HTTP na resposta. O mesmo pode acontecer quando você inclui dados controla-
dos pelo usuário em mensagens de log de depuração ou auditoria (consulte o capí-
tulo 3), permitindo que um invasor injete mensagens de log falsas no arquivo de
log para confundir alguém que tente investigar um ataque posteriormente.

createSpace A operação Natteré vulnerável a um ataque de injeção de SQL por-


que constrói o comando para criar o novo espaço social concatenando a entrada
do usuário diretamente em uma string. O resultado é então enviado para o banco
de dados onde será interpretado como um comando SQL. Como a sintaxe do co-
mando SQL é uma string e a entrada do usuário é uma string, o banco de dados
não tem como saber a diferença.

Essa confusão é o que permite que um invasor obtenha o controle. A linha incor-
reta do código é a seguinte, que concatena o nome do espaço fornecido pelo usuá-
rio e o proprietário na INSERT instrução SQL:

database.updateUnique(
"INSERT INTO espaços(space_id, nome, proprietário)" +
"VALUES(" + spaceId + ", '" + spaceName +
"', '" + proprietário + "');");

o spaceId é um valor numérico que é criado pelo seu aplicativo a partir de uma
sequência, portanto é relativamente seguro, mas as outras duas variáveis ​vêm di-
retamente do usuário. Nesse caso, a entrada vem da carga JSON, mas também
pode vir de parâmetros de consulta na própria URL. Todos os tipos de solicitações
são potencialmente vulneráveis ​a ataques de injeção, não apenas os métodos
POST que incluem uma carga útil.

No SQL, os valores de string são colocados entre aspas simples e você pode ver
que o código se preocupa em adicioná-los ao redor da entrada do usuário. Mas o
que acontece se a própria entrada do usuário contiver uma aspa simples? Vamos
tentar e ver:

$ curl -i -d "{\"name\": \"test'space\", \"owner\": \"demo\"}" ➥ http://localhost:4567


/ spaces
Erro do servidor HTTP/1.1 500
Data: quarta-feira, 30 de janeiro de 2019 16:39:04 GMT
Tipo de conteúdo: text/html;charset=utf-8
Codificação de transferência: em partes
Servidor: Jetty(9.4.8.v20171121)

{"erro":"erro interno do servidor"}


Você recebe uma daquelas terríveis 500 respostas de erro interno do servidor. Se
você observar os logs do servidor, poderá ver o motivo:

org.h2.jdbc.JdbcSQLException: Erro de sintaxe na instrução SQL "INSERT INTO spaces(space

A aspa simples que você incluiu em sua entrada acabou causando um erro de sin-
taxe na expressão SQL. O que o banco de dados vê é a string 'test' , seguida por
alguns caracteres extras (“espaço”) e depois outra aspa simples. Como esta não é
uma sintaxe SQL válida, ele reclama e aborta a transação. Mas e se sua entrada
acabar sendo um SQL válido? Nesse caso, o banco de dados irá executá-lo sem re-
clamar. Vamos tentar executar o seguinte comando:

$ curl -i -d "{\"nome\": \"teste\",\"proprietário\":


➥ \"'); Espaços DROP TABLE; --\"}" http://localhost:4567/spaces
HTTP/1.1 201 Criado
Data: quarta-feira, 30 de janeiro de 2019 16:51:06 GMT
Localização: /espaços/9
Tipo de conteúdo: aplicativo/json
Codificação de transferência: em partes
Servidor: Jetty(9.4.8.v20171121)

{"nome":"', ''); DROP TABLE espaços; --","uri":"/espaços/9"}

A operação foi concluída com sucesso e sem erros, mas vamos ver o que acontece
quando você tenta criar outro espaço:

$ curl -d '{"name": "test space", "owner": "demo"}'


➥ http://localhost:4567/spaces
{"erro":"erro interno do servidor"}
Se você examinar os logs novamente, encontrará o seguinte:

org.h2.jdbc.JdbcSQLException: Tabela "ESPAÇOS" não encontrada;

Oh céus. Parece que, ao passar uma entrada cuidadosamente elaborada, seu usuá-
rio conseguiu excluir totalmente a tabela de espaços e toda a sua rede social com
ela! A Figura 2.5 mostra o que o banco de dados viu quando você executou o pri-
meiro comando curl com o nome engraçado do proprietário. Como os valores de
entrada do usuário são concatenados no SQL como strings, o banco de dados
acaba vendo uma única string que parece conter duas instruções diferentes: a
INSERT instruçãopretendíamos, e uma DROP TABLE declaraçãoque o invasor
conseguiu injetar. O primeiro caractere do nome do proprietário é uma aspa sim-
ples, que fecha a aspa aberta inserida pelo nosso código. Os próximos dois carac-
teres são um parêntese de fechamento e um ponto e vírgula, que juntos garantem
que a INSERT instrução seja encerrada corretamente. A DROP TABLE instrução é
então inserida (injetada) após a INSERT instrução. Por fim, o invasor adiciona ou-
tro ponto e vírgula e dois hífens, que iniciam um comentário no SQL. Isso garante
que a aspa final e os parênteses inseridos pelo código sejam ignorados pelo banco
de dados e não causem um erro de sintaxe.
Figura 2.5 Um ataque de injeção de SQL ocorre quando a entrada do usuário é
misturada em uma instrução SQL sem que o banco de dados seja capaz de dife-
renciá-los. Para o banco de dados, esse comando SQL com um nome de proprietá-
rio engraçado acaba parecendo duas instruções separadas seguidas de um
comentário.

Quando esses elementos são colocados juntos, o resultado é que o banco de dados
vê duas instruções SQL válidas: uma que insere uma linha fictícia na tabela de es-
paços e outra que destrói essa tabela completamente. A Figura 2.6 é um desenho
animado famoso da história em quadrinhos XKCD que ilustra os problemas do
mundo real que a injeção de SQL pode causar.
Figura 2.6 As consequências de não lidar com ataques de injeção de SQL. (Crédito:
XKCD, “Exploits of a Mom,” https://www.xkcd.com/327/. )

2.4.1 Prevenção de ataques de injeção

Lásão algumas técnicas que você pode usar para prevenir ataques de injeção.
Você pode tentar escapar de quaisquer caracteres especiais na entrada para evi-
tar que tenham efeito. Nesse caso, por exemplo, talvez você possa escapar ou re-
mover as aspas simples. Essa abordagem geralmente é ineficaz porque bancos de
dados diferentes tratam caracteres diferentes de maneira especial e usam aborda-
gens diferentes para escapá-los. Pior ainda, o conjunto de caracteres especiais
pode mudar de lançamento para lançamento, então o que é seguro em um ponto
no tempo pode não ser tão seguro após uma atualização.

Uma abordagem melhor é validar rigorosamente todas as entradas para garantir


que elas contenham apenas caracteres que você sabe que são seguros. Essa é uma
boa ideia, mas nem sempre é possível eliminar todos os caracteres inválidos. Por
exemplo, ao inserir nomes, você não pode evitar aspas simples, caso contrário,
poderá proibir nomes genuínos como Mary O'Neill.
A melhor abordagem é garantir que a entrada do usuário esteja sempre clara-
mente separada do código dinâmico usando APIs que suportam instruções prepa-
radas. Uma instrução preparada permite que você escreva o comando ou a con-
sulta que deseja executar com espaços reservados para a entrada do usuário, con-
forme mostrado na figura 2.7. Em seguida, você passa separadamente os valores
de entrada do usuário e a API do banco de dados garante que eles nunca sejam
tratados como instruções a serem executadas.

Figura 2.7 Uma instrução preparada garante que os valores de entrada do usuário
sejam sempre mantidos separados da própria instrução SQL. A instrução SQL con-
tém apenas espaços reservados (representados como pontos de interrogação) e é
analisada e compilada neste formulário. Os valores reais dos parâmetros são pas-
sados ​para o banco de dados separadamente, portanto, nunca pode ser confun-
dido ao tratar a entrada do usuário como código SQL a ser executado.

DEFINIÇÃO Uma declaração preparadaé uma instrução SQL com todas as en-
tradas do usuário substituídas por espaços reservados. Quando a instrução é exe-
cutada, os valores de entrada são fornecidos separadamente, garantindo que o
banco de dados nunca seja enganado para executar a entrada do usuário como
código.

A Listagem 2.6 mostra o createSpace código atualizado para usar uma instrução
preparada. Dalesbred tem suporte embutido para declarações preparadas sim-
plesmente escrevendo a declaração com valores de espaço reservado e incluindo
a entrada do usuário como argumentos extras para o updateUnique métodoli-
gar. Abra o arquivo SpaceController.java em seu editor de texto e localize o cre-
ateSpace método. Atualize o código para corresponder ao código da Listagem
2.6, usando uma instrução preparada em vez de concatenar strings manualmente.
Salve o arquivo quando estiver satisfeito com o novo código.

Listagem 2.6 Usando instruções preparadas

public JSONObject createSpace(solicitação de solicitação, resposta de resposta)


lança SQLException {
var json = new JSONObject(request.body());
var spaceName = json.getString("nome");
var proprietário = json.getString("proprietário");

return database.withTransaction(tx -> {


var spaceId = database.findUniqueLong(
"SELECIONE O PRÓXIMO VALOR PARA space_id_seq;");

database.updateUnique(
"INSERT INTO spaces(space_id, name, owner) " + ❶
"VALUES(?, ?, ?);", spaceId, spaceName, owner); ❶

resposta.status(201);
response.header("Localização", "/espaços/" + spaceId);
retornar novo JSONObject()
.put("nome", espaçoNome)
.put("uri", "/espaços/" + spaceId);
});

❶ Use espaços reservados na instrução SQL e passe os valores como argumentos


adicionais.

Agora, quando sua instrução for executada, o banco de dados receberá a entrada
do usuário separadamente da consulta, impossibilitando que a entrada do usuá-
rio influencie os comandos executados. Vamos ver o que acontece quando você
executa sua chamada de API maliciosa. Desta vez, o espaço foi criado correta-
mente - embora com um nome engraçado!

$ curl -i -d "{\"nome\": \"', ''); Espaços DROP TABLE; --\",


➥ \"owner\": \"\"}" http://localhost:4567/spaces
HTTP/1.1 201 Criado
Data: quarta-feira, 30 de janeiro de 2019 16:51:06 GMT
Localização: /espaços/10
Tipo de conteúdo: aplicativo/json
Codificação de transferência: em partes
Servidor: Jetty(9.4.8.v20171121)

{"nome":"', ''); DROP TABLE espaços; --","uri":"/espaços/10"}

Instruções preparadas em SQL eliminam a possibilidade de ataques de injeção de


SQL se usadas consistentemente. Eles também podem ter uma vantagem de de-
sempenho porque o banco de dados pode compilar a consulta ou instrução uma
vez e reutilizar o código compilado para muitas entradas diferentes; não há des-
culpa para não usá-los. Se você estiver usando um mapeador relacional de
objeto(ORM) ou outra camada de abstração sobre comandos SQL brutos, verifique
a documentação para certificar-se de que está usando instruções preparadas sob
o capô. Se você estiver usando um banco de dados não SQL, verifique se a API do
banco de dados oferece suporte a chamadas parametrizadas que você pode usar
para evitar a criação de comandos por meio de stringconcatenação.

2.4.2 Mitigando a injeção de SQL com permissões

Enquantoinstruções preparadas devem ser sua defesa número um contra ataques


de injeção de SQL, outro aspecto do ataque que vale a pena mencionar é que o
usuário do banco de dados não precisa ter permissões para excluir tabelas em pri-
meiro lugar. Esta não é uma operação que você jamais exigiria que sua API pu-
desse executar, portanto, não deveríamos ter concedido a capacidade de fazê-lo
em primeiro lugar. No banco de dados H2 que você está usando e na maioria dos
bancos de dados, o usuário que cria um esquema de banco de dados herda per-
missões totais para alterar as tabelas e outros objetos nesse banco de dados. O
princípio da menor autoridadediz que você só deve conceder aos usuários e pro-
cessos o mínimo de permissões necessárias para realizar seu trabalho e nada
mais. Sua API nunca precisa descartar tabelas de banco de dados, portanto, você
não deve conceder a ela a capacidade de fazer isso. Alterar as permissões não im-
pedirá ataques de injeção de SQL, mas significa que, se um ataque de injeção de
SQL for encontrado, as consequências serão contidas apenas nas ações que você
permitiu explicitamente.

PRINCÍPIO Oprincípio da menor autoridade (POLA), também conhecido como o


princípio do menor privilégio, diz que todos os usuários e processos em um sis-
tema devem receber apenas as permissões necessárias para realizar seu trabalho
- nem mais nem menos.
Para reduzir as permissões com as quais sua API é executada, você pode tentar re-
mover as permissões desnecessárias (usando o REVOKE comando SQL). Isso corre
o risco de você acidentalmente esquecer de revogar algumas permissões podero-
sas. Uma alternativa mais segura é criar um novo usuário e conceder a ele exata-
mente as permissões necessárias. Para fazer isso, podemos usar o padrão
SQL CREATE USER e GRANT comandos, como mostra a Listagem 2.7. Abra o ar-
quivo schema.sql que você criou anteriormente em seu editor de texto e inclua os
comandos mostrados na listagem na parte inferior do arquivo. A listagem pri-
meiro cria um novo usuário de banco de dados e, em seguida, concede a ele ape-
nas a capacidade de executar SELECT e INSERT declarações em nossas duas tabe-
las de banco de dados.

Listagem 2.7 Criando um usuário de banco de dados restrito

CREATE USER natter_api_user SENHA 'senha'; ❶


GRANT SELECT, INSERT ON espaços, mensagens TO natter_api_user; ❷

❶ Crie o novo usuário do banco de dados.

❷ Conceda apenas as permissões necessárias.

Precisamos então atualizar nossa Main classepara alternar para o uso desse usuá-
rio restrito após o carregamento do esquema do banco de dados. Observe que não
podemos fazer isso antes que o esquema do banco de dados seja carregado, caso
contrário, não teríamos permissões suficientes para criar o banco de dados! Pode-
mos fazer isso simplesmente recarregando o DataSource objeto JDBCdepois de
criarmos o esquema, mudando para o novo usuário no processo. Localize e abra o
arquivo Main.java em seu editor novamente e navegue até o início do main méto-
doonde você inicializa o banco de dados. Altere as poucas linhas que criam e inici-
alizam o banco de dados para as seguintes linhas:

var datasource = JdbcConnectionPool.create( ❶


"jdbc:h2:mem:natter", "natter", "password"); ❶
var database = Database.forDataSource(datasource); ❶
criarTabelas(banco de dados); ❶
datasource = JdbcConnectionPool.create( ❷
"jdbc:h2:mem:natter", "natter_api_user", "password"); ❷
database = Database.forDataSource(datasource); ❷

❶ Inicialize o esquema do banco de dados como o usuário privilegiado.

❷ Alterne para natter_ api_user e recrie os objetos do banco de dados.

Aqui você cria e inicializa o banco de dados usando o usuário “natter” como an-
tes, mas depois recria o pool de conexões JDBC DataSource passando o nome de
usuário e a senha do usuário recém-criado. Em um projeto real, você deve usar
senhas mais seguras do que password , e verá como injetar senhas de conexão
mais seguras no capítulo 10.

Se quiser ver a diferença que isso faz, você pode reverter temporariamente as al-
terações feitas anteriormente para usar instruções preparadas. Se você tentar
executar o ataque de injeção SQL como antes, verá um erro 500. Mas desta vez, ao
verificar os logs, você verá que o ataque não foi bem-sucedido porque o DROP
TABLE comandofoi negado devido ainsuficientepermissões:

Causado por: org.h2.jdbc.JdbcSQLException: direitos insuficientes para o objeto "PUBLIC


Espaços DROP TABLE; --'); [90096-197]
questionário

1. Qual dos seguintes não está no Top 10 da OWASP de 2017?


1. injeção
2. Controle de acesso quebrado
3. Configuração incorreta de segurança
4. Script entre sites (XSS)
5. Falsificação de solicitação entre sites (CSRF)
6. Usando componentes com vulnerabilidades conhecidas
2. Dada a seguinte string de consulta SQL insegura:

consulta de string =
"SELECT msg_text FROM messages WHERE author = '"
+ autor + "'"

e o seguinte author valor de entrada fornecido por um invasor:

john' UNION SELECT senha de usuários; --

qual será a saída da execução da consulta (supondo que a users tabela exista
com uma password coluna)?
1. Nada
2. Um erro de sintaxe
3. senha do João
4. As senhas de todos os usuários
5. Um erro de restrição de integridade
6. As mensagens escritas por João
7. Quaisquer mensagens escritas por John e as senhas de todos os usuários
As respostas estão no final do capítulo.

2.5 Validação de entrada

Segurançaas falhas geralmente ocorrem quando um invasor pode enviar entra-


das que violam suas suposições sobre como o código deve operar. Por exemplo,
você pode supor que uma entrada nunca pode ter mais do que um determinado
tamanho. Se você estiver usando uma linguagem como C ou C++ que carece de se-
gurança de memória, deixar de verificar essa suposição pode levar a uma classe
séria de ataques conhecidos como ataques de estouro de buffer. Mesmo em uma
linguagem com segurança de memória, deixar de verificar se as entradas para
uma API correspondem às suposições do desenvolvedor pode resultar em com-
portamento indesejado.

DEFINIÇÃO Um estouro de buffer ou saturação de bufferocorre quando um in-


vasor pode fornecer entrada que excede o tamanho da região de memória alo-
cada para manter essa entrada. Se o programa ou o tempo de execução da lingua-
gem falhar em verificar esse caso, o invasor poderá sobrescrever a memória
adjacente.

Um estouro de buffer pode parecer bastante inofensivo; ele apenas corrompe um


pouco da memória, então talvez tenhamos um valor inválido em uma variável,
certo? No entanto, a memória sobrescrita pode nem sempre ser de dados simples
e, em alguns casos, essa memória pode ser interpretada como código, resultando
em uma execução remota de códigovulnerabilidade. Essas vulnerabilidades são
extremamente sérias, pois o invasor geralmente pode executar o código em seu
processo com as permissões totais de seu código legítimo.

DEFINIÇÃO Execução remota de código (RCE) ocorre quando um invasor pode


injetar código em uma API executada remotamente e fazer com que ela seja exe-
cutada. Isso pode permitir que o invasor execute ações que normalmente não se-
riam permitidas.

No código da API do Natter, a entrada para a chamada da API é apresentada como


JSON estruturado. Como Java é uma linguagem segura para memória, você não
precisa se preocupar muito com ataques de estouro de buffer. Você também está
usando uma biblioteca JSON bem testada e madura para analisar a entrada, o que
elimina muitos problemas que podem ocorrer. Sempre que possível, você deve
usar formatos e bibliotecas bem estabelecidos para processar todas as entradas
em sua API. JSON é muito melhor do que os formatos XML complexos que substi-
tuiu, mas ainda há diferenças significativas em como diferentes bibliotecas anali-
sam o mesmo JSON.

SAIBA MAIS A análise de entrada é uma fonte muito comum de vulnerabilida-


des de segurança, e muitos formatos de entrada amplamente usados ​são mal espe-
cificados, resultando em diferenças na forma como são analisados ​por diferentes
bibliotecas. O movimento LANGSEC( http://langsec.org ) defende o uso de forma-
tos de entrada simples e inequívocos e analisadores gerados automaticamente
para evitar esses problemas.

Desserialização insegura

Embora Java seja uma linguagem segura para memória e, portanto, menos pro-
pensa a ataques de estouro de buffer, isso não significa que ela seja imune a ata-
ques RCE. Algumas bibliotecas de serialização que convertem objetos Java arbitrá-
rios de e para strings ou formatos binários tornaram-se vulneráveis ​a ataques
RCE, conhecidos como vulnerabilidade de desserialização insegura Serializa-
ble no OWASP Top 10. Isso afeta a estrutura interna do Java, mas também anali-
sadores para formatos supostamente seguros como JSON têm sido vulneráveis,
como o popular Jackson Databind. a O problema ocorre porque o Java executará o
código dentro do construtor padrão de qualquer objeto sendo desserializado por
essas estruturas.

Algumas classes incluídas em bibliotecas Java populares executam operações pe-


rigosas em seus construtores, incluindo leitura e gravação de arquivos e outras
ações. Algumas classes podem até ser usadas para carregar e executar bytecode
fornecido pelo invasor diretamente. Os invasores podem explorar esse comporta-
mento enviando uma mensagem cuidadosamente elaborada que faz com que a
classe vulnerável seja carregada e executada.

A solução para esses problemas é permitir um conjunto conhecido de classes se-


guras e recusar a desserialização de qualquer outra classe. Evite estruturas que
não permitem controlar quais classes são desserializadas. Consulte o OWASP De-
serialization Cheat Sheet para obter conselhos sobre como evitar vulnerabilidades
de desserialização insegura em várias linguagens de programação:
https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html .
Você deve tomar muito cuidado ao usar um formato de entrada complexo, como
XML, porque existem vários ataques específicos contra esses formatos. OWASP
mantém folhas de dicas para processamento seguro de XML e outros ataques, que
você pode encontrar no link da folha de dicas de desserialização.

Consulte https://adamcaudill.com/2017/10/04/exploiting-jackson-rce-cve-2017-7525/
para obter uma descrição da vulnerabilidade. A vulnerabilidade depende de um
recurso do Jackson que está desabilitado por padrão.
Embora a API esteja usando um analisador JSON seguro, ela ainda confia na en-
trada em outros aspectos. Por exemplo, ele não verifica se o nome de usuário for-
necido é menor que o máximo de 30 caracteres configurado no esquema do banco
de dados. O que acontece quando você passa um nome de usuário mais longo?
$ curl -d '{"name":"test", "owner":"um nome de usuário muito longo
➥ com mais de 30 caracteres"}'
➥ http://localhost:4567/spaces -i
Erro do servidor HTTP/1.1 500
Data: sexta-feira, 01 de fevereiro de 2019 13:28:22 GMT
Tipo de conteúdo: aplicativo/json
Codificação de transferência: em partes
Servidor: Jetty(9.4.8.v20171121)

{"erro":"erro interno do servidor"}

Se você examinar os logs do servidor, verá que a restrição do banco de dados de-
tectou o problema:

Valor muito longo para a coluna "OWNER VARCHAR(30) NOT NULL"

Mas você não deve confiar no banco de dados para detectar todos os erros. Um
banco de dados é um ativo valioso que sua API deve proteger contra solicitações
inválidas. O envio de solicitações ao banco de dados que contêm erros básicos
apenas vincula recursos que você preferiria usar para processar solicitações ge-
nuínas. Além disso, pode haver restrições adicionais que são mais difíceis de ex-
pressar em um esquema de banco de dados. Por exemplo, você pode exigir que o
usuário exista no diretório LDAP corporativo. Na listagem 2.8, você adicionará al-
guma validação de entrada básica para garantir que os nomes de usuário tenham
no máximo 30 caracteres e os nomes de espaço até 255 caracteres. Você também
garantirá que os nomes de usuário contenham apenas caracteres alfanuméricos,
usando uma expressão regular.
PRINCÍPIO Sempre defina entradas aceitáveisem vez de inaceitávelao validar
entradas não confiáveis. Uma lista de permissõesdescreve exatamente quais en-
tradas são consideradas válidas e rejeita qualquer outra coisa. 1 Uma lista de
bloqueio(ou lista de negações), por outro lado, tenta descrever quais entradas são
inválidas e aceita qualquer outra coisa. As listas de bloqueio podem levar a falhas
de segurança se você não antecipar todas as entradas maliciosas possíveis. Onde o
intervalo de entradas pode ser grande e complexo, como texto Unicode, considere
listar classes gerais de entradas aceitáveis ​como “dígito decimal” em vez de valo-
res de entrada individuais.

Abra o arquivo SpaceController.java em seu editor e localize o createSpace mé-


todonovamente. Após cada variável ser extraída do JSON de entrada, você adicio-
nará alguma validação básica. Primeiro, você garantirá que spaceName tenha
menos de 255 caracteres e, em seguida, validará que o nome de usuário do propri-
etário corresponde à seguinte expressão regular:

[a-zA-Z][a-zA-Z0-9]{1,29}

Ou seja, uma letra maiúscula ou minúscula seguida de 1 a 29 letras ou dígitos.


Este é um alfabeto básico seguro para nomes de usuário, mas você pode precisar
ser mais flexível se precisar oferecer suporte a nomes de usuário internacionais
ou endereços de e-mail como nomes de usuário.

Listagem 2.8 Validando entradas

public String createSpace(Pedido de solicitação, Resposta de resposta)


lança SQLException {
var json = new JSONObject(request.body());
var spaceName = json.getString("nome");
if (spaceName.length() > 255) { ❶
throw new IllegalArgumentException("nome do espaço muito longo");
}
var proprietário = json.getString("proprietário");
if (!owner.matches("[a-zA-Z][a-zA-Z0-9]{1,29}")) { ❷
throw new IllegalArgumentException("nome de usuário inválido: " + proprietário);
}
..
}

❶ Verifique se o nome do espaço não é muito longo.

❷ Aqui usamos uma expressão regular para garantir que o nome de usuário seja
válido.

As expressões regulares são uma ferramenta útil para validação de entrada, por-
que podem expressar de forma sucinta restrições complexas na entrada. Nesse
caso, a expressão regular garante que o nome de usuário consista apenas em ca-
racteres alfanuméricos, não comece com um número e tenha entre 2 e 30 caracte-
res de comprimento. Embora poderosas, as próprias expressões regulares podem
ser uma fonte de ataque. Algumas implementações de expressões regulares po-
dem ser feitas para consumir grandes quantidades de tempo de CPU ao processar
certas entradas, levando a um ataque conhecido comoataque de negação de ser-
viço de expressão regular (ReDoS)(veja a barra lateral).

Ataques ReDoS

Uma negação de serviço de expressão regular (ou ReDoS) ocorre quando uma ex-
pressão regular pode ser forçada a levar muito tempo para corresponder a uma
string de entrada cuidadosamente escolhida. Isso pode acontecer se a implemen-
tação da expressão regular for forçada a retroceder várias vezes para considerar
as diferentes formas possíveis de correspondência da expressão.

Por exemplo, a expressão regular ^(a|aa)+$ pode corresponder a uma longa


string de a caracteres usando uma repetição de qualquer uma das duas ramifica-
ções. a Dada a string de entrada “aaaaaaaaaaaaab” , ele pode primeiro tentar cor-
responder a uma longa sequência de b caracteres únicos; aa ) sequência, depois
duas sequências double-a, depois três e assim por diante. Depois de tentar todos
eles, ele pode tentar intercalar sequências de a único e duplo, e assim por diante.
Existem várias maneiras de corresponder a essa entrada e, portanto, o correspon-
dente padrão pode levar muito tempo antes de desistir. Algumas implementações
de expressões regulares são inteligentes o suficiente para evitar esses problemas,
mas muitas linguagens de programação populares (incluindo Java) não são. Em
qualquer parte repetida do padrão, cada string de entrada deve corresponder
apenas a uma das alternativas. Se você não tiver certeza, prefira usar operações
de string mais simples.

O Java 11 parece ser menos suscetível a esses ataques do que as versões anteriores.
Se você compilar e executar esta nova versão da API, descobrirá que ainda obtém
um erro 500, mas pelo menos não está mais enviando solicitações inválidas ao
banco de dados. Para comunicar um erro mais descritivo ao usuário, você pode
instalar um manipulador de exceção Spark em sua Main classe, como mostra a
Listagem 2.9. Volte para o arquivo Main.java em seu editor e navegue até o final
do método main. Os manipuladores de exceção do Spark são registrados cha-
mando o Spark.exception() método, que já importamos estaticamente. O mé-
todo recebe dois argumentos: a classe de exceção para manipular e, em seguida,
uma função de manipulador que receberá a exceção, a solicitação e os objetos de
resposta. A função do manipulador pode usar o objeto de resposta para produzir
uma mensagem de erro apropriada. Neste caso, você vai pegar IllegalArgumen-
tException lançado pelo nosso código de validação, e JSONException lançado
pelo analisador JSON quando recebe uma entrada incorreta. Em ambos os casos,
você pode usar um método auxiliar para retornar um erro 400 Bad Request for-
matado ao usuário. Você também pode retornar um resultado 404 Not Found
quando um usuário tentar acessar um espaço que não existe capturando o
Dalesbred's EmptyResultException .

Listagem 2.9 Manipulando exceções

import org.dalesbred.result.EmptyResultException; ❶
import spark.*; ❶

public class Principal {


public static void main(String... args) lança Exception {
..
exceção(IllegalArgumentException.class, ❷
Main::badRequest);
exceção(JSONException.class, ❸
Main::badRequest);
exceção(EmptyResultException.class, ❹
(e, solicitação, resposta) -> resposta.status(404)); ❹
}
private static void badRequest(Exceção ex,
Solicitação de solicitação, resposta de resposta) {
resposta.status(400);
response.body("{\"error\": \"" + ex + "\"}");
}
..
}
❶ Adicione as importações necessárias.

❷ Instale um manipulador de exceção para sinalizar entradas inválidas para o cha-


mador como erros HTTP 400.

❸ Também lida com exceções do analisador JSON.

❹ Retorno 404 não encontrado para exceções de resultados vazios de Dalesbred.

Agora o usuário obtém um erro apropriado se fornecer uma entrada inválida:

$ curl -d '{"name":"test", "owner":"um nome de usuário muito longo


➥ com mais de 30 caracteres"}'
➥ http://localhost:4567/spaces -i
HTTP/1.1 400 Solicitação inválida
Data: sexta-feira, 01 de fevereiro de 2019 15:21:16 GMT
Tipo de conteúdo: text/html;charset=utf-8
Codificação de transferência: em partes
Servidor: Jetty(9.4.8.v20171121)

{"error": "java.lang.IllegalArgumentException: nome de usuário inválido: um nome de usuá

questionário

3. Dado o seguinte código para processamento de dados binários recebidos de um


usuário (como um java.nio.ByteBuffer ):

int msgLen = buf.getInt();


byte[] msg = novo byte[msgLen];
buf.get(msg);
e lembrando desde o início da seção 2.5 que Java é uma linguagem segura para
a memória, qual é a principal vulnerabilidade que um invasor pode explorar
nesse código?
1. Passando um comprimento de mensagem negativo
2. Passando um comprimento de mensagem muito grande
3. Passando um valor inválido para o tamanho da mensagem
4. Passando um comprimento de mensagem maior que o tamanho do buffer
5. Passando um comprimento de mensagem menor que o tamanho do buffer

A resposta está no final do capítulo.

2.6 Produção de saída segura

DentroAlém de validar todas as entradas, uma API também deve tomar cuidado
para garantir que as saídas que ela produz sejam bem formadas e não possam ser
abusadas. Infelizmente, o código que você escreveu até agora não cuida desses de-
talhes. Vamos dar uma olhada novamente na saída que você acabou de produzir:

HTTP/1.1 400 Solicitação inválida


Data: sexta-feira, 01 de fevereiro de 2019 15:21:16 GMT
Tipo de conteúdo: text/html;charset=utf-8
Codificação de transferência: em partes
Servidor: Jetty(9.4.8.v20171121)

{"error": "java.lang.IllegalArgumentException: nome de usuário inválido: um nome de usuá

Existem três problemas separados com esta saída como está:


1. Ele inclui detalhes da exceção Java exata que foi lançada. Embora não seja uma
vulnerabilidade por si só, esses tipos de detalhes nas saídas ajudam um invasor
em potencial a saber quais tecnologias estão sendo usadas para alimentar uma
API. Os cabeçalhos também estão vazando a versão do servidor web Jetty que
está sendo usado pelo Spark sob o capô. Com esses detalhes, o invasor pode ten-
tar encontrar vulnerabilidades conhecidas para explorar. Claro, se houver vul-
nerabilidades, eles podem encontrá-los de qualquer maneira, mas você facilitou
muito o trabalho deles ao fornecer esses detalhes. As páginas de erro padrão ge-
ralmente vazam não apenas nomes de classe, mas rastreamentos de pilha com-
pletos e outras informações de depuração.
2. Ele repete a entrada incorreta que o usuário forneceu na resposta e não faz um
bom trabalho em escapar dela. Quando o cliente da API pode ser um navegador
da web, isso pode resultar em uma vulnerabilidade conhecida comoscript cross-
site refletido (XSS). Você verá como um invasor pode explorar isso na seção
2.6.1.
3. O cabeçalho Content-Type na resposta é definido como text/html em vez do
esperado application/json . Combinado com o problema anterior, isso au-
menta a chance de um ataque XSS ser executado contra um cliente de navega-
dor da web.

Você pode corrigir os vazamentos de informações no ponto 1 simplesmente remo-


vendo esses campos da resposta. No Spark, infelizmente é bastante difícil remo-
ver completamente o cabeçalho do servidor, mas você pode defini-lo como uma
string vazia em um filtro para remover o vazamento de informações:

depoisDepois((solicitação, resposta) ->


response.header("Servidor", ""));
Você pode remover o vazamento dos detalhes da classe de exceção alterando o
manipulador de exceção para retornar apenas a mensagem de erro e não a classe
completa. Mude o badRequest métodovocê adicionou anteriormente para retor-
nar apenas a mensagem de detalhes da exceção.

private static void badRequest(Exceção ex,


Solicitação de solicitação, resposta de resposta) {
resposta.status(400);
response.body("{\"error\": \"" + ex.getMessage() + "\"}");
}

Script entre sites

Script entre sites, ou XSS, é uma vulnerabilidade comum que afeta aplicativos da
Web, em que um invasor pode fazer com que um script seja executado no con-
texto de outro site. Em um XSS persistente, o script é armazenado em dados no
servidor e executado sempre que um usuário acessa esses dados por meio do apli-
cativo da web. Um refletidoO XSS ocorre quando uma entrada criada com códigos
maliciosos para uma solicitação faz com que o script seja incluído (refletido) na
resposta a essa solicitação. O XSS refletido é um pouco mais difícil de explorar
porque a vítima precisa ser induzida a visitar um site sob o controle do invasor
para acionar o ataque. Um terceiro tipo de XSS, conhecido como XSS baseado em
DOM, ataca o código JavaScript que cria HTML dinamicamente no navegador.

Isso pode ser devastador para a segurança de um aplicativo da Web, permitindo


que um invasor roube potencialmente cookies de sessão e outras credenciais e
leia e altere dados nessa sessão. Para entender por que o XSS é um risco tão
grande, você precisa entender que o modelo de segurança dos navegadores da
Web é baseado na política de mesma origem(POP). Os scripts executados na
mesma origem (ou no mesmo site) de uma página da Web são, por padrão, capa-
zes de ler cookies definidos por esse site, examinar elementos HTML criados por
esse site, fazer solicitações de rede a esse site e assim por diante, embora os
scripts de outras origens são impedidos de fazer essas coisas. Um XSS bem-suce-
dido permite que um invasor execute seu script como se viesse da origem de des-
tino, para que o script malicioso faça as mesmas coisas que os scripts genuínos
dessa origem podem fazer. Se eu conseguir explorar com êxito uma vulnerabili-
dade XSS no facebook.com, por exemplo, meu script poderá ler e alterar suas pos-
tagens no Facebook ou roubar suas mensagens privadas.

Embora o XSS seja principalmente uma vulnerabilidade em aplicativos da Web,


na era daaplicativos de página única(SPAs) é comum que os clientes do navegador
da Web conversem diretamente com uma API. Por esse motivo, é essencial que
uma API tome precauções básicas para evitar produzir uma saída que possa ser
interpretada como um script quando processada por um navegador da web.
2.6.1 Explorando ataques XSS

Paraentender o ataque XSS, vamos tentar explorá-lo. Antes de fazer isso, talvez
seja necessário adicionar um cabeçalho especial à sua resposta para desativar as
proteções integradas em alguns navegadores que detectarão e impedirão ataques
XSS refletidos. Essa proteção costumava ser amplamente implementada em nave-
gadores, mas foi recentemente removida do Chrome e do Microsoft Edge. 2 Se
você estiver usando um navegador que ainda o implemente, essa proteção torna
mais difícil realizar esse ataque específico, então você o desativará adicionando o
seguinte filtro de cabeçalho à sua Main classe(um afterAfter filtrono Spark é
executado após todos os outros filtros, incluindo manipuladores de exceção). Abra
o arquivo Main.java em seu editor e adicione as seguintes linhas ao final do mé-
todo main:
afterAfter((solicitação, resposta) -> {
response.header("X-XSS-Protection", "0");
});

O X-XSS-Protection cabeçalho geralmente é usado para garantir que as prote-


ções do navegador estejam ativadas, mas, neste caso, você as desativará tempora-
riamente para permitir que o bug seja explorado.

NOTA As proteções XSS em navegadores podem causar vulnerabilidades de se-


gurança próprias em alguns casos. O projeto OWASP agora recomenda sempre de-
sabilitar o filtro com o X-XSS-Protection: 0 cabeçalho conforme mostrado
anteriormente.

Feito isso, você pode criar um arquivo HTML malicioso que explora o bug. Abra
seu editor de texto e crie um arquivo chamado xss.html e copie o conteúdo da lis-
tagem 2.10 para ele. Salve o arquivo e clique duas vezes nele ou abra-o em seu na-
vegador. O arquivo inclui um formulário HTML com o enctype atributodefinido
para text/plain . Isso instrui o navegador da Web a formatar os campos no for-
mulário como field=value pares de texto simples, que você está explorando
para fazer com que a saída pareça um JSON válido. Você também deve incluir um
pequeno pedaço de JavaScript para enviar automaticamente o formulário assim
que a página for carregada.

Listagem 2.10 Explorando um XSS refletido

<!DOCTYPE html>
<html>
<corpo>
<form id="test" action="http://localhost:4567/spaces"
method="post" enctype="text/plain"> ❶
<input type="hidden" name='{"x":"'
valor='","nome":"x",
➥ "owner":"<script>alert('XSS!');
➥ </script>"}' /> ❷
</form>
<tipo de script="texto/javascript">
document.getElementById("teste").submit(); ❸
</script>
</body>
</html>

❶ O formulário está configurado para POST com tipo de conteúdo text/plain.

❷ Crie cuidadosamente a entrada do formulário para ser um JSON válido com um


script no campo “proprietário”.

❸ Assim que a página carregar, você enviará automaticamente o formulário usando


JavaScript.

Se tudo correr como esperado, você deve receber um pop-up em seu navegador
com a mensagem “XSS”. Então o que aconteceu? A sequência de eventos é mos-
trada na figura 2.8, e é a seguinte:

1. Quando o formulário é enviado, o navegador envia uma solicitação POST para


http://localhost:4567/spaces com um cabeçalho Content-Type text/plain e o
campo de formulário oculto como o valor. Quando o navegador envia o formu-
lário, ele pega cada elemento do formulário e os envia como pares nome=valor.
As entidades &lt; , &gt; e &apos; HTML são substituídas pelos valores lite-
rais <, > e, ' respectivamente.
2. O nome do seu campo de entrada oculto é '{"x":"' , embora o valor seja seu
longo script malicioso. Quando os dois são colocados juntos, a API verá a se-
guinte entrada de formulário:

{"x":"=","name":"x","owner":"<script>alert('XSS!');</script>"}

3. A API vê uma entrada JSON válida e ignora o campo “x” extra (que você adicio-
nou apenas para ocultar habilmente o sinal de igual que o navegador inseriu).
Mas a API rejeita o nome de usuário como inválido, repetindo-o na resposta:

{"error": "java.lang.IllegalArgumentException: nome de usuário inválido: <script>alert

4. Como sua resposta de erro foi exibida com o tipo de conteúdo padrão de
text/html , o navegador interpreta alegremente a resposta como HTML e exe-
cuta o script, resultando no pop-up XSS.
Figura 2.8 Um script cross-site refletido (XSS) contra sua API pode ocorrer quando
um invasor faz com que um cliente de navegador da Web envie um formulário
com campos de entrada cuidadosamente elaborados. Quando enviado, o formulá-
rio parece um JSON válido para a API, que o analisa, mas produz uma mensagem
de erro. Como a resposta é retornada incorretamente com um tipo de conteúdo
HTML, o script mal-intencionado fornecido pelo invasor é executado pelo cliente
do navegador da web.

Às vezes, os desenvolvedores assumem que, se produzirem uma saída JSON vá-


lida, o XSS não é uma ameaça para uma API REST. Nesse caso, a API consumiu e
produziu JSON válido e, ainda assim, foi possível para um invasor explorar uma
vulnerabilidade XSSde qualquer forma.

2.6.2 Prevenção de XSS

Então,como você conserta isso? Existem várias etapas que podem ser tomadas
para evitar que sua API seja usada para iniciar ataques XSS contra clientes de na-
vegadores da web:

Seja rigoroso no que você aceita. Se sua API consumir entrada JSON, exija que
todas as solicitações incluam um Content-Type cabeçalhodefinido para
application/json . Isso evita os truques de envio de formulário usados ​neste
exemplo, pois um formulário HTML não pode enviar application/json con-
teúdo.
Certifique-se de que todas as saídas estejam bem formadas usando uma biblio-
teca JSON adequada, em vez de concatenar strings.
Produza Content-Type cabeçalhos corretos em todas as respostas da sua API e
nunca assuma que os padrões são sensatos. Verifique as respostas de erro em
particular, pois elas geralmente são configuradas para produzir HTML por
padrão.
Se você analisar o Accept cabeçalhopara decidir que tipo de saída produzir,
nunca simplesmente copie o valor desse cabeçalho na resposta. Sempre especi-
fique explicitamente o Content-Type que sua API produziu.

Além disso, existem alguns cabeçalhos de segurança padrão que você pode adicio-
nar a todas as respostas da API para adicionar proteção adicional aos clientes do
navegador da Web (consulte a tabela 2.1).
Tabela 2.1 Cabeçalhos de segurança úteis

Cabeçalho de Descrição Comentários


segurança

X-XSS- Diz ao navega- A orientação atual é definir como “ 0 ”


Protec- dor se deve nas respostas da API para desativar com-
tion bloquear/ignorar pletamente essas proteções devido a pro-
ataques XSS blemas de segurança que elas podem
suspeitos. apresentar.

X-Con- Defina como Sem esse cabeçalho, o navegador pode ig-


tent- nosniff para norar seu Content-Type cabeçalho e
Type- evitar que o na- adivinhar (farejar) qual é realmente o
Options vegador adivi- conteúdo. Isso pode fazer com que a
nhe o tipo de saída JSON seja interpretada como HTML
conteúdo ou JavaScript, portanto, sempre adicione
correto. esse cabeçalho.

X-Frame- Defina como Em um ataque conhecido como drag 'n'


Options DENY para impe- drop clickjacking, o invasor carrega uma
dir que suas res- resposta JSON em um iframe oculto e in-
postas de API se- duz o usuário a arrastar os dados para
jam carregadas um quadro controlado pelo invasor, reve-
em um quadro lando potencialmente informações confi-
ou iframe. denciais. Este cabeçalho impede esse ata-
que em navegadores mais antigos, mas
foi substituído pela Política de segurança
de conteúdo em navegadores mais novos
(veja abaixo). Vale a pena definir os dois
cabeçalhos por enquanto.

Cache- Controla se na- Esses cabeçalhos sempre devem ser defi-


Contro- vegadores e pro- nidos corretamente para evitar que da-
l e Expi- xies podem ar- dos confidenciais sejam retidos no nave-
res mazenar con- gador ou nos caches de rede. Pode ser útil
teúdo em cache definir cabeçalhos de cache padrão em
na resposta e um before() filtro, para permitir que
por quanto pontos de extremidade específicos o
tempo. substituam se tiverem requisitos de ca-
che mais específicos. O padrão mais se-
guro é desabilitar completamente o cache
usando a no-store diretiva e, em se-
guida, reabilitar seletivamente o cache
para solicitações individuais, se necessá-
rio. O Pragma: no-cache cabeçalho
pode ser usado para desativar o armaze-
namento em cache para caches HTTP/1.0
mais antigos.

Os navegadores da web modernos também suportam o Content-Security-


Policy header (CSP) que pode ser usado para reduzir o escopo de ataques XSS,
restringindo de onde os scripts podem ser carregados e o que eles podem fazer.
CSP é uma defesa valiosa contra XSS em um aplicativo da web. Para uma API
REST, muitas das diretivas CSP não são aplicáveis, mas vale a pena incluir um ca-
beçalho CSP mínimo em suas respostas API para que, se um invasor conseguir ex-
plorar uma vulnerabilidade XSS, ele fique restrito ao que pode fazer. A Tabela 2.2
lista as diretivas que recomendo para uma API HTTP. O cabeçalho recomendado
para uma resposta da API HTTPé:

Política de segurança de conteúdo: default-src 'nenhum';


➥ frame-ancestral 'none'; caixa de areia

Diretivas CSP recomendadas para respostas REST

Diretiva Valor Propósito

default- 'none' Evita que a resposta carregue quaisquer


src scripts ou recursos.

frame- 'none' Uma substituição para X-Frame-Options ,


ancestors isso evita que a resposta seja carregada em
um iframe.

sandbox n/D Desativa a execução de scripts e outros con-


teúdos potencialmente perigosos.

2.6.3 Implementando as proteções

Vocêagora deve atualizar a API para implementar essas proteções. Você adicio-
nará alguns filtros executados antes e depois de cada solicitação para impor as
configurações de segurança recomendadas.
Primeiro, adicione um before () filtro que é executado antes de cada solicitação
e verifica se qualquer corpo POST enviado à API possui um cabeçalho Content-
Type correto de application/ json . A API Natter só aceita entrada de solicita-
ções POST, mas se sua API lida com outros métodos de solicitação que podem con-
ter um corpo (como solicitações PUT ou PATCH), você também deve aplicar esse
filtro para esses métodos. Se o tipo de conteúdo estiver incorreto, você deve retor-
nar um status 415 Unsupported Media Type, porque este é o código de status pa-
drão para este caso. Você também deve indicar explicitamente a codificação de ca-
racteres UTF-8 na resposta, para evitar truques para roubar dados JSON especifi-
cando uma codificação diferente, como UTF-16BE (consulte
https://portswigger.net/blog/json-hijacking-for -the-modern-web para detalhes).

Em segundo lugar, você adicionará um filtro que será executado após todas as so-
licitações para adicionar nossos cabeçalhos de segurança recomendados à res-
posta. Você adicionará isso como um Spark afterAfter () filter, que garante
que os cabeçalhos sejam adicionados às respostas de erro, bem como às respostas
normais.

A Listagem 2.11 mostra seu método principal atualizado, incorporando essas me-
lhorias. Localize o arquivo Main.java em natter-api/src/main/java/com/manning/
apisecurityinaction e abra-o em seu editor. Adicione os filtros ao main() méto-
doabaixo do código que você já escreveu.

Listagem 2.11 Protegendo seus endpoints REST

public static void main(String... args) lança Exception {


..
antes(((pedido, resposta) -> {
if (request.requestMethod().equals("POST") && ❶
!"application/json".equals(request.contentType())) { ❶
halt(415, new JSONObject().put( ❷
"erro", "Somente aplicativo/json suportado"
).para sequenciar());
}
}));

afterAfter((solicitação, resposta) -> { ❸


response.type("aplicativo/json;charset=utf-8");
response.header("X-Content-Type-Options", "nosniff");
response.header("X-Frame-Options", "DENY");
response.header("X-XSS-Protection", "0");
response.header("Cache-Control", "no-store");
response.header("Content-Security-Policy",
"default-src 'nenhum'; frame-ancestral 'nenhum'; sandbox");
response.header("Servidor", "");
});

internalServerError(novo JSONObject()
.put("erro", "erro interno do servidor").toString());
notFound(novo JSONObject()
.put("erro", "não encontrado").toString());

exceção(IllegalArgumentException.class, Main::badRequest);
exceção(JSONException.class, Main::badRequest);
}

private static void badRequest(Exceção ex,


Solicitação de solicitação, resposta de resposta) {
resposta.status(400);
response.body(new JSONObject() ❹
.put("erro", ex.getMessage()).toString());
}

❶ Imponha um tipo de conteúdo correto em todos os métodos que recebem entrada


no corpo da solicitação.

❷ Retorne uma resposta padrão 415 Unsupported Media Type para Content-Types
inválidos.

❸ Colete todos os seus cabeçalhos de segurança padrão em um filtro que é executado


depois de todo o resto.

❹ Use uma biblioteca JSON adequada para todas as saídas.

Você também deve alterar suas exceções para não repetir a entrada do usuário
malformada em nenhum caso. Embora os cabeçalhos de segurança devam evitar
quaisquer efeitos ruins, é melhor não incluir a entrada do usuário nas respostas
de erro apenas para ter certeza. É fácil remover acidentalmente um cabeçalho de
segurança, então você deve evitar o problema retornando uma mensagem de erro
mais genérica:

if (!owner.matches("[a-zA-Z][a-zA-Z0-9]{0,29}")) {
throw new IllegalArgumentException("nome de usuário inválido");
}

Se você precisar incluir a entrada do usuário nas mensagens de erro, considere


limpá-la primeiro usando uma biblioteca robusta, como o OWASP HTML Sanitizer
( https://github.com/OWASP/java-html-sanitizer ) ou JSON Sanitizer. Isso removerá
uma ampla variedade de vetores de ataque XSS em potencial.
questionário

4. Qual cabeçalho de segurança deve ser usado para impedir que os navegadores
da Web ignorem o Content-Type cabeçalho em uma resposta?
1. Cache-Control
2. Content-Security-Policy
3. X-Frame-Options: deny
4. X-Content-Type-Options: nosniff
5. X-XSS-Protection: 1; mode=block
5. Suponha que sua API possa produzir saída no formato JSON ou XML, de acordo
com o Accept cabeçalho enviado pelo cliente. Qual das seguintes opções você
não deve fazer? (Pode haver mais de uma resposta correta.)
1. Defina o X-Content-Type-Options cabeçalho.
2. Inclua valores de entrada não sanitizados em mensagens de erro.
3. Produza saída usando uma biblioteca JSON ou XML bem testada.
4. Certifique-se de que o tipo de conteúdo esteja correto em todas as respostas
de erro padrão.
5. Copie o Accept cabeçalho diretamente para o Content-Type cabeçalho na
resposta.

As respostas estão no final do capítulo.

Respostas para perguntas do questionário

1. e. Cross-Site Request Forgery (CSRF) esteve no Top 10 por muitos anos, mas per-
deu importância devido a defesas aprimoradas em estruturas da web. Ataques
e defesas CSRF são abordados no capítulo 4.
2. g. As mensagens de John e as senhas de todos os usuários serão retornadas da
consulta. Isso é conhecido como ataque UNION de injeção SQL e mostra que um
invasor não está limitado a recuperar dados das tabelas envolvidas na consulta
original, mas também pode consultar outras tabelas no banco de dados.
3. b. O invasor pode fazer com que o programa aloque matrizes de bytes grandes
com base na entrada do usuário. int Para um valor Java, o máximo seria um
array de 2 GB, o que provavelmente permitiria ao invasor esgotar toda a memó-
ria disponível com algumas solicitações. Embora a passagem de valores inváli-
dos seja um aborrecimento, lembre-se do início da seção 2.5 que Java é uma lin-
guagem de memória segura e, portanto, isso resultará em exceções em vez de
comportamento inseguro.
4. d. X-Content-Type-Options: nosniff instrui os navegadores a respeitar o
cabeçalho Content-Type na resposta.
5. b e e. Você nunca deve incluir valores de entrada não sanitizados em mensa-
gens de erro, pois isso pode permitir que um invasor injete scripts XSS. Você
também nunca deve copiar o cabeçalho Accept da solicitação para o cabeçalho
Content-Type de uma resposta, mas, em vez disso, construí-lo do zero com base
no tipo de conteúdo real quefoiproduzido.

Resumo

Os ataques de injeção de SQL podem ser evitados usando instruções preparadas


e consultas parametrizadas.
Os usuários do banco de dados devem ser configurados para ter os privilégios
mínimos necessários para realizar suas tarefas. Se a API for comprometida, isso
limitará os danos que podem ser causados.
As entradas devem ser validadas antes do uso para garantir que correspondam
às expectativas. Expressões regulares são uma ferramenta útil para validação
de entrada, mas você deve evitar ataques ReDoS.
Mesmo que sua API não produza saída HTML, você deve proteger os clientes do
navegador da Web contra ataques XSS, garantindo que o JSON correto seja pro-
duzido com cabeçalhos corretos para evitar que os navegadores interpretem er-
roneamente as respostas como HTML.
Os cabeçalhos de segurança HTTP padrão devem ser aplicados a todas as res-
postas, para garantir que os invasores não possam explorar a ambigüidade na
forma como os navegadores processam os resultados. Certifique-se de verificar
novamente todas as respostas de erro, pois elas geralmente sãoesquecido.

1.
Você pode ouvir os termos mais antigos lista branca e lista negra usados ​para es-
ses conceitos, mas essas palavras podem ter conotações negativas e devem ser
evitadas. Consulte https://www.ncsc.gov.uk/blog-post/terminology-its-not-black-
and-white para uma discussão.

2.
Consulte https://scotthelme.co.uk/edge-to-remove-xss-auditor/ para uma discussão
sobre as implicações do anúncio da Microsoft. O Firefox nunca implementou as
proteções em primeiro lugar, então essa proteção logo desaparecerá da maioria
dos principais navegadores. No momento da redação deste artigo, o Safari era o
único navegador que bloqueava o ataque por padrão.

Você também pode gostar