Explorar o código

Fix regDeleteDomain security flaw + D regex modifier

regDeleteDomain() in fn/reg.php used too loose pattern matching for data deletion, that also deleted other domains that included the deleted domain
Miraty %!s(int64=2) %!d(string=hai) anos
pai
achega
567034b8fe

+ 0 - 1
config.ini

@@ -12,7 +12,6 @@ enabled = true
 registry = niver.test.
 registry_file = "/srv/niver/reg/niver.test.zone"
 ttl = 86400
-subdomain_regex = "^[a-z0-9]{4,63}$"
 
 [ns]
 enabled = true

+ 2 - 2
fn/auth.php

@@ -15,12 +15,12 @@ const OPTIONS_PASSWORD = [
 ];
 
 function checkPasswordFormat($password) {
-	if (preg_match('/' . PASSWORD_REGEX . '/u', $password) !== 1)
+	if (preg_match('/' . PASSWORD_REGEX . '/Du', $password) !== 1)
 		output(403, 'Password malformed.');
 }
 
 function checkUsernameFormat($username) {
-	if (preg_match('/' . USERNAME_REGEX . '/u', $username) !== 1)
+	if (preg_match('/' . USERNAME_REGEX . '/Du', $username) !== 1)
 		output(403, 'Username malformed.');
 }
 

+ 1 - 1
fn/common.php

