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
This commit is contained in:
Miraty 2022-11-20 18:17:03 +01:00
parent 18d976217b
commit 567034b8fe
17 changed files with 25 additions and 25 deletions

View File

@ -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

View File

@ -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.');
}

View File

@ -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 {

View File

@ -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.');
}

View File

@ -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;
}

View File

@ -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.');

View File

@ -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]);

View File

@ -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>

View File

@ -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(

View File

@ -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;

View File

@ -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.');

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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']);

View File

@ -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')

View File

@ -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>