Как исправить ссылки target = ”_ blank”: проблема безопасности и производительности на веб-страницах

Почти на каждой веб-странице есть ссылки, которые открываются в новой вкладке, а другая веб-страница остается доступной. Например, информационные агентства будут писать твиты о последних событиях в Твиттере: твит содержит краткое описание статьи и ссылку для просмотра всей истории на их веб-странице.

По умолчанию ссылки в HTML открываются на вашей текущей вкладке, и это обычно полезно. В конце концов, как владелец веб-страницы, вы хотите, чтобы люди оставались на вашей веб-странице и не отвлекались на слишком много открытых вкладок или других веб-страниц. Внимание людей ограничено, поэтому вы хотите, чтобы они сосредоточились на вашем контенте. Кроме того, ссылки, которые открываются в новой вкладке, также плохо влияют на доступность, если вы не предоставляете подсказки и атрибуты ARIA.

Все еще есть случаи, когда вы хотите или должны ссылаться на внешние страницы. Вышеупомянутый пример информационного агентства является хорошим примером: информационные агентства хотят, чтобы люди читали новостные статьи на своих веб-страницах, а не на внешних веб-страницах, таких как Twitter, потому что вам нужны доходы от рекламы от посетителей. Вот почему Twitter позволяет своим пользователям использовать ссылки в своих твитах. Обычно открытие ссылок в новой вкладке используется, когда ссылка принадлежит не вам, а третьему лицу.

Как попасть прямо в ловушку

Итак, как сделать так, чтобы ссылка открывалась на новой вкладке в HTML? Это легко (может быть, слишком просто), нам даже не нужно писать код JavaScript. Все, что нам нужно сделать, это добавить атрибут target=”_blank” к вашей привязке, и все готово.

‹a href="https://sedeo.net" target="_blank"›. Перейти к Sedeo ‹/a›

Теперь каждый раз, когда кто-то нажимает на эту ссылку, веб-страница (в данном случае Sedeo) будет открываться в новой вкладке.

Давайте представим, что веб-страница, на которую я ссылаюсь в примере, безопасна (я знаю, что это безопасно, потому что я ее создал). Но, возможно, внешняя веб-страница принадлежит третьей стороне (например, поставщику услуг или покупателю). Или, может быть, вы разрешите пользовательский контент на своей веб-странице (например, комментарии, в которых пользователи могут использовать ссылки на внешние веб-страницы). В этих случаях вы не контролируете эти связанные веб-страницы и не знаете, что там делают люди, стоящие за ними.

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

Проблема безопасности: табнабинг.

По умолчанию, когда вы открываете веб-страницу в новой вкладке, щелкнув ссылку с target="_blank", эта страница теперь имеет ограниченный доступ к странице, на которую указывает ссылка. Самым важным аспектом безопасности, о котором я могу думать, является то, что вы можете изменить window.opener.location страницы ссылок. Например: веб-страница сомнительных новостей использует Twitter для распространения некоторых историй. Пользователь Twitter видит твит, а затем щелкает, чтобы прочитать всю историю на теневой веб-странице новостей, которая открывается в новой вкладке. Между тем, страница теневых новостей может использовать этот встроенный скрипт на своей странице.

window.opener.location = «http://fake-twitter.com»

и теперь пользователь больше не в Твиттере, а на этой фальшивой веб-странице, похожей на Твиттер. Эта фальшивая страница выглядит точно так же, как Twitter, и требует входа в систему, потому что вы по какой-то причине вышли из системы. Пользователь, не особо задумываясь об этом, вводит свои учетные данные и отправляет форму.

Вы только что отправили свои личные учетные данные кому-то, кто теперь может войти в вашу учетную запись Twitter и сделать что-нибудь вроде быстрого изменения учетных данных.

Этот вид фишинга называется (обратная) табуляция. Напоминаем, что вот определение фишинга из Википедии:

Фишинг - это мошенническая попытка получить конфиденциальную информацию, такую ​​как имена пользователей, пароли и данные кредитной карты (и деньги), часто по злонамеренным причинам, путем маскировки под надежную организацию в электронном сообщении.

Влияние на производительность просмотра

Интересно, что target="_blank" также влияет на производительность вашего просмотра веб-страниц, что я наблюдал воочию. Каждый крупный современный браузер, такой как Chrome и Firefox, является многопроцессорным. Благодаря синхронному межоконному доступу, который DOM предоставляет нам через window.opener, окна, запущенные через target="_blank", попадают в один и тот же процесс и поток. То же самое верно для iFrames и окон, открытых через window.open.

В качестве побочного проекта я разработал персональную «стартовую страницу», которая выглядит и ведет себя так же, как и Speed ​​Dial в Opera. При нажатии на одну из плиток веб-страница открывается в новой вкладке (каждая ссылка имеет target=”_blank”). Я заметил, что мой просмотр веб-страниц по какой-то причине был медленнее, и я подозревал, что это как-то связано с моей собственной страницей, потому что я не сталкивался с этой проблемой, когда не использовал свою собственную страницу. После того, как я узнал об этой проблеме с target=”_blank”, я изменил свой код, и снижение производительности исчезло.

