CVE-2023-1212: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Stored in GitHub repository phpipam/phpipam prior to v1.5.2.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 4.8
Published: 2023-03-07T00:15Z
Repository: https://github.com/phpipam/phpipam
Language: PHP
Files changed: app/dashboard/index.php, functions/classes/class.Common.php, misc/CHANGELOG
Commit date: 2023-03-06 21:08:01 +0000
Commit message: Bugfix: XSS (stored) in user widget settings

Reported by Peng Zhou @zpbrent

Diff: diff --git a/app/dashboard/index.php b/app/dashboard/index.php
index 9c78bfaaf..bd5bc7ed9 100755
--- a/app/dashboard/index.php
+++ b/app/dashboard/index.php
@@ -116,12 +116,12 @@ if ($User->is_admin(false)!==true && (is_blank($User->user->groups) || $User->us
 # split widgets to rows (chunks)
 $currSize = 0;					//to calculate size
 $m=0;							//to calculate chunk index
 
 foreach($uwidgets as $uk=>$uv) {
-	//get fetails
-	$wdet = (array) $widgets[$uv];
+	//get details
+	$wdet = isset($widgets[$uv]) ? (array) $widgets[$uv] : ['wsize' => 6];
 	if(is_blank($wdet['wsize']))	{ $wsize = 6; }
 	else							{ $wsize = $wdet['wsize']; }
 
 	//calculate current size
 	$currSize = $currSize + $wsize;
@@ -146,34 +146,34 @@ if(sizeof($uwidgets)>1) {
 	print '<div class="row-fluid">';
 
 	foreach($uwidgetschunk as $w) {
 		# print itams in a row
 		foreach($w as $c) {
-
 			/* print items */
-			$wdet = (array) $widgets[$c];
 			if(array_key_exists($c, $widgets)) {
+				$wdet = (array) $widgets[$c];
+
 				//reset size if not set
 				if(is_blank($wdet['wsize']))	{ $wdet['wsize'] = 6; }
 
-				print "	<div class='col-xs-12 col-sm-12 col-md-12 col-lg-$wdet[wsize] widget-dash' id='w-$wdet[wfile]'>";
+				print "	<div class='col-xs-12 col-sm-12 col-md-12 col-lg-".escape_input($wdet['wsize'])." widget-dash' id='w-".escape_input($wdet['wfile'])."'>";
 				print "	<div class='inner'><i class='fa fa-times remove-widget icon-action fa-gray pull-right'></i>";
 				// href?
-				if($wdet['whref']=="yes")	{ print "<a href='".create_link("widgets",$wdet['wfile'])."'> <h4>"._($wdet['wtitle'])."<i class='fa fa-external-link fa-gray pull-right'></i></h4></a>"; }
-				else						{ print "<h4>"._($wdet['wtitle'])."</h4>"; }
+				if($wdet['whref']=="yes")	{ print "<a href='".create_link("widgets",$wdet['wfile'])."'> <h4>"._(escape_input($wdet['wtitle']))."<i class='fa fa-external-link fa-gray pull-right'></i></h4></a>"; }
+				else						{ print "<h4>"._(escape_input($wdet['wtitle']))."</h4>"; }
 				print "		<div class='hContent'>";
 				print "			<div style='text-align:center;padding-top:50px;'><strong>"._('Loading widget')."</strong><br><i class='fa fa-spinner fa-spin'></i></div>";
 				print "		</div>";
 				print "	</div>";
 				print "	</div>";
 
 			}
 			# invalid widget
 			else {
-				print "	<div class='col-xs-12 col-sm-12 col-md-12 col-lg-6' id='w-$c'>";
+				print "	<div class='col-xs-12 col-sm-12 col-md-12 col-lg-6' id='w-".escape_input($c)."'>";
 				print "	<div class='inner'>";
-				print "		<blockquote style='margin-top:20px;margin-left:20px;'><p>Invalid widget $c</p></blockquote>";
+				print "		<blockquote style='margin-top:20px;margin-left:20px;'><p>Invalid widget ".escape_input($c)."</p></blockquote>";
 				print "	</div>";
 				print "	</div>";
 			}
 
 		}
diff --git a/functions/classes/class.Common.php b/functions/classes/class.Common.php
index 96055336e..a9a31602d 100644
--- a/functions/classes/class.Common.php
+++ b/functions/classes/class.Common.php
@@ -672,11 +672,11 @@ class Common_functions  {
 		try {
 			$dom = new \DOMDocument();
 			if ($dom->loadHTML("<html>".$html."</html>", LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOBLANKS | LIBXML_NOWARNING | LIBXML_NOERROR) === false)
 				return "";
 
-			$banned_elements = ['script', 'iframe', 'embed'];
+			$banned_elements = ['script', 'iframe', 'embed', 'object'];
 			$remove_elements = [];
 
 			$elements = $dom->getElementsByTagName('*');
 
 			if (is_object($elements) && $elements->length>0) {
diff --git a/misc/CHANGELOG b/misc/CHANGELOG
index d10bb5ca1..76c0aa4f0 100755
--- a/misc/CHANGELOG
+++ b/misc/CHANGELOG
@@ -10,10 +10,11 @@
     + SQL injection in custom field enum/set types;
     + Directory traversal possible in RIPE query;
     + XSS (reflected) in 'bw-calulator-result.php';
     + XSS (reflected) by invalid email address response;
     + XSS (reflected) by /app/tools/subnet-masks/popup.php (#3738);
+    + XSS (stored) in user widget settings;
     + XSS and LDAP injection in ad-search-result.php;
     + XSS and LDAP injection in ad-search-group-result.php;
     + Restrict find_full_subnets.php to CLI;
     + Ensure confidentiality of database password;
 

Vulnerable code: 	//get fetails
	$wdet = (array) $widgets[$uv];

			$wdet = (array) $widgets[$c];
				print "	<div class='col-xs-12 col-sm-12 col-md-12 col-lg-$wdet[wsize] widget-dash' id='w-$wdet[wfile]'>";
				if($wdet['whref']=="yes")	{ print "<a href='".create_link("widgets",$wdet['wfile'])."'> <h4>"._($wdet['wtitle'])."<i class='fa fa-external-link fa-gray pull-right'></i></h4></a>"; }
				else						{ print "<h4>"._($wdet['wtitle'])."</h4>"; }
				print "	<div class='col-xs-12 col-sm-12 col-md-12 col-lg-6' id='w-$c'>";
				print "		<blockquote style='margin-top:20px;margin-left:20px;'><p>Invalid widget $c</p></blockquote>";
			$banned_elements = ['script', 'iframe', 'embed'];
Fixed code: 	//get details
	$wdet = isset($widgets[$uv]) ? (array) $widgets[$uv] : ['wsize' => 6];
				$wdet = (array) $widgets[$c];

				print "	<div class='col-xs-12 col-sm-12 col-md-12 col-lg-".escape_input($wdet['wsize'])." widget-dash' id='w-".escape_input($wdet['wfile'])."'>";
				if($wdet['whref']=="yes")	{ print "<a href='".create_link("widgets",$wdet['wfile'])."'> <h4>"._(escape_input($wdet['wtitle']))."<i class='fa fa-external-link fa-gray pull-right'></i></h4></a>"; }
				else						{ print "<h4>"._(escape_input($wdet['wtitle']))."</h4>"; }
				print "	<div class='col-xs-12 col-sm-12 col-md-12 col-lg-6' id='w-".escape_input($c)."'>";
				print "		<blockquote style='margin-top:20px;margin-left:20px;'><p>Invalid widget ".escape_input($c)."</p></blockquote>";
			$banned_elements = ['script', 'iframe', 'embed', 'object'];
    + XSS (stored) in user widget settings;