Escolar Documentos
Profissional Documentos
Cultura Documentos
Você vaiproteja a API do Natter contra ameaças comuns aplicando alguns meca-
nismos básicos de segurança (também conhecidos como controles de segurança).
A Figura 3.1 mostra os novos mecanismos que você desenvolverá e poderá relaci-
onar cada um deles a uma ameaça STRIDE (capítulo 1) que eles impedem:
A limitação de taxa é usada para evitar que os usuários sobrecarreguem sua
API com solicitações, limitando as ameaças de negação de serviço.
Criptografiagarante que os dados sejam mantidos em sigilo quando enviados de
ou para a API e quando armazenados em disco, evitando a divulgação de infor-
mações. A criptografia moderna também evita que os dados sejam adulterados.
A autenticação garante que os usuários sejam quem dizem ser, evitando falsifi-
cações. Isso é essencial para a responsabilidade, mas também uma base para
outros controles de segurança.
O registro de auditoria é a base da responsabilidade, para evitar ameaças de
repúdio.
Por fim, você aplicará o controle de acesso para preservar a confidencialidade e
a integridade, evitando a divulgação de informações, adulteração e ataques de
elevação de privilégio.
Figura 3.1 Aplicando controles de segurança à API do Natter. A criptografia im-
pede a divulgação de informações. A limitação de taxa protege a disponibilidade.
A autenticação é usada para garantir que os usuários sejam quem dizem ser. O log
de auditoria registra quem fez o quê, para apoiar a responsabilidade. O controle
de acesso é então aplicado para reforçar a integridade e a confidencialidade.
DEFINIÇÃO Uma negação de serviço (DoS) visa impedir que usuários legítimos
acessem sua API. Isso pode incluir ataques físicos, como desconectar cabos de
rede, mas geralmente envolve a geração de grandes quantidades de tráfego para
sobrecarregar seus servidores. Um ataque DoS distribuído (DDoS)usa muitas má-
quinas na internet para gerar tráfego, tornando-o mais difícil de bloquear do que
um único cliente ruim.
Muitos ataques DoS são causados por solicitações não autenticadas. Uma maneira
simples de limitar esses tipos de ataques é nunca permitir que solicitações não au-
tenticadas consumam recursos em seus servidores. A autenticação é abordada na
seção 3.3 e deve ser aplicada imediatamente após a limitação de taxa antes de
qualquer outro processamento. No entanto, a autenticação em si pode ser cara,
então isso não elimina as ameaças DoS por conta própria.
OBSERVAÇÃO Nunca permita que solicitações não autenticadas consumam re-
cursos significativos em seu servidor.
Muitos ataques DDoS dependem de alguma forma de amplificação para que uma
solicitação não autenticada a uma API resulte em uma resposta muito maior que
pode ser direcionada ao alvo real. Um exemplo popular são os ataques de amplifi-
cação de DNS, que aproveitam o sistema de nome de domínio não
autenticado(DNS) que mapeia nomes de host e domínio em endereços IP. Ao falsi-
ficar o endereço de retorno para uma consulta de DNS, um invasor pode enganar
o servidor DNS para inundar a vítima com respostas a solicitações de DNS que
nunca foram enviadas. Se servidores DNS suficientes puderem ser recrutados
para o ataque, uma quantidade muito grande de tráfego poderá ser gerada a par-
tir de uma quantidade muito menor de tráfego de solicitação, conforme mostrado
na figura 3.2. Ao enviar solicitações de uma rede de máquinas comprometidas
(conhecida como botnet), o invasor pode gerar quantidades muito grandes de trá-
fego para a vítima com pouco custo para si. A amplificação de DNS é um exemplo
de ataque DoS em nível de rede. Esses ataques podem ser mitigados filtrando o
tráfego prejudicial que entra em sua rede usando um firewall. Ataques muito
grandes muitas vezes só podem ser tratados por serviços especializados de prote-
ção DoS fornecidos por empresas que têm capacidade de rede suficiente para li-
dar com a carga.
Figura 3.2 Em um ataque de amplificação de DNS, o invasor envia a mesma con-
sulta de DNS para vários servidores DNS, falsificando seu endereço IP para pare-
cer que a solicitação veio da vítima. Ao escolher cuidadosamente a consulta de
DNS, o servidor pode ser induzido a responder com muito mais dados do que na
consulta original, inundando a vítima com tráfego.
Os ataques DoS no nível da rede podem ser fáceis de detectar porque o tráfego
não está relacionado a solicitações legítimas à sua API. Ataques DoS na camada de
aplicativostente sobrecarregar uma API enviando solicitações válidas, mas em ta-
xas muito mais altas do que um cliente normal. Uma defesa básica contra ataques
de negação de serviço na camada de aplicativos é aplicar a limitação de taxa a to-
das as solicitações, garantindo que você nunca tente processar mais solicitações
do que o seu servidor pode suportar. É melhor rejeitar alguns pedidos neste caso,
do que travar tentando processar tudo. Clientes genuínos podem repetir suas soli-
citações mais tarde, quando o sistema voltar ao normal.
DEFINIÇÃO Ataques DoS na camada de aplicativos(também conhecido como
camada 7 ou L7 DoS) envia solicitações sintaticamente válidas para sua API, mas
tenta sobrecarregá-la enviando um volume muito grande de solicitações.
Figura 3.3 A limitação de taxa rejeita solicitações quando sua API está sob carga
excessiva. Ao rejeitar as solicitações antes que elas tenham consumido muitos re-
cursos, podemos garantir que as solicitações que processamos tenham recursos
suficientes para serem concluídas sem erros. A limitação de taxa deve ser a pri-
meira decisão aplicada às solicitações recebidas.
DICA Você deve implementar a limitação de taxa o mais cedo possível, ideal-
mente em um balanceador de carga ou proxy reverso antes mesmo que as solici-
tações cheguem aos seus servidores de API. A configuração de limitação de taxa
varia de produto para produto. Consulte https://medium
.com/faun/understanding-rate-limiting-on-haproxy-b0cf500310b1 para obter um
exemplo de configuração de limitação de taxa para o balanceador de carga
HAProxy de código aberto.
<dependência>
<groupId>com.google.guava</groupId>
<artifactId>goiaba</artifactId>
<version>29.0-jre</version>
</dependência>
DICA O limite de taxa para servidores individuais deve ser uma fração do limite
de taxa geral que você deseja que seu serviço manipule. Se o seu serviço precisar
lidar com mil solicitações por segundo e você tiver 10 servidores, o limite de taxa
por servidor deve ser de cerca de 100 solicitações por segundo. Você deve verifi-
car se cada servidor é capaz de lidar com essa taxa máxima.
Abra o arquivo Main.java em seu editor e adicione uma importação para Guava
no topo do arquivo:
import com.google.common.util.concurrent.*;
O limitador de taxa do Guava é bastante básico, definindo apenas uma taxa sim-
ples de requisições por segundo. Possui recursos adicionais, como poder consu-
mir mais licenças para operações de API mais caras. Ele carece de recursos mais
avançados, como ser capaz de lidar com rajadas ocasionais de atividade, mas é
perfeitamente adequado como uma medida defensiva básica que pode ser incor-
porada a uma API em algumas linhas de código. Você pode experimentá-lo na li-
nha de comando para vê-lo em ação:
$ para i em {1..5}
> fazer
> curl -i -d "{\"proprietário\":\"teste\",\"nome\":\"espaço$i\"}"
➥ -H 'Tipo de conteúdo: application/json'
➥ http://localhost:4567/spaces;
> feito
HTTP/1.1 201 Criado ❶
Data: quarta-feira, 06 de fevereiro de 2019 21:07:21 GMT
Localização: /espaços/1
Tipo de conteúdo: application/json;charset=utf-8
X-Content-Type-Options: nosniff
X-Frame-Opções: NEGAR
X-XSS-Proteção: 0
Cache-Control: no-store
Política de segurança de conteúdo: default-src 'nenhum'; frame-ancestral 'nenhum'; caixa
Servidor:
Codificação de transferência: em partes
HTTP/1.1 201 Criado ❶
Data: quarta-feira, 06 de fevereiro de 2019 21:07:21 GMT
Localização: /espaços/2
Tipo de conteúdo: application/json;charset=utf-8
X-Content-Type-Options: nosniff
X-Frame-Opções: NEGAR
X-XSS-Proteção: 0
Cache-Control: no-store
Política de segurança de conteúdo: default-src 'nenhum'; frame-ancestral 'nenhum'; caixa
Servidor:
Codificação de transferência: em partes
❷ Uma vez excedido o limite de taxa, as solicitações são rejeitadas com um código de
status 429.
questionário
Figura 3.4 A autenticação ocorre após a limitação de taxa, mas antes do log de au-
ditoria ou controle de acesso. Todas as solicitações continuam, mesmo se a auten-
ticação falhar, para garantir que sejam sempre registradas. Solicitações não au-
tenticadas serão rejeitadas durante o controle de acesso, que ocorre após o log de
auditoria.
As operações para ler mensagens atualmente não identificam quem está solici-
tando essas mensagens, o que significa que não podemos dizer se eles devem ter
acesso. Você corrigirá ambos os problemas introduzindo a autenticação.
LáExistem muitas maneiras de autenticar um usuário, mas uma das mais difundi-
das é a autenticação simples de nome de usuário e senha. Em um aplicativo da
Web com uma interface de usuário, podemos implementar isso apresentando ao
usuário um formulário para inserir seu nome de usuário e senha. Uma API não é
responsável por renderizar uma interface do usuário, portanto, você pode usar o
mecanismo de autenticação HTTP básico padrão para solicitar uma senha de uma
forma que não dependa de nenhuma interface do usuário. Este é um esquema pa-
drão simples, especificado no RFC 7617 ( https://tools.ietf.org/html/rfc7617 ), no
qual o nome de usuário e a senha são codificados (usando a codificação Base64;
https://en.wikipedia.org/ wiki/Base64 ) e enviado em um cabeçalho. Um exemplo
de um cabeçalho de autenticação básica para o nome de usuário demo e a senha
changeit é o seguinte:
Redeos navegadores têm suporte embutido para autenticação HTTP Basic (em-
bora com algumas peculiaridades que você verá mais tarde), assim como o curl e
muitas outras ferramentas de linha de comando. Isso nos permite enviar facil-
mente um nome de usuário e senha para a API, mas você precisa armazenar e va-
lidar essa senha com segurança. Um algoritmo de hash de senhaconverte cada se-
nha em uma string de aparência aleatória de comprimento fixo. Quando o usuá-
rio tenta fazer o login, a senha que ele apresenta é hash usando o mesmo algo-
ritmo e comparada com o hash armazenado no banco de dados. Isso permite que
a senha seja verificada sem armazená-la diretamente. Algoritmos modernos de
hash de senha, como Argon2, Scrypt, Bcrypt ouPBKDF2, são projetados para resis-
tir a uma variedade de ataques caso as senhas com hash sejam roubadas. Em par-
ticular, eles são projetados para levar muito tempo ou memória para serem pro-
cessados para evitar ataques de força brutapara recuperar as senhas. Você usará
o Scrypt neste capítulo, pois ele é seguro e amplamente implementado.
DEFINIÇÃO Um algoritmo de hash de senhaconverte senhas em valores de ta-
manho fixo de aparência aleatória, conhecidos como hash. Um hash de senha se-
gura usa muito tempo e memória para desacelerar ataques de força bruta, como
ataques de dicionário, em que um invasor tenta uma lista de senhas comuns para
ver se alguma corresponde ao hash.
Localize o arquivo pom.xml no projeto e abra-o com seu editor favorito. Adicione
a seguinte dependência do Scrypt à seção de dependências e salve oArquivo:
<dependência>
<groupId>com.lambdaworks</groupId>
<artifactId>criptografar</artifactId>
<version>1.4.0</version>
</dependência>
Antes davocê pode autenticar qualquer usuário, você precisa de alguma forma
para registrá-los. Por enquanto, você apenas permitirá que qualquer usuário se
registre fazendo uma solicitação POST para o /users terminal, especificando o
nome de usuário e a senha escolhida. Você adicionará esse endpoint na seção
3.3.4, mas primeiro vamos ver como armazenar senhas de usuários com segu-
rança no banco de dados.
Isso pode parecer uma quantidade excessiva de tempo e memória, mas esses pa-
râmetros foram cuidadosamente escolhidos com base na velocidade com que os
invasores podem adivinhar as senhas. Máquinas dedicadas para quebrar senhas,
que podem ser construídas por quantias relativamente modestas de dinheiro, po-
dem tentar muitos milhões ou até bilhões de senhas por segundo. Os requisitos
caros de tempo e memória dos algoritmos de hash de senha segura, como o
Scrypt, reduzem isso para alguns milhares de senhas por segundo, aumentando
enormemente o custo para o invasor e dando aos usuários um tempo valioso para
alterar suas senhas após a descoberta de uma violação. A orientação mais recente
do NIST sobre armazenamento seguro de senhas (“verificadores secretos memori-
zados” na linguagem tortuosa do NIST) recomenda o uso de funções hash de me-
mória forte, como Scrypt (https://pages.nist.gov/800-63-3/sp800-
63b.html#memsecret ).
pacote com.manning.apisecurityinaction.controller;
import com.lambdaworks.crypto.*;
import org.dalesbred.*;
import org.json.*;
importar faísca.*;
importar java.nio.charset.*;
importar java.util.*;
if (!username.matches(USERNAME_PATTERN)) { ❶
throw new IllegalArgumentException("nome de usuário inválido");
}
if (senha.comprimento() < 8) {
lançar novo IllegalArgumentException(
"A senha deve conter pelo menos 8 caracteres");
}
var hash = SCryptUtil.scrypt(senha, 32768, 8, 1); ❷
database.updateUnique( ❸
"INSERT INTO users(user_id, pw_hash)" +
" VALUES(?, ?)", nome de usuário, hash);
resposta.status(201);
response.header("Localização", "/users/" + nome de usuário);
return new JSONObject().put("nome de usuário", nome de usuário);
}
}
❷ Use a biblioteca Scrypt para hash da senha. Use os parâmetros recomendados para
2019.
A biblioteca Scrypt gera um valor salt aleatório exclusivo para cada hash de se-
nha. A string de hash que é armazenada no banco de dados inclui os parâmetros
que foram usados quando o hash foi gerado, bem como esse valor de sal aleatório.
Isso garante que você sempre possa recriar o mesmo hash no futuro, mesmo que
altere os parâmetros. A biblioteca Scrypt poderá ler esse valor e decodificar os pa-
râmetros ao verificar o hash.
if (!username.matches(USERNAME_PATTERN)) {
throw new IllegalArgumentException("nome de usuário inválido");
}
if (hash.isPresent() && ❹
SCryptUtil.check(password, hash.get())) { ❹
request.attribute("assunto", nome de usuário);
}
}
Você pode conectar isso na Main classecomo um filtro antes de todas as chama-
das de API. Abra o arquivo Main.java em seu editor de texto novamente e adici-
one a seguinte linha ao método main abaixo de onde você criou o userControl-
ler objeto:
before(userController::authenticate);
Agora você pode atualizar seus métodos de API para verificar se o usuário auten-
ticado corresponde a qualquer identidade reivindicada na solicitação. Por exem-
plo, você pode atualizar a operação Create Space para verificar se o owner cam-
pocorresponde ao usuário autenticado no momento. Isso também permite que
você pule a validação do nome de usuário, porque você pode confiar que o ser-
viço de autenticação já fez isso. Abra o arquivo SpaceController.java em seu edi-
tor e altere o createSpace métodopara verificar se o proprietário do espaço cor-
responde ao assunto autenticado, conforme o trecho a seguir:
public JSONObject createSpace(Solicitação de solicitação, Resposta de resposta) {
..
var proprietário = json.getString("proprietário");
var assunto = request.attribute("assunto");
if (!proprietário.igual(assunto)) {
lançar novo IllegalArgumentException(
"proprietário deve corresponder ao usuário autenticado");
}
..
}
Agora você habilitou a autenticação para sua API - toda vez que um usuário faz
uma declaração sobre sua identidade, ele é obrigado a autenticar para fornecer
prova dessa declaração. Você ainda não está aplicando a autenticação em todas as
chamadas de API, portanto ainda pode ler as mensagens sem ser autenticado.
Você abordará isso em breve quando examinar o controle de acesso. As verifica-
ções que adicionamos até agora fazem parte da lógica do aplicativo. Agora vamos
experimentar como a API funciona. Primeiro, vamos tentar criar um espaçose-
mautenticando:
Bom, isso foi evitado. Vamos usar o curl agora para registrar um usuário de
demonstração:
$ curl -d '{"username":"demo","password":"password"}''
➥ -H 'Content-Type: application/json' http://localhost:4567/users
{"nome de usuário":"demonstração"}
Por fim, você pode repetir sua solicitação Create Space com as credenciais de au-
tenticação corretas:
{"nome":"espaço de teste","uri":"/espaços/1"}
questionário
3. Quais das opções a seguir são propriedades desejáveis de um algoritmo de hash
de senha segura? (Pode haver várias respostas corretas.)
1. Deve ser fácil paralelizar.
2. Deve usar muito espaço de armazenamento em disco.
3. Deve usar muita largura de banda de rede.
4. Deve usar muita memória (vários MB).
5. Ele deve usar um sal aleatório para cada senha.
6. Ele deve usar muita energia da CPU para tentar muitas senhas.
4. Qual é a principal razão pela qual a autenticação HTTP Basic deve ser usada
apenas em um canal de comunicação criptografado, como HTTPS? (Escolha
uma resposta.)
1. A senha pode ser exposta no Referer cabeçalho.
2. O HTTPS retarda os invasores que tentam adivinhar senhas.
3. A senha pode ser adulterada durante a transmissão.
4. O Google penaliza os sites nas classificações de pesquisa se eles não usarem
HTTPS.
5. A senha pode ser facilmente decodificada por qualquer pessoa que bisbilhote
o tráfego da rede.
TLS ou SSL?
Segurança da Camada de Transporte (TLS) é um protocolo que se baseia no TCP/IP
e fornece várias funções básicas de segurança para permitir a comunicação se-
gura entre um cliente e um servidor. As primeiras versões do TLS eram conheci-
das como Secure Socket Layer, ou SSL, e muitas vezes você ainda ouvirá TLS refe-
rido como SSL. Os protocolos de aplicativos que usam TLS geralmente têm um S
anexado ao nome, por exemplo, HTTPS ou LDAPS, para significar “seguro”.
Como você está habilitando o HTTPS apenas para fins de desenvolvimento, pode
usar um certificado autoassinado. Em capítulos posteriores, você se conectará à
API diretamente em um navegador da Web, portanto, é muito mais fácil usar um
certificado assinado por uma CA local. A maioria dos navegadores da Web não
gosta de certificados autoassinados. Uma ferramenta chamada mkcert (
https://mkcert.dev ) simplifica consideravelmente o processo. Siga as instruções
na página inicial do mkcert para instalá-lo e, em seguida, execute
mkcert -install
Agora você pode gerar um certificado para seu servidor Spark em execução no lo-
calhost. Por padrão, mkcert gera certificados no Privacy Enhanced Mail(PEM).
Para Java, você precisa do certificado no formato PKCS#12, então execute o se-
guinte comando na pasta raiz do projeto Natter para gerar um certificado para
localhost:
mkcert -pkcs12 host local
AVISO O certificado CA e a chave privada que mkcert gera pode ser usado para
gerar certificados para qualquer site que seja confiável para o seu navegador. Não
compartilhe esses arquivos nem os envie a ninguém. Quando terminar o desen-
volvimento, considere a execução mkcert -uninstall para remover a CA dos
armazenamentos confiáveis do sistema.
Reinicie o servidor para que as alterações entrem em vigor. Se você iniciou o ser-
vidor a partir da linha de comando, pode usar Ctrl-C para interromper o processo
e simplesmente executá-lo novamente. Se você iniciou o servidor a partir do seu
IDE, deve haver um botão para reiniciar o processo.
Por fim, você pode chamar sua API (depois de reiniciar o servidor). Se o curl se re-
cusar a conectar, você pode usar a --cacert opçãoenrolar para dizer a ele para
confiar no mkcertcertificado:
{"nome de usuário":"demonstração"}
response.header("Strict-Transport-Security", "max-age=31536000");
DICA Adicionar um cabeçalho HSTS para localhost não é uma boa ideia, pois
impedirá que você execute servidores de desenvolvimento em HTTP simples até
que o max-age atributoexpira. Se você quiser experimentar, defina um max-
age valor curto.
questionário
Um novo controlador agora pode ser adicionado para lidar com o log de audito-
ria. Você divide o registro em dois filtros, um que ocorre antes do processamento
da solicitação (após a autenticação) e outro que ocorre depois que a resposta foi
produzida. Você também permitirá o acesso aos logs a qualquer pessoa para fins
de ilustração. Normalmente, você deve bloquear os logs de auditoria apenas para
um pequeno número de usuários confiáveis, pois eles geralmente são confidenci-
ais. Freqüentemente, os usuários que podem acessar os logs de auditoria (audito-
res) são diferentes dos administradores de sistema normais, pois as contas de ad-
ministrador são as mais privilegiadas e, portanto, as que mais precisam de moni-
toramento. Este é um importante princípio de segurança conhecido como separa-
ção de funções.
pacote com.manning.apisecurityinaction.controller;
import org.dalesbred.*;
import org.json.*;
importar faísca.*;
importar java.sql.*;
importar java.time.*;
importar java.time.temporal.*;
A Listagem 3.6 mostra o código para ler as entradas do log de auditoria da última
hora. As entradas são consultadas no banco de dados e convertidas em objetos
JSON usando um RowMapper método personalizado. A lista de registros é retor-
nada como uma matriz JSON. Um limite simples é adicionado à consulta para evi-
tar que muitos resultados sejam retornados.
❷ Converta cada entrada em um objeto JSON e colete como uma matriz JSON.
Abra o arquivo Main.java em seu editor e localize as linhas que instalam os filtros
para autenticação. O log de auditoria deve vir logo após a autenticação, então
você deve adicionar os filtros de auditoria entre o filtro de autenticação e a pri-
meira definição de rota, conforme destacado em negrito neste próximo trecho.
Adicione as linhas indicadas e salve o arquivo.
before(userController::authenticate);
post("/espaços",
spaceController::createSpace);
Por fim, você pode registrar um novo endpoint (não seguro) para ler os logs. No-
vamente, em um ambiente de produção, isso deve ser desativado ou bloqueado:
get("/logs", auditController::readAuditLog);
Uma vez instalado e o servidor reiniciado, faça algumas solicitações de amostra e
visualize o log de auditoria. Você pode usar o utilitário jq (
https://stedolan.github.io/jq/ ) para imprimir a saída:
Este estilo de log é um log de acesso básico, que registra as solicitações e respostas
HTTP brutas para sua API. Outra maneira de criar um log de auditoria é capturar
eventos na camada de lógica de negócios de seu aplicativo, como eventos criados
pelo usuário ou mensagens postadas. Esses eventos descrevem os detalhes essen-
ciais do que aconteceu sem referência ao protocolo específico usado para acessar
a API. Ainda outra abordagem é capturar eventos de auditoria diretamente no
banco de dados usando gatilhos para detectar quando os dados são alterados. A
vantagem dessas abordagens alternativas é que elas garantem que os eventos se-
jam registrados independentemente de como a API é acessada, por exemplo, se a
mesma API estiver disponível por HTTP ou usando um protocolo RPC binário. A
desvantagem é que alguns detalhes são perdidos e alguns ataques em potencial
podem ser perdidos devido a essa faltadetalhe.
questionário
6. Qual princípio de design seguro indicaria que os logs de auditoria devem ser ge-
renciados por usuários diferentes dos administradores de sistema normais?
1. O princípio de Pedro
2. O princípio do menor privilégio
3. O princípio da defesa em profundidade
4. O princípio da separação de funções
5. O princípio da segurança através da obscuridade
O controle de acesso deve acontecer após a autenticação, para que você saiba
quem está tentando realizar a ação, conforme a figura 3.7. Se a solicitação for con-
cedida, ela poderá prosseguir para a lógica do aplicativo. No entanto, se for ne-
gado pelas regras de controle de acesso, ele deve ser reprovado imediatamente e
uma resposta de erro retornada ao usuário. Os dois principais códigos de status
HTTP para indicar que o acesso foi negado são 401 Não autorizado e 403 Proibido.
Consulte a barra lateral para obter detalhes sobre o significado desses dois códi-
gos e quando usar um ou outro.
Figura 3.7 O controle de acesso ocorre após a autenticação e a solicitação foi regis-
trada para auditoria. Se o acesso for negado, uma resposta proibida será retor-
nada imediatamente sem executar nenhuma lógica do aplicativo. Se o acesso for
concedido, a solicitação prosseguirá normalmente.
O HTTP inclui dois códigos de status padrão para indicar que o cliente falhou nas
verificações de segurança e pode ser confuso saber qual status usar em quais
situações.
O 403 Forbidden código de status, por outro lado, informa ao cliente que suas
credenciais foram válidas para autenticação, mas que não tem permissão para re-
alizar a operação solicitada. Isso é uma falha de autorização, não de autentica-
ção. Normalmente, o cliente não pode fazer nada sobre isso, a não ser solicitar
acesso ao administrador.
Em seguida, você pode abrir o arquivo Main.java e exigir que todas as chamadas
para a API do Spaces sejam autenticadas, adicionando a seguinte definição de fil-
tro. Conforme mostrado na figura 3.7 e ao longo deste capítulo, verificações de
controle de acesso como esta devem ser adicionadas após autenticação e registro
de auditoria. Localize a linha onde você adicionou o filtro de autenticação anteri-
ormente e adicione um filtro para aplicar a autenticação em todas as solicitações
à API que começam com o caminho de URL /spaces, para que o código se pareça
com o seguinte:
before(userController::authenticate); ❶
before(auditController::auditRequestStart); ❷
afterAfter(auditController::auditRequestEnd); ❷
before("/spaces", userController::requireAuthentication); ❸
post("/spaces", spaceController::createSpace); ..
Repetir a solicitação com credenciais de autenticação permite que ela seja bem-
sucedida:
Cada entrada para um espaço listará um usuário que pode acessar esse espaço,
juntamente com um conjunto de permissõesque definem o que podem fazer. A
API do Natter tem três permissões: ler mensagens em um espaço, postar mensa-
gens nesse espaço e uma permissão de exclusão concedida aos moderadores.
DEFINIÇÃO Uma lista de controle de acesso é uma lista de usuários que podem
acessar um determinado objeto, juntamente com um conjunto de permissõesque
definem o que cada usuário pode fazer.
Por que simplesmente não permitir que todos os usuários autenticados executem
qualquer operação? Em algumas APIs, esse pode ser um modelo de segurança
apropriado, mas para a maioria das APIs algumas operações são mais confidenci-
ais do que outras. Por exemplo, você pode permitir que qualquer pessoa em sua
empresa veja suas próprias informações salariais em sua API de folha de paga-
mento, mas a capacidade de alterar o salário de alguém normalmente não é algo
que você permitiria que qualquer funcionário fizesse! Lembre-se do princípio da
menor autoridade(POLA) do capítulo 1, que diz que qualquer usuário (ou pro-
cesso) deve receber exatamente a quantidade certa de autoridade para fazer os
trabalhos que precisa fazer. Muitas permissões podem causar danos ao sistema.
Muito poucas permissões e eles podem tentar contornar a segurança do sistema
para fazer seu trabalho.
database.updateUnique(
"INSERT INTO espaços(space_id, nome, proprietário)" +
"VALUES(?, ?, ?);", spaceId, spaceName, proprietário);
database.updateUnique( ❶
"INSERT INTO permissions(space_id, user_id, perms) " + ❶
"VALUES(?, ?, ?)", spaceId, owner, "rwd"); ❶
resposta.status(201);
response.header("Localização", "/espaços/" + spaceId);
Agora você precisa adicionar verificações para garantir que o usuário tenha as
permissões apropriadas para as ações que está tentando executar. Você pode codi-
ficar essas verificações em cada método individual, mas é muito mais fácil man-
ter as decisões de controle de acesso usando filtros que são executados antes
mesmo de o controlador ser chamado. Essa separação de preocupações garante
que o controlador possa se concentrar na lógica principal da operação, sem ter
que se preocupar com os detalhes do controle de acesso. Isso também garante
que, se você quiser alterar a forma como o controle de acesso é executado, poderá
fazer isso no filtro comum, em vez de alterar cada método do controlador.
Para impor suas regras de controle de acesso, você precisa de um filtro que possa
determinar se o usuário autenticado tem as permissões apropriadas para execu-
tar uma determinada operação em um determinado espaço. Em vez de ter um fil-
tro que tenta determinar qual operação está sendo executada examinando a soli-
citação, você escreverá um método de fábrica que retorna um novo filtro com de-
talhes sobre a operação. Você pode usar isso para criar filtros específicos para
cada operação. A Listagem 3.7 mostra como implementar esse filtro em sua
UserController classe.
requireAuthentication(solicitação, resposta); ❸
if (!perms.contains(permission)) { ❺
halt(403); ❺
} ❺
};
}
Vocêagora você pode adicionar filtros a cada operação em seu método principal,
conforme mostrado na Listagem 3.8. Antes de cada rota do Spark, você adiciona
uma nova before () filtro que aplica as permissões corretas. Cada caminho de
filtro deve ter um :spaceId parâmetro de caminhopara que o filtro possa deter-
minar em qual espaço está sendo operado. Abra a classe Main.java em seu editor
e certifique-se de que seu main() métodocorresponde ao conteúdo da listagem
3.8. Novos filtros que impõem verificações de permissão são destacados em
negrito.
OBSERVAÇÃO As implementações de todas as operações de API podem ser en-
contradas no repositório GitHub que acompanha o livro em
https://github.com/NeilMadden/apisecurityinaction .
before(auditController::auditRequestStart);
afterAfter(auditController::auditRequestEnd);
before("/spaces", ❷
userController::requireAuthentication); ❷
post("/espaços",
spaceController::createSpace);
before("/espaços/:espaçoId/mensagens", ❸
userController.requirePermission("POST", "w"));
post("/espaços/:espaçoId/mensagens",
spaceController::postMessage);
before("/espaços/:espaçoId/mensagens/*",
userController.requirePermission("GET", "r"));
get("/espaços/:espaçoId/messages/:msgId",
spaceController::readMessage);
before("/espaços/:espaçoId/mensagens",
userController.requirePermission("GET", "r"));
get("/espaços/:espaçoId/mensagens",
spaceController::findMessages);
var moderadorController =
novo ModeradorController(banco de dados);
before("/espaços/:espaçoId/mensagens/*",
userController.requirePermission("DELETE", "d"));
delete("/espaços/:spaceId/messages/:msgId",
moderadorController::deletePost);
post("/users", userController::registerUser); ❹
...
}
❷ Qualquer pessoa pode criar um espaço, então você apenas impõe que o usuário es-
teja logado.
❸ Para cada operação, você adiciona um filtro before() que garante que o usuário te-
nha as permissões corretas.
❹ Qualquer pessoa pode registrar uma conta e não será autenticada primeiro.
Com isso em vigor, se você criar um segundo usuário “demo2” e tentar ler uma
mensagem criada pelo usuário de demonstração existente em seu espaço, rece-
berá um erro 403 Proibidoresposta:
$ curl -i -u demo2:password
➥ https://localhost:4567/spaces/1/messages/1
HTTP/1.1 403 Proibido
...
Entãoaté agora, não há como nenhum usuário que não seja o proprietário do es-
paço postar ou ler mensagens de um espaço. Vai ser uma rede social bastante an-
tissocial, a menos que você possa adicionar outros usuários! Você pode adicionar
uma nova operação que permite que outro usuário seja adicionado a um espaço
por qualquer usuário existente que tenha permissão de leitura nesse espaço. A
próxima listagem adiciona uma operação ao SpaceController para permitir
isso.
if (!perms.matches("r?w?d?")) { ❶
lançar novo IllegalArgumentException("permissões inválidas");
}
database.updateUnique( ❷
"INSERT INTO permissions(space_id, user_id, perms)" +
"VALUES(?, ?, ?);", spaceId, userToAdd, perms);
resposta.status(200);
retornar novo JSONObject()
.put("nome de usuário", userToAdd)
.put("permissões", permissões);
}
Você pode adicionar uma nova rota ao seu método principal para permitir a adi-
ção de um novo membro por POSTing em /spaces/:spaceId/members . Abra
Main.java em seu editor novamente e adicione a seguinte nova rota e filtro de
controle de acesso ao método principal abaixo das rotas existentes:
before("/espaços/:espaçoId/membros",
userController.requirePermission("POST", "r"));
post("/spaces/:spaceId/members", spaceController::addMember);
Você pode testar isso adicionando o usuário demo2 ao espaço e permitindo que
ele leiamensagens:
$ curl -u demo:password
➥ -H 'Content-Type: application/json'
➥ -d '{"username":"demo2","permissions":"r"}'
➥ https://localhost:4567/ espaços/1/membros
{"permissões":"r","nome de usuário":"demo2"}
$ curl -u demo2:senha
➥ https://localhost:4567/spaces/1/messages/1
{"author":"demo","time":"2019-02-06T15:15:03.138Z","message":"Olá, mundo!","uri":"/space
Istoacontece que o usuário demo2 que você acabou de adicionar pode fazer um
pouco mais do que apenas ler mensagens. As permissões no addMember méto-
dopermite que qualquer usuário com acesso de leitura adicione novos usuários
ao espaço e eles podem escolher as permissões para o novo usuário. Portanto, o
demo2 pode simplesmente criar uma nova conta para si e conceder a ela mais
permissões do que você originalmente concedeu, conforme mostrado no exemplo
a seguir.
$ curl -u demo2:password
➥ -H 'Content-Type: application/json'
➥ -d '{"username":"evildemo2","permissions":"rwd"}'
➥ https://localhost:4567/ espaços/1/membros
{"permissions":"rwd","username":"evildemo2"}
Agora eles podem fazer o que quiserem, inclusive deletar suas mensagens:
$ curl -i -X
DELETE -u evildemo2:password
➥ https://localhost:4567/spaces/1/messages/1
HTTP/1.1 200 OK
...
O que aconteceu aqui é que, embora o usuário demo2 tenha recebido apenas per-
missão de leitura no espaço, ele pode usar essa permissão de leitura para adicio-
nar um novo usuário que tenha permissões totais no espaço. Isso é conhecido
como escalonamento de privilégios, em que um usuário com privilégios mais bai-
xos pode explorar um bug para obter privilégios mais altos.
1. Você pode exigir que as permissões concedidas ao novo usuário não sejam mais
do que as permissões concedidas ao usuário existente. Ou seja, você deve garan-
tir que evildemo2 receba apenas o mesmo acesso que o usuário demo2.
2. Você pode exigir que apenas usuários com todas as permissões possam adicio-
nar outros usuários.
Para simplificar, você implementará a segunda opção e alterará o filtro de autori-
zação no addMember operação para exigir todas as permissões. Efetivamente, isso
significa que apenas o proprietário ou outros moderadores podem adicionar no-
vos membros a um espaço social.
Abra o arquivo Main.java e localize o filtro anterior que concede acesso para adi-
cionar usuários a um espaço social. Altere as permissões necessárias de r para
rwd da seguinte forma:
before("/espaços/:espaçoId/membros",
userController.requirePermission("POST", "rwd"));
Se você repetir o ataque com demo2 novamente, descobrirá que eles não são mais
capazes de criar nenhum usuário, muito menos um com privilégios elevados.
questionário
7. Qual código de status HTTP indica que o usuário não tem permissão para aces-
sar um recurso (em vez de não ser autenticado)?
1. 403 Proibido
2. 404 não encontrado
3. 401 não autorizado
4. 418 Eu sou um Bule
5. 405 Método não permitido
1. c. A limitação de taxa deve ser aplicada o mais cedo possível para minimizar os
recursos usados no processamento de solicitações.
2. b. o Retry-After cabeçalhoinforma ao cliente quanto tempo esperar antes de
tentar novamente as solicitações.
3. d, e e f. Um algoritmo de hash de senha segura deve usar muita CPU e memória
para dificultar a execução de força bruta por um invasore ataques de dicioná-
rio. Ele deve usar um sal aleatório para cada senha para evitar que um invasor
pré-compute tabelas de hashes de senha comuns.
4. e. As credenciais HTTP Basic são codificadas apenas em Base64, que, como você
se lembrará da seção 3.3.1, são fáceis de decodificar para revelar a senha.
5. c. O TLS não fornece proteções de disponibilidade por conta própria.
6. d. O princípio da separação de funções.
7. uma. 403 Proibido. Como você deve se lembrar do início da seção 3.6, apesar do
nome, 401 Unauthorized significa apenas que o usuário estánãoautenticado.
Resumo
Use a modelagem de ameaças com STRIDE para identificar ameaças à sua API.
Selecione os controles de segurança apropriados para cada tipo de ameaça.
Aplique limitação de taxa para mitigar ataques DoS. Os limites de taxa são me-
lhor aplicados em um balanceador de carga ou proxy reverso, mas também po-
dem ser aplicados por servidor para defesa em profundidade.
Habilite o HTTPS para todas as comunicações da API para garantir a confidenci-
alidade e integridade das solicitações e respostas. Adicione cabeçalhos HSTS
para informar aos clientes do navegador da Web que sempre usem HTTPS.
Use autenticação para identificar usuários e evitar ataques de falsificação. Use
um esquema seguro de hash de senha como Scrypt para armazenar senhas de
usuários.
Todas as operações significativas no sistema devem ser registradas em um log
de auditoria, incluindo detalhes de quem executou a ação, quando e se foi bem-
sucedida.
Aplicar o controle de acesso após a autenticação. ACLs são uma abordagem sim-
ples para impor permissões.
Evite ataques de escalonamento de privilégios considerando cuidadosamente
quais usuários podem conceder permissões a outrosusuários.
1.
A RateLimiter classe está marcada como instável no Guava, portanto pode mu-
dar em versões futuras.
2.
Alguns serviços retornam um status 503 Serviço indisponível. Ambos são aceitá-
veis, mas 429 é mais preciso, especialmente se você executar a limitação de taxa
por cliente.
3.
Infelizmente, as especificações HTTP confundem os termos autenticação e autori-
zação. Como você verá no capítulo 9, existem esquemas de autorização que não
envolvem autenticação.
4.
O nome de usuário não pode conter dois pontos.
5.
https://tools.ietf.org/html/rfc5802
6.
https://blog.cryptographyengineering.com/2018/10/19/lets-talk-about-pake/