Web 端口敲门的奇思妙想

传统的端口敲门

端口敲门是一种特殊的安全认证方案。它没有固定的标准,每个人的实现各不相同。当然,即使没听说过这个名词,不少人也有类似的想法和实现。

例如我曾经使用 Windows
服务器时,一直对远程桌面颇为不满。不是因为这个服务做得不好,相反,是做得太好了,以至于一个不知道账号密码的陌生人试着访问时,都会为它绘制窗口、传输画面,服务太过周到了!攻击者即使猜不到密码,也能白白消耗服务器资源和网络流量。况且,越复杂的程序出现漏洞的可能性越大,万一哪天出现缓冲区溢出之类的漏洞,攻击者不用知道密码也能控制服务器。这太不完美了!

在我看来,认证应该越早越好。对于不知道认证规则的陌生人,甚至 TCP 连接都不值得为它建立!这即利于 系统安全
(减少密码爆破、漏洞攻击等风险),又利于 网络安全 (减少恶意消耗流量、DDoS 攻击等风险)。

于是写了个简单的防火墙小程序,默认拦截所有 IP。只有当某个 IP
访问了「秘密端口」后,才将其加入白名单,允许进一步通信。这就是一个典型的端口敲门方案,那个访问秘密端口的数据包,便是敲门砖。

当然,仅凭一个端口作为暗号还是有些简陋,很容易被破解,因此需加强。例如使用多个端口、使用特定数据的 UDP
包等等。甚至还可以在数据包中加入更复杂的认证信息,当然这需要通过专门的程序敲门了,而不能简单地使用系统命令手动敲门。

事实上用程序敲门是很常见的方案。例如一些公司要求员工安装某个程序才能访问内网,该程序很可能给网关发送了敲门数据包。

那么,敲门程序是否适用于公网?更进一步,是否能做成 Web 版?

公网 Web 敲门

敲门程序放在公网似乎不太好,毕竟这是一种基于保密的安全方案,细节公开后效果就大打折扣了,除非对程序做很强的混淆保护。

前面提到,敲门有两个意义:保护系统安全、保护网络安全。现在聊聊后者。

接着从之前做防火墙聊起。虽然那只是个玩具级的小程序,但实际效果还不错,并且性能极高(驱动层过滤包)。分享给一些好友试用后,很快就有人思考如何应用到现实中
—— 那些经常被攻击的游戏服务器。

但这并不好实现。总不能要求玩家复制粘贴一堆 cmd
命令手动敲门吧。让玩家下载敲门程序?这相当于是在推广软件,成本不小。除非把敲门程序和登录器捆绑在一起,但这款游戏被魔改的五花八门,登录器各不相同,实现起来很麻烦。

一番摸索后发现,几乎所有的登录器都内嵌网页,显示公告信息之类的。如果实现一个 JS 版的敲门程序,那么直接让管理员插到公告页里就可以,连登录器都不用升级!

TCP/SYN 直接敲门

v1 单端口

第一个实验版本非常简单,甚至都没用上 JS,仅仅敲服务器一个固定端口。

<img src="http://xxx.xxx:12345/logo.gif">

为了隐蔽,URL 里没有暴露服务器 IP,而是用一个指向该 IP 的域名替代,并加上迷惑性路径。

事实上服务器会丢弃 SYN 敲门包,所以连接都无法建立,URL 路径是毫无用途的

试用后效果很好。伪造游戏协议的捣乱机器人 TCP 都建立不了,更别说登录了;即使用 SYN Flood 攻击游戏端口,服务器也是一个 ACK
都不回复,极大节省了系统资源和流量开销(出站流量很贵)。通过这个简单粗暴的敲门方案,L7 和 L4 攻击都能轻松防住。当然,除非特别大的流量直接把服务器
IP 打进黑洞,那倒是无解,不过软件形式的防火墙对此都无能为力。

尽管敲门机关是公开的,但大部分人都不会想到藏在网页里~

当然,被发现也是早晚的事。在被破解之前,这个方案改良了多次。

v2 多端口

敲门端口只有一个,感觉太简陋。但即使有多个,攻击者只要扫描下所有端口,总能敲到这几个。

因此,多端口敲门必须要按顺序,否则意义就不大了。