@@ -110,7 +110,7 @@ function redirUrl($pageId) {
 
 function redir() {
 	if (isset($_GET['redir'])) {
-		if (preg_match('/^[0-9a-z\/-]{0,128}$/', $_GET['redir']) !== 1)
+		if (preg_match('/^[0-9a-z\/-]{0,128}$/D', $_GET['redir']) !== 1)
 			output(403, 'Wrong character in <code>redir</code>.');
 		header('Location: ' . CONF['common']['prefix'] . '/' . $_GET['redir']);
 	} else {

+ 1 - 1
fn/dns.php

@@ -50,7 +50,7 @@ function checkIpFormat($ip) {
 
 function checkAbsoluteDomainFormat($domain) {
 	// If the domain must end with a dot
-	if (!filter_var($domain, FILTER_VALIDATE_DOMAIN) OR !preg_match('/^([a-z0-9_-]{1,63}\.){2,127}$/', $domain))
+	if (!filter_var($domain, FILTER_VALIDATE_DOMAIN) OR !preg_match('/^([a-z0-9_-]{1,63}\.){2,127}$/D', $domain))
 		output(403, 'Domain malformed.');
 }
 

+ 2 - 2
fn/ht.php

@@ -2,7 +2,7 @@
 
 function checkDomainFormat($domain) {
 	// If the domain must end without a dot
-	if (!filter_var($domain, FILTER_VALIDATE_DOMAIN) OR !preg_match('/^([a-z0-9_-]{1,63}\.){1,126}[a-z0-9]{1,63}$/', $domain))
+	if (!filter_var($domain, FILTER_VALIDATE_DOMAIN) OR !preg_match('/^([a-z0-9_-]{1,63}\.){1,126}[a-z0-9]{1,63}$/D', $domain))
 		output(403, 'Domain malformed.');
 }
 
@@ -16,7 +16,7 @@ function listFsDirs($username) {
 	$absoluteDirs = glob(CONF['ht']['ht_path'] . '/' . $username . '/*/', GLOB_ONLYDIR);
 	$dirs = [];
 	foreach ($absoluteDirs as $absoluteDir)
-		if (preg_match('/^[\p{L}\p{N}_-]{1,64}$/u', basename($absoluteDir)))
+		if (preg_match('/^[\p{L}\p{N}_-]{1,64}$/Du', basename($absoluteDir)))
 			array_push($dirs, basename($absoluteDir));
 	return $dirs;
 }

+ 3 - 1
fn/reg.php

@@ -1,5 +1,7 @@
 <?php
 
+define('SUBDOMAIN_REGEX', '^[a-z0-9]{4,63}$');
+
 function regListUserDomains($username) {
 	return query('select', 'registry', ['username' => $username], 'domain');
 }
@@ -14,7 +16,7 @@ function regDeleteDomain($domain) {
 	$regFile = file_get_contents(CONF['reg']['registry_file']);
 	if ($regFile === false)
 		output(500, 'Failed to read current registry File.');
-	$regFile = preg_replace('/[^\n]{0,1024}' . $domain . ' {0,1024}[^\n]{0,1024}\n/', '', $regFile);
+	$regFile = preg_replace('/^(?:[a-z0-9._-]+\.)' . preg_quote($domain, '/') . '[\t ]+.+$/Dm', '', $regFile);
 	if (file_put_contents(CONF['reg']['registry_file'], $regFile) === false)
 		output(500, 'Failed to write new registry file.');
 

+ 1 - 1
pages/ht/add-http-dns.php

@@ -29,7 +29,7 @@ if (processForm()) {
 	$remoteTXTRecords = dns_get_record($_POST['domain'], DNS_TXT);
 	if (is_array($remoteTXTRecords) !== true)
 		output(500, 'Erreur lors de la récupération de l\'enregistrement TXT.');
-	if (preg_match('/^auth-owner=([0-9a-f]{8})-([0-9a-f]{32})$/m', implode(LF, array_column($remoteTXTRecords, 'txt')), $matches) !== 1)
+	if (preg_match('/^auth-owner=([0-9a-f]{8})-([0-9a-f]{32})$/Dm', implode(LF, array_column($remoteTXTRecords, 'txt')), $matches) !== 1)
 		output(403, 'Aucun enregistrement TXT au format correct trouvé.');
 
 	checkAuthToken($matches[1], $matches[2]);

+ 1 - 2
pages/ht/add-http-onion.php

@@ -26,7 +26,7 @@ if (processForm()) {
 	// Get the address generated by Tor
 	exec(CONF['ht']['sudo_path'] . ' -u ' . CONF['ht']['tor_user'] . ' ' . CONF['ht']['cat_path'] . ' ' . CONF['ht']['tor_keys_path'] . '/' . $_SESSION['username'] . '/' . $_POST['dir'] . '/hostname', $output);
 	$onion = $output[0];
-	if (preg_match('/[0-9a-z]{56}\.onion/', $onion) !== 1)
+	if (preg_match('/^[0-9a-z]{56}\.onion$/D', $onion) !== 1)
 		output(500, 'No onion address found.');
 
 	// Store it in the database
@@ -55,7 +55,6 @@ if (processForm()) {
 
 ?>
 
-
 <p>
 	Ajouter un accès en .onion sur un dossier
 </p>

+ 2 - 2
pages/ns/caa.php

@@ -6,10 +6,10 @@ if (processForm()) {
 	if (!($_POST['flag'] >= 0 AND $_POST['flag'] <= 255))
 		output(403, 'Wrong value for <code>flag</code>.');
 
-	if (!(preg_match('/^[a-z]{1,127}$/', $_POST['tag'])))
+	if (!(preg_match('/^[a-z]{1,127}$/D', $_POST['tag'])))
 		output(403, 'Wrong value for <code>tag</code>.');
 
-	if (!(preg_match('/^[a-z0-9.-]{1,255}$/', $_POST['value'])))
+	if (!(preg_match('/^[a-z0-9.-]{1,255}$/D', $_POST['value'])))
 		output(403, 'Wrong value for <code>value</code>.');
 
 	knotcZoneExec($_POST['zone'], array(

+ 4 - 4
pages/ns/edit.php

@@ -7,8 +7,8 @@ if (processForm() AND isset($_POST['zone-content'])) { // Update zone
 	$current_zone_content = file_get_contents(CONF['ns']['knot_zones_path'] . '/' . $_POST['zone'] . 'zone');
 	if ($current_zone_content === false)
 		output(500, 'Unable to read zone file.');
-	if (preg_match('/^(?<soa>' . preg_quote($_POST['zone']) . '[\t ]+[0-9]{1,16}[\t ]+SOA[\t ]+.+)$/m', $current_zone_content, $matches) !== 1)
-		output(500, 'Unable to get current serial from zone file.');
+	if (preg_match('/^(?<soa>' . preg_quote($_POST['zone'], '/') . '[\t ]+[0-9]{1,16}[\t ]+SOA[\t ]+.+)$/Dm', $current_zone_content, $matches) !== 1)
+		output(500, 'Unable to get current SOA record from zone file.');
 
 	// Generate new zone content
 	$new_zone_content = $matches['soa'] . LF;
@@ -16,7 +16,7 @@ if (processForm() AND isset($_POST['zone-content'])) { // Update zone
 		output(403, 'La zone n\'est pas autorisée à dépasser ' . ZONE_MAX_CHARACTERS . ' caractères.');
 	foreach (explode("\r\n", $_POST['zone-content']) as $line) {
 		if ($line === '') continue;
-		if (preg_match('/^(?<domain>[a-z0-9@._-]+)(?:[\t ]+(?<ttl>[0-9]{1,16}))?(?:[\t ]+IN)?[\t ]+(?<type>[A-Z]{1,16})[\t ]+(?<value>.+)$/', $line, $matches) !== 1)
+		if (preg_match('/^(?<domain>[a-z0-9@._-]+)(?:[\t ]+(?<ttl>[0-9]{1,16}))?(?:[\t ]+IN)?[\t ]+(?<type>[A-Z]{1,16})[\t ]+(?<value>.+)$/D', $line, $matches) !== 1)
 			output(403, 'La zone est mal formatée (selon Niver).');
 		if (in_array($matches['type'], ALLOWED_TYPES, true) !== true)
 			output(403, 'Le type <code>' . $matches['type'] . '</code> n\'est pas autorisé.');
@@ -92,7 +92,7 @@ if (processForm()) { // Display zone
 	foreach(explode(LF, $zone_content) as $zone_line) {
 		if (empty($zone_line) OR str_starts_with($zone_line, ';'))
 			continue;
-		if (preg_match('/^(?:(?:[a-z0-9_-]{1,63}\.){1,127})?' . preg_quote($_POST['zone'], '/') . '[\t ]+[0-9]{1,8}[\t ]+(?<type>[A-Z]{1,16})[\t ]+.+$/', $zone_line, $matches)) {
+		if (preg_match('/^(?:(?:[a-z0-9_-]{1,63}\.){1,127})?' . preg_quote($_POST['zone'], '/') . '[\t ]+[0-9]{1,8}[\t ]+(?<type>[A-Z]{1,16})[\t ]+.+$/D', $zone_line, $matches)) {
 			if (in_array($matches['type'], ALLOWED_TYPES, true) !== true)
 				continue;
 			$displayed_zone_content .= $zone_line . LF;

+ 1 - 1
pages/ns/print.php

@@ -61,7 +61,7 @@ if (processForm()) {
 
 	if ($_POST['print'] === 'ds') {
 
-		$found = preg_match('/^' . preg_quote($_POST['zone']) . '[\t ]+0[\t ]+CDS[\t ]+(?<tag>[0-9]{1,5})[\t ]+(?<algo>[0-9]{1,2})[\t ]+(?<digest_type>[0-9])[\t ]+(?<digest>[0-9A-F]{64})$/m', $zoneContent, $matches);
+		$found = preg_match('/^' . preg_quote($_POST['zone'], '/') . '[\t ]+0[\t ]+CDS[\t ]+(?<tag>[0-9]{1,5})[\t ]+(?<algo>[0-9]{1,2})[\t ]+(?<digest_type>[0-9])[\t ]+(?<digest>[0-9A-F]{64})$/Dm', $zoneContent, $matches);
 		if ($found !== 1)
 			output(500, 'Unable to get public key record from zone file.');
 

+ 1 - 1
pages/ns/sshfp.php

@@ -9,7 +9,7 @@ if (processForm()) {
 	if (!($_POST['type'] === '2'))
 		output(403, 'Wrong value for <code>type</code>.');
 
-	if (!(preg_match('/^[a-z0-9]{64}$/', $_POST['fp'])))
+	if (!(preg_match('/^[a-z0-9]{64}$/D', $_POST['fp'])))
 		output(403, 'Wrong value for <code>fp</code>.');
 
 	knotcZoneExec($_POST['zone'], array(

+ 1 - 1
pages/ns/tlsa.php

@@ -12,7 +12,7 @@ if (processForm()) {
 	if (!($_POST['type'] >= 0 AND $_POST['type'] <= 2))
 		output(403, 'Wrong value for <code>type</code>.');
 
-	if (!(preg_match('/^[a-zA-Z0-9.-]{1,1024}$/', $_POST['content'])))
+	if (!(preg_match('/^[a-zA-Z0-9.-]{1,1024}$/D', $_POST['content'])))
 		output(403, 'Wrong value for <code>content</code>.');
 
 	knotcZoneExec($_POST['zone'], array(

+ 1 - 1
pages/ns/txt.php

@@ -3,7 +3,7 @@
 if (processForm()) {
 	$values = nsParseCommonRequirements();
 
-	if (!(preg_match('/^[a-zA-Z0-9 =:!%$+\/\()[\]_-]{5,8192}$/', $_POST['txt'])))
+	if (!(preg_match('/^[a-zA-Z0-9 =:!%$+\/\()[\]_-]{5,8192}$/D', $_POST['txt'])))
 		output(403, 'Wrong value for <code>txt</code>.');
 
 	knotcZoneExec($_POST['zone'], array(

+ 1 - 1
pages/ns/zone-add.php

@@ -13,7 +13,7 @@ if (processForm()) {
 		checkAbsoluteDomainFormat($parentAuthoritative);
 
 	exec(CONF['ns']['kdig_path'] . ' ' . $_POST['domain'] . ' NS @' . $parentAuthoritatives[0] . ' +noidn', $results);
-	if (preg_match('/^' . preg_quote($_POST['domain'], '/') . '[\t ]+[0-9]{1,8}[\t ]+IN[\t ]+NS[\t ]+(?<salt>[0-9a-f]{8})-(?<hash>[0-9a-f]{32})\.auth-owner.+$/m', implode(LF, $results), $matches) !== 1)
+	if (preg_match('/^' . preg_quote($_POST['domain'], '/') . '[\t ]+[0-9]{1,8}[\t ]+IN[\t ]+NS[\t ]+(?<salt>[0-9a-f]{8})-(?<hash>[0-9a-f]{32})\.auth-owner.+$/Dm', implode(LF, $results), $matches) !== 1)
 		output(403, 'Enregistrement d\'authentification introuvable');
 
 	checkAuthToken($matches['salt'], $matches['hash']);

+ 1 - 1
pages/reg/ds.php

@@ -15,7 +15,7 @@ if (processForm()) {
 	) output(403, 'Wrong value for <code>algo</code>.');
 
 	$_POST['keytag'] = intval($_POST['keytag']);
-	if ((!preg_match('/^[0-9]{1,6}$/', $_POST['keytag'])) OR !($_POST['keytag'] >= 1) OR !($_POST['keytag'] <= 65535))
+	if ((!preg_match('/^[0-9]{1,6}$/D', $_POST['keytag'])) OR !($_POST['keytag'] >= 1) OR !($_POST['keytag'] <= 65535))
 		output(403, 'Wrong value for <code>keytag</code>.');
 
 	if ($_POST['dt'] !== '2' AND $_POST['dt'] !== '4')

+ 2 - 2
pages/reg/register.php

@@ -1,7 +1,7 @@
 <?php
 
 if (processForm()) {
-	if (preg_match('/' . CONF['reg']['subdomain_regex'] . '/', $_POST['subdomain']) !== 1)
+	if (preg_match('/' . SUBDOMAIN_REGEX . '/D', $_POST['subdomain']) !== 1)
 		output(403, 'Le nom de domaine doit être composé uniquement d\'entre 4 et 63 lettres minuscules ou chiffre (a-z et 0-9)');
 
 	$domain = formatAbsoluteDomain($_POST['subdomain'] . '.' . CONF['reg']['registry']);
@@ -32,7 +32,7 @@ if (processForm()) {
 <form method="post">
 	<label for="subdomain">Sous-domaine</label>
 	<br>
-	<input id="subdomain" pattern="<?= CONF['reg']['subdomain_regex'] ?>" required="" placeholder="niver" name="subdomain" type="text">.<?= CONF['reg']['registry'] ?>
+	<input id="subdomain" pattern="<?= SUBDOMAIN_REGEX ?>" required="" placeholder="niver" name="subdomain" type="text">.<?= CONF['reg']['registry'] ?>
 	<br>
 	<input value="Valider" type="submit">
 </form>