WebRTC technology and privacy.
1. Technology allows "peer to peer" audio&video communication between browsers (but initialy handshake goes through server). This made flash player obsolete.
1.2. WebRTC made STUN request to stun server. This type of request is not visible in developers tool. how to monitor STUN requestes? Open question
1.3. With webRTC you could get uniq device ID of microphone and videocamera.
1.4. SDP used only for negotiation. IP addreses consisted in SDP pocket.
1.5. WebRTC IP leak reveals your real IP despite VPN or proxy. WEB RTC leaks goes over UDP so your proxy, firewall and router should support UDP!!
1.6. WebRTC shows real IP despite of VPN. And shows mic's id. If you deny UDP protocol on your Firewall it stops WebRTC leak. Or use UDP proxy. To monitor webrtc stun request use chrome://webrtc-internals/
2. In Firefox about:config → media.peerconnection.enabled → false
2.1. about:config → media.navigator.enabled → false
2.2. Disabling is always bad and makes you look bot-like. I have to spoof but spoof to my VPN's IP otherwise is better to disable.
3. To get video from user camera. webrtc.php:
<html><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta id="theme-color" name="theme-color" content="#ffffff">
<title>Use webrtc with getUserMedia</title>
</head>
<body>
<div id="container">
<h1>WebRTC get user's videocamera <span>getUserMedia</span></h1>
<video id="gum-local" autoplay="" playsinline=""></video>
<button id="showVideo">Get camera</button>
<div id="errorMsg"></div>
<p class="warning"><</p>
<script src="media/01_webrtc/webrtc.js"></script>
</body></html>
webrtc.js:
const constraints = window.constraints = { audio: false, video: true };
function handleSuccess(stream) {
const video = document.querySelector('video');
const videoTracks = stream.getVideoTracks();
console.log('Obtain video stream:', constraints);
console.log(`Using videocamera: ${videoTracks[0].label}`);
window.stream = stream; // make variable available to browser console
video.srcObject = stream;}
function handleError(error) { if (error.name === 'OverconstrainedError') {
const v = constraints.video;
errorMsg(`The resolution ${v.width.exact}x${v.height.exact} px is not supported by your device.`);
} else if (error.name === 'NotAllowedError') {
errorMsg('Allow to grab videocamera and mic'); }
errorMsg(`getUserMedia error: ${error.name}`, error);}
function errorMsg(msg, error) {
const errorElement = document.querySelector('#errorMsg');
errorElement.innerHTML += `<p>${msg}</p>`;
if (typeof error !== 'undefined') { console.error(error); } }
async function init(e) {
try { const stream = await navigator.mediaDevices.getUserMedia(constraints);
handleSuccess(stream);
e.target.disabled = true; } catch (e) { handleError(e); } }
document.querySelector('#showVideo').addEventListener('click', e => init(e));
3.1. How to record video from camera. Video is recorded to buffer Blob.
3.2. We got stream from camera with getUserMedia() and asign this stream to <video> element.
3.3. webrtcexample2.php:
<html><head>
<meta charset="utf-8">
<meta name="description" content="WebRTC code samples">
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
<meta itemprop="description" content="Client-side WebRTC code samples">
<meta itemprop="name" content="WebRTC code samples">
<meta name="mobile-web-app-capable" content="yes">
<meta id="theme-color" name="theme-color" content="#ffffff">
<title>Use MediaStream webrtc for recording</title>
</head>
<body>
<div id="container">
<h1>WebRTC example <span>MediaRecorder</span></h1>
<video id="gum" playsinline="" autoplay="" muted=""></video>
<video id="recorded" playsinline="" loop=""></video>
<div>
<button id="start">Start camera</button>
<button id="record" disabled="">Start Recording</button>
<button id="play" disabled="">Play</button>
<button id="download" disabled="">Download</button>
</div>
<div> Recording format: <select id="codecPreferences" disabled=""></select>
</div>
<div>
<h4>Media Stream Constraints options</h4>
<p>Echo cancellation: <input type="checkbox" id="echoCancellation"></p>
</div>
<div> <span id="errorMsg"></span> </div>
</div>
<script src="media/01_webrtc/webrtc2.js" async=""></script>
</body></html>
3.4. webrtc-example.js:
let mediaRecorder;
let recordedBlobs;
const codecPreferences = document.querySelector('#codecPreferences');
const errorMsgElement = document.querySelector('span#errorMsg');
const recordedVideo = document.querySelector('video#recorded');
const recordButton = document.querySelector('button#record');
recordButton.addEventListener('click', () => {
if (recordButton.textContent === 'Start Recording') { startRecording();
} else { stopRecording();
recordButton.textContent = 'Start Recording';
playButton.disabled = false;
downloadButton.disabled = false;
codecPreferences.disabled = false; } });
const playButton = document.querySelector('button#play');
playButton.addEventListener('click', () => {const mimeType = codecPreferences.options[codecPreferences.selectedIndex].value.split(';', 1)[0];
const superBuffer = new Blob(recordedBlobs, {type: mimeType});
recordedVideo.src = null;
recordedVideo.srcObject = null;
recordedVideo.src = window.URL.createObjectURL(superBuffer);
recordedVideo.controls = true;
recordedVideo.play(); });
const downloadButton = document.querySelector('button#download');
downloadButton.addEventListener('click', () => {
const blob = new Blob(recordedBlobs, {type: 'video/webm'});
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'test.webm';
document.body.appendChild(a);
a.click();
setTimeout(() => { document.body.removeChild(a);
window.URL.revokeObjectURL(url); }, 100); });
function handleDataAvailable(event) { console.log('handleDataAvailable', event);
if (event.data && event.data.size > 0) {
recordedBlobs.push(event.data); } }
function getSupportedMimeTypes() { const possibleTypes = [
'video/webm;codecs=vp9,opus',
'video/webm;codecs=vp8,opus',
'video/webm;codecs=h264,opus',
'video/mp4;codecs=h264,aac', ];
return possibleTypes.filter(mimeType => {
return MediaRecorder.isTypeSupported(mimeType); }); }
function startRecording() { recordedBlobs = [];
const mimeType = codecPreferences.options[codecPreferences.selectedIndex].value;
const options = {mimeType};
try { mediaRecorder = new MediaRecorder(window.stream, options);
} catch (e) { console.error('Exception while creating MediaRecorder:', e);
errorMsgElement.innerHTML = `Exception while creating MediaRecorder: ${JSON.stringify(e)}`;
return; }
console.log('Created MediaRecorder', mediaRecorder, 'with options', options);
recordButton.textContent = 'Stop Recording';
playButton.disabled = true;
downloadButton.disabled = true;
codecPreferences.disabled = true;
mediaRecorder.onstop = (event) => {
console.log('Recorder stopped: ', event);
console.log('Recorded Blobs: ', recordedBlobs); };
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start();
console.log('MediaRecorder started', mediaRecorder); }
function stopRecording() {
mediaRecorder.stop(); }
function handleSuccess(stream) {
recordButton.disabled = false;
console.log('getUserMedia() got stream:', stream);
window.stream = stream;
const gumVideo = document.querySelector('video#gum');
gumVideo.srcObject = stream;
getSupportedMimeTypes().forEach(mimeType => {
const option = document.createElement('option');
option.value = mimeType;
option.innerText = option.value;
codecPreferences.appendChild(option); });
codecPreferences.disabled = false; }
async function init(constraints) {
try { const stream = await navigator.mediaDevices.getUserMedia(constraints);
handleSuccess(stream);
} catch (e) { console.error('navigator.getUserMedia error:', e);
errorMsgElement.innerHTML = `navigator.getUserMedia error:${e.toString()}`; } }
document.querySelector('button#start').addEventListener('click', async () => {
document.querySelector('button#start').disabled = true;
const hasEchoCancellation = document.querySelector('#echoCancellation').checked;
const constraints = {
audio: { echoCancellation: {exact: hasEchoCancellation} },
video: { width: 1280, height: 720 } };
console.log('Using media constraints:', constraints);
await init(constraints); });
3.5. Peer to peer communication.
3.6. Use canvas to take screenshot from video.
4. With webRTC technology is possible to share your screen with any webpage! Use navigator.mediaDevices.getDisplayMedia({video: true}) ;
4. How to bypass disabled webRTC in client browser and get client’s IP anyway.
4.1. I have to add iframe to webpage:
<iframe id="iframe" sandbox="allow-same-origin" style="display: none"></iframe>
then use:
var frame01 = iframe.contentWindow;
RTCbypass = frame01.RTCPeerConnection; // Doesn’t work
4.2. Try this to obtain IP address:
// get the IP addresses associated with an account
function getIPs(callback){
var ip_dups = {};
// compatibility for firefox and chrome
var RTCPeerConnection = window.RTCPeerConnection
|| window.mozRTCPeerConnection
|| window.webkitRTCPeerConnection;
var useWebKit = !!window.webkitRTCPeerConnection;
// bypass naive webrtc blocking using an iframe
if(!RTCPeerConnection){
// NOTE: you need to have an iframe in the page right above the script tag
//<iframe id="iframe" sandbox="allow-same-origin" style="display: none"></iframe>
//<script>...getIPs called in here...
var win = iframe.contentWindow;
RTCPeerConnection = win.RTCPeerConnection
|| win.mozRTCPeerConnection
|| win.webkitRTCPeerConnection;
useWebKit = !!win.webkitRTCPeerConnection;
}
// minimal requirements for data connection
var mediaConstraints = {
optional: [{RtpDataChannels: true}]
};
var servers = {iceServers: [{urls: "stun:stun.services.mozilla.com"}]};
// construct a new RTCPeerConnection
var pc = new RTCPeerConnection(servers, mediaConstraints);
function handleCandidate(candidate){
// match just the IP address
var ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/
var ip_addr = ip_regex.exec(candidate)[1];
// remove duplicates
if(ip_dups[ip_addr] === undefined)
callback(ip_addr);
ip_dups[ip_addr] = true;
}
// listen for candidate events
pc.onicecandidate = function(ice){
// skip non-candidate events
if(ice.candidate)
handleCandidate(ice.candidate.candidate);
};
// create a bogus data channel
pc.createDataChannel("");
// create an offer sdp
pc.createOffer(function(result){
// trigger the stun server request
pc.setLocalDescription(result, function(){}, function(){});
}, function(){});
// wait for a while to let everything done
setTimeout(function(){
// read candidate info from local description
var lines = pc.localDescription.sdp.split('\n');
lines.forEach(function(line){
if(line.indexOf('a=candidate:') === 0)
handleCandidate(line);
});
}, 1000);
}
//Test: Print the IP addresses into the console
getIPs(function(ip){console.log(ip);});
It doesn’t work, gives me an error:
myWebRtc.js:69 Uncaught TypeError: Cannot read properties of null (reading 'sdp') at myWebRtc.js:69:41
4.2.1. Another variant:
// The function that checks if the given IPv4 address is valid.
function is_ipv4(ip){
return regex_v4.test(ip);
};
// Checks if the IPv6 address is valid.
function is_ipv6(ip){
return regex_v6.test(ip);
};
// Simple IP regex that works on both IPv6 and IPv4
var simpleIPRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/g;
// IPv4 regex used to determine whether an IP is IPv4 or not.
let regex_v4 = /((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])/;
// The IPv6 regex used when determining an IPv6 address.
let regex_v6 = /((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))/;
// Exporting the two regexes in an array to be used in the main detector.
let ip_regex_array = [regex_v6, regex_v4]
// [---------------------------------------------------------------------------]
// File: peer_conn.js
/*
* This module provides the main WebRTC functions that return IP addresses from * * the STUN request.
*/
function peer(callback){
// Creating the peer connection.
var WebRTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
// Initializing the connection.
var createdConnection;
// Main start function.
function start(){
// Creating the actual connection.
createConnection()
// Log the STUN request.
createStunRequest()
};
// Stop function to reset the connection.
function stop(){
// Checking if the connection has been created or not.
if (createdConnection) {
// Attempt to close it, if RTCPeerConnection.close() is not supported, remove the event listeners.
try {
createdConnection.close();
} finally {
createdConnection.onicecandidate = () => {};
createdConnection = null;
};
};
};
// Function that makes the connection request to Google's STUN server
function createConnection(){
let iceServers = [{
urls: 'stun:stun.l.google.com:19302'
}];
// Creating the connection with the STUN server.
createdConnection = new WebRTCPeerConnection({ iceServers });
// Handling the ICE candidate event.
createdConnection.onicecandidate = (data) => handleCandidates(data);
// Creation of the fake data channel.
createdConnection.createDataChannel('fake_data_channel');
};
// Function that creates the STUN request offer needed to get the IPs.
function createStunRequest(){
// Create the offer that exposes the IP addresses.
return createdConnection.createOffer().then(sdp => createdConnection.setLocalDescription(sdp));
};
// Handling the onIceCandidate event.
function handleCandidates(ice){
// Checking if the ICE candidate lines given are valid.
if (ice && ice.candidate && ice.candidate.candidate) {
// Returning the IPs to the main function.
callback(ice && ice.candidate && ice.candidate.candidate);
};
};
// Returning the main functions needed.
return {
start,
stop,
createConnection,
createStunRequest,
handleCandidates
};
};
// [---------------------------------------------------------------------------]
// File: public_ip.js
/*
* This module provides the worker functions that return the public IP addresses.
*/
function publicIPs(timer){
// Timing validation.
if(timer) if(timer < 100) throw new Error('Custom timeout cannot be under 100 milliseconds.');
// IPs is the final array of all valid IPs found.
var IPs = [];
// Creating the peer connection request while handling the callback event.
var peerConn = peer(handleIceCandidates);
function getIps(){
// Returning a promise.
return new Promise(function(resolve, reject) {
// Starting the peer connection.
peerConn.start();
// Setting the timer.
setTimeout(() => {
// Checking if the IP array exists.
if(!IPs || IPs === []){
// Rejecting the error
reject('No IP addresses were found.')
} else {
// Return the unique IP addresses in an array.
resolve(unique(IPs.flat(Infinity)))
};
// reset the peer connection.
reset();
// Set the Timeout to the custom timer, default to 500 milliseconds.
}, timer || 500);
});
};
function handleIceCandidates(ip){
var array = [];
// Looping over the two regexs for IPv6 and IPv4
for(let regex of ip_regex_array){
let arr = [];
// Lutting all of the strings that match either IP format in an array
let possible_ips_array = regex.exec(ip)
if(possible_ips_array){
// Looping over that array
for(let i = 0; i < possible_ips_array.length; i++){
// Che// The function that checks if the given IPv4 address is valid.
// It works only for Chromium;