~/blog/public-internal-cooperation-platformPOSTED
by Shady Nathan Tawfik · July 25, 2026 · 4 min

A Platform For Internal Communication, Exposed To The Internet

A public-facing cooperation and counselling platform ran a content system with three low-severity findings. The lesson was in what a low-severity result does not tell you.

Interactive[blog-2.0]

TL;DR: Three low findings on a maintained platform sound like a pass. I explain why a low-severity result is only a statement about the vectors I chose, and where the next assessment should start.

The scenario

I ran this as a black-box review: no credentials, only the publicly visible surface. Here is what I saw before I touched anything.

A regional cooperation and counselling platform on a maintained enterprise content system behind a reverse proxy, with a current PHP runtime. Assessment covered version posture, header policy, information disclosure and the exposed administrative surface.

I removed all identifying information. The target is described only by sector and technology class so the pattern can be reused.

The pattern

The pattern I recognised — and that I keep finding in similar estates:

A low-severity result is a statement about the vectors you chose, not about the system. Three low findings on a maintained platform usually means the platform is current, the plugin set is small and the interesting surface is behind something. It does not mean there is nothing to fix; it means the fixes are hygiene items, and hygiene items decay quietly.

The reason this engagement is worth writing up is the shape of the estate. A cooperation platform handles appointments, counselling requests and internal notices. The content system is only the delivery layer, but the data model behind it distinguishes anonymous readers from authenticated participants, and the distinction lives in the routing and session layer rather than in the content. That is where the next audit should start, and this one deliberately did not pretend to have covered it.

The concrete lesson is about sequencing. Header policy, version posture and disclosure fixes are cheap, immediate and permanent once scheduled. Session and authorisation design is expensive and requires knowing what the platform actually does for whom. Doing them in the wrong order wastes the easy wins; doing them in the right order means the version fix that raises a floor never has to be repeated.

TL;DR: A public-facing cooperation and counselling platform ran a content system with three low-severity findings. The lesson was in what a low-severity result does not tell you.

Findings

SeverityFindingEvidence
NIEDRIGSecurity headers absent on the public surfaceNo frame, referrer or content-type protection
NIEDRIGContent system version in asset pathsExact release identifiable
NIEDRIGNo published security contact fileNo channel for coordinated disclosure
INFOPlatform and runtime currentNo known vulnerability in the core platform
INFOAdministrative paths restrictedNo writable endpoint reachable anonymously
INFONo reflected input in public formsNo exploitable injection point found

The attack path

Public visitorContent system delivery layerAuthenticated participantSession and routing layerCooperation platform functionsAppointments and counselling requestsData model boundary not covered by this assessment

Reproduction in my lab

Every command below targets a lab container I control. Nothing here is aimed at a live system.

Establish the baseline on your own platform

~/code/bash bash
$ for h in example.org; do
    printf '%s\n' "$h"
    curl -sI "https://$h/" | grep -iE 'strict-transport|content-security-policy|x-frame|x-content-type|referrer-policy|permissions-policy' || echo '  no security headers'
    curl -s "https://$h/" | grep -oiE 'generator[^>]*content="[^"]+"' | head -3
    curl -so /dev/null -w '  security.txt -> %{http_code}\n' "https://$h/.well-known/security.txt"
  done

Detection in your own estate

Test whether the anonymous and authenticated surfaces differ

~/code/bash bash
# the same path, before and after a session, should differ in behaviour not in existence
$ curl -so /dev/null -w 'anonymous  %{http_code}\n' https://example.org/termine
$ curl -so /dev/null -w 'with-cookie %{http_code}\n' \
  -H 'Cookie: session=<your-own-test-session>' https://example.org/termine

# check what a full session can reach that anonymous cannot
$ curl -s https://example.org/ | grep -oE 'href="/[a-z0-9/-]*"' | sort -u | head -20

Remediation

Ship the header baseline in the platform configuration, once

~/code/nginx nginx
# baseline for every response on the estate
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'self'; base-uri 'self'" always;
server_tokens off;

Publish a security contact file

~/code/text text
# .well-known/security.txt
Contact: mailto:security@example.org
Expires: 2027-12-31T23:59:59.000Z
Preferred-Languages: en, de
Canonical: https://example.org/.well-known/security.txt
Policy: https://example.org/security-policy

Takeaways

  • Low severity means your vector list was short, not that the system is safe.
  • Content systems are the delivery layer; authorisation lives one level down.
  • Fix header and version hygiene first, because they raise the floor permanently.
  • Write down what the assessment did not cover, so the next one starts there.

For educational purposes only. Every reproducible command targets my own lab environment (localhost), never a live system. © 2026