Este tutorial é um dos quatro que colocam em prática os conceitos de Microservices March 2022: Redes no Kubernetes:
- Reduza a latência do Kubernetes com escalonamento automático
- Proteja as APIs do Kubernetes com limitação de taxa
- Proteja aplicações Kubernetes contra injeção de SQL (este post)
- Melhore a disponibilidade e a resiliência com uma implantação canário
Quer orientações detalhadas sobre como usar o NGINX em ainda mais casos de uso de redes no Kubernetes? Baixe gratuitamente nosso eBook, Gerenciamento de tráfego do Kubernetes com o NGINX: um guia prático.
Você trabalha na área de TI de uma loja local popular que vende diversos produtos, de travesseiros a bicicletas. A loja está prestes a lançar sua primeira loja virtual, mas pediu a um especialista em segurança que realizasse um teste de invasão no site antes de disponibilizá-lo ao público. Infelizmente, o especialista em segurança encontrou um problema! A loja virtual está vulnerável a injeção de SQL. O especialista em segurança conseguiu explorar a vulnerabilidade do site para obter informações confidenciais do banco de dados, incluindo nomes de usuário e senhas.
Sua equipe recorreu a você — o engenheiro de Kubernetes — para salvar o dia. Felizmente, você sabe que a injeção de SQL (assim como outras vulnerabilidades) pode ser mitigada usando ferramentas de gerenciamento de tráfego do Kubernetes. Você já implantou um controlador Ingress para expor o aplicativo e, com uma única configuração, consegue garantir que a vulnerabilidade não possa ser explorada. Agora, a loja virtual pode ser lançada dentro do prazo. Muito bem!
Visão geral do laboratório e do tutorial
Este blog acompanha o laboratório da Unidade 3 do Microservices March 2022 – Padrão de segurança de microsserviços no Kubernetes, demonstrando como usar o NGINX e o NGINX Ingress Controller para bloquear injeções de SQL.
Para seguir o tutorial, você precisa de uma máquina com:
- 2 CPUs ou mais
- 2 GB de memória livre
- 20 GB de espaço livre em disco
- Conexão com a internet
- Gerenciador de contêineres ou máquinas virtuais, como Docker, Hyperkit, Hyper-V, KVM, Parallels, Podman, VirtualBox ou VMware Fusion/Workstation
- minikube instalado
- Helm instalado
- Uma configuração que permita abrir uma janela do navegador. Se isso não for possível, você precisará descobrir como acessar os serviços relevantes por meio de um navegador.
Para aproveitar ao máximo o laboratório e o tutorial, recomendamos que, antes de começar, você:
- Assista à gravação da visão geral conceitual transmitida ao vivo
- Confira os blogs, o webinar e o vídeo com informações contextuais
- Assista ao vídeo de 16 minutos que resume o laboratório:
 
