Blog post

Dangerous Import: SourceForge Patches Critical Code Vulnerability

Stefan Schiller photo

Stefan Schiller

Vulnerability Researcher

8 min read

  • Security

Key Information

  • In October 2023, Sonar’s Vulnerability Research Team discovered a critical code vulnerability affecting the popular software platform SourceForge.
  • The vulnerability resides within the Apache Allura software used by SourceForge and is tracked as CVE-2023-46851.
  • Exploitation of the vulnerability would have allowed threat actors to fully compromise SourceForge.
  • This access could have been used to poison deployed files and spread malicious software to nearly 20 million users worldwide
  • SourceForge promptly reacted to our report and immediately disabled the affected feature.
  • The vulnerability has been fixed with Apache Allura version 1.16.0.
  • There were no signs of in-the-wild exploitation.

Introduction

SourceForge is a popular software platform that offers repositories for developers to manage their projects, collaborate with others, and distribute software to a wide audience. Although its popularity as a developer platform has decreased over the past few years, it is still actively used, with more than 2.6 million software downloads a day. It hosts popular projects like KeePass, Apache OpenOffice, or XAMPP.


The centralized nature of a software distribution platform like SourceForge makes it a highly appealing target for malicious actors. By compromising this platform, attackers could poison or backdoor deployed files and spread malicious software, affecting millions of users worldwide.


In our continuous effort to improve our Clean Code technology and contribute to the security of the open-source ecosystem, we decided to look into Apache Allura, which is the underlying software that powers SourceForge. We discovered a critical arbitrary file read vulnerability in the code (CVE-2023-46851) that attackers could leverage to gain remote code execution by signing a malicious serialized session.


In this article, we deep-dive into this code vulnerability and outline how attackers could exploit it. Furthermore, we describe how the vulnerability was fixed and provide general guidance on how to prevent code vulnerabilities like this.

Impact

Apache Allura versions 1.15.0 and below are prone to an arbitrary file read vulnerability. This vulnerability can be leveraged by attackers to retrieve the secret key used to sign session cookies. With access to this secret key, attackers can sign a malicious, serialized session and gain remote code execution:

Dangerous Import: SourceForge Patches Critical Code Vulnerability

The vulnerability was fixed with Apache Allura version 1.16.0.

Technical Details

In this section, we dive into the feature and related code that introduces the arbitrary file read vulnerability. Also, we explain how attackers could leverage the vulnerability to gain remote code execution.

Arbitrary File Read via Discussion Import (CVE-2023-46851)

Apache Allura allows users to self-register an account and create a new project. When a project is created, different tools can be activated for this project.

The tool of interest for our consideration is the Discussion tool. Selecting this tool adds a discussion forum to the project, where users can submit posts. For example, this is how the discussion for KeePass looks like on SourceForge:

Posts added to a discussion can have an optional file attachment:

The user who created the project is able to import/export an existing discussion. For these imports/exports, a JSON file is used. For example, the exported JSON file for the above post and attachment looks like this:

{"forums": [{
  ...
  "threads": [        
    { 
      "_id": "2a528fd3ca",
      "discussion_id": "65390c0d570ac08aec41aa79",
      "subject": "Test",
      ...
      "posts": [
        {
          "slug": "818a",
          "text": "This is a sample post.",
          "subject": "Test",
          ...
          {% mark yellow %}"attachments"{% mark %}: [
            {
              "bytes": 5,
              {% mark yellow %}"url": "http://allura.example.com/p/foo/discussion/general/thread/2a528fd3ca/818a/attachment/sample.txt"{% mark %},
              "path": "discussion/65390c0d570ac08aec41aa79/2a528fd3ca/818a/sample.txt"
            }
          ],
         ...

The highlighted text shows that the content of the attachment is not directly stored, but instead a URL, which references the stored attachment on the Allura server.


When a discussion is imported via a JSON file, the method add_posts in the class ForgeDiscussionImporter is responsible for re-creating the posts. This method passes the url attribute of each entry in the attachments to the constructor of the File class:

class ForgeDiscussionImporter(AlluraImporter):
    # [ ... ]
    def add_posts(self, thread, posts, app):
        created_posts = []
        for post_json in posts:
           # [ ... ]
                p = thread.add_post(...)
                # [ ... ]
                p.add_multiple_attachments(
                        [{% mark yellow %}File(a["url"]) for a in post_json["attachments"]{% mark %}]
                    )

The constructor of the File class passes the url attribute further on to the constructor of the ProjectExtractor class:

class File:
    def __init__(self, url, filename=None):
        extractor = {% mark yellow %}ProjectExtractor{% mark %}(None, {% mark yellow %}url{% mark %}, parser=bytesio_parser)

There are a few nested calls within the ProjectExtractor constructor, but the url parameter eventually ends up in a call to urlopen, which creates a Request object. This object is passed to h.urlopen:

class ProjectExtractor:
    # [ ... ]
    @staticmethod
    def urlopen(url, ...):
        req = six.moves.urllib.request.{% mark yellow %}Request{% mark %}({% mark yellow %}url{% mark %}, **kw)
        # [ ... ]
        return {% mark yellow %}h.urlopen{% mark %}({% mark yellow %}req{% mark %}, retries=retries, codes=codes, timeout=timeout)

This function uses urllib.request.urlopen to retrieve the content of the URL, which is then added to the attachment of the imported post:

def urlopen(url, retries=3, ...):
    # [ ... ]
    attempts = 0
    while True:
        try:
            return {% mark yellow %}six.moves.urllib.request.urlopen(url, timeout=timeout){% mark %}

Since the urllib.request.urlopen function also supports the file:// scheme, this is not only a Server-Side Request Forgery (SSRF) vulnerability, but also an arbitrary file read vulnerability. This vulnerability type is covered by Sonar’s rule S5144.


An attacker can exploit the discussion import feature to read arbitrary files from the server by crafting a JSON file with an attachment and setting the url attribute to a local file:

      ...
          {% mark yellow %}"attachments"{% mark %}: [
            {
              {% mark yellow %}"url": "file:///etc/passwd"{% mark %},
      ...

The /etc/passwd file will be read from the local file system and attached to the imported post:

Remote Code Execution via Signed Serialized Session

The arbitrary file read vulnerability can be used by attackers to read Allura’s configuration file. This file contains the session validation key (session.validate_key):

{% mark red %}session.validate_key = 714bfe3612c42390726f{% mark %}

Sessions are handled via the beaker.middleware.SessionMiddleware library:

{% mark yellow %}from beaker.middleware import SessionMiddleware{% mark %}
# [ ... ]
def _make_core_app(root, global_conf: dict, **app_conf):
    # [ ... ]
    app = SessionMiddleware(app, config, data_serializer={% mark yellow %}BeakerPickleSerializerWithLatin1{% mark %}())

The provided data_serializer (BeakerPickleSerlizerWithLatin1) uses pickle to deserialize session data provided in the session cookie:

class BeakerPickleSerializerWithLatin1(PickleSerializer):
    def loads(self, data_string):
        # [ ... ]
        return {% mark yellow %}pickle.loads(data_string, ...){% mark %}

Unserializing attacker-controlled data using Python’s pickle module allows attackers to execute arbitrary code. This danger arises due to pickle’s ability to serialize and deserialize complex Python objects (see Sonar’s rule S5135).


With access to the session validation key, an attacker can easily craft a malicious, serialized session. When this session cookie is sent to the server, the signature validation check is passed and the attacker-controlled session data will be deserialized. This gives an attacker the ability to execute arbitrary commands.

Patch

In an immediate response to our report, SourceForge completely disabled the discussion import feature. Furthermore, the usage of the feature was reviewed going back for many many years without noticing any attempted exploit.


The vulnerability itself was fixed with Apache Allura 1.16.0. One of the applied changes is a validation that the provided url starts with http:// or https://and a check to determine if the referenced host resolves to a private IP address:

-def urlopen(url, retries=3, codes=(408, 500, 502, 503, 504), timeout=None):
+def urlopen(url: str | urllib.request.Request, retries=3, codes=(408, 500, 502,
503, 504), timeout=None):
     # [ ...]
+    if isinstance(url, urllib.request.Request):
+        url_str =  url.full_url
+    else:
+        url_str = url
+    if not url_str.startswith(('http://', 'https://')):
+        raise ValueError(f'URL must be http(s), got {url_str}')
+    if not asbool(tg.config.get('urlopen_allow_internal_hostnames', 'false')):
+        # will raise error if hostname resolves to private address space:
+        validators.URLIsPrivate().to_python(url_str, None)
     attempts = 0
     while True:
         try:

Furthermore, the existing Pickle session has meanwhile been replaced with JSON Web Tokens (JWT). JWTs are a safer alternative as they cannot be used to serialize arbitrary Python objects. This way, a file read vulnerability does not immediately result in remote code execution via unsafe deserialization.

Timeline

DateAction
2023-10-27We report the issue to SourceForge and the Allura maintainers.
2023-10-27SourceForge and the Allura maintainers acknowledge our report.
2023-11-01The Allura maintainers share a patch with us.
2023-11-02We acknowledge the provided patch.
2023-11-06The Allura maintainers release patched version 1.16.0.

Summary

In this article, we described a critical arbitrary file read vulnerability in Apache Allura affecting the popular software platform SourceForge. Exploitation of this vulnerability could have led to the complete compromise of SourceForge and the poisoning of deployed files, potentially affecting nearly 20 million users worldwide.


It is not particularly surprising that the component affected by the vulnerability is an import feature. Import features are often prone to security vulnerabilities because imported data is not always validated to the same extent as data entered directly. Additionally, import features may enable modification of data that is otherwise restricted from being changed. That’s why we at Sonar prioritize Clean Code. This methodology ensures thorough validation on all code paths, leading to the development of resilient and secure applications.


Finally, we would like to thank SourceForge and the Apache Allura maintainers for promptly reacting to our report and providing a patch.

Related Blog Posts

Get new blogs delivered directly to your inbox!

Stay up-to-date with the latest Sonar content. Subscribe now to receive the latest blog articles. 

By submitting this form, you agree to the Privacy Policy and Cookie Policy.