不过实现后发现有个问题,服务器收到的 SYN 包顺序,未必就是客户端发送时的顺序。因此只能通过延迟发送的方式,尽量保证顺序。

function load(url) {
  new Image().src = url
}

load('http://xxx.xxx:50000')

setTimeout(function() {
  load('http://xxx.xxx:10000')
}, 100)

setTimeout(function() {
  load('http://xxx.xxx:30000')
}, 200)

当然,这么做的后果就是增加了敲门时间。

这里用onerror事件是没有意义的。因为服务器会丢弃 SYN 包,所以客户端需要重发多次 SYN 才会产生 TCP
连接超时错误,可能要几秒甚至几十秒之后才会触发该事件。

v3 动态多端口

多端口实现了,但是端口号仍是固定的,感觉仍不完美。于是接着改进。

如果端口号是动态的,那么前后端如何保持一致?最容易想到的,就是通过时间生成端口号。

var port = gen_port(time())
load('http://xxx.xxx:' + port)

但这也存在一个问题,并非所有用户的时间都是准的。如果误差超出允许范围,那么敲门就会失效。

因此,敲门之前需校准时间。我们提供一个返回时间的接口,或者直接用网上免费公开的接口。为了确保稳定性,JS 里准备多个接口,只要有一个可用就不会失效。

当然,动态端口也有一些问题。例如配有硬件防火墙的服务器可能没法使用,除非服务器能和防火墙设备保持互动。(如今云服务器的云防火墙确实可通过 API
实时修改规则~)

UDP/DNS 直接敲门

事实上,前面几个改良的意义都不大,都是建立在攻击者还没发现的基础上。然而秘密总是会暴露的。

如何继续改进?思考了下问题所在:只发几个 SYN 包就能通过防火墙验证的话,攻击者太容易模拟,伪造成本太低。但如果敲门包里能容纳更多信息,那就可以带上 JS
生成的认证数据,这样伪造起来麻烦多了。

但 TCP 显然不行,毕竟端口号才 16bit,可携带的信息太少了,需要发送几十上百个请求才能勉强传递认证数据,效率太低。因此只能用 UDP。

在 Web 中和 UDP 相关的通信,最容易想到的是 DNS。并且可通过 NS 型泛域名,一次携带可观的数据到指定服务器。

var auth = gen_auth()
load('http://' + encode(auth) + '.xxx.xxx.xxx')

不过这个方案存在本质性问题:服务器收到的 UDP 包,并不是用户发给它的,而是用户运营商 DNS 发的。所以服务器看到的源 IP 是运营商 DNS
IP,而不是用户 IP!既然连用户 IP 都拿不到,那还有什么用。

当时想到一个有趣的解决方案:JS 先通过一些免费公开的接口获取公网 IP,然后将其加密到认证信息里;服务器最终使用认证信息里的 IP,而不是数据包的
IP。这样就能拿到用户 IP 了!

jsonp_public_ip_callback = function(ip) {
  var auth = gen_auth(ip, ...)
  load('http://' + encode(auth) + '.xxx.xxx.xxx')
}

load_js('http://xxx.com/get_public_ip')

当然,这个方案严重依赖 JS 代码混淆。一旦加密算法被破解,攻击者可以轻轻松松将任何 IP 刷进防火墙白名单。

其实 SYN 敲门方案,攻击者也能伪造源 IP,给任意 IP 加白,但需要特殊的网络环境才能发送。而 UDP/DNS 这种方案任何网络环境都可以

UDP/DNS 中转敲门

使用 UDP/DNS 敲门还有一个问题:服务器需要开放 UDP:53 端口,但有些网络可能不允许。

这种情况只能通过中转。NS 域名指向我们的服务器,我们分析 UDP 数据后再将用户 IP
推送给游戏服务器(游戏服务器和我们保持长连接)。这样就不用关心网络问题了。

TCP/HTTP 中转敲门

基于 UDP/DNS 的敲门仍存在不少问题。JS 需要先获取公网 IP 地址,UDP 数据需要经过 DNS
转发,这些额外的链路大幅增加了敲门时间和故障几率。

因此最终还是回归到稳定的 HTTP 中转方案。这个方案原理很简单,没什么骚操作。JS 将认证信息提交到我们的 HTTP
服务器,我们验证后再推送给游戏服务器。由于 HTTP 可携带的信息量没有限制,因此除了基本验证信息外,还可采集浏览器环境、Flash
环境、用户行为等大量信息,进一步加强安全防御。

不过,中心化的验证服务显然会成为众矢之的。攻击者只要打垮这个服务就可以,都用不着攻击游戏服务器~

但相比传统 C/S 架构的网游服务,B/S 架构的 Web 服务更容易防御,并且有很多现成的解决方案,例如 CDN、WAF 等。因此 借助相对稳定的
Web 服务给更脆弱的游戏服务敲门
,还是值得的。

UDP 端口敲门

WebRTC 的出现,终于能让 JS 发送 UDP 包了,并能指定 IP 和端口。

var pc = new RTCPeerConnection({
  iceServers: [
    {urls: 'stun:1.2.3.4:56789'}
  ]
})
pc.createDataChannel('')

pc.createOffer(function(v) {
  pc.setLocalDescription(v)
}, function() {})

但是,如果只能指定端口而不能携带数据,那和 SYN 敲门包有什么区别?

根据 [API 文档](https://developer.mozilla.org/en-
US/docs/Web/API/RTCIceServer/username),iceServers 参数可配置用户名和密码。假如用户名能出现在 UDP
包里,那就能携带自定义数据了。

不过之前多次试验都未成功(也许是还没发现),于是换了种思路。

UDP 数据敲门

WebRTC 的本质是 P2P 通信,让两个内网用户直连。大致步骤是:

1.每个用户通过 STUN 服务,获取自己的公网 IP 及打洞端口

2.通过某个服务器交换各自信息

3.两个用户互相通信

前面的代码只是第 1 步,访问 1.2.3.4:56789 只是查询公网地址而已。对于端口敲门来说,这一步并不关键,假如对方地址已知,不妨跳过 1 和
2,直接从第 3 步开始。

我们可以虚构一份 SDP(Session Description Protocol)欺骗 WebRTC,假装已知对方信息。然后把对方 IP 和端口加入
Ice Candidate,浏览器即可发出 UDP 包!

与第一步不同,这一步可设置ufrag字段,并且最终会明文出现在 UDP 包中!

sendUDP('1.2.3.4', 56789, 'Hello_World_1234567890')

async function sendUDP(addr, port, data) {
  const pc = new RTCPeerConnection()

  const sd = new RTCSessionDescription({
    type: 'offer',
    sdp: `\
v=0
o=- 1234567890 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE data
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=ice-ufrag:${data}
a=ice-pwd:0000000000000000000000
a=ice-options:trickle
a=fingerprint:sha-256 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
a=setup:actpass
a=mid:data
a=sctp-port:5000
a=max-message-size:262144
`
  })
  await pc.setRemoteDescription(sd)

  const answer = await pc.createAnswer()
  const desc = new RTCSessionDescription(answer)
  await pc.setLocalDescription(desc)

  pc.addIceCandidate({
    candidate: `candidate:842163049 1 udp 1677729535 ${addr} ${port} typ srflx raddr 0.0.0.0 rport 0 generation 0 ufrag ${data} network-cost 999`,
    sdpMLineIndex: 0,
    sdpMid: 'data',
  })

  setTimeout(_ => pc.close(), 30)
}

image

该字段可接受字母、数字和#+-/=_共 68 种字符,最大长度 256。即使用 Base64 编码,也能容纳 192 字节。

虽然容量不算大,但编码紧凑点的话,还是可以采集不少端上信息。况且 JS 和服务器直接通信,无需经过第三方中转,链路更短!

当然,基于 UDP/STUN 的敲门方案也有不少缺陷。有些运营商会降低 UDP 优先级,甚至使用不同的公网 IP!而且低版本浏览器并不支持
WebRTC,高版本浏览器的用户也可能禁用了 WebRTC。

此外,浏览器开启 HTTP/SOCKS 代理后,敲门也可能会失效。因为 UDP 不走代理,而之后的 TCP 访问走代理,两者 IP
不一致,显然无法通过验证。

在线演示

https://www.etherdream.com/port-knocking/