Este tutorial usa estas tecnologias:
- NGINX Open Source
- NGINX Ingress Controller (baseado no NGINX Open Source)
- BusyBox
- Helm
- minikube
- Um aplicativo simples com vulnerabilidades de segurança, desenvolvido para este laboratório
As instruções de cada desafio incluem o texto completo dos arquivos YAML usados para configurar os aplicativos. Você também pode copiar o texto do nosso repositório no GitHub. Um link para o GitHub é fornecido junto com o texto de cada arquivo YAML.
Este tutorial inclui quatro desafios:
- Implante um cluster e um aplicativo vulnerável
- Invada o aplicativo
- Use um contêiner sidecar do NGINX para bloquear determinadas solicitações
- Configure o NGINX Ingress Controller para filtrar solicitações
Desafio 1: implantar um cluster e um aplicativo vulnerável
Neste desafio, você vai implantar um cluster do minikube e instalar o Podinfo como um aplicativo de exemplo que apresenta vulnerabilidades de segurança.
Crie um cluster do Minikube
Implante um cluster minikube. Após alguns segundos, uma mensagem confirmará que a implantação foi bem-sucedida.
$ minikube start
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by defaultInstale o aplicativo vulnerável
Aqui, você implanta um aplicativo simples de comércio eletrônico composto por dois microsserviços:
- Um banco de dados MariaDB
- Um microsserviço PHP que se conecta ao banco de dados e recupera dados
Siga estas etapas:
- Usando o editor de texto de sua preferência, crie um arquivo YAML chamado 1-app.yaml com o seguinte conteúdo (ou copie do GitHub).
apiVersion: apps/v1 kind: Deployment
metadata:
name: app
spec:
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
containers:
- name: app
image: f5devcentral/microservicesmarch:1.0.3
ports:
- containerPort: 80
env:
- name: MYSQL_USER
value: dan
- name: MYSQL_PASSWORD
value: dan
- name: MYSQL_DATABASE
value: sqlitraining
- name: DATABASE_HOSTNAME
value: db.default.svc.cluster.local
---
apiVersion: v1
kind: Service
metadata:
name: app
spec:
ports:
- port: 80
targetPort: 80
nodePort: 30001
selector:
app: app
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
spec:
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: db
image: mariadb:10.3.32-focal
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: root
- name: MYSQL_USER
value: dan
- name: MYSQL_PASSWORD
value: dan
- name: MYSQL_DATABASE
value: sqlitraining
---
apiVersion: v1
kind: Service
metadata:
name: db
spec:
ports:
- port: 3306
targetPort: 3306
selector:
app: db - Implante o aplicativo e a API:
$ kubectl apply -f 1-app.yaml deployment.apps/app created
service/app created
deployment.apps/db created
service/db created - Confirme se os pods do Podinfo foram implantados, conforme indicado pelo valor
Runningna colunaSTATUS. A implantação completa pode levar de 30 a 40 segundos. Portanto, aguarde até que o status de ambos os pods sejaRunningantes de prosseguir para a próxima etapa (execute o comando novamente, se necessário).
$ kubectl get podsNAME READY STATUS RESTARTS AGE
app-d65d9b879-b65f2 1/1 Running 0 37s
db-7bbcdc75c-q2kt5 1/1 Running 0 37s - Abra o aplicativo no seu navegador:
$ minikube service app|-----------|------|-------------|--------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|------|-------------|--------------|
| default | app | | No node port |
|-----------|------|-------------|--------------|
😿 service default/app has no node port
🏃 Starting tunnel for service app.
|-----------|------|-------------|------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|------|-------------|------------------------|
| default | app | | http://127.0.0.1:55446 |
|-----------|------|-------------|------------------------|
🎉 Opening service default/app in default browser... Desafio 2: Hackeie o aplicativo
O aplicativo de exemplo é bastante básico. Ele inclui uma página inicial com uma lista de itens (por exemplo, travesseiros) e um conjunto de páginas de produtos com detalhes como descrição e preço. Os dados são armazenados no banco de dados MariaDB. Sempre que uma página é solicitada, uma consulta SQL é executada no banco de dados.
- Para a página inicial, todos os itens do banco de dados são recuperados.
- Para uma página de produto, o item é obtido pelo ID.
Ao abrir a página do produto travesseiros, você pode notar que a URL termina em /product/1. O número 1 é o ID do produto. Para impedir a inserção direta de código malicioso na consulta SQL, é recomendável sanitizar a entrada do usuário antes de encaminhar as solicitações aos serviços de back-end. Mas e se o aplicativo não estiver configurado corretamente e a entrada não passar por escape antes de ser inserida na consulta SQL ao banco de dados?
Exploração 1
Para descobrir se o aplicativo está escapando corretamente a entrada, faça um teste simples alterando o ID para um que não exista no banco de dados.
Altere manualmente o último elemento da URL de 1 para -1. A mensagem de erro Invalid product id "-1" indica que o ID do produto não está sendo escapado – em vez disso, a string é inserida diretamente na consulta. Isso não é nada bom, a menos que você seja um hacker!

