| Beschreibung | Gibbon v30.0.01 exposes modules/Planner/units_add_blockAjax.php without any authorization check. The
file self-bootstraps ../../gibbon.php and so never passes through index.php's isActionAccessible()
gate. An unauthenticated HTTP GET returns the full contents of any gibbonUnitBlock row selected by
integer ID, including the staff-only teachersNotes field.
CLASS AND SCORE
CWE-306 (Missing Authentication for Critical Function).
CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N = 5.3
Read-only, medium severity, and I do not want it overstated: no student PII, no credentials, no write
access, no RCE. One table, five columns.
AFFECTED
- v30.0.01, current stable (2026-02-06), commit b3b0219f49f2328508b989afd7e9b0843450cb9f - exploited
live.
- Default branch v31.0.00 (pushed 2026-07-26): the sink file is BYTE-IDENTICAL. I fetched it at
ref=v31.0.00 via the contents API today (blob 03a64576a8308cce176d59d10c77a7de9661e223, 2325 bytes)
and diffed against my v30.0.01 copy: zero differences, no isActionAccessible, no gibbonPersonID. The
path was last modified 2024-02-23 (298f1dbc, a $_GET null-coalesce refactor) and has never received
an authorization commit. The v31.0.00 changelog's "Security" section is empty.
AFFECTED FILE AND PARAMETERS
modules/Planner/units_add_blockAjax.php - parameters gibbonUnitBlockID (record selector) and mode
(how much of the record is rendered); the makeBlock() call is at line 71.
ENTRY POINT: GET /modules/Planner/units_add_blockAjax.php?id=1&mode=embed&gibbonUnitBlockID=<n>
THE DATA IS STAFF-ONLY BY THE PRODUCT'S OWN PERMISSION MODEL
gibbonAction 0000661 Unit Planner_all / 0000662 Unit Planner_learningAreas:
Staff=Y Student=N Parent=N, granted only to roles Administrator and Teacher
PROOF OF CONCEPT (verbatim from my own run on 2026-07-30)
Environment: the v30.0.01 tree on PHP 8.2 + Apache with MariaDB 10.11, loaded with the project's
SHIPPED gibbon.sql schema and gibbon_demo.sql demo dataset. config.php contains only database
credentials and $guid - no non-default setting, no feature flag, no .htaccess change. Planner is
active in the shipped schema (gibbonModule 0009, active='Y'). Instance reported 30.0.01.
One canary row stands in for a lesson block created by a teacher: gibbonUnitBlockID 424242, title
'PG17-CANARY-TITLE', contents '<p>PG17_SECRET_LESSON_CONTENT_a91f</p>', teachersNotes
'<p>PG17_PRIVATE_TEACHER_NOTE_b73c</p>'.
NEGATIVE CONTROL A - the normal route refuses the same anonymous client:
GET /index.php?q=/modules/Planner/units.php -> HTTP 200 serving the login form; 0 x PG17_
NEGATIVE CONTROL B - a GUARDED sibling AJAX file in the SAME module refuses it:
GET /modules/Planner/resources_addQuick_ajax.php?gibbonUnitBlockID=424242
-> "This page requires you to be logged in to access it. Please login and try again."
That is exactly the behaviour the vulnerable file is missing. Same bootstrap, same module, same
anonymous request. resources_addQuick_ajax.php begins with the $session->has('gibbonPersonID')
guard; units_add_blockAjax.php has none.
NEGATIVE CONTROL C - the exploit request minus the exploit primitive:
?id=1&mode=embed and ?id=1&mode=embed&gibbonUnitBlockID=999999 -> 0 occurrences of PG17
The output really is database-driven; only a valid gibbonUnitBlockID yields data.
EXPLOIT - no cookie, no Authorization header, no CSRF token (confirmed with curl -v):
GET /modules/Planner/units_add_blockAjax.php?id=1&mode=embed&gibbonUnitBlockID=424242
HTTP/1.1 200 OK Content-Length: 1932
<input readonly ... value='PG17-CANARY-TITLE'> <input ... value='Discussion'> <... value='45'>
<div>Block Contents</div><div><p><p>PG17_SECRET_LESSON_CONTENT_a91f</p></p></div>
<div>Teacher's Notes</div>
<div style='background-color: #F6CECB'><p><p>PG17_PRIVATE_TEACHER_NOTE_b73c</p></p></div>
The teacher's notes are rendered in the product's own staff-only highlighted style.
MODE MATRIX - every request unauthenticated
no mode parameter, or mode=masterAdd -> teachersNotes leaks
mode=masterEdit / plannerEdit / workingEdit / workingDeploy / embed / any other value
-> teachersNotes + full lesson contents
The teacher's-notes leak needs no parameter tuning at all.
ENUMERATION - sequential integer IDs, against the project's own shipped demo curriculum
39667 "Want To Make Some Eye Candy?" 39668 "Assessment" 39669 "Getting Started With Acorn"
39670 "Find A CC Texture" 39671 "Use Your Texture"
Database IDs are 12-digit zero-filled ('000000039667') yet plain integers match, so enumeration from 1
upward works. A real, non-canary teacher's-notes body was also extracted verbatim from
gibbonUnitBlockID=39686 (a lesson-preparation note about raster vs vector graphics).
CODE-LEVEL CONFIRMATION
grep -nE "isActionAccessible|gibbonPersonID" modules/Planner/units_add_blockAjax.php -> no output,
NO GUARD, whereas units_edit_working.php:47 and units_edit_deploy.php:47 are both guarded.
The only in-tree callers of the vulnerable file are those two authorization-gated staff pages
(units_edit_working.php:240,249 mode=workingEdit; units_edit_deploy.php:328,337 mode=workingDeploy).
Vendor intent is unambiguously staff-only: mode=embed is invoked nowhere in the tree,
docs.gibbonedu.org documents no public-embed feature, and the shipped .htaccess does not restrict
direct .php access to modules/.
RELATED UNGUARDED ENDPOINTS (same root cause; named so the vendor can fix them together)
Enumerating every module file that includes '../../gibbon.php' directly, exactly five lack a guard on
v30.0.01 and trunk: Planner/units_add_blockAjax.php (this report, confirmed data-bearing);
Students/student_view_details_notes_addAjax.php (same class, data-bearing - reached unauthenticated,
but the shipped demo data leaves gibbonStudentNoteCategory.template empty so no meaningful payload was
demonstrated); and Planner/units_add_blockOutcomeAjax.php, Planner/planner_editorAjax.php,
Rubrics/rubrics_edit_ajax.php (these three reflect only request parameters). Every other AJAX sibling
in these modules already carries a guard, which is why this reads as an omission, not a design
decision.
SUGGESTED FIX
Add the guard the sibling files already use at the top of each of the five:
$session->has('gibbonPersonID') plus isActionAccessible() for the owning action.
PRECONDITION AND SCOPE, STATED HONESTLY
The only precondition is that at least one curriculum unit block exists, i.e. a teacher has used the
Unit Planner. Nothing else - no non-default setting, no feature flag, no authenticated user. The
contents field may in some deployments be visible to enrolled students via the Lesson Planner; the
unambiguously staff-restricted field is teachersNotes, and the defect is that the endpoint enforces no
authentication at all. No XSS is claimed: gibbon.php sanitises $_GET and the raw-HTML echo in
mode=embed renders staff-authored stored content, not attacker input.
PRIOR ART - the root-cause CLASS is public, this instance is not
CVE-2023-45878 disclosed the same "self-bootstrap gibbon.php, bypass index.php authorization" pattern
in modules/Rubrics/rubrics_visualise_saveAjax.php, which now carries the guard. This is a distinct,
still-unfixed instance in a different module with different data. Precedent for separate assignment:
CVE-2023-45881 was assigned for a separate Planner-module instance alongside it.
VENDOR CONTACTED
Yes, before this submission: reported privately on 2026-07-30 to [email protected], the address
named in the vendor's own .github/SECURITY.md. GitHub private vulnerability reporting is disabled on
GibbonEdu/core, so that mailbox is the only private channel. I offered to hold on their timeline, and
to withdraw this request if they publish their own advisory. Default window 90 days.
DUPLICATE CHECK (re-run from scratch on 2026-07-30)
Repository advisories: one entry, GHSA-4mq5-8jvh-qq3p / CVE-2022-27305 (session issue), unrelated. All
19 Gibbon advisories in the GitHub Advisory Database read individually, plus all 20 NVD results for
"Gibbon" - none concerns units_add_blockAjax.php, unit blocks, teachersNotes, or unauthenticated
information disclosure. Closest prior art is CVE-2023-45878/45881, addressed above; both target
different files that now carry guards. Composer advisories: empty. CHANGELOG.txt for v30.0.01/v30.0.00
and the v31.0.00 dev changelog: no entry about authentication on Planner AJAX endpoints. Open and
closed issues and PRs searched for units_add_blockAjax, blockAjax, makeBlock, teachersNotes,
unauthenticated and missing authentication: all zero. No indexed mention of the filename anywhere. Not
documented by the vendor.
VALIDATION NOTE
Validated against the official released source and the project's own shipped schema and demo dataset,
deployed locally in Docker. Not tested against any third party's live instance. All test containers
were removed afterwards; the canary values were synthetic strings created by me.
|
|---|