Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deprecate pickleable sessions, recommend json #3353

Merged
merged 6 commits into from
Sep 27, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ Features
- Add support for Python 3.7. Add testing on Python 3.8 with allowed failures.
See https://github.com/Pylons/pyramid/pull/3333

- Added ``pyramid.session.JSONSerializer``. See "Upcoming Changes to ISession
in Pyramid 2.0" in the "Sessions" chapter of the documentation for more
information about this feature.
See https://github.com/Pylons/pyramid/pull/3353

Bug Fixes
---------

Expand All @@ -79,6 +84,21 @@ Bug Fixes
Deprecations
------------

- The ``pyramid.intefaces.ISession`` interface will move to require
json-serializable objects in Pyramid 2.0. See
"Upcoming Changes to ISession in Pyramid 2.0" in the "Sessions" chapter
of the documentation for more information about this change.
See https://github.com/Pylons/pyramid/pull/3353

- The ``pyramid.session.signed_serialize`` and
``pyramid.session.signed_deserialize`` functions will be removed in Pyramid
2.0, along with the removal of
``pyramid.session.UnencryptedCookieSessionFactoryConfig`` which was
deprecated in Pyramid 1.5. Please switch to using the
``SignedCookieSessionFactory``, copying the code, or another session
implementation if you're still using these features.
See https://github.com/Pylons/pyramid/pull/3353

Backward Incompatibilities
--------------------------

Expand Down
8 changes: 2 additions & 6 deletions docs/api/session.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@

.. automodule:: pyramid.session

.. autofunction:: signed_serialize

.. autofunction:: signed_deserialize

.. autofunction:: SignedCookieSessionFactory

.. autofunction:: UnencryptedCookieSessionFactoryConfig

.. autofunction:: BaseCookieSessionFactory

.. autoclass:: PickleSerializer

.. autoclass:: JSONSerializer

72 changes: 53 additions & 19 deletions docs/narr/sessions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,59 @@ using the :meth:`pyramid.config.Configurator.set_session_factory` method.
By default the :func:`~pyramid.session.SignedCookieSessionFactory`
implementation contains the following security concerns:

- Session data is *unencrypted*. You should not use it when you keep
sensitive information in the session object, as the information can be
easily read by both users of your application and third parties who have
access to your users' network traffic.

- If you use this sessioning implementation, and you inadvertently create a
cross-site scripting vulnerability in your application, because the
session data is stored unencrypted in a cookie, it will also be easier for
evildoers to obtain the current user's cross-site scripting token.

- The default serialization method, while replaceable with something like
JSON, is implemented using pickle which can lead to remote code execution
if your secret key is compromised.

In short, use a different session factory implementation (preferably one
which keeps session data on the server) for anything but the most basic of
applications where "session security doesn't matter", you are sure your
application has no cross-site scripting vulnerabilities, and you are confident
your secret key will not be exposed.
- Session data is *unencrypted* (but it is signed / authenticated).

This means an attacker cannot change the session data, but they can view it.
You should not use it when you keep sensitive information in the session object, as the information can be easily read by both users of your application and third parties who have access to your users' network traffic.

At the very least, use TLS and set ``secure=True`` to avoid arbitrary users on the network from viewing the session contents.

- If you use this sessioning implementation, and you inadvertently create a cross-site scripting vulnerability in your application, because the session data is stored unencrypted in a cookie, it will also be easier for evildoers to obtain the current user's cross-site scripting token.

Set ``httponly=True`` to mitigate this vulnerability by hiding the cookie from client-side JavaScript.

- The default serialization method, while replaceable with something like JSON, is implemented using pickle which can lead to remote code execution if your secret key is compromised.

To mitigate this, set ``serializer=pyramid.session.JSONSerializer()`` to use :class:`pyramid.session.JSONSerializer`. This option will be the default in :app:`Pyramid` 2.0.
See :ref:`pickle_session_deprecation` for more information about this change.

In short, use a different session factory implementation (preferably one which keeps session data on the server) for anything but the most basic of applications where "session security doesn't matter", you are sure your application has no cross-site scripting vulnerabilities, and you are confident your secret key will not be exposed.

.. _pickle_session_deprecation:

Upcoming Changes to ISession in Pyramid 2.0
-------------------------------------------

In :app:`Pyramid` 2.0 the :class:`pyramid.interfaces.ISession` interface will be changing to require that session implementations only need to support json-serializable data types.
This is a stricter contract than the current requirement that all objects be pickleable and it is being done for security purposes.
This is a backward-incompatible change.
Currently, if a client-side session implementation is compromised, it leaves the application vulnerable to remote code execution attacks using specially-crafted sessions that execute code when deserialized.

For users with compatibility concerns, it's possible to craft a serializer that can handle both formats until you are satisfied that clients have had time to reasonably upgrade.
Remember that sessions should be short-lived and thus the number of clients affected should be small (no longer than an auth token, at a maximum). An example serializer:

.. code-block:: python
:linenos:

from pyramid.session import JSONSerializer
from pyramid.session import PickleSerializer

