Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions docs/security/vulnerability-assessment-2026-09.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# Avaliacao tecnica do relatorio de vulnerabilidades - setembro de 2026

## Objetivo e limites

Este documento confronta os 11 achados do relatorio `Res_Seg_Scielo_09_2026.pdf`
com o codigo presente na branch `codex/security-remediation-sep-2026`.

A avaliacao e estatica e nao destrutiva. Nenhuma prova de conceito foi executada
contra ambientes externos. As evidencias de execucao apresentadas no relatorio
foram consideradas evidencias do auditor, e nao foram repetidas.

Foram revisados 17 arquivos de implementacao, configuracao e artefatos diretamente
relacionados aos achados. Tambem foi feita uma varredura de padroes de segredo nos
arquivos rastreados e nos artefatos locais de texto.

Os valores CVSS sao preliminares e usam apenas metricas Base do CVSS 4.0. Devem ser
recalculados com as metricas ambientais depois de confirmar arquitetura, dados
alcancaveis, controles de borda e importancia dos ativos. Referencia:
https://www.first.org/cvss/v4.0/specification-document

## Sumario executivo

- 9 achados foram confirmados diretamente no codigo ou na configuracao de publicacao.
- 2 achados foram confirmados apenas de forma condicional: 008 e 011.
- 1 caminho adicional de SSRF foi identificado nos endpoints de servicos por meio do
parametro `caller`.
- Nao foram encontrados segredos reais hardcoded pelos padroes de alta confianca.
O template de SMTP possui campos de usuario e senha vazios.
- Veredicto: **BLOQUEADO para publicacao sem correcoes**.

As correcoes obrigatorias antes de uma nova publicacao sao: remover os modos de
diagnostico publicos, eliminar destinos HTTP controlados pelo cliente, parar de
exportar dados de request para `$GLOBALS`, corrigir as reflexoes HTML e restringir
arquivos e executaveis legados no Apache.

## Estado da remediacao na branch

Em 21 de setembro de 2026, os itens P0 abaixo foram implementados e validados
localmente. Eles ainda dependem de reteste no ambiente de homologacao antes de serem
considerados encerrados:

- 001 e 005: `debug` e `diag` foram bloqueados para requisicoes externas. O modo
`debug=xml`, necessario ao fluxo legado de estatisticas, permanece disponivel apenas
em loopback e responde como `text/plain`. Os demais modos exigem habilitacao
explicita e loopback.
- 002: `old2new.inc` deixou de copiar chaves de GET, POST, COOKIE, SERVER, ENV e
SESSION para `$GLOBALS`. Permaneceram somente aliases conhecidos dos arrays legados.
- 003: `export.php` passou a usar `SERVER_SCIELO` validado, formatos e PID em allowlist,
query codificada, timeout e bloqueio de redirecionamento.
- A-01: `articleRequestGraphicPage.php` e `sendMail.php` ignoram o host recebido em
`caller` e usam somente `SERVER_SCIELO` validado.
- Variantes: `rss.php`, `clinicaltrials.php` e `projfapesp.php` tambem deixaram de usar
`HTTP_HOST` como destino de requisicoes do servidor.
- Eventos negados e falhas de backend sao registrados em JSON sem payloads, e-mails,
cookies, tokens ou valores de configuracao.

Validacoes executadas:

- lint PHP 8.5 em 15 arquivos: aprovado;
- `tests/security_regression.php`: aprovado;
- smoke HTTP em container local: home, exportacao, RSS, ensaios clinicos e projetos
FAPESP retornaram HTTP 200 mesmo com `Host` adulterado;
- `debug` e `diag` externos nao exibiram marcadores de diagnostico;
- `debug=xml` via loopback retornou HTTP 200 e `text/plain`;
- `git diff --check`: aprovado.

Os endpoints `articleRequestGraphicPage.php` e `sendMail.php` ainda retornam HTTP 500
em PHP 8.5 tanto no caso de controle quanto no caso com `caller` adulterado. O log
atribui a falha preexistente a uma chamada estatica de metodo nao estatico na biblioteca
DOMIT. A resposta confirma que o backend usado foi o host configurado, mas a correcao
de compatibilidade DOMIT deve preceder o reteste funcional completo desses dois fluxos.

