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
11 changes: 10 additions & 1 deletion docker/rocky9/httpd-scielo.conf
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Alias /pdf/ "/var/www/html/bases/pdf/"
<FilesMatch "^(?i:(phpinfo|info)\.php)$">
Require all denied
</FilesMatch>

<FilesMatch "^(?i:(readme(?:\..*)?|version(?:overview)?(?:\..*)?|.*\.(?:inc|def|template)(?:\.php)?))$">
Require all denied
</FilesMatch>
</Directory>

<Directory "/var/www/html/bases/pdf/">
Expand All @@ -27,7 +31,12 @@ Alias /pdf/ "/var/www/html/bases/pdf/"
<Directory "/var/www/html/cgi-bin">
Options -Indexes +ExecCGI +FollowSymLinks
AllowOverride None
Require all granted
AddDefaultCharset ISO-8859-1
Require all denied

<Files "wxis.exe">
Require all granted
</Files>
</Directory>

<IfModule mod_headers.c>
Expand Down
62 changes: 45 additions & 17 deletions docker/rocky9/scielo-instance-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -216,16 +216,17 @@ apply_index() {
backup_iy0 "$db"
}

reindex_with_fst() {
local db="$1"
local fst="$2"
local label="$3"
reindex_to_target_with_fst() {
local source_db="$1"
local target_db="$2"
local fst="$3"
local label="$4"
local safe_label
safe_label="$(sed 's#[^A-Za-z0-9_.-]#_#g' <<<"$label")"
local tmpbase="$TMPDIR/$safe_label"

if ! has_database "$db"; then
warn "$label skipped; database files missing: $db.mst/.xrf"
if ! has_database "$source_db"; then
warn "$label skipped; source database files missing: $source_db.mst/.xrf"
return
fi
if [[ ! -f "$fst" ]]; then
Expand All @@ -235,18 +236,30 @@ reindex_with_fst() {

if [[ "$MODE" == "fix" ]]; then
mkdir -p "$TMPDIR"
"$MX" "$db" "fst=@$fst" "fullinv=$tmpbase" -all now >/dev/null
apply_index "$tmpbase" "$db"
(
cd "$ROOT/proc"
"$MX" "$source_db" "fst=@$fst" \
"actab=tabs/acans.tab" "uctab=tabs/ucans.tab" \
"fullinv=$tmpbase" -all now >/dev/null
)
apply_index "$tmpbase" "$target_db"
ok "$label reindexed from $fst"
else
if [[ -f "$db.iy0" ]]; then
warn "$label has legacy .iy0 index: $db.iy0"
if [[ -f "$target_db.iy0" ]]; then
warn "$label has legacy .iy0 index: $target_db.iy0"
else
ok "$label has modern index files or no legacy .iy0"
fi
fi
}

reindex_with_fst() {
local db="$1"
local fst="$2"
local label="$3"
reindex_to_target_with_fst "$db" "$db" "$fst" "$label"
}

reindex_pid880() {
local db="$1"
local label="$2"
Expand Down Expand Up @@ -286,20 +299,35 @@ check_indexes() {
reindex_with_fst "$ROOT/bases/title/logo" "$ROOT/proc/fst/logo.fst" "logo"
reindex_with_fst "$ROOT/bases/newissue/newissue" "$ROOT/proc/fst/newissue.fst" "newissue"
reindex_with_fst "$ROOT/bases/artigo/artigo" "$ROOT/proc/fst/artigo.fst" "artigo"
reindex_to_target_with_fst "$ROOT/bases/artigo/artigo" "$ROOT/bases/artigo/author" \
"$ROOT/proc/fst/author.fst" "author"
reindex_with_fst "$ROOT/bases/issue/issue" "$ROOT/proc/fst/issue.fst" "issue"
reindex_with_fst "$ROOT/bases/issue/facic" "$ROOT/proc/fst/facic.fst" "facic"
reindex_with_fst "$ROOT/bases/issue/faccount" "$ROOT/proc/fst/faccount.fst" "faccount"
reindex_pid880 "$ROOT/bases/cited/cited" "cited"
reindex_pid880 "$ROOT/bases/related/related" "related"

local search_db
while IFS= read -r search_db; do
reindex_with_fst "${search_db%.mst}" "$ROOT/proc/fst/search.fst" "iah-search-${search_db%.mst}"
done < <(find "$ROOT/bases/iah" -path '*/search.mst' -type f 2>/dev/null | sort)
local iah_dir collection source_db search_db searchp_db
while IFS= read -r iah_dir; do
collection="$(basename "$iah_dir")"
if [[ "$collection" == "library" ]]; then
source_db="$ROOT/bases/artigo/artigo"
else
source_db="$ROOT/bases/$collection/$collection"
fi

while IFS= read -r search_db; do
reindex_with_fst "${search_db%.mst}" "$ROOT/proc/fst/searchp.fst" "iah-searchp-${search_db%.mst}"
done < <(find "$ROOT/bases/iah" -path '*/searchp.mst' -type f 2>/dev/null | sort)
search_db="$iah_dir/search"
if [[ -f "$search_db.iy0" || -f "$search_db.cnt" ]]; then
reindex_to_target_with_fst "$source_db" "$search_db" \
"$ROOT/proc/fst/search.fst" "iah-search-$collection"
fi

searchp_db="$iah_dir/searchp"
if [[ -f "$searchp_db.iy0" || -f "$searchp_db.cnt" ]]; then
reindex_to_target_with_fst "$source_db" "$searchp_db" \
"$ROOT/proc/fst/searchp.fst" "iah-searchp-$collection"
fi
done < <(find "$ROOT/bases/iah" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort)
}

check_remaining_iy0() {
Expand Down
10 changes: 8 additions & 2 deletions docs/rocky9-instance-migration-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ Run this only after confirming the mounted directory is the instance you want to
docker compose exec -T scielo-web scielo-instance-check.sh --root /var/www/html --fix
```

The repair mode rebuilds known ISIS indexes and moves active `.iy0` files to timestamped backups such as `title.iy0.bak.YYYYMMDDHHMMSS`.
The repair mode rebuilds known ISIS indexes and moves active `.iy0` files to
timestamped backups such as `title.iy0.bak.YYYYMMDDHHMMSS`. Derived iAH indexes
that do not have their own `.mst/.xrf` (`author`, `search` and `searchp`) are rebuilt
from the corresponding article database.

## Optional container startup mode

Expand Down Expand Up @@ -110,5 +113,8 @@ only when the instance file does not exist.
## Notes

- The image can be healthy while a mounted legacy instance is not. Treat each instance as data that needs a compatibility pass.
- The most common failure after moving to Rocky9/PHP 8 is `WXIS|fatal error|unavoidable|leafread/ock|`, usually caused by legacy `.iy0` files.
- The most common failure after moving to Rocky9/PHP 8 is
`WXIS|fatal error|unavoidable|leafread/ock|`, usually caused by legacy `.iy0`
files. Browsers may present this response as a download because WXIS emits
`application/octet-stream` for the fatal error.
- Rebuilding indexes does not rewrite `.mst` or `.xrf` data files.
81 changes: 65 additions & 16 deletions docs/security/vulnerability-assessment-2026-09.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
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.
A avaliacao combina revisao estatica com testes locais nao destrutivos. 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 fora do ambiente local isolado.

Foram revisados 17 arquivos de implementacao, configuracao e artefatos diretamente
relacionados aos achados. Tambem foi feita uma varredura de padroes de segredo nos
Expand Down Expand Up @@ -54,6 +55,21 @@ considerados encerrados:
- Eventos negados e falhas de backend sao registrados em JSON sem payloads, e-mails,
cookies, tokens ou valores de configuracao.

Em 22 de setembro de 2026, a primeira rodada P1 tambem foi implementada:

- 004: `statjournal.php` valida idioma, ISSN e colecao; parametros dos links de
analytics sao codificados e marcadores fora da allowlist retornam HTTP 400.
- 007: o Apache bloqueia arquivos `.inc`, `.def`, `.template`, arquivos de versao e
README no DocumentRoot.
- 010: os dois endpoints do grafico validam PID e anos, codificam HTML por contexto e
usam `json_encode` para valores inseridos em JavaScript.
- 011: `sendMail.php` exige POST, token CSRF, validacao de e-mails, nomes e comentario,
limita cinco envios por hora/IP e registra somente resultado, motivo e PID.
- 006 e 008: `cgi-bin` passou a negar tudo por padrao e permitir apenas `wxis.exe`;
`mimetex.cgi` e `temp/wxis` retornam HTTP 403. A remocao fisica de `mimetex.cgi` e o
bloqueio externo completo do WXIS permanecem pendentes para evitar regressao nos
consumidores legados que ainda usam o hostname publico.

Validacoes executadas:

- lint PHP 8.5 em 15 arquivos: aprovado;
Expand All @@ -63,12 +79,45 @@ Validacoes executadas:
- `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.
- lint PHP 8.5 da rodada P1: aprovado;
- sintaxe Apache (`httpd -t`): aprovada;
- arquivos auxiliares, `mimetex.cgi` e `temp/wxis`: HTTP 403;
- entrada XSS nos endpoints de estatisticas: HTTP 400;
- envio sem token CSRF: HTTP 403 com auditoria sem campos do formulario;
- `wxis.exe`: HTTP 200 por permanecer explicitamente na allowlist de compatibilidade.
- parser DOMIT validado com XML real no PHP 8.5;
- paginas de estatisticas e formulario de e-mail retornaram HTTP 200 com `caller`
adulterado, sem refletir o dominio recebido;
- formulario de e-mail apresentou token CSRF de 256 bits e rejeitou entrada invalida
com token valido usando HTTP 400, sem acionar o transporte SMTP.

Em 23 de setembro de 2026, a branch tambem foi testada com a copia da instancia
`cienciaecultura_bvs_br`. Codigo, bases, CGI e procedimentos foram montados como
somente leitura; uma copia temporaria da configuracao teve credenciais removidas e
destinos de rede direcionados para loopback. Com um PID existente na base:

- pagina inicial, artigo, WXIS, exportacao XML, RSS, estatisticas do periodico,
formulario de e-mail e pagina/dados do grafico responderam HTTP 200;
- arquivos de configuracao, templates, README, versao e CGI fora da allowlist
responderam HTTP 403;
- `caller` adulterado nao foi refletido nem usado como backend;
- diagnostico XML continuou disponivel em loopback, mas foi negado para um cliente
em outro contêiner e gerou evento de auditoria;
- POST de e-mail sem CSRF respondeu HTTP 403; sessao e token validos combinados com
e-mail invalido responderam HTTP 400, sem tentativa SMTP;
- o teste revelou e permitiu corrigir dois erros PHP 8.5 no grafico: ausencia de
estatisticas em determinados anos e construtor legado da classe `Graph`.

Os logs ainda registram avisos/depreciacoes fora do escopo imediato, principalmente
propriedades dinamicas em `class.XSLTransformer.php` e chaves opcionais ausentes na
configuracao de estatisticas. Esses pontos entram no backlog P2 de compatibilidade.

A incompatibilidade observada na biblioteca DOMIT foi corrigida de forma localizada:
metodos utilitarios chamados estaticamente foram declarados como estaticos, classes
usadas pelo parser receberam adaptadores de construtor para PHP 8 e a insercao duplicada
do elemento raiz foi removida. O envio real por SMTP nao foi executado localmente para
evitar efeitos externos e ainda deve ser validado com caixa postal sintetica em
homologacao.

## Matriz de validacao e risco

Expand All @@ -77,15 +126,15 @@ de compatibilidade DOMIT deve preceder o reteste funcional completo desses dois
| 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 |
| 004 | Mitigado na branch; aguarda homologacao | Idioma, ISSN e colecao usam allowlist; os valores de query sao codificados e entradas com markup retornam HTTP 400 | 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 |
| 006 | Parcialmente mitigado | `cgi-bin` nega acesso por padrao e permite somente `wxis.exe`; o acesso externo ao WXIS continua habilitado para compatibilidade com consumidores legados | 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 | Mitigado na branch; aguarda homologacao | Apache retorna HTTP 403 para `.inc`, `.def`, `.template`, README e arquivos de versao, mantendo estaticos comuns acessiveis | 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 | Parcialmente mitigado | `mimetex.cgi` retorna HTTP 403 pela politica default-deny de CGI, mas o binario ainda deve ser removido do repositorio e do artefato | 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 |
| 010 | Mitigado na branch; aguarda homologacao | PID, idioma e anos sao validados; HTML usa escape contextual e JavaScript recebe valores por `json_encode`; pagina retornou HTTP 200 no PHP 8.5 | 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 | Mitigado na branch; aguarda teste SMTP em homologacao | O envio exige POST e CSRF, valida e limita campos, aplica cinco tentativas por hora/IP e nao retorna detalhes do PHPMailer | 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; aguarda homologacao | Os endpoints ignoram `caller` como host, usam `SERVER_SCIELO` e retornaram HTTP 200 no PHP 8.5 com dominio adulterado | 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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<?php
class Graph
{
function Graph()
class Graph
{
function __construct()
{
$this->Graph();
}

function Graph()
{
$this->data = array();
$this->x_labels = array();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class domit_doctor {
* @param string The xml text to be repaired
* @return string The repaired xml text
*/
function fixAmpersands($xmlText) {
public static function fixAmpersands($xmlText) {
$xmlText = trim($xmlText);
$startIndex = -1;
$processing = true;
Expand Down Expand Up @@ -91,4 +91,4 @@ function evaluateCharacter($xmlText, $illegalChar, $startIndex) {
return $xmlText;
} //evaluateCharacter
} //domit_doctor
?>
?>
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ class DOMIT_GetElementsByPath {
/**
* Constructor - creates an empty DOMIT_NodeList to store matching nodes
*/
function DOMIT_GetElementsByPath() {
function DOMIT_GetElementsByPath() {
require_once(DOMIT_INCLUDE_PATH . 'xml_domit_nodemaps.php');
$this->nodeList = new DOMIT_NodeList();
} //DOMIT_GetElementsByPath
$this->nodeList = new DOMIT_NodeList();
} //DOMIT_GetElementsByPath

function __construct() {
$this->DOMIT_GetElementsByPath();
}

/**
* Parses the supplied "path"-based pattern
Expand Down Expand Up @@ -219,10 +223,14 @@ class DOMIT_GetElementsByAttributePath {
/**
* Constructor - creates an empty DOMIT_NodeList to store matching nodes
*/
function DOMIT_GetElementsByAttributePath() {
function DOMIT_GetElementsByAttributePath() {
require_once(DOMIT_INCLUDE_PATH . 'xml_domit_nodemaps.php');
$this->nodeList = new DOMIT_NodeList();
} //DOMIT_GetElementsByAttributePath
$this->nodeList = new DOMIT_NodeList();
} //DOMIT_GetElementsByAttributePath

function __construct() {
$this->DOMIT_GetElementsByAttributePath();
}

/**
* Matches the current path segment against the child nodes of the current context node
Expand Down Expand Up @@ -274,4 +282,4 @@ function &parsePattern(&$node, $pattern, $nodeIndex = 0) {
}
} //parsePattern
} //DOMIT_GetElementsByAttributePath
?>
?>
Loading
Loading