CVE-2021-46426: Cross-Site Request Forgery (CSRF) (CWE-352)
value: phpIPAM 1.4.4 allows Reflected XSS and CSRF via app/admin/subnets/find_free_section_subnets.php of the subnets functionality.
CWE description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
CVSS v3 base score: 6.1
Published: 2022-03-25T16:15Z
Repository: https://github.com/phpipam/phpipam
Language: PHP
Files changed: app/admin/subnets/find_free_section_subnets.php, app/subnets/subnets-menu.php, functions/classes/class.Sections.php, misc/CHANGELOG
Commit date: 2022-01-17 22:14:10 +0000
Commit message: Bugfix: Security fix

    - XSS (reflected) in 'find subnets';

Reported by Celso Bezerra <celso.bezerra@tempest.com.br>

Diff: diff --git a/app/admin/subnets/find_free_section_subnets.php b/app/admin/subnets/find_free_section_subnets.php
index 5455eb706..86a90b8f9 100644
--- a/app/admin/subnets/find_free_section_subnets.php
+++ b/app/admin/subnets/find_free_section_subnets.php
@@ -18,10 +18,13 @@ $Tools		= new Tools ($Database);
 $Result 	= new Result ();
 
 # verify that user is logged in
 $User->check_user_session();
 
+# validate csrf cookie
+$User->Crypto->csrf_cookie ("validate", "find_free_section_subnets", $_POST['csrf_cookie']) === false ? $Result->show("danger", _("Invalid CSRF cookie"), true) : "";
+
 # verify that user has permissions to add subnet
 if($Sections->check_permission ($User->user, $_POST['sectionid']) != 3) { $Result->show("danger", _('You do not have permissions to add new subnet in this section')."!", true, true); }
 
 // fetch all section subnets
 $section_subnets = $Subnets->fetch_multiple_objects ("subnets", "sectionId", $_POST['sectionid'], "subnet", true, false, ["id","subnet","mask","isFolder", "masterSubnetId"]);
