CVE-2019-1000010: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: phpIPAM version 1.3.2 and earlier contains a Cross Site Scripting (XSS) vulnerability in subnet-scan-telnet.php that can result in executing code in victims browser. This attack appears to be exploitable via victim visits link crafted by an attacker. This vulnerability appears to have been fixed in 1.4.
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: 6.1
Published: 2019-02-04T21:29Z
Repository: https://github.com/phpipam/phpipam
Language: PHP
Files changed: app/subnets/scan/subnet-scan-execute-scan-icmp.php, app/subnets/scan/subnet-scan-execute-scan-telnet.php, app/subnets/scan/subnet-scan-execute-snmp-arp.php, app/subnets/scan/subnet-scan-execute-snmp-mac.php, app/subnets/scan/subnet-scan-execute-update-icmp.php, app/subnets/scan/subnet-scan-execute-update-snmp-arp.php, app/subnets/scan/subnet-scan-execute.php, app/subnets/scan/subnet-scan-result.php, app/subnets/scan/subnet-scan.php, functions/version.php, js/magic.js, misc/CHANGELOG
Commit date: 2018-12-08 13:35:39 +0000
Commit message: Bugfix: XSS in subnet-scan-telnet.php, CSRF in subnet-scan-execute.php. Fixes #2327

Diff: diff --git a/app/subnets/scan/subnet-scan-execute-scan-icmp.php b/app/subnets/scan/subnet-scan-execute-scan-icmp.php
index f749212c..b2959704 100644
--- a/app/subnets/scan/subnet-scan-execute-scan-icmp.php
+++ b/app/subnets/scan/subnet-scan-execute-scan-icmp.php
@@ -2,15 +2,12 @@
 
 /*
  * Discover new hosts with ping
  *******************************/
 
-# verify that user is logged in
-$User->check_user_session();
-
-# create csrf token
-$csrf = $User->Crypto->csrf_cookie ("create", "scan");
+# Check we have been included and not called directly
+if (!isset($subnet_scan_execute_included)) { $Result->show("danger", _("Invalid request"), true); }
 
 # validate subnetId and type
 if(!is_numeric($_POST['subnetId']))                        { $Result->show("danger", "Invalid subnet Id", true); die(); }
 if(!preg_match('/[^A-Za-z0-9-]*$/', $_POST['type']))       { $Result->show("danger", "Invalid scan type", true); die(); }
 
diff --git a/app/subnets/scan/subnet-scan-execute-scan-telnet.php b/app/subnets/scan/subnet-scan-execute-scan-telnet.php
index e17fb3c1..a4b80c57 100755
--- a/app/subnets/scan/subnet-scan-execute-scan-telnet.php
+++ b/app/subnets/scan/subnet-scan-execute-scan-telnet.php
@@ -2,21 +2,21 @@
 
 /*
  * Discover new hosts with telnet scan
  *******************************/
 
-# verify that user is logged in
-$User->check_user_session();
+# Check we have been included and not called directly
+if (!isset($subnet_scan_execute_included)) { $Result->show("danger", _("Invalid request"), true); }
 
 # get ports
-if(strlen($_POST['port'])==0) 	  { $Result->show("danger", _('Please enter ports to scan').'!', true); }
+if(empty($_POST['port'])) 	  { $Result->show("danger", _('Please enter ports to scan').'!', true); }
 
 //verify ports
 $pcheck = explode(";", str_replace(",",";",$_POST['port']));
 foreach($pcheck as $p) {
 	if(!is_numeric($p)) {
-		$Result->show("danger", _("Invalid port")." ($p)", true);
+		$Result->show("danger", _("Invalid port").' ('.escape_input($p).')', true);
 	}
 }
 $_POST['port'] = str_replace(";",",",$_POST['port']);
 
 // verify subnetId
