Почему нельзя установить размер шрифта у посещенной ссылки

Привет, Хабр! Представляю Вашему вниманию перевод статьи «Why can’t I set the font size of a visited link?» автора Jim Fisher.



Посещенные ссылки отображаются фиолетовым; не посещенные — голубым. Это различие пришло к нам с времен появления веба. Но CSS позволяет нам перенастроить эти свойства с помощью псевдо-селектора :visited! Скажем, вы хотите сделать посещенные ссылки серыми и уменьшить их размер, для того чтобы показать пользователю что эта ссылка уже была посещена.

a:visited {
  color: gray;
  font-size: 6px;
}

Этот стиль применен к текущей странице:


Окрашивание ссылки в серый цвет, как и предполагалось, уведомляет нас о том что она уже была посещена, но размер шрифта остался прежним! Так происходит, потому что изменение размера шрифта может быть причиной уязвимости! Если CSS сможет изменить размер шрифта, я(Jim) могу сказать посещали ли Вы pornhub.com. Но как?

Веб страницы доступны для инспектирования отображенных элементов на странице. Наиболее очевидный способ — использовать window.getComputedStyle(). Он рассказывает о свойствах, примененных к вышеупомянутой посещенной ссылке; как сообщает браузер: font-size: 18px; color: rgb(0, 0, 238).

Если getComputedStyle сообщит об изменении размера шрифта с 18px до 6px для посещенных ссылок, я(Jim) пойму, что на странице создана ссылка на pornhub.com, затем я проверю размер шрифта, для того чтобы определить историю посещений вашего браузера. Я могу использовать эти данные для таргетированной рекламы, могу продать ваши данные или шантажировать вас, и так далее… Дыра в безопасности была закрыта путем запрета на изменение font-size для a:visited.

Но информация, полученная с помощью getComputedStyle, о цвете посещенной ссылки будет: rgb(0, 0, 238) — то есть голубое. Это ложь — ссылка же серая! Для свойства color, браузеры закрывают дыру в уязвимости другим способом: вместо запрета на изменение, они заставляют getComputedStyle лгать о реальном значении.

Почему же используется два подхода? Почему мы не можем заставить getComputedStyle лгать также и о параметре font-size? Причина в том, что можно исследовать отображенные элементы веб страницы и другими методам, кроме getComputedStyle. Позицию элемента на странице можно проверить так же с помощью .pageXOffset или .pageYOffset. Изменение font-size у посещенной ссылки будет влиять на смещение у других элементов, из-за чего можно косвенно проверить какие ссылки были посещены. Отключение font-size для a:visited — это жесткое, но надежное решение.

По этой ссылке содержится краткий список разрешенных свойств, таких же как color, которые не должны влиять на макет страницы, и не могут быть обнаружены. Все они — разные формы изменения цвета. Все другие CSS свойства — заблокированы.

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

P. S.:
Отдельное спасибо за помощь в корректировке перевода Переверзевой Ольге.

Спасибо Олегу Яценко(Samber), в комментариях он обратил внимание на то что решение проблемы с подобной утечкой данных было впервые реализовано в Mozilla — автор решения David Baron.
Поделиться публикацией