@@ -90,11 +93,11 @@ $(document).ready(function() {
     <tr>
         <td class="middle"><?php print _('Maximum results'); ?></td>
         <td>
             <div class="input-group">
                 <input type="text" class="form-control input-sm input-w-auto" name="results" value='50'>
-                <input type="hidden" name="sectionid" value='<?php print $_POST['sectionid']; ?>'>
+                <input type="hidden" name="sectionid" value='<?php print escape_input($_POST['sectionid']); ?>'>
             </div>
 
         </td>
         <td class="info2">
             <?php print _('Enter maximum number of results returned'); ?>
diff --git a/app/subnets/subnets-menu.php b/app/subnets/subnets-menu.php
index bf53330ee..115196003 100755
--- a/app/subnets/subnets-menu.php
+++ b/app/subnets/subnets-menu.php
@@ -7,10 +7,13 @@
  */
 
 # verify that user is logged in
 $User->check_user_session();
 
+# create csrf token
+$csrf_ffss = $User->Crypto->csrf_cookie ("create-if-not-exists", "find_free_section_subnets");
+
 # ID must be numeric
 if(!is_numeric($_GET['section'])) { $Result->show("danger",_('Invalid ID'), true); }
 
 
 # Admin check, otherwise load requested subnets
@@ -131,11 +134,11 @@ if($section_permission == 3) {
 	print "	<button class='btn btn-xs btn-default pull-left' id='hideSubnets' rel='tooltip' title='"._('Hide subnet list')."' data-placement='right'><i class='fa fa-gray fa-sm fa-chevron-left'></i></button>";
 	}
 	print "	<span>";
 	print "	<div class='btn-group'>";
 	print "	 <button id='add_subnet' class='btn btn-xs btn-default btn-success'  rel='tooltip' data-container='body'  data-placement='top' title='"._('Add new subnet to')." $section[name]'  data-subnetId='' data-sectionId='$section[id]' data-action='add'><i class='fa fa-sm fa-plus'></i></button>";
-	print "	 <button class='btn btn-xs btn-default btn-success open_popup' data-script='app/admin/subnets/find_free_section_subnets.php'  data-class='700' rel='tooltip' data-container='body'  data-placement='top' title='"._('Search for free subnets in section ')." $section[name]'  data-sectionId='$section[id]'><i class='fa fa-sm fa-search'></i></button>";
+	print "	 <button class='btn btn-xs btn-default btn-success open_popup' data-script='app/admin/subnets/find_free_section_subnets.php' data-csrf_cookie='$csrf_ffss' data-class='700' rel='tooltip' data-container='body'  data-placement='top' title='"._('Search for free subnets in section ')." $section[name]'  data-sectionId='$section[id]'><i class='fa fa-sm fa-search'></i></button>";
 	# snmp
 	if($User->settings->enableSNMP==1) {
     $csrf = $User->Crypto->csrf_cookie ("create", "scan");
 	print "	 <button class='btn btn-xs btn-default btn-success' id='snmp-routing-section'  rel='tooltip' data-container='body' data-sectionId='$section[id]' data-subnetId='0' data-csrf-cookie='$csrf'  data-placement='top' title='"._('Search for subnets through SNMP')."'><i class='fa fa-sm fa-cogs'></i></button>";
     }
diff --git a/functions/classes/class.Sections.php b/functions/classes/class.Sections.php
index bfdb42f4b..ed38de459 100644
--- a/functions/classes/class.Sections.php
+++ b/functions/classes/class.Sections.php
@@ -535,10 +535,13 @@ class Sections extends Common_functions {
 	 * @return string
 	 */
 	public function print_section_subnets_table($User, $sectionId, $showSupernetOnly = false) {
 		$html = array();
 
+		# create csrf token
+		$csrf_ffss = $User->Crypto->csrf_cookie ("create-if-not-exists", "find_free_section_subnets");
+
 		# set custom fields
 		$Tools = new Tools ($this->Database);
 		$custom = $Tools->fetch_custom_fields ("subnets");
 
 		# set hidden fields
@@ -554,11 +557,11 @@ class Sections extends Common_functions {
 		if ($permission != 0) {
 			// add
 			if ($permission>1) {
 				$html[] = "<div class='btn-group'>";
 				$html[] = '<button class="btn btn-sm btn-default btn-success editSubnet" data-action="add" data-sectionid="'.$sectionId.'" data-subnetId="" rel="tooltip" data-placement="left" title="'._('Add new subnet to section').'"><i class="fa fa-plus"></i> '._('Add subnet').'</button>';
-				$html[] = "<button class='btn btn-sm btn-default btn-success open_popup' data-script='app/admin/subnets/find_free_section_subnets.php'  data-class='700' rel='tooltip' data-container='body'  data-placement='top' title='"._('Search for free subnets in section ')."'  data-sectionId='$sectionId'><i class='fa fa-sm fa-search'></i> "._("Find subnet")."</button>";
+				$html[] = "<button class='btn btn-sm btn-default btn-success open_popup' data-script='app/admin/subnets/find_free_section_subnets.php' data-csrf_cookie='$csrf_ffss' data-class='700' rel='tooltip' data-container='body'  data-placement='top' title='"._('Search for free subnets in section ')."'  data-sectionId='$sectionId'><i class='fa fa-sm fa-search'></i> "._("Find subnet")."</button>";
 				$html[] = "</div>";
 			}
 
 			$html[] = '<table id="manageSubnets" class="table sorted-new table-striped table-condensed table-top table-no-bordered" data-pagination="true" data-cookie-id-table="sectionSubnets"  data-side-pagination="server" data-search="true" data-toggle="table" data-url="app/json/section/subnets.php?sectionId='.$sectionId.'&showSupernetOnly='.$showSupernetOnly.'">';
 			$html[] = '<thead><tr>';
diff --git a/misc/CHANGELOG b/misc/CHANGELOG
index 4ecbecf64..a576281cd 100755
--- a/misc/CHANGELOG
+++ b/misc/CHANGELOG
@@ -85,10 +85,11 @@
     + XSS (reflected) in IP calculator (#3351);
     + XSS in pass-change/result.php (#3373);
     + SQL injection in edit-bgp-mapping-search.php;
     + Stored XSS in the "Site title" parameter;
     + XSS while uploading CVS files;
+    + XSS (reflected) in 'find subnets';
 
     Translations:
     ----------------------------
     + Update Traditional Chinese support to version 1.5 (#2658);
     + Update Simplified Chinese Translation (#2725);

Vulnerable code:                 <input type="hidden" name="sectionid" value='<?php print $_POST['sectionid']; ?>'>
	print "	 <button class='btn btn-xs btn-default btn-success open_popup' data-script='app/admin/subnets/find_free_section_subnets.php'  data-class='700' rel='tooltip' data-container='body'  data-placement='top' title='"._('Search for free subnets in section ')." $section[name]'  data-sectionId='$section[id]'><i class='fa fa-sm fa-search'></i></button>";
				$html[] = "<button class='btn btn-sm btn-default btn-success open_popup' data-script='app/admin/subnets/find_free_section_subnets.php'  data-class='700' rel='tooltip' data-container='body'  data-placement='top' title='"._('Search for free subnets in section ')."'  data-sectionId='$sectionId'><i class='fa fa-sm fa-search'></i> "._("Find subnet")."</button>";
Fixed code: # validate csrf cookie
$User->Crypto->csrf_cookie ("validate", "find_free_section_subnets", $_POST['csrf_cookie']) === false ? $Result->show("danger", _("Invalid CSRF cookie"), true) : "";

                <input type="hidden" name="sectionid" value='<?php print escape_input($_POST['sectionid']); ?>'>
# create csrf token
$csrf_ffss = $User->Crypto->csrf_cookie ("create-if-not-exists", "find_free_section_subnets");

	print "	 <button class='btn btn-xs btn-default btn-success open_popup' data-script='app/admin/subnets/find_free_section_subnets.php' data-csrf_cookie='$csrf_ffss' data-class='700' rel='tooltip' data-container='body'  data-placement='top' title='"._('Search for free subnets in section ')." $section[name]'  data-sectionId='$section[id]'><i class='fa fa-sm fa-search'></i></button>";
		# create csrf token
		$csrf_ffss = $User->Crypto->csrf_cookie ("create-if-not-exists", "find_free_section_subnets");

				$html[] = "<button class='btn btn-sm btn-default btn-success open_popup' data-script='app/admin/subnets/find_free_section_subnets.php' data-csrf_cookie='$csrf_ffss' data-class='700' rel='tooltip' data-container='body'  data-placement='top' title='"._('Search for free subnets in section ')."'  data-sectionId='$sectionId'><i class='fa fa-sm fa-search'></i> "._("Find subnet")."</button>";
    + XSS (reflected) in 'find subnets';