Tag: segurança

  • Gestão centralizada de identidade e autenticação com Keycloak

    Keycloak é uma solução de IAM – Identity and Access Management – de código aberto desenvolvida pela Red Hat, que a utilizou como base para seu produto Red Hat Single Sign-On. Ele atua como um provedor de identidade e permite centralizar a autenticação e autorização para aplicativos e sistemas em ambientes corporativos.

    A centralização da autenticação e da identidade de acesso traz vantagens no aspecto de segurança. Autenticação em múltiplos fatores e requisitos de senha podem ser impostos e, para a suspensão de um acesso, basta fazê-lo em um só lugar, sendo mais rápido e sem a chance de um outro acesso ser esquecido e mantido ativo como pode acontecer quando a identidade é distribuída em diferentes sistemas.

    Keycloak é distribuído de algumas formas, entre elas os famosos pacotes de arquivo ZIP e TAR, bem como contêiner Docker, o que facilita a implantação e manutenção em ambientes de produção. Mas como esse projeto teve o objetivo de entender como funciona a aplicação, o arquivo foi escolhido.

    Para a execução do Keycloak, será criado um usuário de serviço dedicado.

    # useradd -m -d /opt/keycloak -s /usr/sbin/nologin keycloak

    Keycloak é uma aplicação em Java e atualmente a documentação recomenda o OpenJDK 25. Além disso, ele utiliza o PostgreSQL. As configurações suportadas podem ser verificadas no link:

    Considerando um sistema RHEL 10.2:

    # dnf install java-25-openjdk postgresql-server

    Configure o PostgreSQL substituindo o tipo de autenticação ident em conexões do tipo host por scram-sha-256, que utiliza senha. O ident utiliza o usuário no sistema operacional para autenticar no servidor de banco de dados, de forma similar ao peer mas para conexões TCP em vez de socket unix. Para isso, seria necessário utilizar um serviço de ident.

    # postgresql-setup --initdb
    # sed -i 's/ident/scram-sha-256/g' /var/lib/pgsql/data/pg_hba.conf
    # systemctl enable --now postgresql.service

    Criando o banco de dados no PostgreSQL:

    # sudo -u postgres psql
    postgres=# CREATE ROLE keycloak_op WITH LOGIN PASSWORD 'key&peele';
    postgres=# CREATE DATABASE keycloak_db OWNER keycloak_op TEMPLATE template0 ENCODING 'UTF8';

    Então, podemos executar o shell como o usuário de serviço criado para baixar e configurar o Keycloak:

    # sudo -u keycloak bash
    
    $ wget -P /tmp https://github.com/keycloak/keycloak/releases/download/26.7.4/keycloak-26.7.4.tar.gz
    
    $ tar -xzvf /tmp/keycloak-26.7.4.tar.gz -C /opt/keycloak --strip-components=1
    
    $ chmod +x /opt/keycloak/bin/*

    O Keycloak tem um arquivo de configuração em /opt/keycloak/conf/keycloak.conf, onde é necessário determinar os seguintes parâmetros:

    db=postgres
    db-username=keycloak_op
    db-password=key&peele
    db-url=jdbc:postgresql://localhost/keycloak_db
    hostname=https://keycloak.exemplo.com
    
    # Configuração para uso com o proxy reverso HTTP, desabilitando SSL
    http-enabled=true
    proxy-headers=xforwarded
    proxy-trusted-addresses=192.168.1.10

    O template da configuração também oferece os parâmetros de certificado e chave SSL. Por padrão, o Keycloak escuta nas portas 8080 para HTTP e 8443 para HTTPS. No entanto, com o https:// especificado no parametro hostname ele já entende que no proxy reverso o acesso será feito pela porta padrão 443.

    O Keycloak é compilado utilizando um framework chamado Quarkus, que faz uma espécie de intermediação entre o Java e o Keycloak. Quando uma aplicação inicia, o Quarkus tem uma sequência de etapas de descoberta dos componentes da aplicação. Uma recomendação é otimizar esse processo do Quarkus com uma espécie de “snapshot” que ele faz dos componentes da aplicação a serem iniciados, o que permite o Keycloak iniciar mais rápido.

    $ /opt/keycloak/bin/kc.sh build

    Então a aplicação pode ser testada com essa otimização sendo executada como:

    $ /opt/keycloak/bin/kc.sh start --optimized

    O comando de otimização deve ser executado novamente após atualizações do Keycloak. Falta agora criar o administrador inicial da aplicação no banco de dados.

    $ /opt/keycloak/bin/kc.sh bootstrap-admin user

    De volta ao shell como administrador ou root, é necessário agora liberar a porta para HTTP no firewall.

    # firewall-cmd --permanent --add-port=8080/tcp
    # firewall-cmd --reload

    Criando um arquivo para a unidade de serviço do systemd em /etc/systemd/system/keycloak.service:

    [Unit]
    Description=Keycloak IAM
    After=network.target postgresql.service
    Requires=postgresql.service
    
    [Service]
    Type=simple
    User=keycloak
    Group=keycloak
    WorkingDirectory=/opt/keycloak
    
    ExecStart=/opt/keycloak/bin/kc.sh start --optimized
    
    Restart=on-failure
    RestartSec=5
    
    [Install]
    WantedBy=multi-user.target

    Então basta habilitar e iniciar o serviço com:

    # systemctl enable --now keycloak.service

    Após a instalação e a configuração do proxy reverso, o Keycloak pode ser acessado através da página web. O administrador criado através do comando de bootstrap é temporário, devendo se criar outro usuário administrador e excluir o usuário temporário. Para criar outro usuário administrador, deve-se estar no realm master (único criado por padrão na instalação).

    Após criado, vá na aba de credenciais e adicione uma senha.

    Então vá na aba “Role mapping” e adicione o papel de admin.

    Após logar no novo usuário, exclua o usuário temporário.

    Agora sobre a organização interna do Keycloak. Ele é dividido em realms, que são como domínios isolados. Cada realm tem seus usuários, grupos e configurações. O master, único realm existente por padrão após a instalação, é para gerenciamento do próprio Keycloak e não deve ser utilizado para criar usuários de aplicações. Em vez disso, outro realm deve ser criado.

    Dentro desse novo realm pode-se criar um cliente, que é uma aplicação que irá utilizar o Keycloak como provedor de identidade. Vou utilizar o Nextcloud como exemplo por ser uma aplicação que tem suporte oficial a esse tipo de configuração. Podemos configurar o tipo de autenticação como OpenID Connect ou SAML. Seguindo com o exemplo utilizando OIDC:

    A chave de autorização pode ser deixada desabilitada, porque a autorização é definida pelo servidor Nextcloud: ele determina qual usuário é administrador, qual usuário tem acesso a quais compartilhamentos de arquivos, etc.

    A caixa “Standard Flow” determina o fluxo de autenticação padrão para navegadores web. Para fazer o login, o usuário é redirecionado para fazer login no Keycloak.

    Alternativamente, existe a opção “Direct Access Grants”, em que o login é feito na página do Nextcloud, que envia as credenciais para o Keycloak.

    “Standard Flow” é recomendado porque assim a senha do usuário fica restrita ao Keycloak, enquanto em “Direct Access Grants” a senha passa pela aplicação, o que em muitos casos é indesejado – como por exemplo quando o provedor do serviço e o cliente são organizações diferentes. Além disso, a ideia é sempre minimizar a exposição da senha. Assim, caso a aplicação seja comprometida não significa que a identidade do usuário também vai ser.

    No Standard Flow, o fluxo de autenticação acontece da seguinte forma:

    1. Usuário acessa a página de login do Nextcloud.
    2. Nextcloud o redireciona à página de login do Keycloak.
    3. Usuário faz login no Keycloak.
    4. Keycloak gera um código de autorização e redireciona o usuário de volta ao Nextcloud enviando junto esse código de autorização, indicando que o usuário foi autenticado no Keycloak.
    5. Nextcloud faz uma chamada ao Keycloak, enviando as próprias credenciais de autenticação de cliente (quando habilitado) e devolvendo o código de autorização recebido e solicitando um token.
    6. Keycloak valida a autenticação do Nextcloud e o código de autorização e retorna o token para o Nextcloud.
    7. Nextcloud associa o token a uma sessão e estabelece a sessão do usuário.

    Os passos 5 e 6 podem parecer redundantes após o 3 e 4 mas não são. Essa etapa a mais existe porque o código de autorização passa através de um redirect do Keycloak para o Nextcloud, passando pelo navegador web do usuário. O Nextcloud então no passo 5 faz uma chamada diretamente para o Keycloak, que valida o código de autorização e retorna ao Nextcloud um bearer token que o Nextcloud utiliza como credencial do usuário para estabelecer a sessão.

    O “Implicit Flow” corta essa etapa e devolve ao navegador o token de acesso diretamente, o que agiliza o login mas agora o navegador possui o token.

    As URLs de redirect são geradas pelo plugin de OIDC do Nextcloud após o registro do provedor de identidade, podem ser substituídas depois caso ainda não as tenha.

    Após a criação do cliente, crie o segredo para autenticação da aplicação no Keycloak.

    Agora, deve ser configurado OpenID Connect no Nextcloud. A começar pela instalação do plugin:

    Nas configurações de administração, basta registrar um novo provedor:

    O endpoint de descoberta é /realms/<nome do seu dominio>/.well–known/openid-configuration.

    Após essa configuração, o Nextcloud exibe a opção de login com o provedor configurado.

    Ao clicar nesse botão, o usuário é levado à página de login do Keycloak.

    Ao fazer o login, o Nextcloud cria o usuário e inicia a sessão.

    Uma funcionalidade interessante do Keycloak é que ele pode também passar à aplicação os grupos aos quais o usuário pertence e, caso a aplicação tenha suporte, ela pode automaticamente provisionar os grupos e atribuir o usuário a eles.

    Isso pode ser vantajoso quando recursos ou permissões na aplicação são concedidos a grupos em vez de contas individuais, de forma que o usuário pode ser criado já com os acessos desejados. No Nextcloud, por exemplo, um compartilhamento de arquivos pode ser feito para um grupo.

    Para configurar isso no Keycloak, deve-se criar um mapeamento de grupos. Configurando apenas para um cliente específico, isso é feito nos detalhes do cliente, na aba “Client scopes”, no escopo dedicado do cliente, nextcloud-oidc-dedicated.

    Basta adicionar um Mapper do tipo “Group Membership” com o nome que a aplicação espera no token. No caso do Nextcloud, o mapeamento foi configurado para groups. Desative a opção “Full group path” para evitar que o nome do grupo seja passado à aplicação como /nome-do-grupo.

    No Nextcloud, o provedor de identidade precisa ter o provisionamento de grupos habilitado e o regex de whitelist especificado para os grupos desejados. Nesse caso, foi configurado para aceitar todos os grupos.

    Feita essa configuração, podemos atribuir um grupo ao usuário existente no Keycloak, e verificar que, após novo login, o grupo também aparece automaticamente no Nextcloud.

  • Protegendo serviços web com ModSecurity

    Web Application Firewall é um tipo de aplicação que inspeciona as requisições HTTP em busca de ameaças comuns, como injeções de SQL, cross-site scripting (XSS), tentativas de inclusão de arquivos remotos e injeção de comandos, antes de encaminhá-las ao servidor web.

    São tipos de ameaças que costumam ser tratadas no back-end dos sites, mas nem sempre acontece. Eventualmente desenvolvedores esquecem de sanitizar um input em alguma parte do código e vulnerabilidades surgem. Um exemplo disso é a vulnerabilidade por injeção de SQL no Zabbix que foi encontrada no final de 2024.

    Tendo isso em vista, é interessante ter uma camada extra de segurança que verifique cada requisição HTTP. É aí que entra o Web Application Firewall (WAF): ele atua como um proxy reverso + firewall. Recebe a requisição HTTP, inspeciona e, caso ela infrinja alguma regra, impede a conexão. Caso contrário, encaminha a requisição para o servidor web.

    Existem diferentes alternativas de WAF, incluindo serviços em nuvem como o Azure Web Application Firewall. Mas minha infraestrutura já utilizava o nginx como proxy reverso para os meus servidores web, então busquei uma alternativa que fosse gratuita e que usasse poucos recursos de hardware, de forma que coubesse no meu VPS de 1 vCPU e 512 MB de RAM.

    A que melhor se encaixou foi o ModSecurity, um módulo de código aberto originalmente escrito para trabalhar com o Apache, mas que hoje também funciona com o nginx. Para instalar no meu servidor, que roda Debian 12:

    # apt install libnginx-mod-http-modsecurity modsecurity-crs

    O pacote modsecurity-crs é o Core Rule Set da OWASP, um conjunto de regras base utilizado pelo ModSecurity para identificar e bloquear potenciais ameaças.

    Verifique que o módulo está habilitado. Na pasta /etc/nginx/modules-enabled deve haver um softlink como

    50-mod-http-modsecurity.conf -> /usr/share/nginx/modules-available/mod-http-modsecurity.conf

    Em seguida, edite o arquivo de configuração /etc/nginx/modsecurity.conf e configure o seguinte parâmetro:

    SecRuleEngine On

    Edite também o arquivo /etc/nginx/modsecurity_includes.conf, copiando para ele as linhas em /usr/share/modsecurity-crs/owasp-crs.load que começam com “Include”. Essas são o conjunto base de regras da OWASP.

    Seu arquivo deve ficar algo como:

    include modsecurity.conf
    #include /usr/share/modsecurity-crs/owasp-crs.load
    Include /etc/modsecurity/crs/crs-setup.conf
    Include /usr/share/modsecurity-crs/rules/*.conf

    Feito isso, você pode editar os arquivos de configuração dos sites existente para adicionar no topo no bloco server as seguintes linhas:

    modsecurity on;
    modsecurity_rules_file /etc/nginx/modsecurity_includes.conf;

    Teste as configurações e, se tudo estiver ok, reinicie o servidor web:

    # nginx -t
    # systemctl restart nginx

    É isso, o WAF está funcionando.

    Você pode testá-lo fazendo requisições http que ele deve bloquear e verificando tanto o resultado (ele rejeita com resposta 403) quanto os logs:

    # tail -f /var/log/nginx/modsec_audit.log

    Exemplos de requisições para fazer:

    $ curl "https://pid1.com.br/?id=1+UNION+SELECT+1,2,3"
    $ curl "https://pid1.com.br/?q=<script>alert('xss')</script>"
    $ curl "https://pid1.com.br/?page=http://evil.com/shell.txt"
    $ curl "https://pid1.com.br/?cmd=cat+/etc/passwd"

    Você também pode colocar um input como “UNION SELECT” em algum campo do seu site e verificar que ele responde com 403.

    Agora, problemas como falsos positivos podem acontecer. Eu fiquei impossibilitado de publicar ou salvar publicações como rascunho aqui no WordPress, por exemplo, recebendo o erro “Falha ao atualizar. A resposta não é um JSON válido”.

    Para resolver isso, foi necessário coletar as informações do log para criar uma regra que permita esse caso e ignore a regra que causa o bloqueio.

    O log foi o seguinte:

    ModSecurity: Warning. Matched "Operator Pm' with parameter document.cookie document.write .parentnode .innerhtml window.location -moz-binding <!-- --> <![cdata[' against variable ARGS:json.content' (Value: <!-- wp:paragraph -->\x0a<p>teste4</p>\x0a<!-- /wp:paragraph -->\x0a\x0a<!-- wp:paragraph -->\x0a<p> (30 characters omitted)' ) [file "/usr/share/modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf"] [line "232"] [id "941180"] [rev ""] [msg "Node-Validator Blacklist Keywords"] [data "Matched Data: <!-- found within ARGS:json.content: <!-- wp:paragraph -->\x0a<p>teste4</p>\x0a<!-- /wp:paragraph -->\x0a\x0a<!-- wp:paragraph -->\x0a<p></p>\x0a<!-- /wp:paragraph -->"] [severity "2"] [ver "OWASP_CRS/3.3.4"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-xss"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/242"] [hostname "191.252.110.50"] [uri "/wp-json/wp/v2/posts/385"] [unique_id "176202912980.702618"] [ref "o0,4v13,112t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:lowercase,t:removeNulls"] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator Ge' with parameter 5' against variable TX:ANOMALY_SCORE' (Value: 5' ) [file "/usr/share/modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "81"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "2"] [ver "OWASP_CRS/3.3.4"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "191.252.110.50"] [uri "/wp-json/wp/v2/posts/385"] [unique_id "176202912980.702618"] [ref ""]

    Nele, pode-se identificar:

    • A URI: /wp-json/wp/v2/posts/385
    • O ID da regra que estava causando o bloqueio: 941180

    A partir disso, pode-se criar um arquivo de configuração /etc/nginx/modsecurity_custom_exceptions.conf, com a seguinte regra:

    SecRule REQUEST_URI "@beginsWith /wp-json/wp/v2/posts"\
    "id:10001,phase:1,nolog,pass,ctl:ruleRemoveById=941180"

    Esse arquivo deve ser incluído no /etc/nginx/modsecurity_includes.conf – adicione a linha:

    Include modsecurity_custom_exceptions.conf

    A regra é composta da seguinte forma:

    SecRule VARIÁVEIS OPERADOR [AÇÕES]

    Nesse caso,

    • Variável: REQUEST_URI – é a váriável que ele vai verificar para aplicar a ação.
    • Operador: “@beginsWith /wp-json/wp/v2/posts” – o que ele vai verificar na variável.
    • Ações: “id:10001,phase:1,nolog,pass,ctl:ruleRemoveById=941180”

    Entre as ações:

    • id:10001 – ID da regra que você está criando. Deve ser único.
    • phase:1 – indica que a ação deve ocorrer na fase dos cabeçalhos de requisição – quando o ModSecurity recebe o cabeçalho do nginx, antes de receber o corpo da requisição HTTP e aplicar as regras, que seria a phase:2
    • nolog – sem necessidade de registrar esse evento
    • pass – permite a requisição HTTP
    • ctl:ruleRemoveById=941180 – desabilita a regra que causava o bloqueio. Múltiplas ações desse tipo podem ser especificadas na mesma regra.

    Há também no log uma regra de ID 949110, mas ao ver a mensagem associada é possível verificar que ela não é a regra que identifica a ameaça em potencial, apenas a regra que determina um limite de ameaças detectadas. Não desabilite essa regra, isso abriria espaço para outras requisições HTTP que não devem ser recebidas pelo servidor. É preciso analisar o log com atenção e verificar a que mensagens cada regra está associada.

    Esse tipo de ajuste também pode ser feito para outras regras em outras URIs de outros sites para os quais o nginx atua como proxy reverso, permitindo o uso do WAF para diferentes aplicações e sistemas.

    Uma observação importante: a ordem em que as configurações são incluídas no arquivo /etc/nginx/modsecurity_includes.conf é importante, já que as regras são processadas de forma sequencial. Garanta que as exceções são incluídas para serem processadas antes das regras da OWASP.

    Include modsecurity.conf
    Include /etc/modsecurity/crs/crs-setup.conf
    Include modsecurity_custom_exceptions.conf
    Include /usr/share/modsecurity-crs/rules/*.conf

    Para casos em que uploads de arquivos são feitos, como no Nextcloud, considere também ajustar no arquivo /etc/nginx/modsecurity.conf o parâmetro de tamanho máximo do corpo das requisições HTTP, especificado em bytes.

    #SecRequestBodyLimit 13107200
    SecRequestBodyLimit 31457280

    O limite padrão é 12.5 MB, tendo sido ajustado para 30 MB. Idealmente ajustaria para um valor mais alto mas o consumo de memória pelo nginx ultrapassa o limite tolerado pelo systemd-oomd no meu servidor com 512 MB de RAM, levando o systemd-oomd a matar o processo.

    Em casos de múltiplos sites, as configurações podem ser separadas da seguinte forma:

    • /etc/nginx/modsecurity.conf.d/wordpress.conf
    • /etc/nginx/modsecurity.conf.d/nextcloud.conf
    • /etc/nginx/modsecurity.conf.d/proxmox.conf

    Em cada arquivo, se inclui as configurações, as exclusões e as regras. Dessa forma cada site tem suas regras, evitando que todas as exclusões sejam feitas pra todos os sites.

    #Configuração base
    Include ../modsecurity.conf
    Include /etc/modsecurity/crs/crs-setup.conf
    
    # Exclusões
    SecRule REQUEST_URI "@beginsWith /wp-json/wp/v2/posts"\
    "id:10001,phase:1,nolog,pass,ctl:ruleRemoveById=941180"
    
    # Aplicação das regras
    Include /usr/share/modsecurity-crs/rules/*.conf