Security Audit (extern) — Live-Website buergerinitiative-ejun.de (16.09.2026) #18
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Security Audit — externe Sicht, Live-Website
Datum: 16.09.2026, ~04:00–04:25 MESZ
Methodik: Blackbox von außen (hermes-lxc, öffentliche IP
88.133.36.255), ausschließlichcurl/openssl/dig— kein Port-Scanner, keine aktiven Exploits, kein Login. Alle Werte live gemessen.Ziel:
https://www.buergerinitiative-ejun.de(DynDNSsimowncloud.dnshome.de→ OPNsense 10.10.10.1 → HAProxy → nginx 10.20.10.10) + Apex-Domain + DNS.P1 — Hoch (beheben, bevor Mitglieder die Seite nutzen)
1. User-Enumeration über
/odata/verifyÖffentlicher Endpunkt unterscheidet existierende von nicht-existierenden E-Mails:
{"Valid":false,"Role":null,"Status":"not-found"}(HTTP 200){"Valid":false,"Role":null,"Status":"bad-password"}(HTTP 200)Jeder kann die Mitgliederliste (inkl. Admin-Adresse) extern enumerieren. Rate-Limit (~2/s) bremst nur, verhindert nichts.
Fix: Einheitliche Antwort für beide Fälle (z. B. immer
invalid-credentials, HTTP 401, gleiche Antwortzeit) — im OData-Backend (ejun-oapi) oder per Nginx-Abstraktion.2. Apex-Domain zeigt noch auf die alte Zoho-Seite
buergerinitiative-ejun.de(naked) löst auf Cloudflare (104.21.63.34 / 172.67.169.111) → 301 →buergerinitiative-ejun.zohosites.eu(HTTP 200, alte Seite). Wer die Adresse eingibt, sieht nicht unsere Website. Zoho setzt dabei Cookies unter unserer Domain (zalb_…,csrfcmitSameSite=None;Secure) — drittanbieter-Cookies auf unserer Domain, DSGVO-relevant.Fix: DNS (Andreas): naked Domain ebenfalls auf
simowncloud.dnshome.dezeigen lassen + LE-Zertifikat mit SAN für naked + www ausstellen (aktuelles Zert hat nurwww.buergerinitiative-ejun.de) + HAProxy-ACL deckt naked bereits ab (hdr_end(host) -i buergerinitiative-ejun.de).3. Kein DMARC — Phishing-Schutz für @buergerinitiative-ejun.de fehlt
_dmarc.buergerinitiative-ejun.deliefert keine TXT. SPF existiert (v=spf1 include:zohomail.eu ip4:159.69.123.124 -all), MX → Zoho. Ohne DMARC kann jeder E-Mails im Namen unserer Domain fälschen (Phishing an Mitglieder/Bürger).Fix:
v=DMARC1; p=quarantine; rua=mailto:...als TXT auf_dmarc.(nach DNS-Umstellung ggf.p=reject).P2 — Mittel
4. Google Fonts werden extern geladen (DSGVO)
index.htmllädtfonts.googleapis.com/css2+fonts.gstatic.com(Barlow, Space Grotesk) direkt vom Browser. Jeder Besucher-Request geht mit IP an Google (US) — ohne Consent-Grundlage problematisch, für eine Bürgerinitiative ein Vertrauens-Thema.Fix: Fonts self-hosten (Dateien ins Repo,
@font-facelokal, wieapp.js/styles.cssbereits lokal ausgeliefert).5. Drei tote PDF-Links auf der öffentlichen Startseite
index.htmlverlinkt aufhttps://www.buergerinitiative-ejun.de/Dokumente/…:2024_07_09 Windvorranggebiete Bürgermeisterschreiben.pdf→ 4042025_03_12 Stümpfig- Gutachten Umfassung 2021.pdf→ 4042025_06_06 Public Petition … Franken-Neuhofen.pdf→ 404(root und
/dev/getestet, beide 404;/Dokumente/und/docs/existieren nicht). Auchhttps://www.buergerinitiative-ejun.de/docs/→ 404.Fix: PDFs nach
Dokumente/im Deploy legen (oder Pfade korrigieren) und alle Links inindex.htmllive-prüfen.6. Port 80 am WAN geschlossen — reine
http://-Links hängenhttp://www.buergerinitiative-ejun.de/→ Timeout (Exit 28). Bekannter Punkt (ACME läuft über TLS-ALPN-01 auf 443), aber: jederhttp://-Link (z. B. in E-Mails/Druck) bricht ab. HSTS schützt nur Browser mit bereits bekannter HSTS-Angabe.Fix (eine von beiden): Port 80 öffnen mit Redirect
301 → https(dann auch HTTP-01-ACME simpler) — oder dokumentieren, dass nurhttps://-Links verwendet werden dürfen.7. Fehlende Security-Header
Vorhanden:
Strict-Transport-Security: max-age=15768000; includeSubDomains(auch auf 404 — gut).Fehlt:
Content-Security-Policy,X-Frame-Options/frame-ancestors(Clickjacking-Schutz),Referrer-Policy,X-Content-Type-Options: nosniff.Fix: zentral in nginx (
add_headerauf server-Block), CSPdefault-src 'self'; font-src 'self'(nach Self-Hosting der Fonts) +img-src 'self' data:.8.
server: nginx/1.24.0 (Ubuntu)— Versions-DisclosureServer-Header und alle nginx-Fehlerseiten (404/401) verraten exakte Version + Distro.Fix:
server_tokens off;+ eigene Fehlerseiten (error_page 404 /404.htmlim EjUn-Design).P3 — Niedrig / Hygiene
9.
adminapisendet doppelteWWW-Authenticate-HeaderGET /adminapi/und/admin/einwaende.htmlliefern zwei Header gleichzeitig:Basic realm="EjUn Dev Preview"+Basic realm="EjUn Admin". nginx-Config-Doppelung; Clients verhalten sich unbestimmt, Browser-Realm-Cache durcheinander.Fix: Exakten
auth_basic/@admin401-Block prüfen — pro Location genau ein Realm.10.
/api/healthöffentlich +/api/→ HTTP 500GET /api/health→200 {"status":"ok","api":true}(Backend-Existenz-Leak, ohne Auth).GET /api/→ 500 mit leerem Body (HEAD liefert405+allow: DELETE, GET, PUT— inkonsistent).POST/PUT /api/health→ 400,DELETE→ 405.Fix:
/api/healthintern-only oder mit Auth;/api/-Root sauber auf 404/405 statt 500.11.
/dev/ohne Passwort + robots.txt dokumentiert falschen Schutz/dev/liefert exakt denselben Content wie/(21225 B, gleicher ETAG — Dev-Preview = Produktions-Stand, kein sensibler Inhalt).X-Robots-Tag: noindex, nofollow+Cache-Control: no-storesind gesetzt. Aber: dierobots.txtbehauptet, „Der eigentliche Schutz ist die nginx-Schicht (Basic Auth + X-Robots-Tag …)“ — Basic Auth existiert auf/dev/aktuell nicht.Fix: Entweder Basic Auth auf
/dev/wieder aktivieren oder robots.txt-Kommentar korrigieren (Doku = Realität).12. Kein PTR-Record für die WAN-IP
dig -x 88.133.36.255→ leer. Für ausgehende E-Mails (falls die Domain später selbst mailt) und für Mail-Server-Spam-Scores relevant. DynDNS-Provider — ggf. anfragbar.Positiv — geprüft und OK
alert protocol version), keine schwachen Cipher (RC4/3DES/EXPORT/SHA1-CBC alle rejected), AEAD-only (AES-GCM), LE-Zert gültig bis 14.12.2026.max-age=15768000; includeSubDomainsauf allen Antworten inkl. 404./dev/uvp-hayforst-report.html, 6388 B) enthält nur 171 Wörter + CSS-only Skeleton-Balken + Veil-Box „Nur für registrierte Personen“. Kein versteckter Volltext (keindata-full, keinehidden-Blöcke, keine langenaria-labels)./odata/register4×200 dann 503 (~5/min),/odata/verify6×200 dann 503 (~2/s),/odata/submitEinwendung4×400 dann 503./odata/$metadata→ 404;/odata/Members→ 404; nurregister/verify/submitEinwendungöffentlich erreichbar./.git/config,/members.db,/.env,/server-status,/backup/→ alle 404/403.POST /odata/register(frische E-Mail) →200 {"Ok":true,"Status":"created","Email":…}— das generierte Passwort erscheint nicht in der öffentlichen Antwort.Access-Control-Allow-Origin-Header (auch nicht aufOrigin: https://evil.example) — keine CORS-Fehlkonfiguration.Transfer-Encoding-Anomalie → 500 (kein Smuggling, nginx lehnt ab); SNI-Fingerprinting: fremde Hosts bekommen Fallback-Zertai.berleb.net(keine EjUn-Infos).⚠️ Audit-Nebenwirkungen (Cleanup nötig!)
Während des Rate-Limit-Tests wurden 5 Test-Accounts im Live-Backend angelegt (alle
Unverifiziert/Active, keine echten Daten):sec-audit-20260916@example.org(Register-Test,created)rl-test-1@example.org…rl-test-4@example.org(Rate-Limit-Test; Existenz viaverify→bad-passwordbestätigt)Action: Über das Admin-Panel (
/admin/members.html) per Soft-Delete (Status: Deleted) entfernen. Die@example.org-Adressen sind eindeutig als Test erkennbar.Empfohlene Reihenfolge
ejun-oapi)Alle Befunde extern verifiziert (curl/openssl/dig, 16.09.2026). Kein interner Zugriff auf VM/Firewall für dieses Audit verwendet — interne Konfig-Details (HAProxy-ACLs, Zert-SANs) stammen aus früheren Live-Messungen (15.09.2026).
Tote Links — Vollliste + Austausch-Mapping (live verifiziert 16.09.2026, 09:50 korrigiert)
Vollständiger Crawl aller öffentlichen Seiten (index.html + alle 9 verlinkten HTML-Seiten) und aller url:/sourceUrl:-Felder in news.js. 13 tote URLs (einzeln) gefunden, verteilt über 16 Zeilen/Plätze. Alle Dokumente existieren bereits im Repo unter sources/ejun/ (neue Namen) und sind live erreichbar — es müssen nur die Links getauscht werden. Alle 12 Ersatz-Ziele heute erneut live geprüft, alle 200.
Korrektur 09:50: drei Filennamen enthalten planungsverband, in der ersten Fassung stand oben fälschlich planverterband. Die Tabelle unten ist korrekt.
A) index.html — öffentlich klickbar (Sektion Quellen & Unterlagen)
B) news.js — Feld sourceUrl (Metadaten, werden von app.js NICHT gerendert, nur url: ist klickbar. Trotzdem tote URLs im ausgelieferten JS)
Austausch-Mapping (alle Ziele live geprüft 16.09., alle 200, exakte Repo-Dateinamen unter sources/ejun/)
Randnotizen
Umsetzung
index.html (4 Pfade in Sektion Quellen & Unterlagen) + news.js 12× sourceUrl (Zeilen 82, 89, 96, 103, 122, 129, 136, 143, 150, 157, 164, 171, 178) auf sources/ejun/… umsetzen; /docs/-Archiv-Link (A1) nach Entscheidung ersetzen. Danach: Dev-Deploy + Live-Prüfung aller getauschten Links.