Привет. Я в свободное время развиваю свой небольшой сайт — платформу для ведения личных дневников. Похож на ЖЖ или Дайри, но более современный и молодежный, полузакрытый, уютный. И у нас есть необходимость отслеживать, когда пользователи создают себе дополнительные аккаунты. В этом посте хочу поделиться своими идеями и опытом, как это у нас реализовано.

Зачем это нужно

Сразу скажу, что у нас нет никакой рекламы, и мы никому данные пользователей не продаем. Коммерческого интереса у нас от сайта сейчас совсем никакого. И в целом, у нас многие сидят с нескольких аккаунтов. Это нормально.

Наше внимание привлекают два случая. Люди пытаются накручивать рейтинг, голосуя за свои посты и комментарии, либо дважды за чужие. Это нарушение правил. Либо дополнительные аккаунты используются для троллинга. Модератор может наложить ограничения на один аккаунт, но человеку это не страшно, если у него есть еще два. Такое тоже стараемся пресекать. Если твинки не отслеживать, обычные добросовестные пользователи вскоре просто сбегут с сайта.

Особенно забавно, когда новый аккаунт ведут от женского имени. Пишут о каких-то личных переживаниях, довольно эмоционально, но при этом сдержанно в деталях. Записи вызывают отклик у других пользователей, они обсуждают и поддерживают друг друга. Совершенно обычный дневник, ничто не вызывает подозрений. И только модераторы знают, что это парень-тролль.

Очевидно, проблему нельзя решить чисто технически простыми способами. Например, человек может всегда заходить в один аккаунт только из дома, а в другой — только с рабочего компьютера. Связать их между собой будет трудно. Или наоборот, реальный случай, когда три аккаунта появились почти одновременно и регулярно использовались только с одного адреса. Но оказалось, что это просто три друга, которые живут в одной квартире.

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

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

Уникальный cookie

Можно сохранять в каждом браузере пользователя cookie c уникальной строкой. Тогда мы всегда легко сможем видеть, какие аккаунты используются из одного браузера.

Плюсы:

  • очень просто и быстро;

  • если строка совпадает, это гарантированно тот же браузер.

Минусы:

  • cookie можно удалить;

  • можно заходить через приватный режим;

  • можно заходить из разных контейнеров;

  • можно заходить из разных браузеров.

Первый минус решается способами вроде evercookie. Но это дополнительный потенциально тормозящий сайт код.

FingerprintJS

Можно идентифицировать браузер пользователя по набору его характеристик. Если браузеры двух пользователей совпадают по характеристикам, то скорее всего это один и тот же браузер.

Плюсы:

  • готовое простое решение;

  • если идентификаторы браузеров совпадают, то это с высокой вероятностью один тот же браузер.

Минусы:

  • это тяжелая клиентская библиотека;

  • чем выше точность определения, тем сильнее тормоза на клиенте;

  • не работает, если заходить из разных браузеров;

  • популярные скрипты отслеживания блокируются браузерами.

IP-адрес

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

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

Плюсы:

  • также довольно просто и быстро;

  • работает полностью на сервере;

  • позволяет отслеживать вход с разных браузеров и даже с разных устройств.

Минусы:

  • низкая точность отслеживания;

  • частые ложные срабатывания.

Чуть менее простые решения

В итоге мы используем в собственной реализации все три эти метода в совокупности.

Идентификация браузера по заголовкам

Что, если попробовать составлять отпечаток браузера не на клиенте, а на сервере? Тогда это совсем не будет нагружать браузер и не будет блокироваться. Но точность определения, конечно, сильно снизится.

Все знают, что браузеры при каждом запросе отправляют заголовок User-Agent. Вариантов его значения довольно много. Есть еще три менее очевидных заголовка, которые можно использовать. Это Accept, Accept-Encoding и Accept-Language. Их значения зависят не только от самого браузера, но и от настроек системы. И есть совсем неочевидный заголовок Connection. Но я видел только два его варианта, поэтому решил не принимать во внимание.

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

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