diff --git a/app/subnets/scan/subnet-scan-execute-snmp-arp.php b/app/subnets/scan/subnet-scan-execute-snmp-arp.php
index 2f80c001..c2ddb630 100644
--- a/app/subnets/scan/subnet-scan-execute-snmp-arp.php
+++ b/app/subnets/scan/subnet-scan-execute-snmp-arp.php
@@ -2,15 +2,12 @@
 
 /*
  * Discover new hosts with snmp
  *******************************/
 
-# verify that user is logged in
-$User->check_user_session();
-
-# create csrf token
-$csrf = $User->Crypto->csrf_cookie ("create", "scan");
+# Check we have been included and not called directly
+if (!isset($subnet_scan_execute_included)) { $Result->show("danger", _("Invalid request"), true); }
 
 //title
 print "<h5>"._('Scan results').":</h5><hr>";
 
 # scan disabled
diff --git a/app/subnets/scan/subnet-scan-execute-snmp-mac.php b/app/subnets/scan/subnet-scan-execute-snmp-mac.php
index bc4591ef..5b55f1fa 100644
--- a/app/subnets/scan/subnet-scan-execute-snmp-mac.php
+++ b/app/subnets/scan/subnet-scan-execute-snmp-mac.php
@@ -2,15 +2,12 @@
 
 /*
  * Discover new hosts with snmp
  *******************************/
 
-# verify that user is logged in
-$User->check_user_session();
-
-# create csrf token
-$csrf = $User->Crypto->csrf_cookie ("create", "scan");
+# Check we have been included and not called directly
+if (!isset($subnet_scan_execute_included)) { $Result->show("danger", _("Invalid request"), true); }
 
 //title
 print "<h5>"._('Scan results').":</h5><hr>";
 
 # verify that user has write permissionss for subnet
diff --git a/app/subnets/scan/subnet-scan-execute-update-icmp.php b/app/subnets/scan/subnet-scan-execute-update-icmp.php
index d0d53453..df972083 100755
--- a/app/subnets/scan/subnet-scan-execute-update-icmp.php
+++ b/app/subnets/scan/subnet-scan-execute-update-icmp.php
@@ -2,10 +2,13 @@
 
 /*
  * Update alive status of all hosts in subnet
  ***************************/
 
+# Check we have been included and not called directly
+if (!isset($subnet_scan_execute_included)) { $Result->show("danger", _("Invalid request"), true); }
+
 # validate subnetId and type
 if(!is_numeric($_POST['subnetId']))                        { $Result->show("danger", "Invalid subnet Id", true); die(); }
 
 # invoke CLI with threading support
 $cmd = $Scan->php_exec." ".dirname(__FILE__) . '/../../../functions/scan/subnet-scan-icmp-execute.php'." 'update' ".$_POST['subnetId'];
diff --git a/app/subnets/scan/subnet-scan-execute-update-snmp-arp.php b/app/subnets/scan/subnet-scan-execute-update-snmp-arp.php
index 2df8776c..d54728b4 100644
--- a/app/subnets/scan/subnet-scan-execute-update-snmp-arp.php
+++ b/app/subnets/scan/subnet-scan-execute-update-snmp-arp.php
@@ -2,12 +2,12 @@
 
 /*
  * Discover new hosts with snmp
  *******************************/
 
-# verify that user is logged in
-$User->check_user_session();
+# Check we have been included and not called directly
+if (!isset($subnet_scan_execute_included)) { $Result->show("danger", _("Invalid request"), true); }
 
 # scan disabled
 if ($User->settings->enableSNMP!="1")           { $Result->show("danger", "SNMP module disbled", true); }
 # subnet check
 $subnet = $Subnets->fetch_subnet ("id", $_POST['subnetId']);
diff --git a/app/subnets/scan/subnet-scan-execute.php b/app/subnets/scan/subnet-scan-execute.php
index dea64e95..1b642c23 100755
--- a/app/subnets/scan/subnet-scan-execute.php
+++ b/app/subnets/scan/subnet-scan-execute.php
@@ -22,10 +22,13 @@ $Result 	= new Result ();
 # verify that user is logged in
 $User->check_user_session();
 # check maintaneance mode
 $User->check_maintaneance_mode ();
 
