v2fly / v2fly-github-io

V2Fly Website & Documentation

Home Page:https://v2fly.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

SOCKS 5 的认证在使用 UDP 时可被绕过

kslr opened this issue · comments

commented

Author: @studentmain
Send Date: 2021-01-08

v2ray 等代理软件的内置 SOCKS 服务器对所有客户端共用同一 UDP 端口,导致其 SOCKS 5 UDP 服务无需认证即可使用。

复现

为代理软件的 SOCKS 入站配置密码,对于 v2ray 可参考以下配置文件:

{
   "log":{
      "loglevel":"warning"
   },
   "inbounds":[
      {
         "port":1080,
         "listen":"127.0.0.1",
         "tag":"socks-inbound",
         "protocol":"socks",
         "settings":{
            "auth":"password",
            "udp":true,
            "accounts":[
               {
                  "user":"a",
                  "pass":"a"
               }
            ],
            "ip":"127.0.0.1"
         }
      }
   ],
   "outbounds":[
      {
         "protocol":"freedom",
         "tag":"direct"
      },
      {
         "protocol":"blackhole",
         "tag":"blocked"
      }
   ]
}

验证密码是否已起效:

$ curl -x socks5://127.0.0.1:1080/ http://baidu.com/
curl: (7) No authentication method was acceptable. (It is quite likely that the SOCKS5 server wanted a username/password, since none was supplied to the server on this connection.)
$ curl -x socks5://a:b@127.0.0.1:1080/ http://baidu.com/
curl: (7) User was rejected by the SOCKS5 server (1 255).
$ curl -x socks5://a:a@127.0.0.1:1080/ http://baidu.com/
(HTML content)

向 v2ray 设定的 UDP 端口直接发送一个 SOCKS 5 UDP 包(以 DNS 查询为例),用 Wireshark 等监视,若服务端有任何响应,则漏洞存在

$ base64 -d | nc -u 127.0.0.1 1080 | hd
AAAAAXJycnIANavNAQAAAQAAAAAAAAN3d3cHZXhhbXBsZQNjb20AAAEAAQ==
00000000  00 00 00 01 72 72 72 72  00 35 ab cd 81 80 00 01  |....rrrr.5......|
00000010  00 01 00 00 00 00 03 77  77 77 07 65 78 61 6d 70  |.......www.examp|
00000020  6c 65 03 63 6f 6d 00 00  01 00 01 c0 0c 00 01 00  |le.com..........|
^C

分析

SOCKS 5 要求 UDP 客户端先通过 SOCKS 5 TCP 协议请求服务端分配用于通信的 UDP 端口。SOCKS 5 TCP 协议允许多种认证方式,而 SOCKS 5 UDP 协议自身没有任何区分客户端的机制,需通过报文源端口来区分不同的客户端。

当 SOCKS 5 服务端对所有客户端分配同一 UDP 端口时,因为无法预测客户端使用的 UDP 源端口,服务端必须始终接受来自任意源地址的 UDP 报文。在此种情况下,服务端没有措施保证 UDP 客户端已经认证。

解决方案

对每一个 SOCKS 5 UDP ASSOCIATE 请求分配独立的 UDP 端口,此 UDP 端口在收到第一个数据报后忽略来自其他源地址的数据报。

虽 SOCKS 5 UDP 协议自身仍然没有认证,但不固定端口使得攻击者需要进行扫描才能找到 SOCKS 5 UDP 端口。而已经开始监听的 UDP 端口有很大可能在监听开始后极短时间(1RTT)即已收到来自客户端的首个数据报,进而忽略来自端口扫描器的数据报。

未实现任何认证的服务端因为本就没有认证,可忽略该漏洞,仍然对所有客户端分配同一 UDP 端口。显然地,已经实现认证的
SOCKS 服务端也可通过移除认证机能来忽略该漏洞。

-----BEGIN PGP SIGNATURE-----

