Предположим, перед разработчиком поставлен такой вопрос — как со стороны веб-сайта определить, что у пользователя установлено конкретное приложение? Вопрос интересный. На него есть несколько способов ответа. Как вам такой вариант — поставить в систему уникальный шрифт при инсталляции программы? Ведь браузер всегда отдаёт по запросу список системных шрифтов. Значит, проблема решена.
Так делают различные программы, хотя это не назовёшь образцом правильного программирования. У метода свои преимущества и недостатки.
Например, в старых версиях TeamViewer 8 метод работал следующим образом.
Непосредственно скрипт для определения присутствия шрифта располагался здесь (сейчас удалён):
зеркало
Скрипт для подключения:
зеркало
Роберт Хейст, директор по информационной безопасности TeamViewer, прокомментировал ситуацию следующим образом:
Установка на компьютер пользователя проприетарного шрифта — один из способов пометки устройства. Разработчики не видели в этом ничего аморального или незаконного (собственно, ничего противозаконного здесь нет). Пользователю не сообщается о таком методе пометки, потому что ему не положено разбираться в тонкостях работы приложения. Директор по информационной безопасности сказал, что это удобно «для всех групп пользователей» (в том числе для «простых людей»).
Однако через несколько дней после разоблачения и публичного обсуждения нативного шрифта компания TeamViewer решила от него отказаться. Но это не значит, что такой способ распознавания пользователей не используют другие программы. Технически это просто один из вариантов фингерпринтинга. В каком-то смысле, это проприетарная разновидность куков.
Вообще, способов распознавания «заражённой» системы есть огромное количество. Но эти методы «свой-чужой» используют преимущественно создатели вредоносных программ (например, для контроля и управления ботнетом).
Если вы придумали свой оригинальный способ пометки компьютера — то получаете ряд преимуществ, например:
В принципе, это один из примеров применения принципа «безопасность через неясность» (security through obscurity). Преимущества и недостатки этого принципа хорошо известны. Обычно он считается опасным и вредным, но в некоторых отдельных случаях всё-таки есть рациональные основания прибегать к секретности. Хотя отрицательные стороны тоже присутствуют. Главная проблема — что любой посторонний сайт может определить, какое программное обеспечение установлено на вашем компьютере, что не очень хорошо с точки зрения безопасности (именно поэтому TeamViewer принял решение отказаться от этого метода).
Стандартный способ запускать нативное приложение из браузера — это Web App Manifest. Например, iOS запускает нативные программы через адрес
Вероятно, при использовании стандартного способа запуска приложений у сообщества информационной безопасности не возникнет претензий к разработчику.
Так делают различные программы, хотя это не назовёшь образцом правильного программирования. У метода свои преимущества и недостатки.
Например, в старых версиях TeamViewer 8 метод работал следующим образом.
- Чтобы открыть сессию с другим пользователем, программа генерирует пригласительный URL следующего вида:
https://get.teamviewer.com/v15/en/sXXXXXXXX
… гдеXXXXXXXX
— это код сессии.
- Сайт проверяет с помощью JavaScript, что у пользователя присутствует шрифт TeamViewer. Это означает, что программное обеспечение TeamViewer установлено в системе.
Поскольку инсталлятор зарегистрировал в операционной системе обработчик протоколаteamviewer8://
, то веб-сайт (код JavaScript) может напрямую вызвать TeamViewer по следующему URL:teamviewer8://instantsupport/?sid=XXXXXXXX
- Если шрифт в системе не обнаружен, то сайт предлагает пользователю скачать и установить приложение.
Непосредственно скрипт для определения присутствия шрифта располагался здесь (сейчас удалён):
https://get.teamviewer.com/get/res/scripts/fontdetect.js
зеркало
Код в деобфусцированном виде
if (!self.__WB_pmw) {
self.__WB_pmw = function (obj) {
this.__WB_source = obj;
return this;
};
}
{
let window = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("window") || self.window;
let self = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("self") || self.self;
let document = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("document") || self.document;
let location = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("location") || self.location;
let top = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("top") || self.top;
let parent = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("parent") || self.parent;
let frames = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("frames") || self.frames;
let opener = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("opener") || self.opener;
function detect(a) {
var c = document.getElementsByTagName("body")[0], b = document.createElement("span");
b.style.fontSize = "72px";
b.innerHTML = a;
b.style.fontFamily = "nonexistingfonttoforcedefault";
c.appendChild(b);
var e = b.offsetWidth, d = b.offsetHeight;
c.removeChild(b);
b.style.fontFamily = a + ",nonexistingfonttoforcedefault";
c.appendChild(b);
a = b.offsetWidth != e || b.offsetHeight != d;
c.removeChild(b);
return a;
}
function minimalisticMajorCheck(a) {
a = "TeamViewer" + String(a);
return detect(a) ? true : detect(a + "Host") ? true : false;
}
function getMajorVersionArray() {
for (var a = 0, c = [], b = 99; 1 <= b; b--) minimalisticMajorCheck(b) && (c[a++] = b);
return c;
}
function getCharWidth(a, c, b) {
var e = "TeamViewer" + String(a);
a = document.getElementsByTagName("body")[0];
var d = document.createElement("span");
d.style.fontSize = "10px";
d.style.fontFamily = e;
d.innerHTML = String(c);
a.appendChild(d);
c = d.offsetWidth;
a.removeChild(d);
b && 10 == c && (c = 0);
10 < c && (c = -1);
return c;
}
function getVersionFromString(a, c) {
for (var b = 0, e = false, d = 0; d < c.length; d++) {
var f = getCharWidth(a, c[d], true);
if (0 > f || 9 < f) e = true;
b = 10 * b + f;
}
return e ? -1 : b;
}
function CheckForTeamViewer() {
var a = getMajorVersionArray();
return 0 < a.length ? a[0] : 0;
}
;
}
Скрипт для подключения:
https://get.teamviewer.com/get/res/scripts/connect.js
зеркало
Код в деобфусцированном виде
if (!self.__WB_pmw) {
self.__WB_pmw = function (obj) {
this.__WB_source = obj;
return this;
};
}
{
let window = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("window") || self.window;
let self = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("self") || self.self;
let document = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("document") || self.document;
let location = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("location") || self.location;
let top = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("top") || self.top;
let parent = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("parent") || self.parent;
let frames = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("frames") || self.frames;
let opener = self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init("opener") || self.opener;
function isBlackListed(e) {
if (blackList = new Array(42, 43, 44, 47, 59, 61, 91, 92, 93, 106, 107, 110, 111), 65 <= e && e <= 82) return true;
if (84 <= e && e <= 90) return true;
if (145 <= e && e <= 188) return true;
if (190 <= e) return true;
for (i = 0; i < blackList.length; i++) if (blackList[i] == e) return true;
return false;
}
function checkIdFormat(e, t) {
var n;
if ((t = t || window.event).which ? n = t.which : t.keyCode && (n = t.keyCode), t.ctrlKey) return true;
if (t.altKey) return false;
if (isBlackListed(n)) return false;
var i = "";
return e && e.value && (i = e.value), theMidLength = i.length, (83 != n || 0 == theMidLength) && (48 <= n && n <= 57 || 96 <= n && n <= 105 ? (t.shiftKey || (0 == theMidLength && (e.value = "s"), 3 != theMidLength && 7 != theMidLength || (e.value = e.value + "-"), 96 <= n && n <= 105 && (n -= 48), e.value = e.value + String.fromCharCode(n)), false) : 109 != n && 189 != n && 45 != n && 32 != n || (3 != theMidLength && 7 != theMidLength || (e.value = e.value + "-"), false));
}
var timestamp;
function SetFallbackUrl(e) {
timestamp = new Date, setTimeout(function () {
new Date - timestamp < 1e3 && window.location.replace(e);
}, 500);
}
function SetFallbackUrlNoCheck(e) {
timestamp = new Date, setTimeout(function () {
window.location.replace(e);
}, 10);
}
function OpenLinkWithFallbackNoFrame(e, t) {
SetFallbackUrl(t), window.location.replace(e);
}
function OpenLinkWithFallback(e, t) {
SetFallbackUrl(t);
t = document.createElement("iframe");
t.style.display = "none", document.body.appendChild(t), t.src = e;
}
function OpenLinkWithFallbackWithoutIframe(e, t) {
SetFallbackUrlNoCheck(t), window.location.replace(e);
}
function OpenLinkWithoutFallback(e) {
window.location.replace(e);
}
function TryConnectWithTV(e, t) {
var n, i;
8 < CheckForTeamViewer() ? (n = e, i = document.getElementById("dDownloadTeamViewer"), e = document.getElementById("dStartTeamViewer"), i && e && (i.style.display = "none", e.style.display = "")) : n = t, window.location.replace(n);
}
function TryConnectWithTVWithPreferenceCheck(e, t, n, i) {
var o, a, r;
8 < CheckForTeamViewer() ? (o = e, a = document.getElementById("dDownloadTeamViewer"), r = document.getElementById("dStartTeamViewer"), a && r && (a.style.display = "none", r.style.display = ""), window.location.replace(o)) : "" === (a = getCookie("getuserpreference")) ? ($("#userPreferenceDialog").attr("title", "Connect to " + n), r = $(window).width(), n = $(window).height(), $("#userPreferenceDialog").dialog({autoOpen: false, height: 0.32 * n, width: 0.3 * r, modal: true, show: {effect: "fade", duration: 1e3}}).dialog("open"), $("#tdFullClient").click(function () {
HandlePreferenceSelection(e, "launch");
}), "true" === i ? $("#tdQuickSupport").remove() : $("#tdQuickSupport").click(function () {
HandlePreferenceSelection(t, "download");
})) : (o = "launch" === a ? e : t, window.location.replace(o));
}
function HandlePreferenceSelection(e, t) {
window.location.replace(e), $("#chkRememberSelection").is(":checked") && setCookie("getuserpreference", t, 365);
}
function setCookie(e, t, n) {
var i = new Date;
i.setTime(i.getTime() + 24 * n * 60 * 60 * 1e3);
i = "expires=" + i.toUTCString();
document.cookie = e + "=" + t + ";" + i + ";path=/";
}
function getCookie(e) {
for (var t = e + "=", n = decodeURIComponent(document.cookie).split(";"), i = 0; i < n.length; i++) {
for (var o = n[i]; " " === o.charAt(0);) o = o.substring(1);
if (0 === o.indexOf(t)) return o.substring(t.length, o.length);
}
return "";
}
function GetTextLength(e, t, n, i) {
var o = $("#divMeasureText");
return o.css("font-family", t).css("font-size", n).css("font-weight", i), o.text(e), o.width();
}
}
Роберт Хейст, директор по информационной безопасности TeamViewer, прокомментировал ситуацию следующим образом:
Шрифт TeamViewer используется для реализации плавного перехода пользователя от веб-клиента к нативному клиенту, например, при подключении по пригласительной ссылке, для предложения установки или прямой инициации подключения. Это оказалось полезным для улучшения пользовательского опыта у всех групп пользователей. Тем не менее, учитывая возникшую обеспокоенность, мы решили пересмотреть и изменить этот подход в одной из следующих версий, чтобы предотвратить возможное обнаружение установки TeamViewer через шрифт.
Пометка устройства
Установка на компьютер пользователя проприетарного шрифта — один из способов пометки устройства. Разработчики не видели в этом ничего аморального или незаконного (собственно, ничего противозаконного здесь нет). Пользователю не сообщается о таком методе пометки, потому что ему не положено разбираться в тонкостях работы приложения. Директор по информационной безопасности сказал, что это удобно «для всех групп пользователей» (в том числе для «простых людей»).
Однако через несколько дней после разоблачения и публичного обсуждения нативного шрифта компания TeamViewer решила от него отказаться. Но это не значит, что такой способ распознавания пользователей не используют другие программы. Технически это просто один из вариантов фингерпринтинга. В каком-то смысле, это проприетарная разновидность куков.
Вообще, способов распознавания «заражённой» системы есть огромное количество. Но эти методы «свой-чужой» используют преимущественно создатели вредоносных программ (например, для контроля и управления ботнетом).
Преимущества проприетарных куков
Если вы придумали свой оригинальный способ пометки компьютера — то получаете ряд преимуществ, например:
- метод работает даже в том случае, если у пользователя в системе не поддерживаются или заблокированы куки;
- системный шрифт не удаляется по таймеру, у него нет срока действия, как у куков;
- системный шрифт не удаляется при очистке куков из браузера;
- на запись куков требуется спросить разрешение по закону ЕС, на установку системного шрифта пока не требуется;
- и др.
В принципе, это один из примеров применения принципа «безопасность через неясность» (security through obscurity). Преимущества и недостатки этого принципа хорошо известны. Обычно он считается опасным и вредным, но в некоторых отдельных случаях всё-таки есть рациональные основания прибегать к секретности. Хотя отрицательные стороны тоже присутствуют. Главная проблема — что любой посторонний сайт может определить, какое программное обеспечение установлено на вашем компьютере, что не очень хорошо с точки зрения безопасности (именно поэтому TeamViewer принял решение отказаться от этого метода).
Стандартный способ — Web App Manifest
Стандартный способ запускать нативное приложение из браузера — это Web App Manifest. Например, iOS запускает нативные программы через адрес
/.well-known/apple-app-site-association
на домене. Поддержка манифестов related_applications
и prefer_related_applications
сейчас находится на экспериментальной стадии в разных браузерах.Вероятно, при использовании стандартного способа запуска приложений у сообщества информационной безопасности не возникнет претензий к разработчику.
Evengard
С таким же успехом можно открыть специфический порт на локалхосте и проверять коннект к нему (так даже некоторые приложения - в частности всякие игровые лаунчеры - и делают). Можно даже общаться через этот канал данных...
Wernisag
Как обойдете NAT?
mavir
Не надо обходить NAT, из браузера отправлять запрос на 127.0.0.1:<port>
Единственное ограничение - приложение должно быть запущено.
Wernisag
Чтот не подумал про локалхост)
steff
Было дело: https://habr.com/ru/post/456558/