Security Advisory: Arbitrary file inclusion through docutils

Posted by Markus Holtermann on April 21, 2015

The docutils package is the standard package to render reStructuredText (reST). One of reST’s features is including other files in a document. The respective directives are active by default which is a valid case for the original use case of docutils: rendering documentation.

Django ≤ 1.5 has a package contrib.markup which relies on docutils and provides a template filter that is used to render reST to HTML on demand. This happens without deactivating the problematic directives to include files from the file system. If docutils’ rendering is used with unsanitized user input and without disabling the directives, an attacker can access arbitrary files on the host (at least, the files that the user running the WSGI container can access). This could eventually lead to disclosure of secure information, such as the project settings. This scenario has been documented.

In Django 1.6 the contrib.markup app was removed. However, many third-party applications in the Djangoverse still rely on docutils and also copied the pattern Django uses to allow deactivating the directives:

docutils_settings = getattr(settings, 'RESTRUCTUREDTEXT_FILTER_SETTINGS', {})
parts = publish_parts(
    source=smart_bytes(value),
    writer_name="html4css1",
    settings_overrides=docutils_settings
)
return force_text(parts["fragment"])

These packages may not contain the same warnings that Django's documentation includes, and in any case, it's a good idea to disable file inclusion by default in order to make things "secure by default" rather than relying on users disabling it explicitly.

In order to solve the arbitrary file inclusion package maintainers should adapt to the following pattern:

docutils_settings = {
    'raw_enabled': False,
    'file_insertion_enabled': False,
}
docutils_settings.update(getattr(settings, 'RESTRUCTUREDTEXT_FILTER_SETTINGS', {}))
parts = publish_parts(
    source=smart_bytes(value),
    writer_name="html4css1",
    settings_overrides=docutils_settings
)
return force_text(parts["fragment"])

Users of packages that use the above pattern should update their project settings to include:

RESTRUCTUREDTEXT_FILTER_SETTINGS = {
    'raw_enabled': False,
    'file_insertion_enabled': False,
}
Back to Top