Считаем, что пользователь завершил свою текущую активность на сайте, если с последнего запроса прошло больше часа. Маловероятно, что человек всегда будет сидеть одновременно с двух аккаунтов. Чаще бывает, что используют аккаунты поочередно. При этом перелогиниваться необязательно, так как это могут быть разные контейнеры или приватный режим. Поэтому отслеживаем именно периоды активности из идущих с небольшим промежутком времени запросов.

Алгоритм

  1. Читаем запрос клиента.

  2. Если метод не GET, то пропускаем запрос, так как заголовки будут отличаться.

  3. Считаем хеш-сумму нужных заголовков.

  4. Записываем в базу первый и последний хеш за период активности пользователя.

Код подсчета отпечатка выглядит приблизительно так.

func browserID(req *http.Request) uint64 {
    var id uint64
    
    // для каждого заголовка считаем хеш-сумму
    // и приписываем в конец id
    // headers — это массив названий заголовков
    for _, key := range headers {
        val := headerFieldFunc(req, key)
        id = (id << 8) + uint64(val)
    }

    return id
}

func headerFieldFunc(req *http.Request, key string) uint8 {
    val := req.Header.Get(key)
    
    // если заголовка в запросе нет, то возвращаем 255,
    // так как это число не может получиться
    // в результате подсчета хеш-суммы
    if val == "" {
        return 255
    }

    // считаем контрольную сумму
    var s1, s2 uint32

    for _, b := range []byte(val) {
        s1 += uint32(b)
        s2 += s1
    }

    s1 %= 15
    s2 %= 15

    return uint8((s2 << 4) | s1)
}

Так как сервер у меня работает на Go, я написал небольшую библиотеку для составления отпечатков браузеров на Go. По умолчанию использует перечисленные четыре заголовка, можно добавлять свои. Для каждого заголовка считается контрольная сумма Флетчера. Сначала использовал 16-битную сумму, но и 8 бит должно быть достаточно. В результате получается 32-битное целое число — отпечаток браузера.

Результаты использования

За последние десять дней наш сайт использовали с 619 разных браузеров. И на них пришлось 378 уникальных отпечатка. То есть в среднем точность без учета временного ограничения составляет около 60%. При этом большую часть обеспечивает User-Agent — их всего 308. Остальные заголовки увеличивают точность еще примерно на четверть.

Но точность одинакова не всегда. Заголовки User-Agent почти одинаковые у большинства iPhone и у многих устройств на Windows 10, с точностью до номера версии. А вот не самые популярные смартфоны на Android могут быть определены почти стопроцентно. Это возможно благодаря тому, что браузеры передают в заголовке конкретную модель устройства и версию ОС. Вроде бы последнее время от этого решили отказаться как раз в целях защиты от отслеживания, но пока это много где еще остается.

Этот метод можно улучшить, добавив дополнительные заголовки. Например, включить в nginx определение региона по IP и писать его в запрос. Также можно со временем перейти с использования заголовка User-Agent на HTTP Client hints.

Идентификация устройства по характеристикам

Можно сказать, это свой самописный кусочек FingerprintJS. Я взял только те характеристики, которые относятся именно к устройству. Таким образом мы можем отследить вход в разные аккаунты на одном компьютере или смартфоне, даже если всегда используются разные браузеры.

Использованные свойства:

  • платформа,

  • количество процессоров,

  • распознаваемое количество одновременных касаний экрана,

  • разрешение экрана,

  • глубина цвета экрана,

  • часовой пояс.

Все эти свойства меняются очень редко и совпадают в разных браузерах. Также можно было бы брать информацию об ОС и модели из User-Agent, но ее легко подделать.

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

Алгоритм

  1. Получаем значения нужных свойств через JS.

  2. Склеиваем все в одну строку.

  3. Считаем хеш-сумму строки.

  4. Записываем в cookie, чтобы автоматически отправлять на сервер и не пересчитывать каждый раз.

  5. На сервере также записываем первый и последний хеш за период активности пользователя.