Комментарии 28

    –39
    Неужели в CSS нет более интересных и часто встречаемых проблем, о которых можно написать статью? Grid? Flex?

    В каких ситуациях вообще может понадобится менять размер посещенной ссылки? Это сделает UI менее постоянным, вследствии чего ухудшится UX

    Если очень нужно, JS как всегда приходит на помощь в подобных ситуациях:

    <a 
      href="https://habr.com/" 
      target="_blank" 
      onclick="this.classList.add('visited-link')"
    >link text
    </a>


    .visited-link {
      font-size: 35px;
    }
      +7
      В вашем примере размер шрифта изменится при клике по ссылке, а это не означает, что она была посещена. При обновлении страницы стили не сохранятся. При переходе по этой ссылке с другого источника также результата не будет.
        +18

        По мне так знания о непрямых атаках развивают мышление. Заставляют задумываться о последствиях казалось бы безобидных решений.
        Помимо того, что нельзя изменить размер шрифта, нельзя изменить и сам шрифт и его начертание и много чего ещё, что имело бы смысл в дизайне.

          +12
          Вы, видимо, не поняли, тут рассказывается не о проблеме стилизации а о безопасности.
          «В каких ситуациях» — в ситуации когда злоумышленник хочет узнать какие сайты вы посещаете.
          Если бы эта уязвимость работала, злоумышленник мог бы разместить на своём сайте 100500 ссылок, которые хочет проверить, и мог бы увидеть какие ссылки у посетителей отмечались бы другим цветом или размером.
          JS вам в этом никак не поможет.
            +4
            Собственно, когда уязвимость существовала, так и делали.
            0
            Неужели в CSS нет более интересных и часто встречаемых проблем, о которых можно написать статью? Grid? Flex?

            В CSS, как кажется, порой проблем больше, чем решений. И уж про Grid, Flex, а также про много других подходов писали, и не раз, или я не прав? Но почему бы на Хабре (где вообще пишут обо всем, за уши притягивая тему к словам «ит» и «гик», как в том анекдоте про рыбу и блох) не описать такую вот тему, тем более что она, и правда, имеет очень интересную причину — я про откровенно, как кажется, нелогичное поведение браузера, идущее не от бага, а от логики безопасности? И лично мне, кстати, было очень интересно узнать об этом поведении, поскольку проблему, которую оно решает, я как раз считаю очень серьезной.
              +3
              Что-то я не понял, если скрипт может выполнить `getComputedStyle`, то он может и навесить `onclick` на все ссылки, в чем состоит защита в этом случае?

              PS: нашел ответ на reddit, без защиты сторонний скрипт мог бы нарисовать невидимый блок с тысячами ссылок и проверить были ли они посещены или нет.
                +10

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



                Я вижу это так:


                Можно сделать вывод, что я посещал главную Хабра и Ютуба когда-то. Хотя я не нажимал непосредственно на эти ссылки сейчас, обработчик бы не сработал.

                  0
                  У меня не подсветился habr.com, хотя я на habr.com прямо сейчас о_О
                  image
                    +1
                    в ссылке http
                +1
                А почему нельзя делать скрин ссылки, и проверять цвет по конкретной точке? Можно самому воткнуть стиль для посещенных ссылок, за футером помещать интересующие ссылки, и смотреть цвет пикселя
                  +6

                  Попробовал только что поиграться с html2canvas.js — браузер не отдает в читаемый канвас свойства псевдоэлемента :visited.

                    +3

                    html2canvas не делает скриншот, а рендерит страницу с нуля, используя данные из HTML и CSS кода. Поэтому на него действует защаита, встроенная в getComputedStyle.

                    +8
                    Потому-что нет встроенных средств сделать скриншоты по причине безопасности
                    +10
                    В Chrome есть дыра через которую можно узнать состояние :visited. Почти год прошел с тех пор как был открыт Issue но команда Chrome не торопится его закрывать.
                      0
                      А вот оно где. Я до этого не дошел, через час надоело. Можно еще порыться в направлении свойств дочерних/соседних элементов: а:visited [selector]
                        +1
                        Описанный способ так же работает и в последней версии ФФ — можно скачать attack.html из того issue, и попробовать.
                        –1
                        А разве нельзя на a:visited повешать еще что-то типа display:none, и проверять уже дальше, что элемент вообще видимый?
                          +1
                          Display не входит в список разрешённых для :visited свойств, поэтому браузер просто проигнорирует свойство
                            0
                            Allowable CSS properties are color, background-color, border-color, border-bottom-color, border-left-color, border-right-color, border-top-color, column-rule-color, and outline-color.
                            –6
                            А зачем вообще отсылать на сервер данные о размерах и координатах? Надо не столб корчевать, а пистолет снять с боевого взвода. А то мало ли. Вдруг на столб облокотится сама Наташа? Ржевского ещё можно изолировать в гусарской балладе. Но нельзя туда же отправлять и Ротовых. Ссылки же должны просто вести себя понятно для посетителей.
                              0

                              Именно так, «изоляцией Ржевского в гусарской балладе», собираются решать проблему посещенных ссылок в будущем. Но пока уже много лет есть существующее решение, браузеры стремятся хотя бы его поддерживать единообразно. И в этом, имхо, правы.


                              А отправлять на сервер небезопасные данные через CSS, конечно, незачем, но не всегда вас об этом спрашивают..;)

                                0
                                То есть Ростовых. Очепятка. Ржевского ещё можно. Но это не решение, так как облокотиться на не существующий столб может и сама Наташа. А отправлять в Гусарскую Балладу следом за Ржевским всех Ростовых уже нельзя. Пистолет надо со взвода снять, тогда при падении Пьера не будет выстрела и можно оставить столб. И всех троих не пускать на Луну, где столб и корчевали. Тогда можно и столб оставить, а Наташа может даже со Ржевским гулять, а потом жаловаться Пьеру, что ей было скучно потому, что Ржевский не умеет рассказывать каламбуры. Нафига вообще пихать в протокол серверные getComputedStyle, pageXOffset и pageYOffset? И нафига вообще поддерживать стили нажатой и не нажатой ссылки? Мне вот даже для состоящего целиком из ссылок оглавления сайта хватило стилей абзаца и пункта списка. Причём, на фоне цвета 00688B. Даже его сочетание со стандартными цветами ссылок выглядит лучше, чем чёрт знает с чем. Просто потому, что это ожидаемые цвета. Плюс они ещё и понятнее. А с таким подходом, как в статье описан Наташа скоро будет еле ползать в полном противопульном доспехе, а Пьер – робко придерживать её наруч. И всё равно пара пуль залетит Наташе в забрало, после чего Наташу запихают в танк, а Пьеру придётся придерживать уже щиток над правым передним катком. А потом будут не верить, что абрамс не надо даже пробивать, так как он отлично сжигается из любого пистолета, тем более это отлично делается в таких шикарных условиях. А когда поверят и поменяют его на Т90, окажется, что пуля может залететь в ствол и вызвать детонацию снаряда. И вместо того, чтоб убрать снаряд в боеукладку, заварят ствол чем-то средним между пробкой и миномётной миной. Взрывателем внутырь. А когда случайно выстрелит уже Наташа, каюк придёт и ей, и Пьеру, да ещё и призрак Ржевского шашкой помашет.
                                +4
                                плохая метафора как кот с дверью
                                +1
                                Какое же облегчение чувствуешь, читая такие строки. Даже на таком уровне используют костыли. Фух, я не один такой!
                                  +4
                                  Весь веб — это много много костылей, чтобы просто показать пользователям некое изображение (в виде текста, картинок или видео).
                                  0
                                  Для меня не менее интересным открытием стал запрет на использование CSS переменных внутри :visited в Хроме. А вот в FireFox это не запрещено.

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

                                  Самое читаемое