Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2023-39361 — تحليل خطوة بخطوة وإعداد مختبر لـ CVE-2023-39361، حقن SQL غير مصادق عليه في Cacti v1.2.24، مع دليل استغلال وتخفيف. | Kitploit
أدوات/GitHubGitHub/hpt-intern-task-submission/cve-2023-39361
تحليل الثغرات الأمنيةالاستغلالاستغلال تطبيقات الويبالتعلم والتعليممختبرات وتدريب عملي
GitHubhpt-intern-task-submission/cve-2023-39361

CVE-2023-39361

تحليل خطوة بخطوة وإعداد مختبر لـ CVE-2023-39361، حقن SQL غير مصادق عليه في Cacti v1.2.24، مع دليل استغلال وتخفيف.

عرض المستودع
3منذ 2 سنواتلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

[CVE-2023-39361] حقن SQL غير موثَّق في Cacti الإصدار 1.2.24

نظرة عامة

Cacti هي أداة مراقبة تشغيلية مفتوحة المصدر مكتوبة بلغة PHP وMySQL/MariaDB، وتوفر واجهة سهلة الاستخدام. تم اكتشاف الثغرة الأمنية في عام 2023 والتي أثرت على جميع الإصدارات قبل 1.2.24. يكمن هذا الخلل الأمني في التنفيذ غير السليم عند إدخال القيم في استعلام SQL. هذه ثغرة حقن SQL حرجة تسمح للمهاجمين بـ تعديل قاعدة البيانات وكذلك تنفيذ الأكواد عن بُعد.

إعداد المختبر:

في هذا التحليل، سأقوم بتشغيل Cacti في Docker للتبسيط. أولاً، سنقوم بإنشاء ملف docker-compose.yml كما هو موضح أدناه وتشغيل الأمر docker-compose up -d.

root@kitploit:~
version: '3.5'

services:

  
  

cacti:

image: "smcline06/cacti"

container_name: CVE-2023-39361

domainname: example.com

hostname: cacti

ports:

- "80:80"

- "443:443"

environment:

- DB_NAME=cacti_master

- DB_USER=cactiuser

- DB_PASS=cactipassword

- DB_HOST=db

- DB_PORT=3306

- DB_ROOT_PASS=rootpassword

- INITIALIZE_DB=1

- TZ=America/Los_Angeles

volumes:

- cacti-data:/cacti

- cacti-spine:/spine

- cacti-backups:/backups

links:

- db

  
  

db:

image: "mariadb:10.3"

container_name: CVE-2023-39361_db

domainname: example.com

hostname: db

ports:

- "3306:3306"

command:

- mysqld

- --character-set-server=utf8mb4

- --collation-server=utf8mb4_unicode_ci

- --max_connections=200

- --max_heap_table_size=128M

- --max_allowed_packet=32M

- --tmp_table_size=128M

- --join_buffer_size=128M

- --innodb_buffer_pool_size=1G

- --innodb_doublewrite=ON

- --innodb_flush_log_at_timeout=3

- --innodb_read_io_threads=32

- --innodb_write_io_threads=16

- --innodb_buffer_pool_instances=9

- --innodb_file_format=Barracuda

- --innodb_large_prefix=1

- --innodb_io_capacity=5000

- --innodb_io_capacity_max=10000

environment:

- MYSQL_ROOT_PASSWORD=rootpassword

- TZ=America/Los_Angeles

volumes:

- cacti-db:/var/lib/mysql

  
  

volumes:

cacti-db:

cacti-data:

cacti-spine:

cacti-backups:

بعد بناء الحاوية، يمكننا الوصول إلى Cacti عن طريق التصفح إلى http://localhost:80. الخطوة التالية هي تحديث Cacti إلى الإصدار 1.2.24. يمكنك تنزيل سكريبت التحديث من هنا: https://pastebin.com/NfRiHLjR. احفظ الملف في نفس الدليل مع ملف docker-compose.yml وأعد تسميته إلى upgrade_cacti.sh. ثم قم بتشغيل هذين الأمرين:

root@kitploit:~
docker cp upgrade_cacti.sh CVE-2023-39361:/tmp/upgrade_cacti.sh && docker exec -it CVE-2023-39361 bash -c "bash /tmp/upgrade_cacti.sh"

