**Tipo:** Recursão não controlada via inclui mútua. A diretiva `Incluir` verifica a auto- referência direta (`a. md` não pode incluir `a. md`), mas não detecta ciclos indiretos. Dois arquivos de marcação que se incluem (`a.md` → inclui `b.md` → inclui `a.md`) causam recursão sem limites até que o limite de pilha do Python inicie `RecursãoError`. A exceção propaga-se do renderizador e bloqueia o código de chamada. **Arquivo:** `src/mistune/directives/include.py`, linhas 33 - 37 (a verificação de auto-incluir é a única lógica de detecção de ciclos). ** Causa de rote:** a lógica de incluir apenas compara `os.path.abspath(dest) == os.path.abspath(source_file)`. Não há um conjunto de "files já incluídos" por renda que possa capturar ciclos transitivos. Quando ` a. md` inclui ` b. md`, a chamada recursiva `block. parse(new_state)` usa `dest` (b. md) como o novo `__file__', que então inclui ` a. md` (passando a auto- verificação, porque o arquivo pai imediato é ` b. md`, não ` a. md`), que então inclui ` b. md`, e assim por diante. Cada nível de recursão adiciona quadros Python; o limite padrão da pilha 1000 imagens viaja depois de ~ 7 - 10 as iterações de ciclo e o Python aumentam o `RecursionError`. Como a diretriz não capta a exceção, ela se propaga do `Markdown.parse()' e aparece no código de chamada, quebrando o pedido. **Arquivo:** `src/mistune/directives/include.py`, linhas 28 - 54. ```python relpath = self.parse_title(m) dest = os.path.join(os.path.dirname(source_file), relpath) dest = os.path.normpath(dest). se os.path.abspath(dest) == os.path.abspath(source_file): # <-- apenas captura o retorno direto do self-include {"tipo": "block_error", "raw": "Não pode incluir self: " + relpath}.
se não os.path.isfile(dest): retorna {"tipo": "block_error", "raw": "Não foi possível encontrar o arquivo: " + relpath} com open( dest, "rb") como f: conteúdo = f.read(. decode(encoding)). ext = os.path.splitext(relpath)[ 1 ] se ext em {.md", ".markdown", ".mkd"}: new_state = block.state_cls() new_state.env["___file_"] = dest new_state.process(content) block.parse(new_state) # <-- análise recursiva, nenhum rastreamento de ciclo retorna new_state.tokens ```.
**Por que está errado:** a verificação de detecção de ciclos é de um nível profundo. Ciclos de arquivos múltiplos deslizam trivialmente. O limite de recursão padrão do Python é 1000 frames, então um ciclo de comprimento 2 viagens após algumas centenas de mútuos inclui; a exceção é descoberta pela diretiva, propagando- se de `Markdown.__call__()` e falhando o que quer que lhe tenha sido chamado. 1. O aplicativo usa mistune com a diretiva `Include` habilitada. Aplicativo aceita arquivos de marcação fornecidos pelo usuário (CMS, wiki, plataforma de documentação de vários usuários, aplicativo de tomada de notas, renderizador de documentos CI/CD). 2. O atacante carrega dois arquivos de marcação: - ` a. md`: `.. incluem:: b. md` - ` b. md`: `.. incluem:: a. md` 3. O renderizador é invocado em ` a. md` (ou qualquer marcação que refere este par). A diretiva `Incluir` inclui ` b. md`, que inclui ` a. md`, que inclui ` b. md`,... Cada recursão adiciona quadros Python. 4. Após ~ 340 iterações de ciclo (dependendo do padrão `sys.setrecursionlimit( 1000 )` e a profundidade de moldura por-incluído), Python levanta `RecursãoErro: a profundidade máxima de recursão excedeu'. 5. A exceção não é pego pela diretiva. Ele se propaga através de `block.parse`, através de `Markdown.__call__`, e para o gestor de pedidos do aplicativo. Se o aplicativo não o pegar explicitamente, os erros de solicitação são desligados (HTTP) 500 em contextos web, bloqueie nas ferramentas CLI).
** Capacidade do Attacker:** bloqueia o motor de renderização sob demanda, submetendo qualquer marcagem que acione o ciclo. Repetidos pedidos negam serviço. Se o renderizador for usado em um caminho quente (representação de documentos por página, regeneração de índice de pesquisa, trabalhos agendados de exportação de documentos), o ciclo persiste em todo o pipeline. **Condições prévias:** o aplicativo usa mistune com a diretiva "Incluir" habilitada e torna o markdown fornecido pelo usuário que pode fazer referência a outros arquivos carregados pelo usuário. O atacante precisa de escrever o acesso a dois arquivos.md no caminho de busca incluir (ou a um único arquivo incluindo um par conhecido-recorrente). **Diferencial:** Verificado com PoC contra mistune@ 3.2.1: ```python import os, mistune from mistune. directives importar RSTDirective, Incluir. os. makedirs('/tmp/mistune-recur', exist_ok=True) com open('/tmp/mistune-recur/a.md', 'w') como f.write('A\n\n.. incluem::: b.md') com open('/tmp/mistune-recur/b.md', 'w') como f.write('B\n\n. incluem:: a.md').
md = mistune.create_markdown(plugins=[RSTDirectiva([Include())]]) state = md.block.state_cls() state.env['__file___] = '/tmp/mistune-recur/a.md' md.parse('.. incluem:: b.md', state=state) # RecursãoError: profundidade máxima de recursão excedida ```` A compilação correcionada (com a correção sugerida abaixo) retorna um token `block_ error` como a verificação existente de auto- inclusão, em vez de recursing para sempre.
Rastreio incluído caminhos em `state.env` e rejeitar qualquer incluído que reinicie um caminho já na pilha de incluídos: ````diff --- a/ src/mistune/directives/include.py +++ b/ src/mistune/directives/include.py @@ - 28,8 + 28,18 @@ class Include(DirectivePlugin): relpath = self.parse_title(m) - dest = os.path.join(os.path.dirname(source_file), relpath) - dest = os.path.normpath(dest) + base = os.path.realpath(os.path.dirname(source_file)) + dest = os.path.realpath(os.path.join(base, relpath)) + + # Pista inclui pilha em diferentes parás recursivos para detectar ciclos. + include_stack = state.env.setdefault("__include_stack__", []) + se dest em include_stack ou dest == os.path.realpath(source_file): + retorno { + "tipo": "block_erro", + "raw": "Não pode incluir (ciclo), + }.
- se os.path.abspath(dest) == os.path.abspath(source_file): - retorno { - "tipo": "block_error", - "raw": "Não poderia incluir a si mesmo: " + relpath, - } @@... no ramo markdown-include... + include_stack.append(dest) + tentar: + new_state = block.state_cls() + new_state.env["__file__"] = dest + new_state.env["_include_stack_"] = include_stack + new_state.process(content) + block.parse(new_state) + retorna new_state.tokens + finalmente: + include_stack.pop() ``` Isto captura ciclos de qualquer comprimento (`a → b → a`, `a → b → c → a`, etc.). Emparelha isso com a correção de contenção de caminhos a partir do aviso LFI e a correção de extensão HTML a partir do aviso include- XSS; juntos esses três patches tornam a diretriz "Incluir" segura para ativar no marcador fornecido pelo usuário.
Adicione um teste de regressão afirmando que um 2 - ciclo e um 3 - ciclo ambos produzem `block_error` em vez de `RecursionError`. Registro de aconselhamento: GHSA- 8 mpj- m 6 qm-. 5 qr 8. Identificadores relacionados: CVE- 2026 - 59927.
Tempo: GitHub Advisory Database publicou este registro em 2026 - 07 - 20 T 21: 24: 42.000 Z e lista a sua última modificação como 2026 - 07 - 20 T 21: 24: 42.000 Z. Gravidade: MODERAR. Dados de pontuação publicados: CVSS_V 3: CVSS: 3.1 /AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L.
Software afetado e informações de versão: mistune do pacote PyPI — ECOSYSTEM: introduzido 0, corrigido 3.3.0. Classificação e evidência: identificadores de fraqueza CWE- 674, CWE- 755. O registro contém 6 suporte de referências nestes tipos: WEB, AVISO, EMBALAGAMENTO.