Suponha que a consulta ao banco de dados seja algo como:
SELECT * FROM some_table WHERE id = "1"Para explorar a vulnerabilidade causada pela falta de escape da entrada, substitua 1 por -1" <malicious_query> -- // de modo que:
- As aspas (
") após-1concluem a primeira consulta. - Você pode adicionar sua própria consulta maliciosa após as aspas.
- A sequência
--//descarta o restante da consulta.
Assim, por exemplo, se você alterar o elemento final da URL para ‑1" ou 1 -- //, a consulta será compilada como:
SELECT * FROM some_table WHERE id = "-1" OR 1 -- //"
--------------
^ injected ^ Isso seleciona todas as linhas do banco de dados, o que é útil em um ataque. Para descobrir se esse é o caso, altere o final da URL para ‑1". A mensagem de erro resultante fornece informações mais úteis sobre o banco de dados:
Fatal error: Uncaught mysqli_sql_exception: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '"-1""' at line 1 in /var/www/html/product.php:23 Stack trace: #0 /var/www/html/product.php(23): mysqli->query('SELECT * FROM p...') #1 {main} thrown in /var/www/html/product.php on line 23Agora, você pode começar a manipular o código injetado na tentativa de ordenar os resultados do banco de dados por ID:
-1" OR 1 ORDER BY id DESC -- //O resultado é a página do produto correspondente ao último item do banco de dados.
Exploração 2
Forçar o banco de dados a ordenar os resultados é interessante, mas não é especialmente útil se o seu objetivo é invadir o sistema. Tentar extrair nomes de usuário e senhas do banco de dados vale muito mais a pena.
É seguro presumir que há uma tabela de usuários no banco de dados contendo nomes de usuário e senhas. Mas como ampliar seu acesso da tabela de produtos para a tabela de usuários?
A resposta é injetar um código como este:
-1" UNION SELECT * FROM users -- //onde
‑1"força o retorno de um conjunto vazio da primeira consulta.UNIONforça a união de duas tabelas do banco de dados — neste caso, products e users — permitindo obter informações (senhas) que não estão na tabela original (products).SELECT*FROMusersseleciona todas as linhas da tabela users.- A sequência
--//descarta tudo o que vem depois da consulta maliciosa.
Ao modificar a URL para que termine com o código injetado, você recebe uma nova mensagem de erro:
Fatal error: Uncaught mysqli_sql_exception: The used SELECT statements have a different number of columns in /var/www/html/product.php:23 Stack trace: #0 /var/www/html/product.php(23): mysqli->query('SELECT * FROM p...') #1 {main} thrown in /var/www/html/product.php on line 23Essa mensagem revela que as tabelas products e users não têm o mesmo número de colunas, portanto a instrução UNION não pode ser executada. No entanto, você pode descobrir o número de colunas por tentativa e erro, adicionando uma coluna (nome de campo) por vez como parâmetro da instrução SELECT. Um bom palpite para o nome de um campo em uma tabela users é password, então tente isso:
# select 1 column-1" UNION SELECT password FROM users; -- //
# select 2 columns
-1" UNION SELECT password,password FROM users; -- //
# select 3 columns
-1" UNION SELECT password,password,password FROM users; -- /
# select 4 columns
-1" UNION SELECT password,password,password,password FROM users; -- //
# select 5 columns
-1" UNION SELECT password,password,password,password,password FROM users; -- //A última consulta é bem-sucedida (indicando que há cinco colunas na tabela users) e você vê a senha de um usuário:

Neste ponto, você não sabe qual nome de usuário corresponde a essa senha. Mas, sabendo o número de colunas da tabela users, você pode usar os mesmos tipos de consulta usados anteriormente para revelar essa informação. Suponha que o nome do campo relevante seja username. E isso acaba se confirmando – a consulta a seguir revela tanto o nome de usuário quanto a senha da tabela users. O que é ótimo – a menos que esse aplicativo esteja hospedado na sua infraestrutura!
-1" UNION SELECT username,username,password,password,username FROM users where id=1 -- //
Desafio 3: use um contêiner sidecar do NGINX para bloquear determinadas requisições
O desenvolvedor do aplicativo da loja online obviamente precisa prestar mais atenção à sanitização das entradas dos usuários (por exemplo, usando consultas parametrizadas), mas, como engenheiro de Kubernetes, você também pode ajudar a evitar a injeção de SQL impedindo que o ataque chegue ao aplicativo. Dessa forma, o fato de o aplicativo ser vulnerável não é tão relevante.
Há muitas maneiras de proteger seus aplicativos. No restante deste laboratório, vamos nos concentrar em duas:
- Neste desafio, você injeta um contêiner sidecar no pod para intermediar todo o tráfego e negar qualquer solicitação que contenha
UNIONna URL. Primeiro, implante o NGINX Open Source como um sidecar e depois teste se ele filtra consultas maliciosas. Observação: usamos essa técnica apenas para fins ilustrativos. Na prática, implantar proxies manualmente como sidecars não é a melhor solução (falaremos mais sobre isso adiante). - No Desafio 4, usamos o NGINX Ingress Controller para filtrar todo o tráfego que entra no cluster.
Implante o NGINX Open Source como um sidecar
- Crie um arquivo YAML chamado 2-app-sidecar.yaml com o seguinte conteúdo (ou copie do GitHub). Os aspectos importantes da configuração incluem:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
containers:
- name: app
image: f5devcentral/microservicesmarch:1.0.3
ports:
- containerPort: 80
env:
- name: MYSQL_USER
value: dan
- name: MYSQL_PASSWORD
value: dan
- name: MYSQL_DATABASE
value: sqlitraining
- name: DATABASE_HOSTNAME
value: db.default.svc.cluster.local
- name: proxy # <-- sidecar
image: "nginx"
ports:
- containerPort: 8080
volumeMounts:
- mountPath: /etc/nginx
name: nginx-config
volumes:
- name: nginx-config
configMap:
name: sidecar
---
apiVersion: v1
kind: Service
metadata:
name: app
spec:
ports:
- port: 80
targetPort: 8080 # <-- the traffic is routed to the proxy
nodePort: 30001
selector:
app: app
type: NodePort
---
apiVersion: v1
kind: ConfigMap
metadata:
name: sidecar
data:
nginx.conf: |-
events {}
http {
server {
listen 8080 default_server;
listen [::]:8080 default_server;
location ~* "(\'|\")(.*)(drop|insert|md5|select|union)" {
deny all;
}
location / {
proxy_pass http://localhost:80/;
}
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
spec:
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: db
image: mariadb:10.3.32-focal
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: root
- name: MYSQL_USER
value: dan
- name: MYSQL_PASSWORD
value: dan
- name: MYSQL_DATABASE
value: sqlitraining
---
apiVersion: v1
kind: Service
metadata:
name: db
spec:
ports:
- port: 3306
targetPort: 3306
selector:
app: db- Um contêiner sidecar executando o NGINX Open Source é iniciado na porta 8080.
- O NGINX encaminha todo o tráfego para o aplicativo.
- Qualquer solicitação que contenha (entre outras cadeias de caracteres)
SELECTouUNIONserá negada (consulte o primeiro blocolocationna seçãoConfigMap). - O serviço do aplicativo encaminha primeiro todo o tráfego para o contêiner NGINX.
- Implante o sidecar:
$ kubectl apply -f 2-app-sidecar.yaml deployment.apps/app configured
service/app configured
configmap/sidecar created
deployment.apps/db unchanged
service/db unchanged Teste o sidecar como um filtro
Teste se o sidecar está filtrando o tráfego voltando ao aplicativo e tentando novamente a injeção de SQL. O NGINX bloqueia a solicitação antes que ela chegue ao aplicativo!
-1" UNION SELECT username,username,password,password,username FROM users where id=1 -- // 
Desafio 4: configure o NGINX Ingress Controller para filtrar requisições
Proteger seu aplicativo como no Desafio 3 é uma experiência educacional interessante, mas não recomendamos essa abordagem em produção porque:
- Não é uma solução de segurança completa.
- Não é escalável (não é possível aplicar facilmente essa proteção a vários aplicativos).
- Atualizá-la é complicado e ineficiente.
Uma solução muito melhor é usar o NGINX Ingress Controller para estender a mesma proteção a todos os seus aplicativos! Os controladores de Ingress podem ser usados para centralizar todos os tipos de recursos de segurança, desde o bloqueio de solicitações, como faz um firewall de aplicações web (WAF), até a autenticação e a autorização.

Neste desafio, você implanta o NGINX Ingress Controller, configura o roteamento de tráfego, e verifica se o filtro bloqueia a injeção de SQL.
Implantar o NGINX Ingress Controller
A maneira mais rápida de instalar o NGINX Ingress Controller é usando o Helm.
- Adicione o repositório do NGINX ao Helm:
$ helm repo add nginx-stable https://helm.nginx.com/stable - Baixe e instale o NGINX Ingress Controller baseado no NGINX Open Source, mantido pela F5 NGINX. Observe o parâmetro
enableSnippets=true: os snippets são usados para configurar o NGINX para bloquear a injeção de SQL. A última linha da saída confirma que a instalação foi bem-sucedida.
$ helm install main nginx-stable/nginx-ingress \ --set controller.watchIngressWithoutClass=true --set controller.service.type=NodePort \ --set controller.service.httpPort.nodePort=30005 \ --set controller.enableSnippets=true
NAME: main
LAST DEPLOYED: Day Mon DD hh:mm:ss YYYY
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES: The NGINX Ingress Controller has been installed. - Confirme se o pod do NGINX Ingress Controller foi implantado, conforme indicado pelo valor
Runningna colunaSTATUS.
$ kubectl get pods NAME READY STATUS ...
main-nginx-ingress-779b74bb8b-mtdkr 1/1 Running ...
... RESTARTS AGE
... 0 18sDirecione o tráfego para seu aplicativo
- Crie um arquivo YAML chamado 3-ingress.yaml com o conteúdo a seguir (ou copie-o do GitHub). Ele define o manifesto do Ingress necessário para encaminhar o tráfego ao aplicativo (desta vez, sem passar pelo proxy sidecar). Observe o bloco
annotations:em que um snippet é usado para personalizar a configuração do NGINX Ingress Controller com o mesmo blocolocationda definição do ConfigMap no Desafio 3: ele rejeita qualquer solicitação que contenha (entre outras cadeias de caracteres)SELECTouUNION.
apiVersion: v1 kind: Service
metadata:
name: app-without-sidecar
spec:
ports:
- port: 80
targetPort: 80
selector:
app: app
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: entry
annotations:
nginx.org/server-snippets: |
location ~* "(\'|\")(.*)(drop|insert|md5|select|union)" {
deny all;
}
spec:
ingressClassName: nginx
rules:
- host: "example.com"
http:
paths:
- backend:
service:
name: app-without-sidecar
port:
number: 80
path: /
pathType: Prefix - Implante o recurso Ingress:
$ kubectl apply -f 3-ingress.yaml service/app-without-sidecar created
ingress.networking.k8s.io/entry created Verificar a operação do filtro
- Inicie um contêiner descartável do BusyBox para enviar uma solicitação ao pod do NGINX Ingress Controller usando o nome de host correto.
$ kubectl run -ti --rm=true busybox --image=busybox$ wget --header="Host: example.com" -qO- main-nginx-ingress
<!DOCTYPE html>
<html lang="en">
<head>
# ...- Tente realizar a injeção de SQL. O código de status
403Proibidoconfirma que o NGINX bloqueia o ataque!
$ wget --header="Host: example.com" -qO- 'main-nginx-ingress/product/-1"%20UNION%20SELECT%20username,username,password,password,username%20FROM%20users%20where%2 0id=1%20--%20//'
wget: server returned error: HTTP/1.1 403 Forbidden Próximos passos
O Kubernetes não é seguro por padrão. Um controlador Ingress pode mitigar vulnerabilidades de injeção de SQL (e muitas outras). No entanto, lembre-se de que o tipo de funcionalidade semelhante a um WAF que você acabou de implementar com o NGINX Ingress Controller não substitui um WAF de verdade, nem dispensa uma arquitetura segura para os aplicativos. Um hacker experiente ainda pode fazer o ataque com UNION funcionar com algumas pequenas alterações no código. Para saber mais sobre esse assunto, consulte Guia de injeção de SQL (SQLi) para pentesters.
Dito isso, um controlador de Ingress ainda é uma ferramenta poderosa para centralizar a maior parte da sua segurança, proporcionando mais eficiência e proteção, inclusive em casos de uso de autenticação e autorização centralizadas (mTLS, logon único), além de permitir o uso de um WAF robusto como o F5 NGINX App Protect WAF.
A complexidade dos seus aplicativos e da sua arquitetura pode exigir um controle mais granular. Se a sua organização precisar de confiança zero (Zero Trust) e criptografia de ponta a ponta, considere uma malha de serviços sempre gratuita, como o F5 NGINX Service Mesh para controlar a comunicação entre os serviços no cluster do Kubernetes (tráfego leste-oeste). Abordamos as malhas de serviços na Unidade 4, Estratégias avançadas de implantação do Kubernetes.
Para obter detalhes sobre como adquirir e implantar o NGINX Open Source, acesse nginx.org.
Para experimentar o NGINX Ingress Controller baseado no NGINX Plus com NGINX App Protect, inicie sua avaliação gratuita de 30 dias hoje mesmo ou entre em contato conosco para conversar sobre seus casos de uso.
Para experimentar o NGINX Ingress Controller baseado no NGINX Open Source, consulte NGINX Ingress Controller Releases em nosso repositório do GitHub ou baixe um contêiner pré-compilado do DockerHub.
Sobre o autor
Publicações relacionadas no blog

Como o F5 NGINX One aprimora o gerenciamento e a observabilidade
Descubra como o F5 NGINX One aprimora o gerenciamento e a observabilidade de ambientes híbridos e multicloud ao oferecer um console web centralizado baseado em SaaS, com insights em tempo real, conformidade automatizada e gerenciamento simplificado de configurações.

Do OpenAPI ao NGINX como gateway de API usando uma API declarativa
Descubra como transformar uma definição de esquema OpenAPI em uma configuração NGINX totalmente funcional, operando como um API Gateway com a segurança de um Web Application Firewall e um Portal do Desenvolvedor, usando uma abordagem declarativa de API.

Como gerenciar picos repentinos de tráfego e sobrecarga do servidor
Picos de tráfego e sobrecarga de servidores são preocupações sérias para varejistas online em qualquer época, mas especialmente durante a “Cyber Season” de compras de fim de ano. Detalhamos como você pode aproveitar as soluções de entrega de aplicações de nível de produção da NGINX para se preparar para aumentos repentinos de tráfego.

Distribuição segura de chaves privadas SSL com o NGINX
Descrevemos três maneiras progressivamente mais seguras de proteger chaves privadas SSL ao configurar o NGINX para processar tráfego HTTPS: permitir acesso de leitura apenas ao usuário root, criptografar as chaves com senhas armazenadas separadamente e distribuir as senhas a partir de um repositório central.
Agrupando solicitações de API em lotes com NGINX Plus e o módulo JavaScript do NGINX
O módulo JavaScript do NGINX lançado com o NGINX Plus R15 oferece suporte a subsolicitações, possibilitando uma classe totalmente nova de casos de uso para o NGINX Plus. Este exemplo completo também usa a API do NGINX Plus e os módulos de chave-valor para converter solicitações de API dos clientes em solicitações separadas para as APIs dos microsserviços que compõem um aplicativo.
Como superar o esgotamento de portas efêmeras no NGINX e no NGINX Plus
Confira dicas para evitar o esgotamento de portas efêmeras com ajustes no kernel do Linux, conexões keepalive e conexões vinculadas dinamicamente a endereços IP locais.