## Matriz de validacao e risco

| ID | Status no codigo | Evidencia principal | CWE | CVSS 4.0 Base preliminar | Prioridade interna |
| --- | --- | --- | --- | --- | --- |
| 001 | Mitigado na branch; aguarda homologacao | O modo debug publico foi bloqueado; XML de compatibilidade ficou restrito a loopback e todos os diagnosticos usam `text/plain` | CWE-79, CWE-489 | 6.3 Media - `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N` | P0 |
| 002 | Mitigado na branch; aguarda homologacao | `old2new.inc` nao exporta mais chaves arbitrarias para `$GLOBALS`; `classRequestVars.php` usa superglobais explicitas | CWE-621, CWE-915 | 8.8 Alta - `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:H/VA:L/SC:N/SI:N/SA:N` | P0 |
| 003 | Mitigado na branch; aguarda homologacao | `export.php` usa exclusivamente `SERVER_SCIELO` validado; variantes em RSS e paginas SciELO Org receberam a mesma protecao | CWE-918 | 8.8 Alta - `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:L/SC:N/SI:N/SA:N` | P0 |
| 004 | Confirmado | `statjournal.php:33-39` nao altera `$_GET`; `statjournal.php:186-220` insere `collection` em atributos `href` sem escape | CWE-79 | 5.1 Media - `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N` | P1 |
| 005 | Mitigado na branch; aguarda homologacao | `diag` exige habilitacao explicita e loopback; tentativas externas seguem o fluxo normal e geram auditoria sem dados sensiveis | CWE-200, CWE-489 | 6.9 Media - `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N` | P0 |
| 006 | Confirmado | `httpd-scielo.conf:27-30` concede acesso publico e execucao a todo o diretorio `cgi-bin`; nao existe regra especifica para WXIS | CWE-200, CWE-284 | 6.9 Media - `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N` | P1 |
| 007 | Confirmado | `httpd-scielo.conf:11-19` publica todo o DocumentRoot e bloqueia somente `phpinfo.php`/`info.php`; os arquivos citados ficam dentro de `htdocs` | CWE-552 | 6.3 Media - `CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N` | P1 |
| 008 | Condicional | `mimetex.cgi` existe e `httpd-scielo.conf:27-30` permite CGI; o arquivo e um executavel PE32 para Windows e tende a falhar no Rocky Linux | CWE-1104 | N/A ate identificar versao/CVE e ambiente em que o binario pode executar | P1 - remover |
| 009 | Parcialmente confirmado | `scielolog.php`, `scielologofi.php` e `sci_statart.php` existem e dependem de globais legadas; o HTTP 500 nao foi reproduzido localmente | CWE-1050 | N/A - superficie legada sem impacto exploravel demonstrado | P2 |
| 010 | Confirmado | `articleRequestGraphicPage.php:17-33,44,109-123` reflete `pid`, `caller`, `lang` e anos em HTML/JavaScript sem codificacao contextual | CWE-79 | 5.1 Media - `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N` | P1 |
| 011 | Condicional | `sendMail.php:24-36,41-47,80-104` aceita destinatario, Reply-To, nomes e comentario sem autenticacao, rate limit ou validacao no servidor | CWE-400, CWE-799, CWE-20 | 6.3 Media - `CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:L/SC:L/SI:L/SA:L` | P1 se SMTP ativo; P2 se desativado |
| A-01 | Mitigado na branch; reteste funcional bloqueado por DOMIT | Os endpoints ignoram `caller` como host e usam `SERVER_SCIELO`; testes confirmaram o destino configurado antes da falha preexistente da biblioteca DOMIT | CWE-918 | 8.8 Alta - `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:L/SC:N/SI:N/SA:N` | P0 |

### Observacoes sobre as severidades

