WebRTC (Web Real-Time Communications) — это набор открытых стандартов, протоколов и API, которые позволяют браузерам и мобильным приложениям обмениваться аудио-, видеоданными и произвольными данными напрямую (peer-to-peer, P2P) без необходимости установки плагинов или стороннего программного обеспечения.

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

Напишем на javascript простую страницу, которая определит есть ли в вашем браузере утечки, а заодно посмотрим параметры SDP протокола, которые ваш браузер генерирует для установления WebRTC соединения.

SDP протокол описывает, какие медиа-потоки будут передаваться, их параметры, сетевые данные и механизмы безопасности. SDP-предложение само по себе не является утечкой. Это просто техническое предложение о соединении. Реальная утечка происходит, если браузер, обрабатывая этот SDP, для установления соединения: Находит ваши реальные ICE-кандидаты (ваш локальный и публичный IP-адреса). И использует для кандидата типа host не маскированное mDNS-имя (xxx.local), а ваш реальный локальный IP-адрес (например, 192.168.1.5), который затем виден на сайте.

Современные браузеры (Chrome, Firefox, Safari) по умолчанию используют mDNS-маскировку для host-кандидатов, что предотвращает утечку локального IP. Данные же вашего публичного IP-адреса необходимы для установления любого P2P-соединения и, по сути, не являются скрываемой информацией для сайта, с которым вы общаетесь.

Первым делом надо проверить поддержку WebRTC:



// Функция проверки поддержки WebRTC API браузером
    function checkWebRTCSupport() {
        // Проверяем поддержку RTCPeerConnection в различных версиях
        const peerConnectionSupport = !!(
            window.RTCPeerConnection ||         // Стандартная реализация
            window.webkitRTCPeerConnection ||   // WebKit (Chrome, Safari) префикс
            window.mozRTCPeerConnection        // Firefox префикс
        );
        
        // Выводим на страницу в элемент с id=peerconnection-result результат проверки поддержки RTCPeerConnection
        $('#peerconnection-result').text(peerConnectionSupport ? 'Да' : 'Нет');
        
        // Проверяем поддержку RTCDataChannel (только если поддерживается RTCPeerConnection)
        let dataChannelSupport = false;
        if (peerConnectionSupport) {
            try {
                // Создаем тестовое RTCPeerConnection соединение
                const pc = new (window.RTCPeerConnection || 
                               window.webkitRTCPeerConnection || 
                               window.mozRTCPeerConnection)({iceServers: []});
                // Проверяем наличие метода createDataChannel
                dataChannelSupport = typeof pc.createDataChannel === 'function';
            } catch (e) {
                // В случае ошибки устанавливаем поддержку в false
                dataChannelSupport = false;
            }
        }
        
        // Выводим на страницу в элемент с id=datachannel-result результат проверки поддержки RTCDataChannel
        $('#datachannel-result').text(dataChannelSupport ? 'Да' : 'Нет');
    } 

Следующим шагом проверим утечку WebRTC Затем создается WebRTC соединение для получения ICE-кандидатов, которые содержат IP-адрес


function checkWebRTCLeaks() {
        // Вызываем функцию получения IP-адресов через WebRTC
        getIPAddresses().then(ips => {
            // Инициализируем переменные для хранения различных типов IP-адресов
            let ipv4 = '';          // IPv4 адрес
            let ipv6 = '';          // IPv6 адрес
            let localIPs = [];      // Массив локальных IP-адресов
            let publicIPs = [];     // Массив публичных IP-адресов
            
            // Проходим по всем полученным IP-адресам
            ips.forEach(ip => {
                // Проверяем является ли адрес IPv6 (содержит двоеточие)
                if (ip.includes(':')) {
                    ipv6 = ip;  // Сохраняем IPv6 адрес
                } else {
                    ipv4 = ip;  // Сохраняем IPv4 адрес
                }
                
                // Проверяем является ли IP локальным с помощью вспомогательной функции
                if (isLocalIP(ip)) {
                    localIPs.push(ip);  // Добавляем в массив локальных адресов
                } else {
                    publicIPs.push(ip); // Добавляем в массив публичных адресов
                }
            });
            
            // Обновляем DOM с результатами проверки IPv4
            $('#ipv4-result').text(ipv4 || 'Не обнаружен');
            // Обновляем DOM с результатами проверки IPv6
            $('#ipv6-result').text(ipv6 || 'Не обнаружен');
            
            // Проверяем были ли обнаружены локальные IP-адреса (утечки)
            if (localIPs.length > 0) {
                // Если да - показываем их и отмечаем утечку
                $('#local-ip').html(localIPs.join(', '));
                $('#leak-result').html('Да');
            } else {
                // Если нет - показываем что утечек не обнаружено
                $('#local-ip').text('Не обнаружены');
                $('#leak-result').html('Нет');
            }
            
            // Обновляем DOM с публичными IP-адресами
            $('#public-ip').text(publicIPs.length > 0 ? publicIPs.join(', ') : 'Не обнаружен');
            
            // Получаем SDP информацию для детального отображения
            getSDPInfo().then(sdp => {
                // Вставляем SDP информацию в соответствующий элемент
                $('#sdp-content').text(sdp);
                

            }).catch(error => {
                // В случае ошибки при получении SDP информации
                $('#sdp-content').text('Не удалось получить SDP информацию: ' + error);
                

            });
            
        }).catch(error => {
            // Обработка ошибок при получении IP-адресов
            $('#ipv4-result').text('Ошибка определения');
            $('#ipv6-result').text('Ошибка определения');
            $('#leak-result').html('Нет (WebRTC не поддерживается)');
            $('#local-ip').text('Не применимо');
            $('#public-ip').text('Не применимо');
            $('#sdp-content').text('WebRTC не поддерживается или заблокирован в вашем браузере.');
            

        });
    }