الآن نحن جاهزون، دعنا نتعمق في الشفرة لنرى ما حدث الذي سمح لنا بهجوم حقن SQL. الملف المعرض للخطر هو graph_view.php ونحتاج فقط إلى مستخدم ضيف للوصول إلى هذا الملف، مما يؤدي إلى السماح للجميع باستغلال هذه الثغرة. في هذا الملف، الدالة غير الآمنة هي grow_right_pane_tree().

grow_right_pane_tree

قبل أن نتعمق في هذه الدالة، نحتاج إلى تتبع المسار العكسي لمعرفة كيف يتم تنفيذ الدوال.

root@kitploit:~
<?php
switch (get_nfilter_request_var('action')) {
//.... 
// حالات أخرى كثيرة
case  'tree_content':
	//..... بعض الشيفرات هنا
	// ---------
	if (isset_request_var('node')) {
		$parts = explode('-', sanitize_search_string(get_request_var('node')));
		// التحقق من مرساة الشجرة
		if (strpos(get_nfilter_request_var('node'), 'tree_anchor') !== false) {
			$tree_id = $parts[1];
			$node_id = 0;
		} 
	//..... بعض الشيفرات هنا

	if ($tree_id > 0) {
		if (!is_tree_allowed($tree_id)) {
			header('Location: permission_denied.php');
			exit;
		}
		// يتم استدعاء الدالة المعرضة للخطر هنا
		grow_right_pane_tree($tree_id, $node_id, $hgdata);
	}

من مقتطف الشفرة أعلاه، يمكننا أن نرى أن الدالة سيتم تنفيذها إذا كان $tree_id > 0. يمكن شرح جميع الخطوات على النحو التالي:

  • أولاً، يقوم البرنامج بأخذ قيمة معامل action من إدخال المستخدم والدخول في جملة switch/case.

  • في حالة tree_content، تأخذ الشفرة معامل الطلب node من المستخدم. بعد ذلك، تقوم بتقسيم الإدخال بواسطة الحرف -، وحفظه في $part وأخيراً التحقق مما إذا كان tree_anchor موجوداً في node. إذا تم استيفاء جميع الشروط، سيتم تعيين $tree_id كعنصر ثانٍ في $part. على سبيل المثال، tree_content-1 أو 1-2-tree_content ستكون صالحة وستكون قيمة $tree_id على التوالي.

من هذه النقطة، سيبدو عنوان URL الخاص بنا هكذا: http://localhost:80?action=tree_content&node=tree_anchor-1. الآن حان الوقت لتحليل الدالة grow_right_pane_tree() لاستغلال الخلل.

grow_right_pane_tree_analyze

المعامل المعرض للخطر هو rfilter والذي يتم تمريره مباشرة إلى عامل RLIKE داخل جملة WHERE. ما يجعله عرضة للخطر هو أن rfilter محاط بعلامتي اقتباس مزدوجة "، ومع ذلك يتم التحقق من المعامل بواسطة الدالة html_validate_tree_vars()، والتي تبدو هكذا:

root@kitploit:~
function  html_validate_tree_vars() {
/* ================= التحقق من الإدخال وتخزين الجلسة ================= */
$filters = array(
	// .......
	'rfilter' => array(
			'filter' => FILTER_VALIDATE_IS_REGEX,
			'pageset' => true,
			'default' => '',
			),
	// ..........
	);

	validate_store_request_vars($filters, 'sess_grt');
	/* ================= التحقق من الإدخال ================= */
	// ........
}

تم تعيين نوع الفلتر الخاص بـ rfilter إلى FILTER_VALIDATE_IS_REGEX وتخزينه داخل $filters، ثم يتم تمرير $filter إلى الدالة validate_store_request_vars(). دعنا نتعمق في هذه الدالة، سيكشف هذا باقي تحليلنا:

root@kitploit:~
function validate_store_request_vars($filters, $sess_prefix = '') {
	// .......
	if (cacti_sizeof($filters)) {
		foreach($filters as $variable => $options) {
		// ....
		elseif ($options['filter'] == FILTER_VALIDATE_IS_REGEX) {
					if (is_base64_encoded($_REQUEST[$variable])) {
						$_REQUEST[$variable] = base64_decode($_REQUEST[$variable]);
					}
					$valid = validate_is_regex($_REQUEST[$variable]);
					if ($valid === true) {
						$value = $_REQUEST[$variable];
					} else {
						$value = false;
						$custom_error = $valid;
					}
				}
// ........
function validate_is_regex($regex) {
// ........
    if (@preg_match("'" . $regex . "'", NULL) !== false) {
		ini_set('track_errors', $track_errors);
		return true;
	}

الدالة validate_store_request_vars() مسؤولة عن التحقق من صحة إدخال المستخدم. عندما يكون نوع filter هو FILTER_VALIDATE_IS_REGEX، تستدعي الدالة validate_is_regex() مع الدالة preg_match للتحقق مما إذا كان إدخالنا صالحاً. وفقاً لوثائق PHP:

preg_match() ترجع 1 إذا كان النمط يطابق الموضوع المحدد، و0 إذا لم يطابق، أو false عند الفشل.

تستخدم جملة if مقارنة صارمة مما يعني أنه فقط عند حدوث خطأ لا يمكننا الدخول إلى جملة if. إن $regex، وهو إدخالنا، محاط بين علامتي اقتباس مفردة '. كان نية المطورين تجنب قيام المستخدم بحقن علامة الاقتباس المفردة '، والتي غالباً ما تستخدم لبدء هجوم حقن SQL. يبدو هذا وكأنه تنفيذ أمني قوي، لكن دعنا نلقي نظرة مرة أخرى على كيفية تمرير rfilter إلى جملة WHERE:

root@kitploit:~
$sql_where  .=  ' (gtg.title_cache RLIKE "'  .  get_request_var('rfilter') .  '" OR gtg.title RLIKE "'  .  get_request_var('rfilter') .  '")';

آه، لقد أصبح جهد التحقق من صحة إدخال المستخدم بلا معنى حيث أن rfilter محاط بعلامات اقتباس مزدوجة "، مما يسمح للمهاجمين بحقن " بسهولة للخروج من الجملة.

بعد أن فهمنا لماذا هي عرضة للخطر، سنتعمق الآن في الاستعلام حيث يتم تمرير $sql_where إليه.

grow_right_pane_tree_analyze

بعد تعيين $sql_where، سيقوم البرنامج باستدعاء الدالة get_allowed_tree_header_graphs(). ستبدو الدالة هكذا:

root@kitploit:~
function  get_allowed_tree_header_graphs($tree_id, $leaf_id = 0, $sql_where = '', $sql_order = 'gti.position', $sql_limit = '', &$total_rows = 0, $user_id = 0) {
	// ......
	if ($sql_where != '') {
		$sql_where = " AND ($sql_where)";
}
	$sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id)" . $sql_where;
	
	$graphs = db_fetch_assoc("SELECT gti.id, gti.title, gtg.local_graph_id, h.description, gt.name AS template_name, gtg.title_cache, gtg.width, gtg.height, gl.snmp_index, gl.snmp_query_id
	FROM graph_templates_graph AS gtg
	INNER JOIN graph_local AS gl
	ON gl.id = gtg.local_graph_id
	INNER JOIN graph_tree_items AS gti
	ON gti.local_graph_id = gl.id
	LEFT JOIN graph_templates AS gt
	ON gt.id = gl.graph_template_id
	LEFT JOIN host AS h
	ON h.id = gl.host_id
	$sql_where
	$sql_order
	$sql_limit");

	$sql = "SELECT  COUNT(*)
	FROM graph_templates_graph AS gtg
	INNER JOIN graph_local AS gl
	ON gl.id=gtg.local_graph_id
	INNER JOIN graph_tree_items AS gti
	ON gti.local_graph_id=gl.id
	LEFT JOIN graph_templates AS gt
	ON gt.id=gl.graph_template_id
	LEFT JOIN host AS h
	ON h.id=gl.host_id
	$sql_where";
	$total_rows = get_total_row_data($user_id, $sql, array(), 'graph');
	return  $graphs;
}

هذا هو المكان الذي يتم فيه تمرير $sql_where إلى استعلام. لاحظ أن المتغير يتم تعيينه مرتين أخريين قبل تمريره، وهما $sql_where = " AND ($sql_where)"; و $sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id)" . $sql_where;. بالدمج مع التعيين الأول، هكذا يتم إنشاء $sql_where في النهاية:

root@kitploit:~
$sql_where  .=  ' (gtg.title_cache RLIKE "'  .  get_request_var('rfilter') .  '" OR gtg.title RLIKE "'  .  get_request_var('rfilter') .  '")';
$sql_where = " AND ($sql_where)";
$sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id)" . $sql_where;

بعد هذه التعيينات الثلاثة، سيبدو $sql_where هكذا:

root@kitploit:~
$sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id) AND ((gtg.title_cache RLIKE "' . get_request_var('rfilter') . '" OR gtg.title RLIKE "' . get_request_var('rfilter') . '"))";

الاستعلام الذي يتم تمرير $sql_where إليه يبدو طاغياً، لكن يمكننا إنشاء استعلام بسيط آخر بهيكل مماثل:

select * from users WHERE (TRUE) AND ((username RLIKE "' get_request_var('rfilter') '" -- شيء خلفه ليس ضرورياً))

لحقن الحمولة بنجاح، نحتاج إلى الخروج من " و )). لذلك، قد تعمل هذه الحمولة "));SELECT SLEEP(5) --. ستبدو حمولتنا هكذا، select * from users WHERE (TRUE) AND **((username RLIKE "'"));SELECT SLEEP(5) --. يبدو جيداً! لنجربها على Cacti

failed_injection_1

السبب في أن الخادم لم ينتظر لمدة ثانيتين هو أننا تسببنا في خطأ في الدالة preg_match(). دعنا ننظر إلى الدالة مرة أخرى

root@kitploit:~
if (@preg_match("'"  .  $regex  .  "'", NULL) !== false) {
ini_set('track_errors', $track_errors);
return  true;
}

ستأخذ هذه الدالة إدخالنا كـ تعبير منتظم. والحرف ) يستخدم لتجميع مجموعة من الأحرف. نظراً لأنه غير مغلق، فقد أعادت الدالة خطأ. من ناحية أخرى، إذا جربنا "(());SELECT SLEEP(5) --، لا يمكننا الخروج من (( من استعلام SQL. ومع ذلك، هناك خدعة هنا، وهي "OR"(("));SELECT SLEEP(5) --. اسمح لي أن أشرح ذلك، لقد قمت بتغليف (( بين علامتي اقتباس مزدوجة لجعلها سلسلة في استعلام SQL واستخدام OR لمطابقة username قبلها. ثم، علامة الاقتباس المزدوجة " في التعبير المنتظم، تعتبر مجرد حرف عادي، ولهذا يمكننا الاستفادة من هذه الميزة للخروج من استعلام SQL.

successful_injection

بناءً على وقت الاستجابة، يمكننا التأكد من أننا نجحنا في حقن SQL في قاعدة البيانات. هذه ثغرة حقن SQL حرجة. يمكن للمهاجم استغلال هذا الخلل للتحكم في حساب المسؤول أو حتى تنفيذ أوامر عن بُعد.

التخفيف

كالعادة، تحدث ثغرات الحقن بشكل عام بسبب غياب التحقق من صحة الإدخال. ومع ذلك، في هذه الحالة، طبق المطورون عمليات التحقق، ولكن بشكل غير صحيح. لإصلاح ذلك، نقوم ببساطة بتغليف المعامل rfilter داخل علامة الاقتباس المفردة ' بدلاً من علامة الاقتباس المزدوجة "

fix

مع هذا الإصلاح البسيط، سيعمل جهد التحقق من صحة إدخال المستخدم بشكل صحيح الآن. لنجرب الحمولة مرة أخرى لنرى إذا كانت لا تزال عرضة للخطر.

fixed_request

مع نفس الحمولة ولكن الآن الاستجابة سريعة حقاً، مما يعني أنه لا يمكننا حقن أمر SQL بعد الآن.


من تحليل CVE هذا، يمكننا أن نتعلم أنه حتى الخطأ البسيط يمكن أن يؤدي إلى نتيجة كارثية. يجب علينا مراجعة الشفرة بعناية واختبار موقعنا بشكل متكرر لجعله أكثر أماناً. هذه نهاية التحليل، أتمنى أن تكون قد تعلمت شيئاً مفيداً اليوم. اختراق سعيد!

تنزيل الأداة