CVE-2020-14971: Missing Authorization (CWE-862)
value: Pi-hole through 5.0 allows code injection in piholedhcp (the Static DHCP Leases section) by modifying Teleporter backup files and then restoring them. This occurs in settings.php. To exploit this, an attacker would request a backup of limited files via teleporter.php. These are placed into a .tar.gz archive. The attacker then modifies the host parameter in dnsmasq.d files, and then compresses and uploads these files again.
CWE description: The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
CVSS v3 base score: 7.8
Published: 2020-06-23T14:15Z
Repository: https://github.com/pi-hole/AdminLTE
Language: PHP
Files changed: dns_records.php, scripts/pi-hole/js/customdns.js, scripts/pi-hole/js/groups-adlists.js, scripts/pi-hole/js/groups-clients.js, scripts/pi-hole/js/groups-domains.js, scripts/pi-hole/js/groups.js, scripts/pi-hole/php/database.php, scripts/pi-hole/php/func.php, scripts/pi-hole/php/groups.php, scripts/pi-hole/php/teleporter.php, settings.php
Commit date: 2020-06-13 18:50:36 +0100
Commit message: make use of utils.escapeHtml on the JS side of things, and html_entity_decode/htmlentities in PHP

Signed-off-by: Adam Warner <me@adamwarner.co.uk>

Diff: diff --git a/dns_records.php b/dns_records.php
index 4d429af4..dc9e5d5f 100644
--- a/dns_records.php
+++ b/dns_records.php
@@ -87,10 +87,11 @@
         </div>
         <!-- /.box -->
     </div>
 </div>
 
+<script src="scripts/pi-hole/js/utils.js"></script>
 <script src="scripts/pi-hole/js/ip-address-sorting.js"></script>
 <script src="scripts/pi-hole/js/customdns.js"></script>
 
 <?php
 require "scripts/pi-hole/php/footer.php";
diff --git a/scripts/pi-hole/js/customdns.js b/scripts/pi-hole/js/customdns.js
index 116ea602..d2f4ad56 100644
--- a/scripts/pi-hole/js/customdns.js
+++ b/scripts/pi-hole/js/customdns.js
@@ -3,10 +3,12 @@
  *  Network-wide ad blocking via your own hardware.
  *
  *  This file is copyright under the latest version of the EUPL.
  *  Please see LICENSE file for your rights under this license. */
 