Здесь мы использовали вспомогательную функцию isLocalIP для определения локальности ip адреса, функцию getIPAddresses получения ip адресов через WebRTC и функцию getSDPInfo для получения параметров SDP протокола. Функция getIPAddresses:



function getIPAddresses() {
        return new Promise((resolve, reject) => {
            // Проверяем поддержку WebRTC API в браузере
            if (!window.RTCPeerConnection && 
                !window.webkitRTCPeerConnection && 
                !window.mozRTCPeerConnection) {
                reject(new Error('WebRTC не поддерживается'));
                return;
            }
            
            // Создаем новый экземпляр RTCPeerConnection
            // iceServers: [] - пустой массив означает что мы не используем STUN/TURN серверы
            const rtc = new (window.RTCPeerConnection || 
                            window.webkitRTCPeerConnection || 
                            window.mozRTCPeerConnection)({iceServers: []});
            
            const ips = [];  // Массив для хранения обнаруженных IP-адресов
            
            // Обработчик события появления ICE кандидатов
            rtc.onicecandidate = function(event) {
                // Если candidate равен null - все кандидаты собраны
                if (!event.candidate) {
                    // Проверяем были ли найдены какие-либо IP-адреса
                    if (ips.length === 0) {
                        reject(new Error('Не удалось определить IP-адреса'));
                    } else {
                        // Возвращаем уникальные IP-адреса (убираем дубликаты)
                        resolve([...new Set(ips)]);
                    }
                    return;
                }
                
                // Получаем строку кандидата
                const candidate = event.candidate.candidate;
                if (candidate) {
                    // Регулярное выражение для поиска IP-адресов в строке кандидата
                    // Ищет IPv4 (xxx.xxx.xxx.xxx) или IPv6 (xxxx:xxxx:...)
                    const match = candidate.match(/([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/);
                    if (match) {
                        ips.push(match[1]);  // Добавляем найденный IP-адрес в массив
                    }
                }
            };
            
            // Создаем канал данных (необходимо для инициализации соединения)
            rtc.createDataChannel('');
            
            // Создаем SDP offer и устанавливаем его как локальное описание
            rtc.createOffer()
                .then(offer => rtc.setLocalDescription(offer))
                .catch(reject);  // В случае ошибки отклоняем Promise
            
            // Устанавливаем таймаут на случай если WebRTC не вернет кандидатов
            setTimeout(() => {
                if (ips.length === 0) {
                    reject(new Error('Таймаут при определении IP-адресов'));
                }
            }, 5000);  // Таймаут 5 секунд
        });
    }

Функция getSDPInfo. Формируется SDP предложение, которое отображается в детализированном виде


function getSDPInfo() {
        return new Promise((resolve, reject) => {
            // Проверяем поддержку WebRTC
            if (!window.RTCPeerConnection && 
                !window.webkitRTCPeerConnection && 
                !window.mozRTCPeerConnection) {
                reject(new Error('WebRTC не поддерживается'));
                return;
            }
            
            // Создаем новый экземпляр RTCPeerConnection
            const rtc = new (window.RTCPeerConnection || 
                            window.webkitRTCPeerConnection || 
                            window.mozRTCPeerConnection)({iceServers: []});
            
            // Создаем канал данных (необходимо для генерации SDP offer)
            rtc.createDataChannel('');
            
            // Создаем SDP offer
            rtc.createOffer()
                .then(offer => {
                    // Устанавливаем созданное предложение как локальное описание
                    rtc.setLocalDescription(offer);
                    // Возвращаем SDP строку из предложения
                    resolve(offer.sdp);
                })
                .catch(reject);  // В случае ошибки отклоняем Promise
            
            // Устанавливаем таймаут на случай если SDP не будет сгенерировано
            setTimeout(() => {
                reject(new Error('Таймаут при получении SDP информации'));
            }, 5000);  // Таймаут 5 секунд
        });
    }

Функция isLocalIP. Анализируются полученные адреса - определяются локальные и публичные IP.


    function isLocalIP(ip) {
        // Проверка IPv6 адресов
        if (ip.includes(':')) {
            // Unique Local Addresses (fc00::/7 - частные IPv6 адреса)
            if (/^f[c-d][0-9a-f]{2}(::1$|:)/i.test(ip)) {
                return true;
            }
            // Link-local addresses (fe80::/10 - адреса локальной ссылки)
            if (/^fe[89ab][0-9a-f](::1$|:)/i.test(ip)) {
                return true;
            }
        } else {
            // Проверка IPv4 адресов
            const parts = ip.split('.');
            // 10.0.0.0/8 - частная сеть класса A
            if (parts[0] === '10') return true;
            // 172.16.0.0/12 - частная сеть класса B
            if (parts[0] === '172' && parseInt(parts[1]) >= 16 && parseInt(parts[1]) <= 31) return true;
            // 192.168.0.0/16 - частная сеть класса C
            if (parts[0] === '192' && parts[1] === '168') return true;
            // 127.0.0.0/8 - loopback адреса
            if (parts[0] === '127') return true;
            // 169.254.0.0/16 - link-local адреса (APIPA)
            if (parts[0] === '169' && parts[1] === '254') return true;
        }
        
        // mDNS маскировка (современные браузеры используют .local для маскировки)
        if (ip.endsWith('.local')) {
            return true;
        }
        
        // Если ни одно из условий не выполнилось - адрес публичный
        return false;
    }

Проверить работу скрипта можно по ссылке WebRTC утечки
Скачать рабочий код целиком: webrtcleals.zip