Предотвращение Open Redirect

Page.js — это небольшая библиотека для клиентской маршрутизации в браузере. Она позволяет обрабатывать изменения URL без перезагрузки страницы, создавая SPA-подобное поведение. Основной принцип работы основан на подписке на события изменения window.location и сопоставлении текущего пути с зарегистрированными маршрутами через page(path, callback).

Маршруты могут содержать динамические параметры, которые автоматически извлекаются и передаются в колбэки:

page('/user/:id', ctx => {
    console.log(ctx.params.id); // извлеченный id пользователя
});

Каждый маршрут получает объект ctx с ключевыми свойствами: path, params, querystring и hash.

Опасность Open Redirect

Open Redirect возникает, когда приложение принимает внешние URL и автоматически перенаправляет на них без проверки. Это критическая уязвимость, так как злоумышленник может использовать её для фишинга или распространения вредоносных ссылок.

Пример уязвимого кода с Page.js:

page('/redirect', ctx => {
    const target = ctx.querystring.split('=')[1];
    window.location.href = target; // опасно!
});

В этом случае любой может передавать сторонний URL через ?url= и инициировать перенаправление на вредоносный сайт.

Ограничение редиректов

Для предотвращения Open Redirect необходимо проверять передаваемые URL. Существует несколько подходов:

1. Белый список допустимых URL

Создание массива допустимых путей внутри приложения:

const allowedPaths = ['/dashboard', '/profile', '/settings'];

page('/redirect', ctx => {
    const target = ctx.querystring.split('=')[1];
    if (allowedPaths.includes(target)) {
        page(target);
    } else {
        page('/error');
    }
});

Белый список гарантирует, что редирект произойдет только на заранее определённые маршруты внутри приложения.

2. Ограничение внутренними маршрутами

Часто достаточно разрешать редирект только на пути без доменного имени:

page('/redirect', ctx => {
    const target = ctx.querystring.split('=')[1];
    if (target && target.startsWith('/')) {
        page(target);
    } else {
        page('/error');
    }
});

Это исключает возможность указания внешнего URL (https://evil.com) и оставляет только внутренние маршруты.

3. Преобразование параметров в безопасный формат

Если редирект зависит от динамических данных, лучше использовать идентификаторы, а не полный URL:

const redirectMap = {
    home: '/',
    dashboard: '/dashboard',
    profile: '/profile'
};

page('/redirect/:target', ctx => {
    const safePath = redirectMap[ctx.params.target];
    if (safePath) {
        page(safePath);
    } else {
        page('/');
    }
});

В этом подходе пользователь никогда не передает полный URL, что полностью устраняет возможность Open Redirect.

Защита querystring и hash

Page.js позволяет работать с query-параметрами и хешами. Для безопасной маршрутизации их также необходимо проверять:

page('/redirect', ctx => {
    const params = new URLSearchParams(ctx.querystring);
    const target = params.get('page');
    
    if (target && target.startsWith('/')) {
        page(target);
    } else {
        page('/error');
    }
});

Использование URLSearchParams упрощает обработку и предотвращает ошибки при парсинге строки запроса.

Применение middleware для проверки редиректов

Page.js поддерживает middleware-функции, которые вызываются перед основным колбэком маршрута. Это удобно для централизованной проверки редиректов:

function validateRedirect(ctx, next) {
    const params = new URLSearchParams(ctx.querystring);
    const target = params.get('page');

    if (target && target.startsWith('/')) {
        next();
    } else {
        page('/error');
    }
}

page('/redirect', validateRedirect, ctx => {
    const target = new URLSearchParams(ctx.querystring).get('page');
    page(target);
});

Middleware позволяет масштабировать приложение и добавлять дополнительные проверки, не повторяя код для каждого маршрута.

Логирование подозрительных попыток редиректа

Для усиления безопасности полезно вести логирование всех попыток редиректа на недопустимые URL:

page('*', ctx => {
    if (ctx.path === '/redirect') {
        const params = new URLSearchParams(ctx.querystring);
        const target = params.get('page');
        if (!target.startsWith('/')) {
            console.warn(`Попытка недопустимого редиректа на: ${target}`);
        }
    }
});

Такой подход помогает обнаруживать потенциальные атаки и анализировать их источники.

Использование Page.js с безопасными шаблонами

При построении маршрутов важно комбинировать безопасные колбэки с шаблонами, которые не выводят пользовательский ввод напрямую в DOM без экранирования. Это снижает риск не только Open Redirect, но и XSS-уязвимостей.


Page.js предоставляет гибкие возможности маршрутизации, но безопасность редиректов требует строгого контроля входных данных. Белые списки, проверка внутренних путей и использование middleware являются ключевыми методами предотвращения Open Redirect, обеспечивая надежную работу SPA без риска фишинга и внешних атак.