CVE-2022-0339 (Python, published 2022-01-30T14:15Z)
Description: value: Server-Side Request Forgery (SSRF) in Pypi calibreweb prior to 0.6.16.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: CWE-918 \u2014 Server-Side Request Forgery (SSRF)
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.
Repository: https://github.com/janeczku/calibre-web
Files changed:
cps/admin.py
cps/editbooks.py
cps/kobo_auth.py
cps/static/js/main.js
cps/templates/generate_kobo_auth_url.html
Commit message: 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


Vulnerable code:
            log.info(u"User {} deleted".format(content.name))
            return(_(u"User '%(nick)s' deleted", nick=content.name))
            log.warning(_(u"Can't delete Guest User"))
            raise Exception(_(u"Can't delete Guest User"))
        log.warning(u"No admin user remaining, can't delete user")
        raise Exception(_(u"No admin user remaining, can't delete user"))
    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
        )
    # Invalidate any prevously generated Kobo Auth token for this user.
  <p>
    {{_('Open the .kobo/Kobo eReader.conf file in a text editor and add (or edit):')}}</a>
  <p>
    {% if not warning %}api_endpoint={{kobo_auth_url}}{% else %}{{warning}}{% endif %}</a>
  </p>
  <p>

Fixed code:
            log.info("User {} deleted".format(content.name))
            return(_("User '%(nick)s' deleted", nick=content.name))
            log.warning(_("Can't delete Guest User"))
            raise Exception(_("Can't delete Guest User"))
        log.warning("No admin user remaining, can't delete user")
        raise Exception(_("No admin user remaining, can't delete user"))
                    kobo_sync_status.remove_synced_book(book.id, 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
    )
    # Invalidate any previously generated Kobo Auth token for this user
            $(e.relatedTarget).one('focus', function(e){$(this).blur();});
<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 %}