写在前面
继上次代理池之后,我们有了动态切换IP的代理池方案,看着挺不错,封不完IP,作为防守方要怎么揪攻击者的小辫子呢?
WebRTC 获取IP是比较老的技术了,看了一圈大家好像也没怎么注意这玩意儿,今天再把这剩饭拿出来热一热,毕竟江郎才尽了不是。
起因
代理是大家在打点的时候经常会用到的,其中就以 socks5 最为方便。但是我们注意到,socks5 协议本身是支持 UDP 协议的,只是大部分的客户端软件/库的作者比较懒,没有实现这部分,遇到之后都直接写个 TODO
那么思路来了,我们能不能在网页上发起一个 udp 请求呢?试想一下,攻击者在代理后面,所有的 tcp 请求都会被攻击者通过 socks5 代理转发出去,如果是 udp 请求,由于代理不支持 udp 会直接由攻击者自己去访问,那么是不是就可以抓到攻击者的真实的出口IP地址了呢?
理论上好像是这么个道理,不如搞个 demo 测试一下
寻找合适的方法
说干就干,Google 一下,马上知道。我们找一下 HTML/JS/CSS 这些语言能不能发起 udp 请求。
StackOverFlow 上面给了我们答案,可以用 WebRTC。
哇,这玩意儿可不陌生啊,老朋友了。大家以前在用 XSS 平台的时候,最喜欢的 XSS 获取受害者内网/公网 IP ,就是用的 WebRTC。
https://github.com/diafygi/webrtc-ips 这个仓库里面已经有现成的 demo 了。好家伙,6 年前的 demo,结合着新版本的开发文档,我们把这个 demo 稍稍改动了一些:
为了明显的对比 TCP 获取到的 IP 地址和 WebRTC 获取到的 IP 地址, 我们改成 php 的, 然后给 javascript 里面加了一些 try catch 处理,最终长这个样子:
webrtc.php
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
Remote Addr: <?=$_SERVER['REMOTE_ADDR']?>
<hr>
<h3>WebRTC</h3>
<h4>Your local IP addresses:</h4>
<ul id="localip"></ul>
<h4>Your public IP addresses:</h4>
<ul id="publicip"></ul>
<h4>Your IPv6 addresses:</h4>
<ul id="ipv6"></ul>
<iframe id="rtc_iframe" sandbox="allow-same-origin" style="display: none"></iframe>
<script>
//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.msRTCPeerConnection
|| window.webkitRTCPeerConnection;
var useWebKit = !!window.webkitRTCPeerConnection;
//bypass naive webrtc blocking using an iframe
if(!RTCPeerConnection){
var win = document.getElementById("rtc_iframe").contentWindow;
RTCPeerConnection = win.RTCPeerConnection
|| win.mozRTCPeerConnection
|| win.msRTCPeerConnection
|| win.webkitRTCPeerConnection;
useWebKit = !!win.webkitRTCPeerConnection;
}
//minimal requirements for data connection
var mediaConstraints = {
optional: [{RtpDataChannels: true}]
};
var servers = {
iceServers: [
{
urls: [
'stun:stun.l.google.com:19302?transport=udp',
'stun:stun1.l.google.com:19302?transport=udp',
'stun:stun2.l.google.com:19302?transport=udp',
'stun:stun3.l.google.com:19302?transport=udp',
'stun:stun4.l.google.com:19302?transport=udp',
"stun:stun.ekiga.net?transport=udp",
"stun:stun.ideasip.com?transport=udp",
"stun:stun.rixtelecom.se?transport=udp",
"stun:stun.schlund.de?transport=udp",
"stun:stun.stunprotocol.org:3478?transport=udp",
"stun:stun.voiparound.com?transport=udp",
"stun:stun.voipbuster.com?transport=udp",
"stun:stun.voipstunt.com?transport=udp",
"stun:stun.voxgratia.org?transport=udp"
]
}
]
};
//construct a new RTCPeerConnection
var pc;
try {
pc = new RTCPeerConnection(servers, mediaConstraints);
} catch (e) {
return
}
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("bl");
//create an offer sdp
try {
pc.createOffer().then(function(result) {
pc.setLocalDescription(result);
});
} catch (e) {
pc.createOffer().then(function(result) {
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);
}
//insert IP addresses into the page
getIPs(function(ip){
var li = document.createElement("li");
li.textContent = ip;
//local IPs
if (ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/))
document.getElementById("localip").appendChild(li);
//IPv6 addresses
else if (ip.match(/^[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7}$/))
document.getElementById("ipv6").appendChild(li);
//assume the rest are public IPs
else
document.getElementById("publicip").appendChild(li);
});
</script>
</body>
</html>
ok, 找台服务器,把这个脚本放上去,然后开启代理试一下:
挂代理的方式我也顺便测试了,你们可以自己测一下
Chrome 插件 SwitchyOmega
Proxifier 直接给浏览器进程挂代理
Clash 开系统代理
然后访问我们的目标站点:
18.167.94.x 是代理的地址, 上 IPIP 查一下地理位置是 HK 的
我们再查一下 117.136.x.x 这个用 WebRTC 获取到的 IP 地址:
好家伙,果然是我本机的出口IP了
事情就是这么个事情,老套路,不算什么新东西。说一下注意的点:
这玩意儿默认是启用的(对的没错,包括你现在在看文章用的微信的浏览器)
那挂个支持 udp 的 socks5 代理行不行?不行,WebRTC设计之初就是为了点对点通信,毕竟是传流的,压根就不会走代理,懂了啵
VPN 行不行?可以。
受限于时间原因,像 QUIC 等其它的基于UDP的协议,没来得及一一去尝试,大家自己有兴趣可以实验实验。我们来说说想到的几处应用。
蓝队发散思维
1. WAF 拦截页面埋点,打点爱用代理是吧?埋在 WAF 拦截后的页面里面,抓到的IP都不是好人
2. 往蜜罐里埋
3. 可以挖掘一下其它的未被注意到的基于 UDP 协议的功能
至于拿到真实 IP 地址之后,回传的问题,可以用 ajax 发 http 包带回去,也可以自己实现一个 WebRTC 服务器,走 udp 来收这部分的数据。除了 WebRTC 服务器之外,像 WebRTC 中用于的 STUN / TURN 服务器,你也可以自己实现一套,反正不让你知道我在哪一步记你就完事。
红队发散思维
1. 专用打点浏览器,禁掉 WebRTC 功能。
是的,我找了一圈的人测试,发现很少有人会禁掉这个功能,所以写了这篇文章出来。
Chrome浏览器禁用 WebRTC 的方法是:在Chrome应用商店里,安装一个名为 WebRTC Leak Prevent 的扩展,然后选择 Disable non-proxied UDP(force proxy) 即可。
Firefox浏览器禁用WebRTC的方法是:在浏览器上输入:about:config。之后搜索:media.peerconnection.enabled。找到它后双击,将其改成 false 即可。
2. 就算你禁掉了 WebRTC,也有挂代理,推荐用热点,再挂代理池
就像我上面提到的,我只是测试了 WebRTC,其它的浏览器支持的基于 UDP 的协议我都没来得及去试。
3. 改用 VPN
想拿清明节 3 倍工资的同学,别愣着,快去优化吧
不如关注一波再走?