CVE-2022-0339 (published 2022-01-30T14:15Z)

Server-Side Request Forgery (SSRF) in Pypi calibreweb prior to 0.6.16.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-918
cwe_name: Server-Side Request Forgery (SSRF)
description: The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
extended_description: By providing URLs to unexpected hosts or ports, attackers can make it appear that the server is sending the request, possibly bypassing access controls such as firewalls that prevent the attackers from accessing the URLs directly. The server can be used as a proxy to conduct port scanning of hosts in internal networks, use other URLs such as that can access documents on the system (using file://), or use other protocols such as gopher:// or tftp://, which may provide greater control over the contents of requests.
         

Fix information:
repo_url: https://github.com/janeczku/calibre-web
commit_details: repo_url: https://github.com/janeczku/calibre-web
author: Ozzie Isaacs
author_date: 2022-01-22 18:23:08+01:00
msg: Kobo sync token is now also created if accessed from localhost(fixes #1990)
Create kobo sync token button is now "unclicked" after closing dialog
Additional localhost route is catched
If book format is deleted this also deletes the book synced to kobo status
repository: repo_url: https://github.com/janeczku/calibre-web
repo_name: janeczku/calibre-web
description: :books: Web app for browsing, reading and downloading eBooks stored in a Calibre database
repo_language: Fluent
owner: janeczku
file_changes: filename: admin.py
change_type: ModificationType.MODIFY
diff: @@ -1426,14 +1426,14 @@ def _delete_user(content):
             for kobo_entry in kobo_entries:
                 ub.session.delete(kobo_entry)
             ub.session_commit()
-            log.info(u"User {} deleted".format(content.name))
-            return(_(u"User '%(nick)s' deleted", nick=content.name))
+            log.info("User {} deleted".format(content.name))
+            return(_("User '%(nick)s' deleted", nick=content.name))
         else:
-            log.warning(_(u"Can't delete Guest User"))
-            raise Exception(_(u"Can't delete Guest User"))
+            log.warning(_("Can't delete Guest User"))
+            raise Exception(_("Can't delete Guest User"))
     else:
-        log.warning(u"No admin user remaining, can't delete user")
-        raise Exception(_(u"No admin user remaining, can't delete user"))
+        log.warning("No admin user remaining, can't delete user")
+        raise Exception(_("No admin user remaining, can't delete user"))
 
 
 def _handle_edit_user(to_save, content, languages, translations, kobo_support):

programming_language: Python
filename: editbooks.py
change_type: ModificationType.MODIFY
diff: @@ -341,6 +341,7 @@ def delete_book_from_table(book_id, book_format, jsonResponse):
                 else:
                     calibre_db.session.query(db.Data).filter(db.Data.book == book.id).\
                         filter(db.Data.format == book_format).delete()
+                    kobo_sync_status.remove_synced_book(book.id, True)
                 calibre_db.session.commit()
             except Exception as ex:
                 log.debug_or_exception(ex)

programming_language: Python
filename: kobo_auth.py
change_type: ModificationType.MODIFY
diff: @@ -118,55 +118,49 @@ kobo_auth = Blueprint("kobo_auth", __name__, url_prefix="/kobo_auth")
 @kobo_auth.route("/generate_auth_token/<int:user_id>")
 @login_required
 def generate_auth_token(user_id):
+    warning = False
     host_list = request.host.rsplit(':')
     if len(host_list) == 1:
         host = ':'.join(host_list)
     else:
         host = ':'.join(host_list[0:-1])
-    if host.startswith('127.') or host.lower() == 'localhost' or host.startswith('[::ffff:7f'):
-        warning = _('PLease access calibre-web from non localhost to get valid api_endpoint for kobo device')
-        return render_title_template(
-            "generate_kobo_auth_url.html",
-            title=_(u"Kobo Setup"),
-            warning = warning
-        )
-    else:
-        # Invalidate any prevously generated Kobo Auth token for this user.
-        auth_token = ub.session.query(ub.RemoteAuthToken).filter(
-            ub.RemoteAuthToken.user_id == user_id
-        ).filter(ub.RemoteAuthToken.token_type==1).first()
-
-        if not auth_token:
-            auth_token = ub.RemoteAuthToken()
-            auth_token.user_id = user_id
-            auth_token.expiration = datetime.max
-            auth_token.auth_token = (hexlify(urandom(16))).decode("utf-8")
-            auth_token.token_type = 1
-
-            ub.session.add(auth_token)
-            ub.session_commit()
-
-        books = calibre_db.session.query(db.Books).join(db.Data).all()
-
-        for book in books:
-            formats = [data.format for data in book.data]
-            if not 'KEPUB' in formats and config.config_kepubifypath and 'EPUB' in formats:
-                helper.convert_book_format(book.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name)
-
-        return render_title_template(
-            "generate_kobo_auth_url.html",
-            title=_(u"Kobo Setup"),
-            kobo_auth_url=url_for(
-                "kobo.TopLevelEndpoint", auth_token=auth_token.auth_token, _external=True
-            ),
-            warning = False
-        )
+    if host.startswith('127.') or host.lower() == 'localhost' or host.startswith('[::ffff:7f') or host == "[::1]":
+        warning = _('Please access Calibre-Web from non localhost to get valid api_endpoint for kobo device')
+
+    # Generate auth token if none is existing for this user
+    auth_token = ub.session.query(ub.RemoteAuthToken).filter(
+        ub.RemoteAuthToken.user_id == user_id
+    ).filter(ub.RemoteAuthToken.token_type==1).first()
+
+    if not auth_token:
+        auth_token = ub.RemoteAuthToken()
+        auth_token.user_id = user_id
+        auth_token.expiration = datetime.max
+        auth_token.auth_token = (hexlify(urandom(16))).decode("utf-8")
+        auth_token.token_type = 1
+
+        ub.session.add(auth_token)
+        ub.session_commit()
+
+    books = calibre_db.session.query(db.Books).join(db.Data).all()
+
+    for book in books:
+        formats = [data.format for data in book.data]
+        if not 'KEPUB' in formats and config.config_kepubifypath and 'EPUB' in formats:
+            helper.convert_book_format(book.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name)
+
+    return render_title_template(
+        "generate_kobo_auth_url.html",
+        title=_(u"Kobo Setup"),
+        auth_token=auth_token.auth_token,
+        warning = warning
+    )
 
 
 @kobo_auth.route("/deleteauthtoken/<int:user_id>", methods=["POST"])
 @login_required
 def delete_auth_token(user_id):
-    # Invalidate any prevously generated Kobo Auth token for this user.
+    # Invalidate any previously generated Kobo Auth token for this user
     ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.user_id == user_id)\
         .filter(ub.RemoteAuthToken.token_type==1).delete()
 

programming_language: Python
filename: main.js
change_type: ModificationType.MODIFY
diff: @@ -535,6 +535,7 @@ $(function() {
 
     $("#modal_kobo_token")
         .on("show.bs.modal", function(e) {
+            $(e.relatedTarget).one('focus', function(e){$(this).blur();});
             var $modalBody = $(this).find(".modal-body");
 
             // Prevent static assets from loading multiple times

programming_language: JavaScript
filename: generate_kobo_auth_url.html
change_type: ModificationType.MODIFY
diff: @@ -1,12 +1,15 @@
 {% extends "fragment.html" %}
 {% block body %}
 <div class="well">
-  <p>
-    {{_('Open the .kobo/Kobo eReader.conf file in a text editor and add (or edit):')}}</a>
+<p>
+  {% if not warning %}
+      {{_('Open the .kobo/Kobo eReader.conf file in a text editor and add (or edit):')}}
+    </p><p>
+      api_endpoint={{url_for("kobo.TopLevelEndpoint", auth_token=auth_token, _external=True)}}
+  {% else %}
+      {{warning}}
+    </p><p>{{_('Kobo Token:')}} {{ auth_token }}
+  {% endif %}
   </p>
-  <p>
-    {% if not warning %}api_endpoint={{kobo_auth_url}}{% else %}{{warning}}{% endif %}</a>
-  </p>
-  <p>
 </div>
 {% endblock %}

programming_language: HTML