Какие популярные веб-страницы уязвимы?

Проблема хорошо известна, но в последние несколько лет она стала более известной. Вот несколько веб-страниц, на которых работает этот подход с использованием табуляции (на момент написания этой статьи). Я надеюсь, что эти веб-страницы, которыми пользуются миллионы людей, быстро решат эту проблему:

  • Ebay.com (хотя влияние на безопасность должно быть уменьшено, поскольку большинство внешних ссылок работает в iFrame)
  • Heroku.com
  • Моя Нинтендо

Некоторые веб-страницы, которые успешно блокируют этот подход табуляции:

  • GitHub.com
  • Slack.com
  • Twitter.com
  • Facebook.com
  • United-domains Webmailer (они исправили это, когда я их уведомил. В этом дух!)
  • dev.to (они исправили это быстро, когда я их уведомил)

Простые решения

Есть два решения для устранения этих проблем. Первое решение - добавить атрибут rel="noopener noreferrer" к каждой ссылке с target="_blank". noopener - необходимое значение, чтобы гарантировать, что связанные страницы не имеют доступа к странице, на которую указывает ссылка. Используйте этот подход, когда у вас есть доступ к HTML-коду и когда не так много случаев, чтобы исправить это вручную. Некоторые CMS, такие как WordPress (≥ 4.7.4), делают это автоматически, поэтому вам не нужно предпринимать никаких действий. Приведенный выше (теперь безопасный) пример теперь будет выглядеть так:

<a href=”https://sedeo.net” target=”_blank” rel=”noopener noreferrer”>Go to sedeo</a>

Браузеры поддерживают noopener довольно хорошо в современных браузерах, браузеры Microsoft, такие как Internet Explorer, как обычно, являются исключением. Однако Microsoft Edge предположительно не поддерживает window.opener для target="_blank" ссылок.

Второй подход - использовать JavaScript для решения этой проблемы. Используйте это, когда вы управляете CMS или если существует слишком много вхождений, чтобы исправить все эти ссылки вручную.

var otherWindow = window.open();
otherWindow.opener = null;

Если вы разрешаете контент, создаваемый пользователями (например, в социальной сети), не забудьте очистить ссылки. Twitter автоматически добавляет rel="noopener" к каждой ссылке в твите.

ОБНОВЛЕНИЕ: раннее выявление небезопасных статических / динамических ссылок

Вы можете определить небезопасные ссылки на раннем этапе, если добавите правило lint. Таким образом, вы сможете идентифицировать небезопасные ссылки в вашем HTML. Этот линтер будет. Вот реализация такого правила для популярного линтера HTMLHint:

module.exports = (HTMLHint) => {
  HTMLHint.addRule({
    id: 'noopener-external-links',
    description: 'Links with target="_blank" must have a rel="noopener noreferrer" attribute to prevent reverse tabnabbing.',
    init: function (parser, reporter) {
      var self = this;
      parser.addListener('tagstart', function (event) {
        var tagName = event.tagName.toLowerCase();
        var col = event.col + event.tagName.length + 1;
        var mapAttrs = parser.getMapAttrs(event.attrs);
        if (tagName === 'a' && mapAttrs.target && (!mapAttrs.rel || mapAttrs.rel.indexOf('noopener') === -1 || mapAttrs.rel.indexOf('noreferrer') === -1)) {
          reporter.warn('Prevent reverse tabnabbing of external links by providing rel="noopener noreferrer" attribute.', event.line, col, self, event.raw);
        }
      });
    }
  });
};

Чтобы определить, небезопасно ли открытие страницы в новом окне с помощью JavaScript, вы можете написать ESLint / TSLint правило, которое запрещает прямое использование window.open в пользу настраиваемой функции, которая сбрасывает открыватель.

Динамически созданные ссылки (например, через jQuery) не будут идентифицироваться линтерами. Один из способов идентифицировать их - многократно сканировать DOM на наличие небезопасных ссылок при локальной разработке. Вот простой способ сделать это на простом JavaScript:

if (!environment.production) {
  setInterval(() => {
    const links = Array.from(document.querySelectorAll('a[target]'));
    for (let link of links) {
      const target = link.getAttribute('target');
      if (target && (!link.getAttribute('rel') || link.getAttribute('rel').indexOf('noopener') === -1)) {
        console.error(`Unsafe link ${link} is vulnerable to reverse tabnabbing.`);
      }
    }
  }, 5000);
}

Заключение

Интересно, что Google не считает это серьезной проблемой безопасности.

На мой взгляд, нет серьезных недостатков в применении первого решения (добавление rel="noopener" к каждой ссылке target="_blank"). Этот выпуск показывает, насколько легко создать лазейки в безопасности вашей веб-страницы. Кроме того, влияние target=”_blank” ссылок на производительность также может быть существенным для веб-приложений с высокой потребностью в производительности. Если вы веб-разработчик, я бы посоветовал вам проверить свой код, чтобы устранить эту проблему.

Спасибо, что прочитали мои мысли по этому поводу. Я хотел бы услышать ваши мысли в комментариях.

Источники: