CVE-2022-4018 (Python, published 2022-11-16T13:15Z)
Description: value: Missing Authentication for Critical Function in GitHub repository ikus060/rdiffweb prior to 2.5.0a6.
Severity: nan (CVSS v3 base score: 4.3)
Weakness: CWE-306 \u2014 Missing Authentication for Critical Function
The product does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources.
Repository: https://github.com/ikus060/rdiffweb
Files changed:
README.md
doc/configuration.md
rdiffweb/controller/tests/test_page_login.py
rdiffweb/core/config.py
rdiffweb/rdw_app.py
rdiffweb/tools/auth_form.py
Commit message: Define idle and absolute session timeout with agressive default to protect usage on public computer


Vulnerable code:
        '--session-timeout',
        metavar='MINUTES',
        help='Sessions will be revoke after this period of inactivity, unless the user selected "remember me". Default 15 minutes.',
        default=15,
        '--session-persistent-timeout',
        help='Persistent sessions (remember me) will be revoke after this period of inactivity. Default 30 days.',
        default=43200,
        '--rate-limit',
        metavar='LIMIT',
        type=int,
        help='maximum number of requests per hour that can be made on sensitive endpoints. When this limit is reached, an HTTP 429 message is returned to the user or the user is logged out. This security measure is used to limit brute force attacks on the login page and the RESTful API.',
                'tools.sessions.timeout': cfg.session_timeout,  # minutes
                'tools.auth_form.timeout': cfg.session_persistent_timeout,  # minutes
        return (
            session.get(SESSION_KEY) is not None
            and session.get(LOGIN_TIME) is not None
            and session[LOGIN_TIME] + datetime.timedelta(minutes=session.timeout) > session.now()
        )
    def run(self, login_url='/login/', logout_url='/logout', timeout=43200):
        # If login is persistent, update the cookie max-age/expires
        if cherrypy.session.get(LOGIN_PERSISTENT, False):
            cherrypy.session.timeout = timeout
            cookie = cherrypy.serving.response.cookie
            cookie['session_id']['max-age'] = timeout * 60
            cookie['session_id']['expires'] = httputil.HTTPDate(time.time() + timeout * 60)
        else:
            session_timeout = cherrypy.request.config.get('tools.sessions.timeout', 60)
            cherrypy.session.timeout = session_timeout

Fixed code:
* Define idle and absolute session timeout with agressive default to protect usage on public computer [CVE-2022-3327](https://nvd.nist.gov/vuln/detail/CVE-2022-3327)
## Configure User Session

A user session is a sequence of request and response transactions associated with a single user. The user session is the means to track each authenticated user. 

| Option | Description | Example |
| --- | --- | --- |
| session-idle-timeout | This timeout defines the amount of time a session will remain active in case there is no activity in the session. User Session will be revoke after this period of inactivity, unless the user selected "remember me". Default 10 minutes. | 5 |
| session-absolute-timeout | This timeout defines the maximum amount of time a session can be active. After this period, user is forced to (re)authenticate, unless the user selected "remember me". Default 20 minutes. | 30 |
| session-persistent-timeout | This timeout defines the maximum amount of time to remember and trust a user device. This timeout is used when user select "remember me". Default 30 days | 43200 |

        # Then cookie is not persistent
        self.assertNotIn('expires', self.cookies[0][1])
        self.assertNotIn('Max-Age', self.cookies[0][1])
    def test_login_persistent(self):
        # Given a user authenticated with persistent
        self.getPage('/logout/')
        self.assertStatus(303)
        self.getPage(
            '/login/', method='POST', body={'login': self.USERNAME, 'password': self.PASSWORD, 'persistent': '1'}
        )
        self.assertStatus(303)
        # Then a persistent cookie is return
        self.assertIn('expires', self.cookies[0][1])
        self.assertIn('Max-Age', self.cookies[0][1])
        # Then a session is created with persistent flag
        session = DbSession(id=self.session_id)
        session.load()
        self.assertTrue(session['login_persistent'])
        # Then session timeout is 30 days in future
        self.assertAlmostEqual(session.timeout, 43200, delta=2)

        '--rate-limit',
        metavar='LIMIT',
        type=int,
        default=20,
        help='maximum number of requests per hour that can be made on sensitive endpoints. When this limit is reached, an HTTP 429 message is returned to the user or the user is logged out. This security measure is used to limit brute force attacks on the login page and the RESTful API.',
        '--session-idle-timeout',
        help='This timeout defines the amount of time a session will remain active in case there is no activity in the session. User Session will be revoke after this period of inactivity, unless the user selected "remember me". Default 5 minutes.',
        default=5,
        '--session-absolute-timeout',
        metavar='MINUTES',
        help='This timeout defines the maximum amount of time a session can be active. After this period, user is forced to (re)authenticate, unless the user selected "remember me". Default 20 minutes.',
    )

    parser.add(
        '--session-persistent-timeout',
        metavar='MINUTES',
        help='This timeout defines the maximum amount of time to remember and trust a user device. This timeout is used when user select "remember me". Default 30 days.',
        default=43200,
                'tools.sessions.timeout': cfg.session_idle_timeout,  # minutes
                'tools.auth_form.persistent_timeout': cfg.session_persistent_timeout,  # minutes
                'tools.auth_form.absolute_timeout': cfg.session_absolute_timeout,  # minutes
        # We don't need to verify the timeout value since expired session get deleted automatically.
        return session.get(SESSION_KEY) is not None and session.get(LOGIN_TIME) is not None
    def _update_session_timeout(self, persistent_timeout=43200, absolute_timeout=30):
        """
        Since we have multiple timeout value (idle, absolute and persistent) We need to update the session timeout and possibly the cookie timeout.
        """
        persistent_timeout = cherrypy.request.config.get('tools.auth_form.persistent_timeout', 43200)
        absolute_timeout = cherrypy.request.config.get('tools.auth_form.absolute_timeout', 30)
        # If login is persistent, update the cookie max-age/expires
        session = cherrypy.session
        if session.get(LOGIN_PERSISTENT, False):
            expiration = session[LOGIN_TIME] + datetime.timedelta(minutes=persistent_timeout)
            session.timeout = int((expiration - session.now()).total_seconds() / 60)
            cookie = cherrypy.serving.response.cookie
            cookie['session_id']['max-age'] = session.timeout * 60
            cookie['session_id']['expires'] = httputil.HTTPDate(time.time() + session.timeout * 60)
        else:
            session_idle_timeout = cherrypy.request.config.get('tools.sessions.timeout', 60)
            expiration1 = session.now() + datetime.timedelta(minutes=session_idle_timeout)
            expiration2 = session[LOGIN_TIME] + datetime.timedelta(minutes=absolute_timeout)
            expiration = min(expiration1, expiration2)
            session.timeout = int((expiration - session.now()).total_seconds() / 60)

    def run(self, login_url='/login/', logout_url='/logout', persistent_timeout=43200, absolute_timeout=30):
        self._update_session_timeout()
        # Update the session timeout
        self._update_session_timeout()