let dev = Cookies.get("dev")
if(!dev) {
    let w = window
    let s = w.screen
    let n = w.navigator

    // склеиваем характеристики в строку
    let isLs = ((screen.orientation || {}).type || "").startsWith("landscape")
    dev = (n.platform || "no") + ";" + (n.hardwareConcurrency || 0) + ";" + (n.maxTouchPoints || 0) +
        ";" + (isLs ? s.width : s.height) + ";" + (isLs ? s.height : s.width) + ";" + s.colorDepth +
        ";" + new Date().getTimezoneOffset()

    // считаем хеш-сумму
    let a = 1, b = 0
    for(let i = 0; i < dev.length; i++)
    {
        let c = dev.charCodeAt(i)
        a += c
        b += a
    }
    dev = b % 65521 * 65536 + a % 65521
    dev = dev.toString(16)

    Cookies.set("dev", dev, { expires: 1826, sameSite: "Lax" })   
}

Результаты использования

За последние десять дней метод позволяет различить 241 уникальных отпечатка устройства. То есть точность определения ниже, чем по отпечатку браузера. Но точно так же основная проблема в популярных устройствах вроде iPhone, которых много, и они одинаковы. Среди не самых модных смартфонов совпадения более редки.

Мы можем объединить два этих метода. Очевидно, что нам не нужно отслеживать совпадения отпечатков браузера при несовпадении устройств. В совокупности они дают 607 уникальных значений. То есть точность определения получается уже достаточно высокой.

Уникальное значение cookie

Первая версия. Генерируем случайное число — уникальный идентификатор прямо на клиенте и записываем в cookie.

Мы можем доверять в этом клиенту, так как подделывать идентификатор не имеет смысла. Нам не важно конкретное значение, важно только, чтобы оно не совпадало с чужими. А чтобы выдать себя за другого, человек должен как-то подсмотреть cookie в его браузере.

У нас около двух тысяч пользователей, у каждого по два-три устройства. Итого пять тысяч устройств. Достаточно хранить 32-битное целое, так как проблема совпадений не должна проявиться до 65 тысяч идентификаторов.

let uid = Cookies.get("uid")
if(!uid || uid === "0") {
    let uid = Math.floor(Math.random() * 4294967296).toString(16)
    Cookies.set("uid", uid, { expires: 1826, sameSite: "Lax" })
}

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

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

func Uid2(user, salt string) string {
    sum := sha256.Sum256([]byte(user + salt))
    return hex.EncodeToString(sum[:8])
}

Результаты использования

По задумке, это самый точный метод обнаружения. Количество различных браузеров выше я определяю именно по этому полю.

IP-адрес

Аналогично записываем адрес каждых первого и последнего запроса. Можно считать хеш-сумму, чтобы хранить меньше ПДн. Что тут еще добавить?

Результаты использования

За десять дней у нас в логе 2484 адреса. Как видно, они меняются очень часто. Сам по себе этот метод обычно дает мало информации.

Бэкенд

После обработки клиентского запроса различными сервисами и на различных этапах, все сходится в одном месте — в структуру userLog. Она хранит данные последних запросов и периодически сохраняет их в БД PostgreSQL.

type userLog struct {
    db   *sql.DB
    log  *zap.Logger
    ch   chan *userRequest
    tick *time.Ticker
    prev map[string]*userRequest
}

func CreateUserLog(db *sql.DB, log *zap.Logger) middleware.Builder {
    ul := &userLog{
        db:   db,
        log:  log,
        ch:   make(chan *userRequest, 200),
        tick: time.NewTicker(time.Hour),
        prev: make(map[string]*userRequest),
    }

    // запускаем горутину для работы с БД
    go ul.run()

    // возвращаем функцию-обработчик HTTP-запросов
    return func(handler http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ul.ServeHTTP(w, r)
            handler.ServeHTTP(w, r)
        })
    }
}

В обработчике запроса читаем созданные на предыдущих этапах HTTP-заголовки и сохраняем в структуру userRequest. Затем через канал передаем ее в горутину. Структура определена так.

type userRequest struct {
    user string
    ip   string
    ua   string
    dev  string
    app  string
    uid  string
    at   time.Time
}