iQIzBAEBCAAdFiEEdbyjO018JNZhjd2SqnhRnCCMh0IFAl/2+vAACgkQqnhRnCCM
h0J07A/9H3BOim+1Q8Ob5KrBUM7C2+769Go7AaTVNnrB6ACooXcuXCytyorN4Bz9
W8w9xqNAjZ67P4yYl3MMZl7zWnoLrT9oG2vebFB4HHd3Os6CKkkyWARoyVZUlAbZ
XsreOvf5JIEg+XUBsPFSZkmmuGPK9r/txTpgJyYDIZAqk5weP9+FfnKceu2cXHau
cwp2IAWtbo/3JxOsoYmEF1ZuarMy61HsNITfAqjNjeuluGUAWfwyCVv8Kph5czy9
ZHdcuZc+R4jawbGLO0T9uUckqcDqhBu+k+LwepLeObmzb7lMy5avvGO2oSNRqfvY
SmI+/9RzoCwfHoBWJLb02k9UKhKO4QUClYGHyr04Ht1KX5dBAm14S2JlKBGbjFIi
kGNE8xkxUnJ8Wzpg0w4WqaI8WY85lZ2T0OfVsfpqTHgkxQ/JhTiiFQ3flAsAwAYy
M7Pg3ptNY0PrPYw6n7/zQfwQ/iPvL41cuG1RvLR0cLJYw4R4qF3DEmoF3eMHwCJB
QzAzNTkL9rX2RzNdTpUh/TTyt02wRvmlPuRaI4vLBJEB3ohoO8YEIiqsc6XRRui2
/rEbI4ZKepsPGGFfFmUMieuqE34ojzwE429xVBYgi9Ct7EcR2xqOkTG/eCufPq2a
kS4VqszMtBWuWJdymthzE933Kok1wvraoWUgIcfWKA+KzT/mekQ=
=2gl8
-----END PGP SIGNATURE-----

The proposed new behavior that randomly allocates UDP ports should be opt-in. Otherwise it would be a huge breaking change to existing setups.

Just refrain from using password authentication for SOCKS5, for now.

Attempting to fix the issue directly could easily break existing use scenarios of SOCKS5 inbounds. Instead of doing that and breaking things, what about just dropping SOCKS5 authentication support, and asking those who need authentication to use Shadowsocks instead? With Shadowsocks they get extra protections with reliable authentication and secure encryption.

commented

这个问题我也发现了,事实上 v2ray 的整个 Socks5 实现都是相当不规范的,比如出站的密码认证行为和 UDP Associate 请求

回到这个问题,本质是 VR 当初可能没有理解 UDP Associate 请求是在做什么,我简单说一下 RFC 1928 的规范:

  1. 客户端 TCP 发起 UDP Associate 请求,附带自己这边要使用的 UDP 地址和端口(但实践中全留空,因为不知道公网的情况)
  2. 服务端收到请求后,监听一个 UDP 端口,并把服务端的地址和 UDP 端口通过这条 TCP 发回去告知客户端
  3. 只要这一条 TCP 连接没断开,服务端就可以接收客户端的 UDP 包(代理请求),这里可能会限制包的来源

然后各代理软件的实现大多是简单粗暴地监听固定的 UDP 端口,并且完全不管上面这些流程的限制,无论带不带密码认证

commented

As a temporary measure, I will warn in the docs need to use the firewall, If the sharing socks5 proxy on a public network.

Hi, @kslr
Could you explain more details about the UDP Auth issue? as you replied above, you mean we should block the UDP port with a firewall and ensure it is allowed to access only from localhost? right?

commented

Hi, @kslr
Could you explain more details about the UDP Auth issue? as you replied above, you mean we should block the UDP port with a firewall and ensure it is allowed to access only from localhost? right?

Yes

got, have a good day.

Was this vulnerability addressed afterwards? I don't see any output:

$ curl -x socks5://arinc9:123456@127.0.0.1:19230/ 1.1.1.1
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>cloudflare</center>
</body>
</html>

$ base64 -d | nc -u 127.0.0.1 19230 | hd             
^C (no output for a long time)

v2ray config:

{
  "inbounds": [
    {
      "port": 19230,
      "protocol": "socks",
      "settings": {
        "auth": "password",
        "accounts": [
          {
            "user": "arinc9",
            "pass": "123456"
          }
        ],
        "udp": true
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom"
    }
  ]
}

Edit: I'm hiding this as I did not test it correctly. It seems this situation is a matter of the SOCKS5 specification.