只有点了 Knock 按钮,你才能访问 Test 页面,否则和测试服务器的 TCP 连接都无法建立。可以抓包试试~

出于演示,敲门服务直接用 iptables 实现:

# 放行白名单 IP(600s 后需重新敲门)
iptables \
  -t raw --append PREROUTING \
  -m recent --name knocked --rcheck --seconds 600 \
  -j ACCEPT

# 敲门包
iptables \
  -t raw --append PREROUTING \
  -p udp --dport 30000 \
  -m string --string "OpenSesame" --algo bm \
  -m recent --name knocked --set \
  -j DROP

# 默认拒绝所有 IP
iptables \
  -t raw --append PREROUTING \
  -j DROP

# 实时查看白名单 IP
watch -n1 \
  cat /proc/net/xt_recent/knocked

除了 recent 模块,还可使用更灵活的 ipset 模块

当然这里没有解析数据,仅仅判断 UDP 包是携带某个暗号(OpenSesame)。

实际应用中,你可以自由发挥想象,带上更多有意义的认证信息,以及更完善的加密。

更多认证信息

HTML5 的发展使得越来越多有趣的 API 加入到 Web 中。例如 WebGL、WebAssembly、Web Crypto API 等等,这些 API
都可参与到安全防御中。

例如通过 WebGL2 调用 GPU
实现工作量证明
,使得攻击者生成敲门认证数据需要大量算力,而无法大批量生成。

例如通过 WebAssembly 混淆敲门数据的生成逻辑,使得 DDoS 攻击者还需掌握二进制逆向能力。

如果攻击者需要执行复杂耗时的 JS 才能让一个 IP 进入防火墙白名单,那么我们的目的也就达到了。

更多敲门方案

Web 仍在不断发展,例如 Chrome 的 QUIC 协议也使用 UDP 通信,尽管目前还无法用于敲门场合。未来 HTTP/3
的出现,是否会有更多的改进,开放更强大的网络通信能力呢。拭目以待中…

ttps://www.etherdream.com/FunnyScript/glminer/glminer.html),使得攻击者生成敲门认证数据需要大量算力,而无法大批量生成。

例如通过 WebAssembly 混淆敲门数据的生成逻辑,使得 DDoS 攻击者还需掌握二进制逆向能力。

如果攻击者需要执行复杂耗时的 JS 才能让一个 IP 进入防火墙白名单,那么我们的目的也就达到了。

更多敲门方案

Web 仍在不断发展,例如 Chrome 的 QUIC 协议也使用 UDP 通信,尽管目前还无法用于敲门场合。未来 HTTP/3
的出现,是否会有更多的改进,开放更强大的网络通信能力呢。拭目以待中…

接下来我将给各位同学划分一张学习计划表!

学习计划

那么问题又来了,作为萌新小白,我应该先学什么,再学什么?
既然你都问的这么直白了,我就告诉你,零基础应该从什么开始学起:

阶段一:初级网络安全工程师

接下来我将给大家安排一个为期1个月的网络安全初级计划,当你学完后,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web渗透、安全服务、安全分析等岗位;其中,如果你等保模块学的好,还可以从事等保工程师。

综合薪资区间6k~15k

1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)

2、渗透测试基础(1周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等

3、操作系统基础(1周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)

4、计算机网络基础(1周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现

5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固

6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)

那么,到此为止,已经耗时1个月左右。你已经成功成为了一名“脚本小子”。那么你还想接着往下探索吗?

阶段二:中级or高级网络安全工程师(看自己能力)

综合薪资区间15k~30k

7、脚本编程学习(4周)
在网络安全领域。是否具备编程能力是“脚本小子”和真正网络安全工程师的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力。

零基础入门的同学,我建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习
搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP,IDE强烈推荐Sublime;

Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,没必要看完

用Python编写漏洞的exp,然后写一个简单的网络爬虫

PHP基本语法学习并书写一个简单的博客系统

熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选)

了解Bootstrap的布局或者CSS。

阶段三:顶级网络安全工程师

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

学习资料分享

当然,只给予计划不给予学习资料的行为无异于耍流氓,这里给大家整理了一份【282G】的网络安全工程师从入门到精通的学习资料包,可点击下方二维码链接领取哦。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值