O XSS 001 foi classificado como alto no relatorio. No CVSS Base 4.0 preliminar ele
resulta em 6.3 porque exige interacao ativa da vitima e o impacto ocorre
principalmente no navegador. A prioridade interna permanece P0 porque o endpoint e
central, usa o dominio institucional e a funcionalidade de debug tambem divulga
informacoes internas.

O achado 002 e uma fraqueza sistemica confirmada. O impacto exato depende dos sinks
alcancados por cada global sobrescrita, mas a possibilidade de alterar variaveis de
controle sem autenticacao justifica a prioridade P0.

O SSRF 003 tem evidencia dinamica no relatorio e correspondencia direta no codigo. O
impacto deve ser refinado testando somente em homologacao se o processo alcanca rede
interna, metadata de nuvem ou portas administrativas.

## Verificacao dos oito dominios NSI.04

| Dominio | Resultado |
| --- | --- |
| Injecao | SSRF confirmado em `export.php` e no fluxo `caller -> ArticleService`; concatenacao manual de parametros tambem aparece em varios servicos legados |
| Autenticacao e sessao | Os endpoints avaliados sao publicos; `sendMail.php` nao possui controle antiabuso no servidor |
| XSS | Confirmado nos IDs 001, 004 e 010; a CSP atual permite `unsafe-inline` e `unsafe-eval`, reduzindo sua utilidade como defesa adicional |
| Controle de acesso | Debug, diagnostico, WXIS, CGI e arquivos auxiliares estao acessiveis sem restricao especifica |
| Validacao de entrada | Predomina uso direto de `$_REQUEST`; validacao client-side em `sendMail.php` nao protege o servidor |
| Criptografia e dados | Nenhum problema criptografico novo foi necessario para confirmar os 11 achados; SMTP usa configuracao externa e o template nao contem senha |
| Configuracao de seguranca | `Require all granted` abrange `htdocs` e `cgi-bin`; a CSP permite origens HTTP/HTTPS e execucao inline/eval |
| Logging | Nao ha trilha de auditoria ou telemetria antiabuso suficiente nos fluxos avaliados; adicionar logs sem registrar payloads, e-mails ou tokens |

## Plano de tratamento

### P0 - antes da proxima publicacao

1. Remover o processamento publico de `debug` e `diag`. Se diagnostico operacional
continuar necessario, usar feature flag desativada por padrao, autenticacao forte,
allowlist de rede e resposta sem paths, XML ou URLs internas.
2. Em `export.php`, usar host interno vindo de configuracao confiavel. Validar o PID e
o formato por allowlist, montar query com `http_build_query` e aplicar timeout.
3. Remover a copia para `$GLOBALS` em `old2new.inc`. Manter temporariamente apenas os
aliases de arrays legados e migrar consumidores para superglobais explicitas.
4. Remover o parametro `caller` como origem do host. Resolver o dominio por
configuracao/tenant conhecido e usar allowlist exata, com bloqueio de IPs privados,
loopback, link-local e redirecionamentos.

### P1 - mesma janela de remediacao

1. Aplicar `htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')` em
contextos HTML e `json_encode` para valores inseridos em JavaScript.
2. Restringir `cgi-bin` no Apache aos executaveis realmente necessarios e impedir
acesso externo direto ao WXIS quando a aplicacao puder chama-lo localmente.
3. Bloquear no servidor web extensoes e nomes auxiliares: `.inc`, `.def`, `.template`,
arquivos de versao e documentacao operacional.
4. Remover `mimetex.cgi` do artefato Linux se nao houver dependencia funcional.
5. Em `sendMail.php`, validar todos os campos no servidor, exigir POST e protecao CSRF,
fixar o dominio do artigo, limitar tamanho, aplicar rate limit e CAPTCHA quando
apropriado. Nao retornar detalhes internos do PHPMailer.

### P2 - reducao de superficie e manutencao

1. Remover ou reimplementar `scielolog.php`, `scielologofi.php` e `sci_statart.php`.
2. Inventariar bibliotecas vendorizadas antigas e substituir componentes sem suporte.
3. Tornar a CSP restritiva, removendo `unsafe-eval`, `unsafe-inline`, `http:` e o
curinga implicito de hosts HTTPS apos inventariar dependencias legitimas.