+/* global utils:false */
+
 var table;
 var token = $("#token").text();
 
 function showAlert(type, message) {
   var alertElement = null;
@@ -74,12 +76,12 @@ $(function () {
   input.setAttribute("autocapitalize", "off");
   input.setAttribute("spellcheck", false);
 });
 
 function addCustomDNS() {
-  var ip = $("#ip").val();
-  var domain = $("#domain").val();
+  var ip = utils.escapeHtml($("#ip").val());
+  var domain = utils.escapeHtml($("#domain").val());
 
   showAlert("info");
   $.ajax({
     url: "scripts/pi-hole/php/customdns.php",
     method: "post",
diff --git a/scripts/pi-hole/js/groups-adlists.js b/scripts/pi-hole/js/groups-adlists.js
index 4bcdde81..4f58b1c4 100644
--- a/scripts/pi-hole/js/groups-adlists.js
+++ b/scripts/pi-hole/js/groups-adlists.js
@@ -210,12 +210,12 @@ function initTable() {
     input.setAttribute("spellcheck", false);
   }
 }
 
 function addAdlist() {
-  var address = $("#new_address").val();
-  var comment = $("#new_comment").val();
+  var address = utils.escapeHtml($("#new_address").val());
+  var comment = utils.escapeHtml($("#new_comment").val());
 
   utils.disableAll();
   utils.showAlert("info", "", "Adding adlist...", address);
 
   if (address.length === 0) {
@@ -256,13 +256,13 @@ function addAdlist() {
 function editAdlist() {
   var elem = $(this).attr("id");
   var tr = $(this).closest("tr");
   var id = tr.attr("data-id");
   var status = tr.find("#status_" + id).is(":checked") ? 1 : 0;
-  var comment = tr.find("#comment_" + id).val();
+  var comment = utils.escapeHtml(tr.find("#comment_" + id).val());
   var groups = tr.find("#multiselect_" + id).val();
-  var address = tr.find("#address_" + id).text();
+  var address = utils.escapeHtml(tr.find("#address_" + id).text());
 
   var done = "edited";
   var notDone = "editing";
   switch (elem) {
     case "status_" + id:
@@ -336,11 +336,11 @@ function editAdlist() {
 }
 
 function deleteAdlist() {
   var tr = $(this).closest("tr");
   var id = tr.attr("data-id");
-  var address = tr.find("#address_" + id).text();
+  var address = utils.escapeHtml(tr.find("#address_" + id).text());
 
   utils.disableAll();
   utils.showAlert("info", "", "Deleting adlist...", address);
   $.ajax({
     url: "scripts/pi-hole/php/groups.php",
diff --git a/scripts/pi-hole/js/groups-clients.js b/scripts/pi-hole/js/groups-clients.js
index 609606ce..70ff81ff 100644
--- a/scripts/pi-hole/js/groups-clients.js
+++ b/scripts/pi-hole/js/groups-clients.js
@@ -244,13 +244,13 @@ function initTable() {
   });
 }
 
 function addClient() {
   var ip = $("#select").val();
-  var comment = $("#new_comment").val();
+  var comment = utils.escapeHtml($("#new_comment").val());
   if (ip === "custom") {
-    ip = $("#ip-custom").val().trim();
+    ip = utils.escapeHtml($("#ip-custom").val().trim());
   }
 
   utils.disableAll();
   utils.showAlert("info", "", "Adding client...", ip);
 
@@ -301,13 +301,13 @@ function addClient() {
 function editClient() {
   var elem = $(this).attr("id");
   var tr = $(this).closest("tr");
   var id = tr.attr("data-id");
   var groups = tr.find("#multiselect_" + id).val();
-  var ip = tr.find("#ip_" + id).text();
-  var name = tr.find("#name_" + id).text();
-  var comment = tr.find("#comment_" + id).val();
+  var ip = utils.escapeHtml(tr.find("#ip_" + id).text());
+  var name = utils.escapeHtml(tr.find("#name_" + id).text());
+  var comment = utils.escapeHtml(tr.find("#comment_" + id).val());
 
   var done = "edited";
   var notDone = "editing";
   switch (elem) {
     case "multiselect_" + id:
@@ -368,11 +368,11 @@ function editClient() {
 
 function deleteClient() {
   var tr = $(this).closest("tr");
   var id = tr.attr("data-id");
   var ip = tr.find("#ip_" + id).text();
-  var name = tr.find("#name_" + id).text();
+  var name = utils.escapeHtml(tr.find("#name_" + id).text());
 
   if (name.length > 0) {
     ip += " (" + name + ")";
   }
 
diff --git a/scripts/pi-hole/js/groups-domains.js b/scripts/pi-hole/js/groups-domains.js
index 74768f8d..5128ef14 100644
--- a/scripts/pi-hole/js/groups-domains.js
+++ b/scripts/pi-hole/js/groups-domains.js
@@ -316,12 +316,12 @@ function addDomain() {
     domainRegex = "regex";
     domainEl = $("#new_regex");
     commentEl = $("#new_regex_comment");
   }
 
-  var domain = domainEl.val();
-  var comment = commentEl.val();
+  var domain = utils.escapeHtml(domainEl.val());
+  var comment = utils.escapeHtml(commentEl.val());
 
   utils.disableAll();
   utils.showAlert("info", "", "Adding " + domainRegex + "...", domain);
 
   if (domain.length > 0) {
@@ -383,14 +383,14 @@ function addDomain() {
 
 function editDomain() {
   var elem = $(this).attr("id");
   var tr = $(this).closest("tr");
   var id = tr.attr("data-id");
-  var domain = tr.find("#domain_" + id).text();
+  var domain = utils.escapeHtml(tr.find("#domain_" + id).text());
   var type = tr.find("#type_" + id).val();
   var status = tr.find("#status_" + id).is(":checked") ? 1 : 0;
-  var comment = tr.find("#comment_" + id).val();
+  var comment = utils.escapeHtml(tr.find("#comment_" + id).val());
 
   // Show group assignment field only if in full domain management mode
   // if not included, just use the row data.
   var rowData = table.row(tr).data();
   var groups = table.column(5).visible() ? tr.find("#multiselect_" + id).val() : rowData.groups;
@@ -483,11 +483,11 @@ function editDomain() {
 }
 
 function deleteDomain() {
   var tr = $(this).closest("tr");
   var id = tr.attr("data-id");
-  var domain = tr.find("#domain_" + id).text();
+  var domain = utils.escapeHtml(tr.find("#domain_" + id).text());
   var type = tr.find("#type_" + id).val();
 
   var domainRegex;
   if (type === "0" || type === "1") {
     domainRegex = "domain";
diff --git a/scripts/pi-hole/js/groups.js b/scripts/pi-hole/js/groups.js
index f7d16d53..9835c4c7 100644
--- a/scripts/pi-hole/js/groups.js
+++ b/scripts/pi-hole/js/groups.js
@@ -125,12 +125,12 @@ $(function () {
     $("#resetButton").addClass("hidden");
   });
 });
 
 function addGroup() {
-  var name = $("#new_name").val();
-  var desc = $("#new_desc").val();
+  var name = utils.escapeHtml($("#new_name").val());
+  var desc = utils.escapeHtml($("#new_desc").val());
 
   utils.disableAll();
   utils.showAlert("info", "", "Adding group...", name);
 
   if (name.length === 0) {
@@ -164,13 +164,13 @@ function addGroup() {
 
 function editGroup() {
   var elem = $(this).attr("id");
   var tr = $(this).closest("tr");
   var id = tr.attr("data-id");
-  var name = tr.find("#name_" + id).val();
+  var name = utils.escapeHtml(tr.find("#name_" + id).val());
   var status = tr.find("#status_" + id).is(":checked") ? 1 : 0;
-  var desc = tr.find("#desc_" + id).val();
+  var desc = utils.escapeHtml(tr.find("#desc_" + id).val());
 
   var done = "edited";
   var notDone = "editing";
   switch (elem) {
     case "status_" + id:
@@ -237,11 +237,11 @@ function editGroup() {
 }
 
 function deleteGroup() {
   var tr = $(this).closest("tr");
   var id = tr.attr("data-id");
-  var name = tr.find("#name_" + id).val();
+  var name = utils.escapeHtml(tr.find("#name_" + id).val());
 
   utils.disableAll();
   utils.showAlert("info", "", "Deleting group...", name);
   $.ajax({
     url: "scripts/pi-hole/php/groups.php",
diff --git a/scripts/pi-hole/php/database.php b/scripts/pi-hole/php/database.php
index 0a6af814..46b7dece 100644
--- a/scripts/pi-hole/php/database.php
+++ b/scripts/pi-hole/php/database.php
@@ -159,13 +159,13 @@ function add_to_table($db, $table, $domains, $comment=null, $wildcardstyle=false
 			continue;
 
 		if($wildcardstyle)
 			$domain = "(\\.|^)".str_replace(".","\\.",$domain)."$";
 
-		$stmt->bindValue(":$field", $domain, SQLITE3_TEXT);
+		$stmt->bindValue(":$field", htmlentities($domain), SQLITE3_TEXT);
 		if($bindcomment) {
-			$stmt->bindValue(":comment", $comment, SQLITE3_TEXT);
+			$stmt->bindValue(":comment", htmlentities($comment), SQLITE3_TEXT);
 		}
 
 		if($stmt->execute() && $stmt->reset())
 			$num++;
 		else
diff --git a/scripts/pi-hole/php/func.php b/scripts/pi-hole/php/func.php
index 37eb8e9d..efb7c3b8 100644
--- a/scripts/pi-hole/php/func.php
+++ b/scripts/pi-hole/php/func.php
@@ -212,35 +212,38 @@ function deleteCustomDNSEntry()
     }
 }
 
 function deleteAllCustomDNSEntries()
 {
-    $handle = fopen($customDNSFile, "r");
-    if ($handle)
+    if (isset($customDNSFile))
     {
-        try
+        $handle = fopen($customDNSFile, "r");
+        if ($handle)
         {
-            while (($line = fgets($handle)) !== false) {
-                $line = str_replace("\r","", $line);
-                $line = str_replace("\n","", $line);
-                $explodedLine = explode (" ", $line);
+            try
+            {
+                while (($line = fgets($handle)) !== false) {
+                    $line = str_replace("\r","", $line);
+                    $line = str_replace("\n","", $line);
+                    $explodedLine = explode (" ", $line);
 
-                if (count($explodedLine) != 2)
-                    continue;
+                    if (count($explodedLine) != 2)
+                        continue;
 
-                $ip = $explodedLine[0];
-                $domain = $explodedLine[1];
+                    $ip = $explodedLine[0];
+                    $domain = $explodedLine[1];
 
-                pihole_execute("-a removecustomdns ".$ip." ".$domain);
+                    pihole_execute("-a removecustomdns ".$ip." ".$domain);
+                }
+            }
+            catch (\Exception $ex)
+            {
+                return errorJsonResponse($ex->getMessage());
             }
-        }
-        catch (\Exception $ex)
-        {
-            return errorJsonResponse($ex->getMessage());
-        }
 
-        fclose($handle);
+            fclose($handle);
+        }
     }
 
     return successJsonResponse();
 }
 
diff --git a/scripts/pi-hole/php/groups.php b/scripts/pi-hole/php/groups.php
index 1eb4e72b..04a9030c 100644
--- a/scripts/pi-hole/php/groups.php
+++ b/scripts/pi-hole/php/groups.php
@@ -56,11 +56,12 @@ if ($_POST['action'] == 'get_groups') {
         JSON_error($ex->getMessage());
     }
 } elseif ($_POST['action'] == 'add_group') {
     // Add new group
     try {
-        $names = str_getcsv(trim($_POST['name']), ' ');
+        $input = html_entity_decode(trim($_POST['name']));
+        $names = str_getcsv($input, ' ');
         $total = count($names);
         $added = 0;
         $stmt = $db->prepare('INSERT INTO "group" (name,description) VALUES (:name,:desc)');
         if (!$stmt) {
             throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
@@ -94,25 +95,27 @@ if ($_POST['action'] == 'get_groups') {
         JSON_error($ex->getMessage());
     }
 } elseif ($_POST['action'] == 'edit_group') {
     // Edit group identified by ID
     try {
+        $name = html_entity_decode($_POST['name']);
+        $desc = html_entity_decode($_POST['desc']);
+
         $stmt = $db->prepare('UPDATE "group" SET enabled=:enabled, name=:name, description=:desc WHERE id = :id');
         if (!$stmt) {
             throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
         }
 
         $status = ((int) $_POST['status']) !== 0 ? 1 : 0;
         if (!$stmt->bindValue(':enabled', $status, SQLITE3_INTEGER)) {
             throw new Exception('While binding enabled: ' . $db->lastErrorMsg());
         }
 
-        if (!$stmt->bindValue(':name', $_POST['name'], SQLITE3_TEXT)) {
+        if (!$stmt->bindValue(':name', $name, SQLITE3_TEXT)) {
             throw new Exception('While binding name: ' . $db->lastErrorMsg());
         }
 
-        $desc = $_POST['desc'];
         if (strlen($desc) === 0) {
             // Store NULL in database for empty descriptions
             $desc = null;
         }
         if (!$stmt->bindValue(':desc', $desc, SQLITE3_TEXT)) {
@@ -261,11 +264,11 @@ if ($_POST['action'] == 'get_groups') {
         foreach ($ips as $ip) {
             if (!$stmt->bindValue(':ip', $ip, SQLITE3_TEXT)) {
                 throw new Exception('While binding ip: ' . $db->lastErrorMsg());
             }
 
-            $comment = $_POST['comment'];
+            $comment = html_entity_decode($_POST['comment']);
             if (strlen($comment) === 0) {
                     // Store NULL in database for empty comments
                     $comment = null;
             }
             if (!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
@@ -291,11 +294,11 @@ if ($_POST['action'] == 'get_groups') {
         $stmt = $db->prepare('UPDATE client SET comment=:comment WHERE id = :id');
         if (!$stmt) {
             throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
         }
 
-        $comment = $_POST['comment'];
+        $comment = html_entity_decode($_POST['comment']);
         if (strlen($comment) === 0) {
                 // Store NULL in database for empty comments
                 $comment = null;
         }
         if (!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
@@ -451,11 +454,11 @@ if ($_POST['action'] == 'get_groups') {
         JSON_error($ex->getMessage());
     }
 } elseif ($_POST['action'] == 'add_domain') {
     // Add new domain
     try {
-        $domains = explode(' ', trim($_POST['domain']));
+        $domains = explode(' ', html_entity_decode(trim($_POST['domain'])));
         $before = intval($db->querySingle("SELECT COUNT(*) FROM domainlist;"));
         $total = count($domains);
         $added = 0;
         $stmt = $db->prepare('REPLACE INTO domainlist (domain,type,comment) VALUES (:domain,:type,:comment)');
         if (!$stmt) {
@@ -472,11 +475,11 @@ if ($_POST['action'] == 'get_groups') {
 
         if (!$stmt->bindValue(':type', $type, SQLITE3_TEXT)) {
             throw new Exception('While binding type: ' . $db->lastErrorMsg());
         }
 
-        $comment = $_POST['comment'];
+        $comment = html_entity_decode($_POST['comment']);
         if (strlen($comment) === 0) {
             // Store NULL in database for empty comments
             $comment = null;
         }
         if (!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
@@ -571,11 +574,11 @@ if ($_POST['action'] == 'get_groups') {
 
         if (!$stmt->bindValue(':enabled', $status, SQLITE3_INTEGER)) {
             throw new Exception('While binding enabled: ' . $db->lastErrorMsg());
         }
 
-        $comment = $_POST['comment'];
+        $comment = html_entity_decode($_POST['comment']);
         if (strlen($comment) === 0) {
                 // Store NULL in database for empty comments
                 $comment = null;
         }
         if (!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
@@ -740,20 +743,20 @@ if ($_POST['action'] == 'get_groups') {
         JSON_error($ex->getMessage());
     }
 } elseif ($_POST['action'] == 'add_adlist') {
     // Add new adlist
     try {
-        $addresses = explode(' ', trim($_POST['address']));
+        $addresses = explode(' ', html_entity_decode(trim($_POST['address'])));
         $total = count($addresses);
         $added = 0;
 
         $stmt = $db->prepare('INSERT OR IGNORE INTO adlist (address,comment) VALUES (:address,:comment)');
         if (!$stmt) {
             throw new Exception('While preparing statement: ' . $db->lastErrorMsg());
         }
 
-        $comment = $_POST['comment'];
+        $comment = html_entity_decode($_POST['comment']);
         if (strlen($comment) === 0) {
             // Store NULL in database for empty comments
             $comment = null;
         }
         if (!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
@@ -798,11 +801,11 @@ if ($_POST['action'] == 'get_groups') {
 
         if (!$stmt->bindValue(':enabled', $status, SQLITE3_INTEGER)) {
             throw new Exception('While binding enabled: ' . $db->lastErrorMsg());
         }
 
-        $comment = $_POST['comment'];
+        $comment = html_entity_decode($_POST['comment']);
         if (strlen($comment) === 0) {
                 // Store NULL in database for empty comments
                 $comment = null;
         }
         if (!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT)) {
diff --git a/scripts/pi-hole/php/teleporter.php b/scripts/pi-hole/php/teleporter.php
index 52951067..cfd4bc47 100644
--- a/scripts/pi-hole/php/teleporter.php
+++ b/scripts/pi-hole/php/teleporter.php
@@ -171,11 +171,11 @@ function archive_restore_table($file, $table, $flush=false)
 	// Loop over rows and inject the entries into the database
 	$num = 0;
 	foreach($contents as $row)
 	{
 		// Limit max length for a domain entry to 253 chars
-		if(strlen($row[$field]) > 253)
+		if(isset($field) && strlen($row[$field]) > 253)
 			continue;
 
 		// Bind properties from JSON data
 		// Note that only defined above are actually used
 		// so even maliciously modified Teleporter files
@@ -194,11 +194,11 @@ function archive_restore_table($file, $table, $flush=false)
 					$sqltype = SQLITE3_NULL;
 				break;
 				default:
 					$sqltype = "UNK";
 			}
-			$stmt->bindValue(":".$key, $value, $sqltype);
+			$stmt->bindValue(":".$key, htmlentities($value), $sqltype);
 		}
 
 		if($stmt->execute() && $stmt->reset() && $stmt->clear())
 			$num++;
 		else
diff --git a/settings.php b/settings.php
index 51292f4c..06512b48 100644
--- a/settings.php
+++ b/settings.php
@@ -712,11 +712,11 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
                                                         <?php foreach ($dhcp_leases as $lease) { ?>
                                                         <tr data-placement="auto" data-container="body" data-toggle="tooltip"
                                                             title="Lease type: IPv<?php echo $lease["type"]; ?><br/>Remaining lease time: <?php echo $lease["TIME"]; ?><br/>DHCP UID: <?php echo $lease["clid"]; ?>">
                                                             <td id="MAC"><?php echo $lease["hwaddr"]; ?></td>
                                                             <td id="IP" data-order="<?php echo bin2hex(inet_pton($lease["IP"])); ?>"><?php echo $lease["IP"]; ?></td>
-                                                            <td id="HOST"><?php echo $lease["host"]; ?></td>
+                                                            <td id="HOST"><?php echo htmlentities($lease["host"]); ?></td>
                                                             <td>
                                                                 <button type="button" id="button" class="btn btn-warning btn-xs" data-static="alert">
                                                                     <span class="fas fas fa-file-import"></span>
                                                                 </button>
                                                             </td>
@@ -740,11 +740,11 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "adlists", "
                                                     <tbody>
                                                         <?php foreach ($dhcp_static_leases as $lease) { ?>
                                                         <tr>
                                                             <td><?php echo $lease["hwaddr"]; ?></td>
                                                             <td data-order="<?php echo bin2hex(inet_pton($lease["IP"])); ?>"><?php echo $lease["IP"]; ?></td>
-                                                            <td><?php echo $lease["host"]; ?></td>
+                                                            <td><?php echo htmlentities($lease["host"]); ?></td>
                                                             <td><?php if (strlen($lease["hwaddr"]) > 0) { ?>
                                                                 <button type="submit" class="btn btn-danger btn-xs" name="removestatic"
                                                                         value="<?php echo $lease["hwaddr"]; ?>">
                                                                     <span class="far fa-trash-alt"></span>
                                                                 </button>

Vulnerable code:   var ip = $("#ip").val();
  var domain = $("#domain").val();
  var address = $("#new_address").val();
  var comment = $("#new_comment").val();
  var comment = tr.find("#comment_" + id).val();
  var address = tr.find("#address_" + id).text();
  var address = tr.find("#address_" + id).text();
  var comment = $("#new_comment").val();
    ip = $("#ip-custom").val().trim();
  var ip = tr.find("#ip_" + id).text();
  var name = tr.find("#name_" + id).text();
  var comment = tr.find("#comment_" + id).val();
  var name = tr.find("#name_" + id).text();
  var domain = domainEl.val();
  var comment = commentEl.val();
  var domain = tr.find("#domain_" + id).text();
  var comment = tr.find("#comment_" + id).val();
  var domain = tr.find("#domain_" + id).text();
  var name = $("#new_name").val();
  var desc = $("#new_desc").val();
  var name = tr.find("#name_" + id).val();
  var desc = tr.find("#desc_" + id).val();
  var name = tr.find("#name_" + id).val();
		$stmt->bindValue(":$field", $domain, SQLITE3_TEXT);
			$stmt->bindValue(":comment", $comment, SQLITE3_TEXT);
    $handle = fopen($customDNSFile, "r");
    if ($handle)
        try
            while (($line = fgets($handle)) !== false) {
                $line = str_replace("\r","", $line);
                $line = str_replace("\n","", $line);
                $explodedLine = explode (" ", $line);
                if (count($explodedLine) != 2)
                    continue;
                $ip = $explodedLine[0];
                $domain = $explodedLine[1];
                pihole_execute("-a removecustomdns ".$ip." ".$domain);
        }
        catch (\Exception $ex)
        {
            return errorJsonResponse($ex->getMessage());
        }
        fclose($handle);
        $names = str_getcsv(trim($_POST['name']), ' ');
        if (!$stmt->bindValue(':name', $_POST['name'], SQLITE3_TEXT)) {
        $desc = $_POST['desc'];
            $comment = $_POST['comment'];
        $comment = $_POST['comment'];
        $domains = explode(' ', trim($_POST['domain']));
        $comment = $_POST['comment'];
        $comment = $_POST['comment'];
        $addresses = explode(' ', trim($_POST['address']));
        $comment = $_POST['comment'];
        $comment = $_POST['comment'];
		if(strlen($row[$field]) > 253)
			$stmt->bindValue(":".$key, $value, $sqltype);
                                                            <td id="HOST"><?php echo $lease["host"]; ?></td>
                                                            <td><?php echo $lease["host"]; ?></td>
Fixed code: <script src="scripts/pi-hole/js/utils.js"></script>
/* global utils:false */

  var ip = utils.escapeHtml($("#ip").val());
  var domain = utils.escapeHtml($("#domain").val());
  var address = utils.escapeHtml($("#new_address").val());
  var comment = utils.escapeHtml($("#new_comment").val());
  var comment = utils.escapeHtml(tr.find("#comment_" + id).val());
  var address = utils.escapeHtml(tr.find("#address_" + id).text());
  var address = utils.escapeHtml(tr.find("#address_" + id).text());
  var comment = utils.escapeHtml($("#new_comment").val());
    ip = utils.escapeHtml($("#ip-custom").val().trim());
  var ip = utils.escapeHtml(tr.find("#ip_" + id).text());
  var name = utils.escapeHtml(tr.find("#name_" + id).text());
  var comment = utils.escapeHtml(tr.find("#comment_" + id).val());
  var name = utils.escapeHtml(tr.find("#name_" + id).text());
  var domain = utils.escapeHtml(domainEl.val());
  var comment = utils.escapeHtml(commentEl.val());
  var domain = utils.escapeHtml(tr.find("#domain_" + id).text());
  var comment = utils.escapeHtml(tr.find("#comment_" + id).val());
  var domain = utils.escapeHtml(tr.find("#domain_" + id).text());
  var name = utils.escapeHtml($("#new_name").val());
  var desc = utils.escapeHtml($("#new_desc").val());
  var name = utils.escapeHtml(tr.find("#name_" + id).val());
  var desc = utils.escapeHtml(tr.find("#desc_" + id).val());
  var name = utils.escapeHtml(tr.find("#name_" + id).val());
		$stmt->bindValue(":$field", htmlentities($domain), SQLITE3_TEXT);
			$stmt->bindValue(":comment", htmlentities($comment), SQLITE3_TEXT);
    if (isset($customDNSFile))
        $handle = fopen($customDNSFile, "r");
        if ($handle)
            try
            {
                while (($line = fgets($handle)) !== false) {
                    $line = str_replace("\r","", $line);
                    $line = str_replace("\n","", $line);
                    $explodedLine = explode (" ", $line);
                    if (count($explodedLine) != 2)
                        continue;
                    $ip = $explodedLine[0];
                    $domain = $explodedLine[1];
                    pihole_execute("-a removecustomdns ".$ip." ".$domain);
                }
            }
            catch (\Exception $ex)
            {
                return errorJsonResponse($ex->getMessage());
            fclose($handle);
        }
        $input = html_entity_decode(trim($_POST['name']));
        $names = str_getcsv($input, ' ');
        $name = html_entity_decode($_POST['name']);
        $desc = html_entity_decode($_POST['desc']);

        if (!$stmt->bindValue(':name', $name, SQLITE3_TEXT)) {
            $comment = html_entity_decode($_POST['comment']);
        $comment = html_entity_decode($_POST['comment']);
        $domains = explode(' ', html_entity_decode(trim($_POST['domain'])));
        $comment = html_entity_decode($_POST['comment']);
        $comment = html_entity_decode($_POST['comment']);
        $addresses = explode(' ', html_entity_decode(trim($_POST['address'])));
        $comment = html_entity_decode($_POST['comment']);
        $comment = html_entity_decode($_POST['comment']);
		if(isset($field) && strlen($row[$field]) > 253)
			$stmt->bindValue(":".$key, htmlentities($value), $sqltype);
                                                            <td id="HOST"><?php echo htmlentities($lease["host"]); ?></td>
                                                            <td><?php echo htmlentities($lease["host"]); ?></td>