+# validate csrf cookie
+$User->Crypto->csrf_cookie ("validate", "scan", $_POST['csrf_cookie']) === false ? $Result->show("danger", _("Invalid CSRF cookie"), true) : "";
+
 # subnet Id must be a integer
 if(!is_numeric($_POST['subnetId']))	{ $Result->show("danger", _("Invalid ID"), true); }
 
 # verify that user has write permissionss for subnet
 if($Subnets->check_permission ($User->user, $_POST['subnetId']) != 3) 	{ $Result->show("danger", _('You do not have permissions to modify hosts in this subnet')."!", true, true); }
@@ -41,32 +44,24 @@ $_POST['sectionId'] = $subnet->sectionId;
 if ($_POST['type']!="update-icmp" && $subnet->isFull==1)                { $Result->show("warning", _("Cannot scan as subnet is market as used"), true, true); }
 
 # verify php path
 if(!file_exists($Scan->php_exec))	{ $Result->show("danger", _("Invalid php path"), true, true); }
 
-# scan
-switch ($_POST['type']) {
+$type = $_POST['type'];
+
+switch ($type) {
+#scan
     case "scan-icmp":
-        include("subnet-scan-execute-scan-icmp.php");
-        break;
     case "scan-telnet":
-        include("subnet-scan-execute-scan-telnet.php");
-        break;
     case "snmp-arp":
-        include("subnet-scan-execute-snmp-arp.php");
-        break;
     case "snmp-mac":
-        include("subnet-scan-execute-snmp-mac.php");
-        break;
     case "snmp-route-all":
-        include("subnet-scan-execute-snmp-route-all.php");
-        break;
 # discovery
     case "update-icmp":
-        include("subnet-scan-execute-update-icmp.php");
-        break;
     case "update-snmp-arp":
-        include("subnet-scan-execute-update-snmp-arp.php");
+        $csrf = $_POST['csrf_cookie'];
+        $subnet_scan_execute_included = true;
+        require("subnet-scan-execute-$type.php");
         break;
     default:
         $Result->show("danger", _("Invalid scan type"), true);
 }
diff --git a/app/subnets/scan/subnet-scan-result.php b/app/subnets/scan/subnet-scan-result.php
index 5676960c..30c201de 100644
--- a/app/subnets/scan/subnet-scan-result.php
+++ b/app/subnets/scan/subnet-scan-result.php
@@ -27,10 +27,10 @@ switch ($type) {
     case "scan-telnet":
     case "snmp-arp":
     case "snmp-mac":
     case "snmp-route-all":
         $subnet_scan_result_included = true;
-        include("subnet-scan-result-$type.php");
+        require("subnet-scan-result-$type.php");
         break;
     default:
         $Result->show("danger", _("Invalid scan type"), true);
 }
\ No newline at end of file
diff --git a/app/subnets/scan/subnet-scan.php b/app/subnets/scan/subnet-scan.php
index f7b81dd0..fa44cdc6 100755
--- a/app/subnets/scan/subnet-scan.php
+++ b/app/subnets/scan/subnet-scan.php
@@ -15,10 +15,12 @@ $Subnets	= new Subnets ($Database);
 $Result 	= new Result ();
 
 # verify that user is logged in
 $User->check_user_session();
 
+# create csrf token
+$csrf = $User->Crypto->csrf_cookie ("create", "scan");
 
 # ID must be numeric
 if(!is_numeric($_POST['subnetId']))										{ $Result->show("danger", _("Invalid ID"), true, true); }
 
 # verify that user has write permissionss for subnet
@@ -102,10 +104,10 @@ $subnet->description = strlen($subnet->description)>0 ? "(".$subnet->description
 
 <!-- footer -->
 <div class="pFooter">
 	<div class="btn-group">
 		<button class="btn btn-sm btn-default hidePopups"><?php print _('Cancel'); ?></button>
-		<button class="btn btn-sm btn-default btn-success" id="subnetScanSubmit" data-subnetId='<?php print $_POST['subnetId']; ?>'><i class="fa fa-gears"></i> <?php print _('Scan subnet'); ?></button>
+		<button class="btn btn-sm btn-default btn-success" id="subnetScanSubmit" data-subnetId='<?php print $_POST['subnetId']; ?>' data-csrf-cookie='<?php print $csrf; ?>'><i class="fa fa-gears"></i> <?php print _('Scan subnet'); ?></button>
 	</div>
 
 	<div class="subnetTruncateResult"></div>
 </div>
\ No newline at end of file
diff --git a/functions/version.php b/functions/version.php
index 23b38838..b0c393aa 100644
--- a/functions/version.php
+++ b/functions/version.php
@@ -2,11 +2,11 @@
 /* set latest version */
 define("VERSION", "1.4");									//decimal release version e.g 1.32
 /* set latest version */
 define("VERSION_VISIBLE", "1.4");							//visible version in footer e.g 1.3.2
 /* set latest revision */
-define("REVISION", "030");									//increment on static content changes (js/css) or point releases to avoid caching issues
+define("REVISION", "031");									//increment on static content changes (js/css) or point releases to avoid caching issues
 /* set last possible upgrade */
 define("LAST_POSSIBLE", "1.1");								//minimum required version to be able to upgrade
 
 // Automatically set DBVERSION as everyone forgets!
 function get_dbversion() {
diff --git a/js/magic.js b/js/magic.js
index faa9415f..3cf50f09 100755
--- a/js/magic.js
+++ b/js/magic.js
@@ -730,16 +730,17 @@ $(document).on('change', "table.table-scan select#type", function() {
 //start scanning
 $(document).on('click','#subnetScanSubmit', function() {
 	showSpinner();
 	$('#subnetScanResult').slideUp('fast');
 	var subnetId = $(this).attr('data-subnetId');
+	var csrf     = $(this).attr('data-csrf-cookie');
 	var type 	 = $('select[name=type]').find(":selected").val();
 	if($('input[name=debug]').is(':checked'))	{ var debug = 1; }
 	else										{ var debug = 0; }
 	var port     = $('input[name=telnetports]').val();
 	$('#alert-scan').slideUp('fast');
-	$.post('app/subnets/scan/subnet-scan-execute.php', {subnetId:subnetId, type:type, debug:debug, port:port}, function(data) {
+	$.post('app/subnets/scan/subnet-scan-execute.php', {subnetId:subnetId, type:type, debug:debug, port:port, csrf_cookie:csrf}, function(data) {
         $('#subnetScanResult').html(data).slideDown('fast');
 		hideSpinner();
     }).fail(function(jqxhr, textStatus, errorThrown) { showError(jqxhr.statusText + "<br>Status: " + textStatus + "<br>Error: "+errorThrown); });	return false;
 });
 //remove result
diff --git a/misc/CHANGELOG b/misc/CHANGELOG
index 8fde1761..2d202ce8 100755
--- a/misc/CHANGELOG
+++ b/misc/CHANGELOG
@@ -51,10 +51,11 @@
     + Hide LDAP/AD/Radius username / password and secret from logs and syslog (#2006);
     + XSS in phpipamredirect cookie (#2338);
     + XSS in print-user.php, CSRF in user-edit.php (#2326);
     + Possible SQL injection in nat/item-add-submit.php (#2344);
     + Fixed possible file inclusion in editing authenticaiton methods;
+    + XSS in subnet-scan-telnet.php, CSRF in subnet-scan-execute.php (#2327);
 
 == 1.3.2
 
     Enhancements, changes:
     ----------------------------

Vulnerable code: # verify that user is logged in
$User->check_user_session();

# create csrf token
$csrf = $User->Crypto->csrf_cookie ("create", "scan");
# verify that user is logged in
$User->check_user_session();
if(strlen($_POST['port'])==0) 	  { $Result->show("danger", _('Please enter ports to scan').'!', true); }
		$Result->show("danger", _("Invalid port")." ($p)", true);
# verify that user is logged in
$User->check_user_session();

# create csrf token
$csrf = $User->Crypto->csrf_cookie ("create", "scan");
# verify that user is logged in
$User->check_user_session();

# create csrf token
$csrf = $User->Crypto->csrf_cookie ("create", "scan");
# verify that user is logged in
$User->check_user_session();
# scan
switch ($_POST['type']) {
        include("subnet-scan-execute-scan-icmp.php");
        break;
        include("subnet-scan-execute-scan-telnet.php");
        break;
        include("subnet-scan-execute-snmp-arp.php");
        break;
        include("subnet-scan-execute-snmp-mac.php");
        break;
        include("subnet-scan-execute-snmp-route-all.php");
        break;
        include("subnet-scan-execute-update-icmp.php");
        break;
        include("subnet-scan-execute-update-snmp-arp.php");
        include("subnet-scan-result-$type.php");
		<button class="btn btn-sm btn-default btn-success" id="subnetScanSubmit" data-subnetId='<?php print $_POST['subnetId']; ?>'><i class="fa fa-gears"></i> <?php print _('Scan subnet'); ?></button>
define("REVISION", "030");									//increment on static content changes (js/css) or point releases to avoid caching issues
	$.post('app/subnets/scan/subnet-scan-execute.php', {subnetId:subnetId, type:type, debug:debug, port:port}, function(data) {
Fixed code: # Check we have been included and not called directly
if (!isset($subnet_scan_execute_included)) { $Result->show("danger", _("Invalid request"), true); }
# Check we have been included and not called directly
if (!isset($subnet_scan_execute_included)) { $Result->show("danger", _("Invalid request"), true); }
if(empty($_POST['port'])) 	  { $Result->show("danger", _('Please enter ports to scan').'!', true); }
		$Result->show("danger", _("Invalid port").' ('.escape_input($p).')', true);
# Check we have been included and not called directly
if (!isset($subnet_scan_execute_included)) { $Result->show("danger", _("Invalid request"), true); }
# Check we have been included and not called directly
if (!isset($subnet_scan_execute_included)) { $Result->show("danger", _("Invalid request"), true); }
# Check we have been included and not called directly
if (!isset($subnet_scan_execute_included)) { $Result->show("danger", _("Invalid request"), true); }

# Check we have been included and not called directly
if (!isset($subnet_scan_execute_included)) { $Result->show("danger", _("Invalid request"), true); }
# validate csrf cookie
$User->Crypto->csrf_cookie ("validate", "scan", $_POST['csrf_cookie']) === false ? $Result->show("danger", _("Invalid CSRF cookie"), true) : "";

$type = $_POST['type'];

switch ($type) {
#scan
        $csrf = $_POST['csrf_cookie'];
        $subnet_scan_execute_included = true;
        require("subnet-scan-execute-$type.php");
        require("subnet-scan-result-$type.php");
# create csrf token
$csrf = $User->Crypto->csrf_cookie ("create", "scan");
		<button class="btn btn-sm btn-default btn-success" id="subnetScanSubmit" data-subnetId='<?php print $_POST['subnetId']; ?>' data-csrf-cookie='<?php print $csrf; ?>'><i class="fa fa-gears"></i> <?php print _('Scan subnet'); ?></button>
define("REVISION", "031");									//increment on static content changes (js/css) or point releases to avoid caching issues
	var csrf     = $(this).attr('data-csrf-cookie');
	$.post('app/subnets/scan/subnet-scan-execute.php', {subnetId:subnetId, type:type, debug:debug, port:port, csrf_cookie:csrf}, function(data) {
    + XSS in subnet-scan-telnet.php, CSRF in subnet-scan-execute.php (#2327);