## Plano seguro de reteste em homologacao

### Regras gerais

- Executar somente em ambiente autorizado, com dados sinteticos e destino de rede
controlado pela equipe.
- Registrar ID do caso, commit, data, requisicao, codigo HTTP, trecho minimo da
resposta e logs correlacionados.
- Nao usar cookies reais, e-mails de terceiros, metadata cloud real nem sistemas
internos de producao.
- Cada teste deve ter um caso positivo funcional e um caso negativo de seguranca.

### Casos por achado

| ID | Procedimento de reteste | Criterio de aprovacao |
| --- | --- | --- |
| 001 | Solicitar a pagina com `debug` e um marcador HTML inerte em parametro comum | `debug` e ignorado ou retorna 403/404; marcador nunca e interpretado; nenhuma informacao interna aparece |
| 002 | Enviar chaves com nomes de variaveis internas e executar testes unitarios do adaptador de request | Nenhuma chave externa e criada em `$GLOBALS`; `$_SERVER` e variaveis internas permanecem inalteradas |
| 003 | Enviar `Host` apontando para um mock HTTP controlado, com e sem porta alternativa | Nenhuma conexao chega ao mock; o backend usa somente o destino configurado; Host invalido retorna 400 |
| 004 | Inserir marcador contendo aspas e sinais `<`/`>` em `collection` | O HTML possui entidades, o DOM nao cria novos atributos/elementos e os links continuam validos |
| 005 | Solicitar `diag=1` e variantes de caixa/metodo | Resposta 403/404 ou fluxo normal, sem path, XML, URI interna ou URL WXIS |
| 006 | Acessar WXIS externamente e executar o fluxo normal da aplicacao | Acesso direto externo e negado; chamada interna autorizada continua funcional |
| 007 | Solicitar cada arquivo citado e extensoes equivalentes | Todos retornam 403/404; CSS, imagens e demais estaticos permitidos continuam acessiveis |
| 008 | Solicitar `mimetex.cgi` e verificar a imagem final/container | Endpoint retorna 404 e o arquivo nao existe no artefato implantado |
| 009 | Solicitar os tres endpoints legados | Endpoints removidos retornam 404; se mantidos, retornam resposta controlada sem stack trace e possuem testes proprios |
| 010 | Inserir marcador inerte em `pid`, `caller`, `lang`, `startYear` e `lastYear` | Valores sao validados e codificados conforme HTML/JS; nenhum novo atributo ou elemento e criado |
| 011 | Usar caixa postal e SMTP de teste, exceder o limite configurado e enviar campos invalidos | Destinos invalidos sao rejeitados; limite produz 429; somente mensagens autorizadas chegam ao sink de teste; nenhuma quebra de cabecalho/HTML ocorre |
| A-01 | Apontar `caller` para mock HTTP, loopback e IP privado sintetico | Nenhuma conexao externa ocorre; somente dominios mapeados na configuracao sao aceitos e redirecionamentos sao revalidados |

## Evidencias requeridas para encerramento

Um achado somente deve ser marcado como corrigido quando houver:

1. commit ou pull request identificando a alteracao;
2. teste automatizado que falhe no codigo vulneravel e passe no codigo corrigido;
3. evidencia de reteste em homologacao;
4. verificacao de regressao do fluxo funcional;
5. aprovacao do responsavel tecnico e do responsavel de seguranca.

## Resultado da deteccao de segredos

**APROVADO para esta etapa de analise**, com o seguinte limite: a varredura procurou
chaves privadas, tokens AWS/GitHub/Slack/Google, JWTs e URLs de conexao com senha.
Nenhum valor real de alta confianca foi encontrado. Campos de senha vazios, argumentos
de bibliotecas vendorizadas e exemplos sem credencial real foram tratados como falsos
positivos. Uma nova varredura deve ser executada antes de cada commit e pull request.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