В горутине ожидаем новые запросы и по таймеру сбрасываем старые запросы в БД.

func (ul *userLog) run() {
    for {
        select {
        case <-ul.tick.C:
            // переносим старые запросы из словаря в БД
            ul.clearOld()
        case req := <-ul.ch:
            // добавляем запрос в словарь
            ul.addRequest(req)
        }
    }
}

При добавлении нового запроса проверяем, есть ли уже такой ключ в словаре. Если да, то просто обновляем последнее время запроса. Если нет, то добавляем запрос в словарь и сохраняем в БД как начало периода активности. Ключ — это IP плюс браузер, плюс устройство, плюс уникальное значение cookie, плюс имя пользователя.

func (ul *userLog) addRequest(req *userRequest) {
    key := req.key()
    prevReq, found := ul.prev[key]

    if found {
        prevReq.at = req.at
        return
    }

    ul.prev[key] = req
    ul.save(req, true)
}

func (req userRequest) key() string {
    var str strings.Builder
    str.Grow(68)

    str.WriteString(req.ip)
    str.WriteString(req.dev)
    str.WriteString(req.app)
    str.WriteString(req.uid)
    str.WriteString(req.user)

    return str.String()
}

Периодически записываем завершенные периоды активности в таблицу. Таблица объявлена так.

CREATE TABLE "user_log" (
    "name" Text NOT NULL,
    "user_agent" Text NOT NULL,
    "ip" Inet NOT NULL,
    "device" Integer NOT NULL,
    "app" Bigint NOT NULL,
    "uid" Integer NOT NULL,
    "at" Timestamp With Time Zone NOT NULL,
    "first" Boolean NOT NULL -- true, если это начала периода
);

Использование

Как сделать это все удобным в использовании? Конечно, надо написать Телеграм-бот. Тем более, у нас на сайте уже есть бот, который отправляет пользователям уведомления о новых событиях. Достаточно только добавить в него пару модераторских команд.

По команде сначала проверяем, существует ли вообще такой аккаунт в системе. Если да, то ищем в таблице совпадения по IP, браузеру, устройству и уникальному идентификатору.

// поиск совпадений по IP, другие запросы аналогичны
sqlf.Select("ul.name, COUNT(*) AS cnt").
    From("user_log AS ul").
    Where("ul.name <> lower(?)", user).
    Where("ol.name = lower(?)", user).
    Where("ul.first <> ol.first").
    GroupBy("ul.name").
    OrderBy("cnt DESC").
    Limit(10).
    Join("user_log AS ol", "ul.ip = ol.ip").
    Where("(CASE WHEN ul.at > ol.at THEN ul.at - ol.at ELSE ol.at - ul.at END) < interval '1 hour'")

Вот пример выполнения команды. Совпадения по IP, устройству и браузеру показаны в течение коротких промежутков времени, совпадения по идентификатору в cookie — за все время. Совпадения по браузеру учитываются только в совокупности с устройством. В скобках указано количество совпадений с каждым аккаунтом. Чем это число выше относительно других, тем менее вероятна случайность.

Здесь совершенно определенно видно, что я использую два аккаунта. Вторым пунктом в список по IP попала моя знакомая. Вероятно, это тот день, когда я заходил на сайт у нее в гостях. А третий пункт — это совершенно случайное совпадение. Мы живем в одном городе, но никогда не виделись. Также видно много людей, случайно совпавших по устройству.

А тут у человека закончилась фантазия для новых ников. Все восемь аккаунтов — одно и то же лицо. Похоже, что он очищал хранилище браузера, и у него часто менялись адреса. Но все равно аккаунты нашлись.

Второй вариант использования — сравнение двух аккаунтов, чтобы проверить свои подозрения. Сначала также проверяем, существуют ли пользователи в системе. Потом загружаем списки совпадающих и различных IP и User-Agent.

