mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-04 04:02:30 +02:00
A successful admin or reseller login wrote the signed-in user into whatever session the visitor arrived with; nothing in the panel ever called session_regenerate_id. With session.use_strict_mode off, PHP adopts any id a client presents, so an id planted in an admin's browser beforehand (a cookie set from a sibling subdomain, a shared machine) became a signed-in admin session the moment they logged in — session fixation. Login (admin and reseller) and the first-run setup page now move the session onto a fresh id and discard the old one. The admin session also starts with use_strict_mode on, so ids this server never issued are refused, and with the cookie HttpOnly: no panel script reads it, and an XSS should not be able to. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EuZvjFSdodqgpyXtaoH1Xt
322 lines
18 KiB
PHP
322 lines
18 KiB
PHP
<?php
|
|
|
|
use XcVm\Core\Auth\Authenticator;
|
|
use XcVm\Core\Auth\AuthRepository;
|
|
use XcVm\Core\Auth\PageAuthorization;
|
|
use XcVm\Core\Database\DatabaseHandler;
|
|
use XcVm\Core\Database\QueryHelper;
|
|
use XcVm\Core\Http\RequestManager;
|
|
use XcVm\Core\Util\AdminHelpers;
|
|
use XcVm\Core\Util\NetworkUtils;
|
|
|
|
include 'functions.php';
|
|
if (!RequestManager::has('update')):
|
|
$rFirstRun = true;
|
|
$db->query('SELECT COUNT(`id`) AS `count` FROM `users` LEFT JOIN `users_groups` ON `users_groups`.`group_id` = `users`.`member_group_id` WHERE `users_groups`.`is_admin` = 1;');
|
|
|
|
if ($db->get_row()['count'] > 0) {
|
|
$rFirstRun = false;
|
|
include 'session.php';
|
|
|
|
if (!PageAuthorization::checkPermissions()) {
|
|
AdminHelpers::goHome();
|
|
}
|
|
}
|
|
|
|
$rMigrating = false;
|
|
|
|
if (file_exists(TMP_PATH . '.migration.status') && file_exists(TMP_PATH . '.migration.pid')) {
|
|
$rPID = file_get_contents(TMP_PATH . '.migration.pid');
|
|
|
|
if (file_exists('/proc/' . $rPID)) {
|
|
$rMigrating = true;
|
|
}
|
|
}
|
|
|
|
if (RequestManager::has('migrate')) {
|
|
$rMigrateOptions = array();
|
|
|
|
foreach (RequestManager::getAll() as $rKey => $rValue) {
|
|
if (substr($rKey, 0, 8) == 'migrate#') {
|
|
list(, $rMigrateOptions[]) = explode('#', $rKey);
|
|
}
|
|
}
|
|
|
|
if (count($rMigrateOptions) != 0) {
|
|
if (file_exists(TMP_PATH . '.migration.pid')) {
|
|
$rPID = intval(file_get_contents(TMP_PATH . '.migration.pid'));
|
|
exec('kill -9 ' . $rPID);
|
|
}
|
|
|
|
file_put_contents(TMP_PATH . '.migration.options', json_encode($rMigrateOptions));
|
|
unlink(TMP_PATH . '.migration.status');
|
|
unlink(TMP_PATH . '.migration.pid');
|
|
unlink(TMP_PATH . '.migration.log');
|
|
shell_exec(PHP_BIN . ' ' . MAIN_HOME . 'console.php migrate > ' . TMP_PATH . '.migration.log 2>&1 &');
|
|
$rMigrating = true;
|
|
} else {
|
|
header('Location: ./setup');
|
|
exit();
|
|
}
|
|
} else {
|
|
if (RequestManager::has('new_user') && $rFirstRun) {
|
|
if (strlen(RequestManager::get('password')) < 8 || strlen(RequestManager::get('username')) < 8) {
|
|
RequestManager::update('new', 1);
|
|
$_STATUS = STATUS_FAILURE;
|
|
} else {
|
|
$rArray = QueryHelper::verifyPostTable('users');
|
|
$rArray['username'] = RequestManager::get('username');
|
|
$rArray['password'] = Authenticator::hashPassword(RequestManager::get('password'));
|
|
$rArray['email'] = RequestManager::get('email');
|
|
$rArray['last_login'] = time();
|
|
$rArray['date_registered'] = $rArray['last_login'];
|
|
$rArray['member_group_id'] = 1;
|
|
$rArray['ip'] = NetworkUtils::getUserIP();
|
|
$rArray['last_login'] = time();
|
|
$rPrepare = QueryHelper::prepareArray($rArray);
|
|
$rQuery = 'INSERT INTO `users`(' . $rPrepare['columns'] . ') VALUES(' . $rPrepare['placeholder'] . ');';
|
|
|
|
if ($db->query($rQuery, ...$rPrepare['data'])) {
|
|
session_regenerate_id(true); // signed in from here: a fresh id, as at login
|
|
$_SESSION['hash'] = $db->last_insert_id();
|
|
$_SESSION['ip'] = NetworkUtils::getUserIP();
|
|
$_SESSION['code'] = AuthRepository::getCurrentCode();
|
|
$_SESSION['verify'] = md5($rArray['username'] . '||' . $rArray['password']);
|
|
$db->query('UPDATE `servers` SET `server_ip` = ? WHERE `is_main` = 1 AND `server_type` = 0 LIMIT 1;', $_SERVER['SERVER_ADDR']);
|
|
$db->query('UPDATE `settings` SET `live_streaming_pass` = ? WHERE `id` = 1', AdminHelpers::generateString(25));
|
|
|
|
if ($_SESSION['code'] == 'setup') {
|
|
header('Location: ./codes');
|
|
|
|
exit();
|
|
}
|
|
|
|
header('Location: ./dashboard');
|
|
exit();
|
|
}
|
|
|
|
RequestManager::update('new', 1);
|
|
$_STATUS = STATUS_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$rMigrating) {
|
|
$rMigrateConnection = false;
|
|
$odb = new DatabaseHandler(migrate: true);
|
|
|
|
if ($odb->connected) {
|
|
$rMigrateConnection = true;
|
|
}
|
|
|
|
$odb->query("SHOW TABLES LIKE 'access_codes';");
|
|
|
|
if ($odb->num_rows() > 0) {
|
|
$rCount = array('access_codes' => array('Access Codes', 0), 'users' => array('Users & Resellers', 0), 'blocked_ips' => array('Blocked IP Addresses', 0), 'blocked_uas' => array('Blocked User-Agents', 0), 'blocked_isps' => array("Blocked ISP's", 0), 'bouquets' => array('Bouquets', 0), 'enigma2_devices' => array('Device Info - Engima2', 0), 'mag_devices' => array('Device Info - MAG', 0), 'epg' => array('EPG Providers ', 0), 'users_groups' => array('User Groups', 0), 'users_packages' => array('User Packages', 0), 'rtmp_ips' => array("RTMP IP's", 0), 'streams_series' => array('TV Series', 0), 'streams_episodes' => array('TV Episodes', 0), 'servers' => array('Servers - Load Balancers', 0), 'streams' => array('Streams - Live, Radio, Created & VOD', 0), 'streams_options' => array('Stream Options', 0), 'streams_servers' => array('Stream Servers', 0), 'streams_categories' => array('Stream Categories', 0), 'tickets' => array('Tickets', 0), 'tickets_replies' => array('Ticket Replies', 0), 'profiles' => array('Transcoding Profile', 0), 'providers' => array('Streaming Providers', 0), 'lines' => array('Lines - Standard, MAG & Enigma2 Devices', 0), 'watch_folders' => array('Watch Folders', 0));
|
|
} else {
|
|
$rCount = array('reg_users' => array('Users & Resellers', 0), 'users' => array('Lines - Standard, MAG & Enigma2 Devices', 0), 'enigma2_devices' => array('Device Info - Engima2', 0), 'mag_devices' => array('Device Info - MAG', 0), 'user_output' => array('Line Output - HLS, MPEG-TS & RTMP', 0), 'streaming_servers' => array('Servers - Load Balancers', 0), 'series' => array('TV Series', 0), 'series_episodes' => array('TV Episodes', 0), 'streams' => array('Streams - Live, Radio, Created & VOD', 0), 'streams_sys' => array('Stream Servers', 0), 'streams_options' => array('Stream Options', 0), 'stream_categories' => array('Stream Categories', 0), 'bouquets' => array('Bouquets', 0), 'member_groups' => array('Member Groups', 0), 'packages' => array('Reseller Packages', 0), 'rtmp_ips' => array("RTMP IP's", 0), 'epg' => array('EPG Providers ', 0), 'blocked_ips' => array('Blocked IP Addresses', 0), 'blocked_user_agents' => array('Blocked User-Agents', 0), 'isp_addon' => array("Blocked ISP's", 0), 'tickets' => array('Tickets', 0), 'tickets_replies' => array('Ticket Replies', 0), 'transcoding_profiles' => array('Transcoding Profile', 0), 'watch_folders' => array('Watch Folders', 0), 'members' => array('Users & Resellers', 0), 'epg_sources' => array('EPG Providers', 0), 'blocked_isps' => array("Blocked ISP's", 0), 'categories' => array('Stream Categories', 0), 'groups' => array('Member Groups', 0), 'servers' => array('Servers - Load Balancers', 0), 'stream_servers' => array('Stream Servers', 0));
|
|
}
|
|
|
|
foreach (array_keys($rCount) as $rTable) {
|
|
try {
|
|
$odb->query("SHOW TABLES LIKE '" . $rTable . "';");
|
|
|
|
if ($odb->num_rows() > 0) {
|
|
$odb->query('SELECT COUNT(*) AS `count` FROM `' . $rTable . '`;');
|
|
$rCount[$rTable][1] = $odb->get_row()['count'];
|
|
}
|
|
} catch (Exception $e) {
|
|
}
|
|
}
|
|
$rTotalCount = 0;
|
|
|
|
foreach ($rCount as $rTable => $rItemCount) {
|
|
$rTotalCount += $rItemCount[1];
|
|
}
|
|
ksort($rCount);
|
|
}
|
|
|
|
if (!($rFirstRun || PageAuthorization::checkPermissions())) {
|
|
AdminHelpers::goHome();
|
|
}
|
|
|
|
$_TITLE = 'Database Migration';
|
|
$_SETUP = true;
|
|
$GLOBALS['_SETUP'] = true;
|
|
require_once __DIR__ . '/../layouts/admin.php';
|
|
renderUnifiedLayoutHeader('admin', ['_SETUP' => true]);
|
|
?>
|
|
<h4 class="py-3 mb-4"><?= $language::get('database_migration') ?></h4>
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<?php if ($rMigrating) { ?>
|
|
<!-- State (a): migration in progress -->
|
|
<div class="text-center">
|
|
<i class="icon-base ti tabler-database-export text-info" style="font-size:2.5rem;"></i>
|
|
<h5 class="text-info mt-2 mb-3"><?= $language::get('setup_migrating') ?></h5>
|
|
<textarea readonly id="migration_progress"
|
|
class="form-control bg-dark text-white font-monospace mb-3"
|
|
style="height:360px; resize:none;"></textarea>
|
|
<div class="text-end">
|
|
<button disabled onClick="migrateServer();" class="btn btn-info" id="migrate_button">
|
|
<i class="icon-base ti tabler-refresh me-1"></i><?= $language::get('try_again') ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<?php } else {
|
|
if (RequestManager::has('new') && $rFirstRun) { ?>
|
|
<!-- State (b): first-run admin account creation -->
|
|
<form action="./setup" method="POST" data-parsley-validate="">
|
|
<?php if (isset($_STATUS) && $_STATUS == STATUS_FAILURE) { ?>
|
|
<div class="alert alert-danger" role="alert"><?= $language::get('setup_password_length_error') ?></div>
|
|
<?php } else { ?>
|
|
<div class="alert alert-info" role="alert"><?= $language::get('setup_create_admin_intro') ?></div>
|
|
<?php } ?>
|
|
<div class="row mb-3">
|
|
<label class="col-md-4 col-form-label" for="username"><?= $language::get('admin_username') ?></label>
|
|
<div class="col-md-8">
|
|
<input type="text" class="form-control" id="username" name="username" value=""
|
|
required data-parsley-trigger="change">
|
|
</div>
|
|
</div>
|
|
<div class="row mb-3">
|
|
<label class="col-md-4 col-form-label" for="password"><?= $language::get('admin_password') ?></label>
|
|
<div class="col-md-8">
|
|
<input type="password" class="form-control" id="password" name="password"
|
|
value="" required data-parsley-trigger="change">
|
|
</div>
|
|
</div>
|
|
<div class="row mb-3">
|
|
<label class="col-md-4 col-form-label" for="email"><?= $language::get('email_address') ?></label>
|
|
<div class="col-md-8">
|
|
<input type="text" class="form-control" id="email" name="email" value="">
|
|
</div>
|
|
</div>
|
|
<div class="text-end">
|
|
<input name="new_user" type="submit" class="btn btn-primary" value="<?= htmlspecialchars($language::get('create'), ENT_QUOTES) ?>" />
|
|
</div>
|
|
</form>
|
|
<?php } else { ?>
|
|
<!-- State (c): migration table + Migrate / Don't-Migrate actions -->
|
|
<form action="./setup" method="POST" data-parsley-validate="">
|
|
<div class="alert alert-secondary" role="alert"><?= $language::get('setup_migration_instructions') ?></div>
|
|
<?php if (!$rMigrateConnection): ?>
|
|
<div class="alert alert-danger" role="alert"><?= $language::get('setup_migrate_connection_error') ?></div>
|
|
<?php endif; ?>
|
|
<?php if ($rMigrateConnection && $rTotalCount > 0): ?>
|
|
<div class="alert alert-secondary" role="alert"><?= $language::get('setup_migration_records_intro') ?></div>
|
|
<div class="table-responsive mb-4">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th><?= $language::get('description') ?></th>
|
|
<th><?= $language::get('table_name') ?></th>
|
|
<th class="text-center"><?= $language::get('records') ?></th>
|
|
<th class="text-center"><?= $language::get('migrate') ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($rCount as $rTable => $rItem) {
|
|
if ($rItem[1] != 0) { ?>
|
|
<tr>
|
|
<td><?php echo htmlspecialchars($rItem[0]); ?></td>
|
|
<td><?php echo htmlspecialchars($rTable); ?></td>
|
|
<td class="text-center"><span class="badge bg-label-<?php echo (0 < $rItem[1] ? 'info' : 'secondary'); ?>"><?php echo $rItem[1]; ?></span></td>
|
|
<td class="text-center">
|
|
<div class="form-check d-inline-block">
|
|
<input name="migrate#<?php echo htmlspecialchars($rTable); ?>"
|
|
<?php echo (0 < $rItem[1] ? 'checked' : 'disabled'); ?>
|
|
type="checkbox" class="form-check-input activate">
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php }
|
|
} ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<?php if ($rFirstRun) { ?>
|
|
<a href="./setup?new"><button name="dont_migrate" class="btn btn-danger"
|
|
type="button"><?= $language::get('dont_migrate') ?></button></a>
|
|
<?php } else { ?>
|
|
<span></span>
|
|
<?php }
|
|
if ($rMigrateConnection && $rTotalCount > 0) { ?>
|
|
<input name="migrate" type="submit" class="btn btn-primary" value="<?= htmlspecialchars($language::get('migrate'), ENT_QUOTES) ?>" />
|
|
<?php } ?>
|
|
</div>
|
|
</form>
|
|
<?php }
|
|
} ?>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
require_once __DIR__ . '/../layouts/footer.php';
|
|
renderUnifiedLayoutFooter('admin', ['_SETUP' => true]);
|
|
?>
|
|
<?php if ($rMigrating): ?>
|
|
<script>
|
|
function getMigrationStatus() {
|
|
$.getJSON("./setup?update=1", function(data) {
|
|
// Use the raw <textarea> .value (not jQuery .html()) so log text
|
|
// is set reliably as plain content, never parsed as markup.
|
|
var box = document.getElementById('migration_progress');
|
|
if (data.result === true) {
|
|
if (box) box.value = data.data;
|
|
if (data.status == 1) {
|
|
setTimeout(getMigrationStatus, 1000);
|
|
} else if (data.status == 2) {
|
|
window.location.href = 'dashboard';
|
|
} else if (data.status == 3) {
|
|
$("#migrate_button").prop("disabled", false);
|
|
}
|
|
} else {
|
|
if (box) box.value = <?= json_encode($language::get('setup_no_progress')) ?>;
|
|
setTimeout(getMigrationStatus, 1000);
|
|
}
|
|
if (box) {
|
|
box.scrollTop = box.scrollHeight;
|
|
}
|
|
});
|
|
}
|
|
|
|
function migrateServer() {
|
|
window.location.href = 'setup?migrate=1';
|
|
}
|
|
|
|
$(document).ready(function() {
|
|
$(window).keypress(function(event) {
|
|
if (event.which == 13 && event.target.nodeName != "TEXTAREA") return false;
|
|
});
|
|
getMigrationStatus();
|
|
});
|
|
</script>
|
|
<?php endif; ?>
|
|
<?php
|
|
else:
|
|
if (file_exists(TMP_PATH . '.migration.log')) {
|
|
$rLog = file_get_contents(TMP_PATH . '.migration.log');
|
|
$rStatus = intval(file_get_contents(TMP_PATH . '.migration.status'));
|
|
|
|
if (!$rStatus) {
|
|
$rStatus = 1;
|
|
}
|
|
|
|
if ($rStatus == 2) {
|
|
unlink(TMP_PATH . '.migration.options');
|
|
unlink(TMP_PATH . '.migration.status');
|
|
unlink(TMP_PATH . '.migration.pid');
|
|
unlink(TMP_PATH . '.migration.log');
|
|
}
|
|
|
|
echo json_encode(array('result' => true, 'status' => $rStatus, 'data' =>
|
|
$rLog));
|
|
} else {
|
|
echo json_encode(array('result' => false));
|
|
}
|
|
|
|
exit();
|
|
endif;
|
|
?>
|