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