class JSONSerializerWithPickleFallback(object):
def __init__(self):
self.json = JSONSerializer()
self.pickle = PickleSerializer()

def dumps(self, value):
# maybe catch serialization errors here and keep using pickle
# while finding spots in your app that are not storing
# json-serializable objects, falling back to pickle
return self.json.dumps(value)

def loads(self, value):
try:
return self.json.loads(value)
except ValueError:
return self.pickle.loads(value)

.. index::
single: session object
Expand Down
8 changes: 8 additions & 0 deletions pyramid/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,14 @@ class ISession(IDict):

Keys and values of a session must be pickleable.

.. warning::

In :app:`Pyramid` 2.0 the session will only be required to support
types that can be serialized using JSON. It's recommended to switch any
session implementations to support only JSON and to only store primitive
types in sessions. See :ref:`pickle_session_deprecation` for more
information about why this change is being made.

.. versionchanged:: 1.9

Sessions are no longer required to implement ``get_csrf_token`` and
Expand Down
59 changes: 56 additions & 3 deletions pyramid/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
import hmac
import os
import time
import warnings

from zope.deprecation import deprecated
from zope.interface import implementer

from webob.cookies import SignedSerializer
from webob.cookies import (
JSONSerializer,
SignedSerializer,
)

from pyramid.compat import (
pickle,
Expand Down Expand Up @@ -60,6 +64,14 @@ def signed_serialize(data, secret):

cookieval = signed_serialize({'a':1}, 'secret')
response.set_cookie('signed_cookie', cookieval)

.. deprecated:: 1.10

This function will be removed in :app:`Pyramid` 2.0. It is using
pickle-based serialization, which is considered vulnerable to remote
code execution attacks and will no longer be used by the default
session factories at that time.

"""
pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
try:
Expand All @@ -70,6 +82,13 @@ def signed_serialize(data, secret):
sig = hmac.new(secret, pickled, hashlib.sha1).hexdigest()
return sig + native_(base64.b64encode(pickled))

deprecated(
'signed_serialize',
'This function will be removed in Pyramid 2.0. It is using pickle-based '
'serialization, which is considered vulnerable to remote code execution '
'attacks.',
)

def signed_deserialize(serialized, secret, hmac=hmac):
""" Deserialize the value returned from ``signed_serialize``. If
the value cannot be deserialized for any reason, a
Expand All @@ -82,6 +101,13 @@ def signed_deserialize(serialized, secret, hmac=hmac):

cookieval = request.cookies['signed_cookie']
data = signed_deserialize(cookieval, 'secret')

.. deprecated:: 1.10

This function will be removed in :app:`Pyramid` 2.0. It is using
pickle-based serialization, which is considered vulnerable to remote
code execution attacks and will no longer be used by the default
session factories at that time.
"""
# hmac parameterized only for unit tests
try:
Expand All @@ -105,6 +131,13 @@ def signed_deserialize(serialized, secret, hmac=hmac):

return pickle.loads(pickled)

deprecated(
'signed_deserialize',
'This function will be removed in Pyramid 2.0. It is using pickle-based '
'serialization, which is considered vulnerable to remote code execution '
'attacks.',
)


class PickleSerializer(object):
""" A serializer that uses the pickle protocol to dump Python
Expand All @@ -131,6 +164,10 @@ def dumps(self, appstruct):
"""Accept a Python object and return bytes."""
return pickle.dumps(appstruct, self.protocol)


JSONSerializer = JSONSerializer # api


def BaseCookieSessionFactory(
serializer,
cookie_name='session',
Expand All @@ -145,8 +182,6 @@ def BaseCookieSessionFactory(
set_on_exception=True,
):
"""
.. versionadded:: 1.5

Configure a :term:`session factory` which will provide cookie-based
sessions. The return value of this function is a :term:`session factory`,
which may be provided as the ``session_factory`` argument of a
Expand Down Expand Up @@ -508,6 +543,7 @@ def dumps(self, appstruct):
'so existing user session data will be destroyed if you switch to it.'
)


def SignedCookieSessionFactory(
secret,
cookie_name='session',
Expand Down Expand Up @@ -618,14 +654,31 @@ def SignedCookieSessionFactory(
should be raised for malformed inputs. If a serializer is not passed,
the :class:`pyramid.session.PickleSerializer` serializer will be used.

.. warning::

In :app:`Pyramid` 2.0 the default ``serializer`` option will change to
use :class:`pyramid.session.JSONSerializer`. See
:ref:`pickle_session_deprecation` for more information about why this
change is being made.

.. versionadded: 1.5a3

.. versionchanged: 1.10

Added the ``samesite`` option and made the default ``Lax``.

"""
if serializer is None:
serializer = PickleSerializer()
warnings.warn(
'The default pickle serializer is deprecated as of Pyramid 1.9 '
'and it will be changed to use pyramid.session.JSONSerializer in '
'version 2.0. Explicitly set the serializer to avoid future '
'incompatibilities. See "Upcoming Changes to ISession in '
'Pyramid 2.0" for more information about this change.',
DeprecationWarning,
stacklevel=1,
)

signed_serializer = SignedSerializer(
secret,
Expand Down