// поиск совпадений по IP
sqlf.Select("COUNT(*) AS cnt, MIN(ul.at), MAX(ul.at)").
    Select("ul.ip").
    From("user_log AS ul").
    Where("ul.name = lower(?)", userA).
    Where("ol.name = lower(?)", userB).
    Where("ul.first <> ol.first").
    Where("(CASE WHEN ul.at > ol.at THEN ul.at - ol.at ELSE ol.at - ul.at END) < interval '1 hour'").
    Join("user_log AS ol", "ul.ip = ol.ip").
    OrderBy("cnt DESC").
    GroupBy("ul.ip").
    Limit(10)
    
// поиск различий по IP
sub := sqlf.Select("*").From("user_log").Where("name = ?", b)
sqlf.Select("COUNT(*) AS cnt, MIN(ol.at), MAX(ol.at)").
    Select("ol.ip").
    From("").SubQuery("(", ") AS ul", sub).
    Where("ol.name = lower(?)", a).
    Where("ul.ip IS NULL").
    RightJoin("user_log AS ol", "ul.ip = ol.ip").
    OrderBy("cnt DESC").
    GroupBy("ol.ip").
    Limit(10)

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

Итоги

Большинство твинков однозначно видно по идентификатору в cookie. Если человек очищает хранилище в браузере или заходит через приватный режим, его все равно видно по IP, браузеру и устройству. Если человек заходит из другого браузера, остается IP и устройство. Если даже человек заходит с другого устройства, вероятнее всего в логе будет совпадение IP. Либо наоборот, если меняется адрес, то человек может попасться по другим методам. Но в последних случаях требуется дополнительно проверить, что это не ложное срабатывание.

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

