Spaces:
Sleeping
Sleeping
File size: 7,552 Bytes
bad7a01 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | """Sprint S1.3 โ XSS dans le rapport HTML gรฉnรฉrรฉ.
Vรฉrifie que tout contenu utilisateur (corpus_name, sentences narratives,
nom de moteur) est รฉchappรฉ HTML dans le rapport produit par
``picarones.reports.html.ReportGenerator``.
Bandit B701 (CWE-94) avait flaggรฉ ``Environment(autoescape=False)`` โ
ce test concrรฉtise l'attaque que l'audit dรฉcrivait : un corpus nommรฉ
``</title><script>alert(1)</script>`` doit รชtre inerte dans le HTML.
"""
from __future__ import annotations
from pathlib import Path
from picarones.evaluation.benchmark_result import (
BenchmarkResult,
DocumentResult,
EngineReport,
)
def _make_minimal_benchmark(
corpus_name: str,
engine_name: str = "tesseract",
) -> BenchmarkResult:
"""Construit un BenchmarkResult minimal valide avec le ``corpus_name`` fourni."""
from picarones.evaluation.metric_result import MetricsResult
metrics = MetricsResult(cer=0.0, wer=0.0, mer=0.0, wil=0.0)
doc = DocumentResult(
doc_id="doc01",
image_path="doc01.png",
ground_truth="Bonjour",
hypothesis="Bonjour",
metrics=metrics,
duration_seconds=0.1,
)
engine = EngineReport(
engine_name=engine_name,
engine_version="5.3.0",
engine_config={},
document_results=[doc],
)
return BenchmarkResult(
corpus_name=corpus_name,
corpus_source=None,
document_count=1,
engine_reports=[engine],
)
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 1. corpus_name avec script tag โ doit รชtre รฉchappรฉ dans <title>
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestCorpusNameXSS:
"""Le ``corpus_name`` est injectรฉ dans ``<title>`` de
``base.html.j2``. Sans รฉchappement, un nom malicieux compromet
tout rapport partagรฉ."""
def test_script_tag_in_corpus_name_is_escaped(self, tmp_path: Path) -> None:
from picarones.reports.html import ReportGenerator
bench = _make_minimal_benchmark(
corpus_name="</title><script>alert('xss')</script>",
)
gen = ReportGenerator(bench)
out = tmp_path / "report.html"
gen.generate(out)
html = out.read_text()
# Le tag </title> ne doit PAS apparaรฎtre au milieu du HTML
# (autre que celui lรฉgitime ร la fin du <head>) au point
# d'attacher un <script>.
assert "<script>alert('xss')</script>" not in html, (
"XSS confirmรฉ : le script malicieux est exรฉcutable dans le rapport.\n"
"Causes possibles : autoescape=False dans Jinja2 + corpus_name "
"non filtrรฉ."
)
# Forme correcte : caractรจres dangereux รฉchappรฉs.
assert "<script>alert(" in html or "<script>" in html.lower(), (
"Le ``<`` dans corpus_name doit รชtre รฉchappรฉ en ``<``."
)
def test_html_attribute_injection_in_corpus_name(self, tmp_path: Path) -> None:
"""Cas d'attaque attribute-based : ``" onerror=alert(1)``
peut casser une balise si corpus_name est utilisรฉe dans
un attribut."""
from picarones.reports.html import ReportGenerator
bench = _make_minimal_benchmark(
corpus_name='" onerror="alert(1)" foo="',
)
gen = ReportGenerator(bench)
out = tmp_path / "report.html"
gen.generate(out)
html = out.read_text()
# Le caractรจre ``"`` doit รชtre รฉchappรฉ en ``"`` ou
# ``"`` partout oรน corpus_name est rendu. Aucune
# attribute injection ne doit subsister.
assert ' onerror="alert(' not in html, (
"Attribute injection : un guillemet non รฉchappรฉ permet "
"d'injecter onerror=."
)
def test_corpus_name_with_unicode_renders_correctly(
self, tmp_path: Path,
) -> None:
"""Corollaire โ vรฉrifie qu'un nom Unicode lรฉgitime
(``Manuscrit mรฉdiรฉval โ chartes XIIIยฐ``) reste lisible
aprรจs รฉchappement."""
from picarones.reports.html import ReportGenerator
bench = _make_minimal_benchmark(
corpus_name="Manuscrit mรฉdiรฉval โ chartes XIIIยฐ",
)
gen = ReportGenerator(bench)
out = tmp_path / "report.html"
gen.generate(out)
html = out.read_text()
# Caractรจres Unicode safe doivent rester intacts.
assert "Manuscrit mรฉdiรฉval" in html
assert "XIIIยฐ" in html
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 2. Engine name (moteur OCR) avec injection
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestEngineNameXSS:
"""Le nom de moteur peut venir d'un import HuggingFace ou d'une
config utilisateur. Doit รชtre รฉchappรฉ dans tous les renderers
qui l'affichent."""
def test_engine_name_with_script_is_escaped(self, tmp_path: Path) -> None:
from picarones.reports.html import ReportGenerator
bench = _make_minimal_benchmark(
corpus_name="test",
engine_name="<script>alert('engine')</script>",
)
gen = ReportGenerator(bench)
out = tmp_path / "report.html"
gen.generate(out)
html = out.read_text()
assert "<script>alert('engine')</script>" not in html, (
"Engine name XSS : un nom de moteur malicieux est exรฉcutรฉ."
)
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 3. Bandit B701 ne doit plus signaler autoescape=False
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestJinja2EnvIsAutoescaped:
"""Garde-fou contre la rรฉgression : ``_build_jinja_env`` doit
retourner un Environment avec autoescape activรฉ pour les
extensions HTML/J2."""
def test_env_has_autoescape_enabled_for_html(self) -> None:
from picarones.reports.html.generator import _build_jinja_env
env = _build_jinja_env()
# autoescape doit รชtre un Callable (select_autoescape) ou True,
# pas False ni None.
autoescape = env.autoescape
assert autoescape, (
f"Jinja2 Environment.autoescape={autoescape!r} โ XSS exposรฉ."
)
# Si c'est une fonction (select_autoescape), elle doit
# retourner True pour les HTML/J2.
if callable(autoescape):
assert autoescape("base.html.j2"), (
"select_autoescape doit activer l'รฉchappement pour .j2"
)
assert autoescape("any.html"), (
"select_autoescape doit activer l'รฉchappement pour .html"
)
|