require_once($DirNameLocalGraphPage."../../users/functions.php");
require_once($DirNameLocalGraphPage."../../users/langs.php");
require_once($DirNameLocalGraphPage."../../../../php/include.php");
require_once($DirNameLocalGraphPage."../../../../php/include.php");
require_once($DirNameLocalGraphPage."../../../../security.php");
require_once($DirNameLocalGraphPage."../../classes/services/ArticleServices.php");
require_once(dirname(__FILE__)."/../../classes/services/AccessServiceBar.php");
require_once(dirname(__FILE__)."/../../classes/Open_Flash_Chart/ofc-library/open_flash_chart_object.php");
Expand All @@ -14,8 +15,18 @@
$scielodef = parse_ini_file($DirNameLocalGraphPage."/../../scielo.def.php", true);
$scielomaindef = parse_ini_file($DirNameLocalGraphPage."/../../../../scielo.def.php", true);

$pid = $_REQUEST['pid'];
$caller = $_REQUEST["caller"];
$pid = isset($_REQUEST['pid']) ? $_REQUEST['pid'] : '';
$requestedCaller = isset($_REQUEST['caller']) ? $_REQUEST['caller'] : '';
$caller = scielo_internal_host_from_config($scielomaindef);
if ($requestedCaller !== '' && $requestedCaller !== $caller) {
scielo_audit_event(
'access.denied',
'unauthorized',
'service.article_metadata',
null,
array('reason' => 'caller_override_ignored')
);
}

$articleService = new ArticleService($caller);
$articleService->setParams($pid);
Expand Down
15 changes: 13 additions & 2 deletions htdocs/applications/scielo-org/pages/services/sendMail.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
require_once(dirname(__FILE__)."/../../users/langs.php");
require_once(dirname(__FILE__)."/../../includes/phpmailer/class.phpmailer.php");
require_once(dirname(__FILE__)."/../../classes/services/ArticleServices.php");
require_once(dirname(__FILE__)."/../../../../php/include.php");
require_once(dirname(__FILE__)."/../../../../php/include.php");
require_once(dirname(__FILE__)."/../../../../security.php");

$bvsSiteIni = parse_ini_file(dirname(__FILE__)."/../../../../bvs-site-conf.php",true);

Expand All @@ -25,7 +26,17 @@

$acao = $cgi["acao"];
$pid = $cgi["pid"];
$caller = $cgi["caller"];
$requestedCaller = isset($cgi['caller']) ? $cgi['caller'] : '';
$caller = scielo_internal_host_from_config($mainscielodef);
if ($requestedCaller !== '' && $requestedCaller !== $caller) {
scielo_audit_event(
'access.denied',
'unauthorized',
'service.article_metadata',
null,
array('reason' => 'caller_override_ignored')
);
}

//geting metadatas from PID
$articleService = new ArticleService($caller);
Expand Down
13 changes: 7 additions & 6 deletions htdocs/classRequestVars.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ function __construct()

function RequestVars ()
{
global $HTTP_GET_VARS, $HTTP_POST_VARS, $REQUEST_URI, $SCRIPT_NAME;
$requestUri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
$scriptName = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : '';

if (strpos($REQUEST_URI, "?") === false)
if (strpos($requestUri, "?") === false)
{
$QSCnav = $REQUEST_URI;
$QSCscript = $SCRIPT_NAME;
$QSCnav = $requestUri;
$QSCscript = $scriptName;
$QSCnav = preg_replace('/^' . preg_quote($QSCscript, '/') . '/', '', $QSCnav);
$QSCvars = explode("/", $QSCnav);
$QSCArray = array();
Expand All @@ -38,11 +39,11 @@ function RequestVars ()
}
}

$this->_request = array_merge($HTTP_GET_VARS, $HTTP_POST_VARS, $QSCArray);
$this->_request = array_merge($_GET, $_POST, $QSCArray);
}
else
{
$this->_request = array_merge($HTTP_GET_VARS, $HTTP_POST_VARS);
$this->_request = array_merge($_GET, $_POST);
}

if (!isset($this->_request['lng']) || strpos("|en|pt|es|", $this->_request['lng']) == 0) {
Expand Down
Loading
Loading