Overview

cross-course blog api authorization bypass in chamilo

August 1, 2026
2 min read

TL;DR

I reported GHSA-438r-9jh4-mmfc in Chamilo LMS.

This was a broad object-level authorization bypass across the Blog API subsystem.

Authenticated low-privileged users (ROLE_USER) could read, create, edit, and comment on blogs in courses they were not enrolled in.

Because the API endpoints lacked course-level boundary checks, any self-registered student account could target another course’s blog resources just by manipulating entity IDs.

The bug

Chamilo exposes several REST API endpoints for managing course blogs and posts (e.g. c_blog, c_blog_post, c_blog_comment, and related membership endpoints).

The pattern across these endpoints was consistent:

Request to Blog API endpoint (e.g. /api/c_blogs/{id})
-> Authentication check passes (valid JWT token)
-> API loads target Blog / Post object by ID
-> Executes CRUD operation directly

There was no authorization voter or context check asking:

Is the current user enrolled in the course that owns this blog, and do they have permission to perform this action on this blog?

Without that boundary, the ID parameters in the URL served as the only control.

Impacted actions

The missing authorization checks allowed an authenticated user outside the victim course or session to perform multiple operations:

  1. Read Private Blogs: Fetch blog titles, posts, and internal course discussions from blogs in restricted courses.
  2. Modify Blog Settings: Update blog metadata and configuration belonging to another course.
  3. Create Unauthorized Posts: Inject new blog posts into another course’s blog.
  4. Manipulate Blog Users: Add users to or modify membership list of another course’s blog.
  5. Inject Blog Comments: Add comments onto posts in un-enrolled courses.

This affected both the confidentiality of course blog discussions and the integrity of blog contents across the LMS.

Attack scenario

An attacker with a standard student account registers on a public Chamilo instance.

They obtain an API token and begin querying the blog endpoints:

GET /api/c_blogs/15 HTTP/1.1
Host: chamilo.local
Authorization: Bearer <attacker_token>

Even if blog 15 belongs to a private course the attacker never joined, the API returns 200 OK with the full blog object and post history.

To create an unauthorized post on that victim blog:

POST /api/c_blog_posts HTTP/1.1
Host: chamilo.local
Authorization: Bearer <attacker_token>
Content-Type: application/json
{
"blog": "/api/c_blogs/15",
"title": "Unauthorized Post",
"content": "<p>Content injected across course boundary</p>"
}

The server accepts the post and links it to blog 15.

The fix

The fix involves introducing strict object-level authorization on all blog API endpoints:

  • Verify that the target blog entity belongs to a course/session the user is actively enrolled in.
  • For write operations (modifying blog configuration, creating posts, adding users), enforce that the user holds the appropriate role (e.g. course teacher/admin or blog manager).

Versions and details

  • Advisory: GHSA-438r-9jh4-mmfc
  • Package: chamilo/chamilo-lms
  • Affected: <= 2.0.2
  • Severity: High
  • Weakness: CWE-862 (Missing Authorization)
  • Credit: baradika

Reference