Комментарии (46)


  1. Rumidu
    06.02.2023 02:54
    +4

    Похож на ЖЖ или Дайри, но более современный и молодежный, полузакрытый, уютный.


    1. Wesha
      06.02.2023 04:11
      +11

      With blackjack and hookers

      With canvas and cookies же!


  1. akakoychenko
    06.02.2023 02:55
    +17

    Сколько же еще вам предстоит пройти на этом пути...
    Занимался подобными задачами в масштабах на три порядке больших, и в более агрессивной среде, где по ту сторону команды инженеров и коробочный софт вроде Linken Sphere и Vector T13, а по мою было несколько команд разработки и алгоритмы, от которых иногда приходилось отказываться по причине того, что они эффективно не отрабатывали по памяти на сервере с 1.5ТБ RAM.

    По сути, вы идете +- путем коммерчески доступного B2B типа Threatmetrix, iovation, который, к слову, может стоить неадекватных денег, но при этом абсолютно игнорирует специфику бизнеса клиента. Да, можно дальше развивать тему - начать анализировать поведение браузера на более низком уровне (в частности, тот же процесс хендшейка), докручивать математику, чтобы подавлять false positive от тех же айфонов и мобильных вышек, пытаться искать аномалии, чтобы вычислить тех, кто, условно, собрал эксель табличку вида прокси-useragent-логин, и подставляет их, купить базы IP адресов для детекта проксей...
    Но, к примеру, человека с двумя смартфонами, один из которых работает от мобильной сети, оно не вычислит никак.

    Вопрос в том, решает ли это реальную проблему? Врядли тролли просто троллят от балды и абсолютно рандомно?
    Скорее всего, поведение троллей вписывается в 5-10 паттернов. Условно, есть паттерн, как работают спамеры и сеошники, есть паттерн, как работают те, кто хотят залайкать себя, и так далее. Интересно, почему не пытаетесь копать в эту сторону? Сила методов, основанных на анализе логов поведения в том, что их гораздо сложнее обойти, продолжая эффективно достигать свою абузерскую цель. Условно, если ваша цель пресечь самолайк, то почему не копать в эту сторону? Да, ip, фингерпринты, куки имеют место быть, но стоит ли им быть первичным, а не подтверждающим фактором?


    1. DGN
      06.02.2023 04:38
      +5

      Вот да, поведенческие факторы... Вероятнее всего, троль имеет основной аккаунт и вторичные. Соответственно, будет или взаимный лайк или лайкподдержка. Аккаунт который не пишет свои посты, а только лайкает вычисляется. Если он поддерживает тролля, то всегда его пост после.

      Далее, чекаем по словарю опечаток и грамматических ошибок, они будут одинаковыми скорее всего.

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

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


      1. binque Автор
        06.02.2023 11:58

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


      1. gmtd
        06.02.2023 12:01

        Хорошо помогает проверка активности подозрительных аккаунтов примерно в одно время

        Больше вручную, но можно и автоматизировать


    1. binque Автор
      06.02.2023 11:50

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

      Я пока плохо представляю, какие паттерны поведения отслеживать. Самолайки могут быть не на каждый пост, а время от времени. С дополнительных аккаунтов могут лайкать и других людей. Чтобы иметь возможность лайкать, надо написать несколько постов и получить приглашение, то есть это не совсем пустые аккаунты. Но у нас много и обычных малоактивных аккаунтов. И для разных профилей люди могут придумывать разные личности со своими историями и вести дневники об этом. Могут заходить редко и в разное время.
      Если говорить про троллинг, то дополнительные аккаунты для этого могут совсем не использоваться. Но начнут, как только мы забаним основной. И хочется это предупредить.

      Буду благодарен, если поделитесь полезными статьями по теме.


  1. BugM
    06.02.2023 03:02
    +3

    Это начало длинного пути. При посещаемости в сотни уников все так просто... При росте на пару десятичных порядков все становится заметно веселее. Все технические методы становятся примерно полностью бесполезны. Разве что коммерческие решения на что-то способны. Анализ паттернов поведения посетителей становится единственным работающим способом.


  1. UltimateOrb
    06.02.2023 04:23

    Мне всегда было интересно, почему не сделают доступ для всех желающих к "Единой системе идентификации и аутентификации", что бы множество сервисов могло лучше бороться с ботами/спамерами/твинками и прочим.


    1. BugM
      06.02.2023 04:30
      +1

      Давно уже всё сделали. Oauth2.0 Войти через Гугл, войти через Яндекс и все такое.

      Но ведь все хотят лично себе пользователей набирать, боятся что Гугл забанит их или еще чего сделает.


      1. mayorovp
        06.02.2023 09:05

        Гуглоаккаунтов тоже можно назаводить в любых количествах


        1. BugM
          06.02.2023 12:11

          Иметь возможность завести 5-6 виртуальных личностей это важно.

          Это создает достаточный барьер от сотен и тысяч личностей и хорошо.


      1. binque Автор
        06.02.2023 12:02

        У нас подтверждение аккаунта через почту. Человек со скриншота в статье создает себе адреса на Яндексе для регистрации у нас. Так что это немного усложит ему работу, но не остановит. К тому же, не все люди пользуются Яндексом и Гуглом, и мы так себе ограничим аудиторию.


        1. BugM
          06.02.2023 12:12
          -1

          Усложнение это достаточно на практике.

          Ну и перестать беспокоиться про отдельные клики заодно. Массовое обслуживание. Один плюс не имеет значения.


        1. venanen
          07.02.2023 03:15

          Знаете ли вы о ten minute mail? Можно и в яндексе аккаунты не создавать. Притом, в некоторых сервисах стараются бороться с ними (хз как, по маскам доменов, наверное), но не очень успешно.


          1. binque Автор
            07.02.2023 10:23

            Да, мы тоже при регистрации проверяем почтовый домен по базе disposable email через открытый API. В большинстве случаев срабатывает, но не всегда. А раньше тролли ими активно пользовались.


      1. UltimateOrb
        06.02.2023 16:21

        Не, учеток через Гугл, Яндекс и прочих, можно сделать много. А вот с учеткой госуслуг это сильно сложнее.


    1. DGN
      06.02.2023 04:40
      +5

      Когда, наконец, все хорошие люди соберутся вместе, и убьют всех плохих людей...


    1. Wesha
      06.02.2023 04:47
      +5

      Госуслуги аплодируют стоя (а вместе с ними — тащи майоры в правой части зала и всевозможные скамеры в левой).


    1. zojl
      06.02.2023 09:07

      Многим сервисам это не подойдёт. Например, тем, у кого хотя бы часть пользователей никак не связана с РФ.


    1. binque Автор
      06.02.2023 12:03

      Ага, уже шутили с другими админами, что единственный гарантированный способ — регистрация по паспорту.


      1. DGN
        07.02.2023 04:50

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


  1. gmtd
    06.02.2023 07:25
    +2

    По моему опыту ручное или полуручное администрирование - это самый надёжный способ. Рано или поздно двойной аккаунт сделает ошибку и, скажем, зайдёт со второго браузера, но по первому IP. Система должна искать подобные совпадения и показывать модератору, чтобы он дальше уже анализировал самостоятельно и ставил специальные проверки и ловушки уже конкретным людям, чтобы выяснить, двойной это аккаунт или нет


  1. HellWalk
    06.02.2023 09:53
    +1

    Можно сохранять в каждом браузере пользователя cookie c уникальной строкой. Тогда мы всегда легко сможем видеть, какие аккаунты используются из одного браузера.

    Есть способ отслеживания пользователя через специально закэшированные файлы. Более хитрый способ, и не все "домашние хакеры" отключают кэширование в браузере

    Можно идентифицировать браузер пользователя по набору его характеристик. Если браузеры двух пользователей совпадают по характеристикам, то скорее всего это один и тот же браузер

    Получить информацию о версии браузера/OC не сложно. Конечно, это легко подделывается, но как защита для совсем нубов - пойдет. Плюс не нужны никакие библиотки, чистый js

    Проблема — у большинства людей динамический адрес

    Да, но динамический он в определенных границах. Конечно IP подделывается на раз, но собирать его как простую защиту от дурака стоит

    В итоге мы используем в собственной реализации все три эти метода в совокупности.

    Да по-моему все так делают) Описанные методы - это самые простые и элементарные.

    P.S.
    Твинков для накручивания рейтинга можно выявлять не только по cookie/ip/параметрам браузера, но и по поведенческим действиям. Можно же построить карту лайков на сайте, и выявить те аккаунты, которые лайкают только одного пользователя. Или наоборот, аккаунт, у которого большинство лайков только с нескольких аккаунтов.

    Другое дело, что все это никак не мешает особо упорным людям. И тут нужно усложнять саму регистрацию, делая так, чтобы она стоила денег - например, через активацию через СМС. Есть сервисы, для приема СМС, но они стоят денег

    P.P.S.
    Ну и конечно же нужно доработать удаление аккаунтов-твинков - чтобы все сделанные им лайки откатывались обратно. Чтобы человек понимал, что он может сколько угодно дней тратить на накрутку рейтингов - админ нажмет пару кнопок, и все труды обнулятся.


    1. binque Автор
      06.02.2023 12:20

      Есть способ отслеживания пользователя через специально закэшированные файлы.

      Слышал, надо будет изучить. Спасибо.

      Получить информацию о версии браузера/OC не сложно

      Это уже и так есть в User-Agent.

      Да, но динамический он в определенных границах.

      Не всегда. Но да, есть мысль добавить сравнение первых двух октетов от адреса.

      выявить те аккаунты, которые лайкают только одного пользователя. Или наоборот, аккаунт, у которого большинство лайков только с нескольких аккаунтов.

      Это совсем необязательно будет так. Чтобы иметь возможность лайкать, человек должен получить приглашение на сайт. А для этого ему нужно написать посты, которые лайкнут несколько других людей. И дополнительные аккаунты обычно используются не чисто для лайков себя или троллинга, а и как обычные дневники.

      чтобы все сделанные им лайки откатывались обратно

      Конечно, при бане аккаунта за лайки удаляем его голоса за последнее время.


  1. fedorovpishet
    06.02.2023 10:59

    А что за сайт-то? Я не разработчик сайтов, а вот тема личных дневников мне близка. Хотя бы название сервиса скажи)


    1. binque Автор
      06.02.2023 12:23

      Ой, боюсь хабраэффекта.) Скину в личку. Ну при желании его легко найти, я думаю.


      1. Shado_vi
        06.02.2023 18:43
        +1

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


  1. AnnaRubkaleva
    06.02.2023 10:59

    ...cвой небольшой сайт — платформу для ведения личных дневников.

    Что же, что же это за уютный полузакрытый сайт?! ...


    1. binque Автор
      06.02.2023 12:24

      Скину в ЛС.


      1. AnnaRubkaleva
        06.02.2023 15:27

        Спасибо :)


      1. redlevin
        07.02.2023 23:40
        +1

        Можно и мне? Спасибо! У меня есть знакомые, которые решают проблемы твинков и рейтинга не техническим, а социальным способом. Они как раз ищут относительно небольшой социальный проект, с которым можно было бы обсудить партнерство. Я бы передал им ваш контакт, если интересно.


        1. binque Автор
          08.02.2023 15:33

          Хабраэффекта уже не должно быть, так что могу выложить открыто. Только ссылки на свои проекты ведь по правилам нельзя? Лучше просто название?
          Партнерство, возможно, будет интересно. Но только некоммерческое, естественно.


          1. redlevin
            08.02.2023 18:01
            +1

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


            1. binque Автор
              08.02.2023 21:01

              Хорошо, тогда вот: https://mindwell.win/


      1. alum4u
        07.02.2023 23:43

        Можно мне тоже пожалуйста ☺️


        1. binque Автор
          08.02.2023 21:01

          Чуть выше


  1. iig
    06.02.2023 11:48

    Хм. А что плохого в тролинге? Хоть каменты интересные будут иногда попадаться ;)


    1. binque Автор
      06.02.2023 12:27

      У нас люди пишут о своих личных переживаниях. И мы стараемся, чтобы им было комфортно, чтобы их никто не осуждал, не смеялся, не оскорблял.


      1. iig
        06.02.2023 12:49
        +3

        Осталось взять chatgpt и научить его писать приятные комментарии. Лайки можно ставить автоматически. Да и приятные днявки пусть лучше робот пишет.


  1. Dart55
    06.02.2023 12:41
    +1

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


    1. mayorovp
      06.02.2023 13:17
      +1

      Вот как раз описанное в статье и требуется чтобы система репутации заработала. Иначе вредитель просто забьёт на свою репутацию и будет создавать по аккаунту в день.


      Что прекрасно видно в той самой аналогии с Хабром.


    1. binque Автор
      06.02.2023 13:18

      Одним устройством, могут пользоваться разные люди

      Да, я про это писал. Поэтому все равно нужна ручная проверка.

      Лучше продумайте систему рейтингов, репутации … например по аналогии с хабром.

      У нас есть системы приглашений и рейтингов, похожие на Хабр. Но этого недостаточно. Люди хорошо умеют притворяться, либо у них по другим причинам радикально меняется поведение. И само по себе это не помогает от накруток лайков.


  1. AlexM2001
    06.02.2023 13:36

    Работаю одним из администраторов на крупнейшем российском сайте по технике HVAC.

    Одновременно на сайте находятся более 10 тысяч человек. IP каждого сообщения записывается . Видно когда кто логинится с какого адреса.


  1. Mausglov
    06.02.2023 16:44

    И только модераторы знают, что это парень-тролль.

    С 1909 года ничего не изменилось!

    Когда поэт, описывая даму,
    Начнет: «Я шла по улице. В бока впился корсет»,‑
    Здесь «я» не понимай, конечно, прямо —
    Что, мол, под дамою скрывается поэт.
    Я истину тебе по‑дружески открою:
    Поэт — мужчина. Даже с бородою.


  1. tigmen
    07.02.2023 23:43

    Интересная статья. Та самая ситуация, когда существует множество фреймворков и библиотек, но они не подходят по разным причинам (например, из-за огромного лишнего функционала, который излишне нагружает систему), поэтому важно знать как работают все эти фреймворки, так сказать "что у них под капотом". Недавно как раз читал статью о написании rest api на чистом Java. Очень много людей в комментариях не понимают зачем уметь писать это, если давно есть фреймворк. Эх, тут мы возвращаемся к извечному вопросу: "А нужно ли писать бинарный поиск?"