【WEB安全-Web安全入门-网络资源的访问-隧道】此文章归类为:WEB安全。
基于URL的网络资源访问并不灵活,在正常的逻辑下它只能访问服务端特定端口拥有的资源(由与该端口绑定的程序提供资源,不排除程序内部的错误处理导致未开放资源被访问)。
比较理想的网络资源访问场景,应该是客户端可以和服务端建立持久且直接的网络连接,并且对远端的网络资源拥有更强大的控制能力。
对!就是向正常使用计算机那样,操作计算机的命令行,甚至是操作计算机的图形化界面。
远程桌面的实现目标主要有两个,一是客户端可以实时看到目标端的桌面的图像(最好还可以听到声音),二是客户端对桌面的操作(比如鼠标和键盘)可以实时的传递给目标端,并让目标端按照预期的行为执行操作。
1 2 | client - > mouse && keyboard - > target client < - video && audio < - target |
远程桌面常见的协议有两种,一是RDP Remote Desktop Protocol
协议,该协议经常在Windows中使用,二是RFB Remotr Frame Buffer
协议,该协议经常出现在Linux中。
远程命令行的场景相对于远程桌面会简单一些,远程命令行只要保证目标端输出信息可以实时的返回到客户端,并保证客户端向命令行输入的内容可以在目标端起到预期中的作用即可。
1 | client < - data - > target |
远程命令行通常都是使用SSH Secure Shell
协议或TelNet协议,SSH的强大在于兼容性,主流的操作系统(Windows、Linux、MacOS)都是支持SSH协议的,当然TelNet协议也非常强大,TelNet协议相较于SSH协议的缺点在于,它使用明文进行数据传输。
SSH协议会对数据进行加密,用于加密的算法数量可以说是非常之多,暂且不论这些加密算法的强度如何,但它们的目的都是相同的,就是将人类可以识别出语义的信息转换成人类无法识别出语义的信息。
1 2 3 4 | can be semantically identified? - > Yes - > plain text - > No - > cipher text plain text - > encryption algorithm - > cipher text |
密文产生后其强度是依赖于加密算法的,当密文产生后,所有知道加密算法的人都可以对密文进行解密,解密能力拥有者的范围未必就是通信双方了,这在一定程度上背离了加密的初衷。
为了不让密文的安全性建立在算法的保密程度上,密码学产生了密钥这一重要的概念,明文和加密密钥一起进入加密算法的熔炉后产生密文,该密文要求持有者必须在持有密钥的情况下才可以对密文进行解密。
1 2 | plain text && encryption key - > encryption algorithm - > cipher text cipher text && decryption key - > decryption algorithm - > plain text |
密钥诞生后,加解密算法往往会公开出来,密文的抗破译强度往往依赖于与密钥,此时密钥的保存就成了一个大问题。
基于密钥最先产生了对称式加密的概念,加解密的双方使用同一把密钥,它要求密钥必须被妥善的保存,任意一方的密钥泄露,都会造成严重的问题,常见的对称式加密算法有DES、AES、RC2/4/5、TDEA、Blowfish等等。
对称式加密需要加密方将密钥共享给解密方,显然最好的情况是解密方掌握一个单独的密钥,出于这个需求,非对称式加密的概念就此产生,加密方会使用公钥(所有人都知道)进行加密,解密方使用私钥(特定方知道)进行解密,常见的非对称式加密算法有RSA、PGP、ECC、Elgamal、Rabin等等。
在现实情况在,明文加密后未必是希望可以被解密的,为了解决这个需求,密码学产生了散列的概念,散列是不可逆的加密算法,散列经常被用于产生哈希值,使用哈希值可以确认数据的完整性和正确性。
比如经常可以看到软件下载按钮的旁边跟着一个哈希值,用户下载完软件后,可以将本地软件的哈希值与服务商提供的哈希值进行比对,确认自己下载的是正确的文件。
可以通过ssh的-Q key
选项查询支持的非对称加密算法,ssh也支持对称式加密,通过选项-Q cipher
可以查询支持的对称式加密算法。
客户端发出登录请求后,客户端首先需要验证服务端的可信性,如果客户端连接到了攻击者的服务端,就可能会导致客户端发生的关键私密数据泄露。
现在客户端常见的服务端验证方式是基于公私钥加密进行的,服务端产生公私钥后,会将公钥发给客户端,客户端确认公钥可信后,就会向服务端发送自己的身份凭证进行登录验证,服务端验证客户端可信后,就会允许客户端登录。
1 2 3 | client - > login request - > server client < - public key < - server client - > login check - > server |
客户端获取公钥的方式依赖于服务端的传递,假如攻击者拦截了客户端的登录请求,并将攻击者的公钥发给客户端,那么攻击者就可以接收密文并自行解密,导致客户端的数据被泄露。
因为公钥和私钥都是服务端生成的,所以客户端难以验证公钥的正确性,因此客户端采用主动验证的方式确保公钥的正确性。
主动验证的前提要求就是假设操作客户端的人员是知道正确公钥的,客户端收到公钥后,会将公钥的指纹(哈希值)打印出来,让操作人员确认,只要操作人员确认收到的公钥与服务端的公钥是一致的,那就可以输入yes
继续,反正则输入no
结束。
1 2 3 | The authenticity of host 'xxx' can't be established. RSA key fingerprint is xxx Are you sure you want to continue connecting (yes / no)? |
输入yes
后会打印出下面的警告信息,这是因为客户端与服务端建立链接后,公钥会被添加到/path/to/.ssh/known_hosts
文件中(双端),下次登录时客户端根据known_hosts
中的公钥与服务端发送的公钥进行比对,只要两者一致就不会出现主动验证提示。
1 | Warning: Permanently added 'xxx' (RSA) to the list of known hosts. |
公钥指纹可以通过ssh-keygen
在本地生成和获取,而ssh-keyscan
获取远端目标的公钥。
1 2 3 4 5 6 | get public key - > ssh - keyscan ${ip / hostname} - > output: ${ip / hostname} ${encryption type } ${public key} get fingerprint - > ssh - keyscan [ip / hostname] | ssh - keygen - lf - - > output: ${ len } ${ hash type }:${fingerprint} ${ip / hostname} (${encryption type }) |
公钥的主动验证并不是必需品,客户端可以假设自己收到的公钥永远都是正确的,要支持这种假设,客户端需要进行一些设置。
通过将StrictHostKeyChecking
设置成no
,可以假设客户端收到的永远都是正确的公共密钥,同时可以设置UserKnownHostsFile
指向黑洞文件/dev/null
,避免公钥数据被添加到known_hosts
文件中。
可以设置StrictHostKeyChecking
和UserKnownHostsFile
信息的地方有两处,一是客户端的ssh_config
配置文件,而是ssh命令后添加-o
选项,让属性为指定数值。
公钥的劫持可以分成两个场景,一是服务端产生密文时的公钥指纹验证,二是客户端登录时借助公私钥进行的免密登录。
known_hosts
文件中保存的已知公钥比对。假设有攻击者拦截了服务端发送的公钥并替换成了自己的公钥,客户端还把这个错误公钥看作是正确的,那么攻击者就可以控制客户端和服务端间的通信流程。1 2 3 | client < - public key B < - hacker < - public key A < - server client - > check public key - > public key B is ok! client - > cipher text - > hacker - > private key B - > plain text - > public key A - > cipher text - > server - > plain text |
authorized_keys
,客户端登陆时会通过私钥生成签名发给服务端确认,服务器确认公私钥匹配后,就会允许客户端直接登录。假设客户端发生公钥时被攻击者劫持了,那么服务端收到的就是攻击者的公钥,之后攻击者就可以凭借自己的私钥进行免密登录。1 2 3 | client - > generate key - > public key A - > hacker - > public key B - > server - > authorized_keys hacker - > signature (private key B) - > server server - > check public and private key match - > hacker - > login |
公钥验证是为密文传输所作的准备,在拥有密文的基础上,SSH还需要验证客户端的登录行为是完全合法合规的,因此要求客户端必须使用服务端上已存在的用户进行登录。
此时服务端如何验证客户端是可信的就非常关键了。
SSH支持的身份验证方式可以通过sshd_config
确认当前支持的认证方式,身份验证选项需要服务端在sshd_config
中设置,开启就设置yes
,反之则设为no
。
1 2 3 4 5 6 7 | cat / etc / ssh / sshd_config | grep Authentication #PubkeyAuthentication yes #HostbasedAuthentication no #PasswordAuthentication yes #KbdInteractiveAuthentication no #KerberosAuthentication no #GSSAPIAuthentication no |
PasswordAuthentication
的认证要求并不复杂,它需要客户端提交密码信息,与之对应的还有键盘交互验证KbdInteractiveAuthentication
,键盘交互验证会强制客户端通过键盘输入密码信息,除此之外和密码认证再无区别。
密码认证是非常常见的认证方式,但密码登录难免有些麻烦,因此SSH支持无密码登录,无密码登录需要PubkeyAuthentication
选项为yes
状态,其次无密码登录要求服务端将可信公钥信息记录在/path/to/.ssh/authorized_keys
文件中,当客户端使用私钥登录时,只要服务端发现私钥和公钥是匹配的就会允许客户端登录,在这种情况下,只要客户端的私钥没有发生泄露现象,就不会有安全风险。
HostbasedAuthentication
指的是客户端携带凭证(如证书、2FA等)向服务端发起登录请求,服务端只要判断凭证可信,就会让客户端登录。
GSSAPIAuthentication
中的GSSAPI
字符串,它的全称是通用化安全服务应用程序编程接口Generic Security Service Application Program Interface
,GSSAPI的优点是支持多种安全机制,使用者只需要使用GSSAPI就可以保障安全,而不需要考虑保障安全的背后机制是什么东西以及细节是什么。
GSSAPI一般基于Kerberos
实现,就是KerberosAuthentication
中的Kerberos
,在该机制下,客户端先向密钥分发中心KDC Key Distribution Center
发送明文请求,KDC会通过认证服务器AS Authentication Server
判读用户是否在自己的可信名单内,如果在,AC会给客户端TGT Ticket Granting Ticket
凭证,客户端会先验证TGT的有效性,如果TGT有效,客户端会将TGT发给TGS Ticket Granting Server
服务端,TGS会验证TGT并根据TGT判断客户端能否访问服务端,如果可以,TGS就返回票据Ticket
给客户端,客户端会将票据发给服务端,服务端验证票据有效后会开始与客户端进行安全的通信。
如果想要分析通过ssh登录过程的情况,可以加-v
选项,将建立销毁连接的流程打印出来,-v
这个选项可以叠加三次,每叠加一次,打印的消息都会更加完善。
在Linux系统中,有一个非常出名目录叫做/var/log
,在该目录中记录着各种软件的日志信息,大部分软件以及系统信息的日志都是放在这里的,日志目录中存在一个名为auth.log
的文件,它记录着与系统相关的各种身份认证授权活动,SSH的登录记录也被记录在此。
SSH登录过程以及身份认证的基础调试方案都有了,那么在系统运行过程中,应该如何确认SSH的活跃链接有哪些呢?
服务端跟客户端建立连接后,服务端一定会有进程保持着与客户端间的通信,因此可以根据活跃的进程ps -ef | grep ssh
确认。
网络连接一定会占用端口后,对于服务端来讲,默认的端口22会跟多个客户端建立关系,因此通过命令lsof -i:22
,可以将建立通信关系的服务进程sshd信息打印出来。
在Linux中,ss命令会对网络连接信息进行统计,所以命令ss | grep ssh
可以将与服务端建立连接的进程信息列出来。
Linux中提供who、w、last命令,这两个命令可以将登录用户的信息列出来,所以通过ssh进行远程登录的客户端一定会出现在这里。
SSH和HTTP一样同属于应用层协议,应用层协议的主要作用就是提供规则定义,让通信双方可以从收到的数据中解析出原始数据。
1 2 3 4 5 6 7 8 9 10 11 | + - - - - - - - - osi - - - - - - - - - * - - - - - tcp / ip - - - - - - + | app layer | app layer | | presentation layer | | | session layer | | + - - - - - - - - - - - - - - - - - - - - * - - - - - - - - - - - - - - - - - + | transport layer | transport layer | + - - - - - - - - - - - - - - - - - - - - * - - - - - - - - - - - - - - - - - + | network layer | network layer | + - - - - - - - - - - - - - - - - - - - - * - - - - - - - - - - - - - - - - - + | physical layer | link layer | + - - - - - - - - - - - - - - - - - - - - * - - - - - - - - - - - - - - - - - + |
想要分析SSH协议的流量状况,首先需要有能力捕捉到网络发到本机上的流量,tcpdump就是提供这一功能的强大的工具,它的强大在于可以捕捉除物理层外其余所有层的数据。
-w
代表将捕获的数据输出到文件,-U
控制数据实时写入,-
代表向标准输出stdout
中写入数据,-i
代表只捕获来自网络接口enp0s3
的流量,"host 192.168.1.119"
用于筛选通信双方的IP地址。
tcpdump虽然可以捕获流量数据,但是获取的数据都是经过层层协议封装后的结果,所以难以直观的阅读数据,好在tshark工具(wireshark
的命令行版本)提供了协议解析的功能,只要将tcpdump捕获到的数据交割tshark解析就可以。
tshark工具中的-T选项会将解析好的数据按照特定的格式输出,-f
选项用于筛选网络连接的类型,最后就是-i -
,它要求tshark向标准输出stdout
中写入数据。
1 | tcpdump - U "host 192.168.1.119" - i enp0s3 - w - | tshark - T json - f "tcp port 55160" - i - |
通过tcpdump ... | tshark -T json ...
解析出来非常多的数据,单个数据包的JSON结构在下面给了出来。
tshark基于TCP/IP模型进行解析时,会解析出来五层,其中frame
层并不是TCP/IP模型定义的,而是tcpdump工具自己添加的一层,可以忽略不理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | { "_index" : "packets-2025-02-06" , "_type" : "doc" , "_score" : null, "_source" : { "layers" : { "frame" : {...}, "eth" : {...}, "ip" : {...}, "tcp" : {...}, "ssh" : {...}, } } } |
frame
层后就是eth
物理链路层,这里面统计的到不是物理层传输的比特数据,而是源和目的地的MAC地址信息"eth":{"eth.dst":"xxx",...,"eth.src":"xxx",...}
。
源MAC地址和目的地MAC地址未必就一定和源IP地址和目的地IP地址所属的机器对应,源IP地址和目的地IP地址对应是起始点和终点,至于源MAC地址对应发数据包给当前机器的设备,目的地MAC地址指的是当前机器向下一站转发的机器MAC地址。
1 2 | source ip [sip] - machine [mmac] - destination ip [dip] | - [sip] |
比如有这样的一个场景,机器A上运行着虚拟机B,A通过NAT的方式共享网络给B,当B向机器C发生网络包时(机器A、机器C、虚拟机B位于同一局域网下),那么此时目的地地址就不是机器C的MAC地址,而是虚拟机设备的MAC地址。
再比如有这样的一个场景,机器A位于局域网内,机器B位于公网上,当机器A向机器B发生网络包十,目的地地址就是路由器的MAC地址。
发生网络包时,发送方一般会指定IP地址或域名作为目的地,但物理设备发送数据帧,依据可就不是IP地址了,而是使用MAC地址,为了解决根据IP地址获取MAC地址的问题,地址解析协议ARP Address Resolution Protocol
就此诞生,ARP协议并不复杂,就是在主机或路由器中维护了一张由诸多设备的IP地址和MAC地址组成的ARP表,只要查询这张ARP表就可以找到IP地址对应的MAC地址。
在Linux中可以通过arp-scan -l
或ip neighbour
等命令查看当前的ARP表信息,在Linux系统中可以通过虚文件/proc/net/arp
查看ARP表,arp-scan工具获取ARP表的方式更加高级一些,它通过netlink
接口获取ARP表中数据。
ip
层和tcp
层记录着IP协议和TCP协议的封装信息,IP协议定义了发送设备和接收设备的信息,首发设备通过IP地址进行识别,通信双方通过TCP协议中的内容确认数据的可靠性。
最后就是应用层协议SSH,SSH协议帧主要由包长度、包内容以及方向组成,包长度和包内容都是被加密过的,方向为0时代表SSH协议帧由客户端发出,反之则有服务端发出。
1 2 3 4 5 | "ssh" : { "ssh.packet_length_encrypted" : "4f:f8:c7:42" , "ssh.encrypted_packet" : "1c:1d:f2:f5:eb:01:90:4e:12:d6:20:cf:47:da:47:d6:75:fe:3a:3b:a9:cc:aa:7e:f1:87:dc:a4:f1:ef:3b:56" , "ssh.direction" : "0" } |
上面的格式是SSH连接建立好后的常见格式,下面的内容则可以在SSH连接建立时看到。
SSH建立初期会先通过ssh.protocol
交换SSH版本信息,之后客户端和服务端会发送各自支持的加密算法列表,客户端了解服务端支持的加密算法后,客户端会和服务端进行密钥交换协议DH Diffie-Hellman
协商出会话密钥。
1 2 3 4 5 6 7 8 | client send: "ssh.protocol" : "SSH-2.0-OpenSSH_9.9p1 Debian-3" server send: "ssh.protocol" : "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.10" client send: "SSH Version 2" : {..., "Key Exchange" : {... "Algorithms" ...}} server send: "SSH Version 2" : {..., "Key Exchange" : {... "Algorithms" ...}} client send: "SSH Version 2" : {..., "Key Exchange" : {... "ssh.dh.e" ...}} server send: "SSH Version 2" : {..., "Key Exchange" : {... "ssh.dh.f" ...}} |
DH协议是SSH中非常重要的协议,DH协议的作用是协商出共享密钥,在密钥协商完成前,SSH协议中记录的都是明文信息。
DH协议要求客户端和客户端互相发送Algorithms
包(支持的加密算法信息),客户端确认加密算法后,会计算出客户端的DH公钥,向服务端发送ssh.dh.e
客户端DH公钥包,服务端收到ssh.dh.e
包后,服务端会制作自己的DH公钥,并生成ssh.dh.f
服务端DH公钥包发送给客户端,客户端收到后,会根据包信息构建出服务端的DH公钥,DH公钥确认呢后,会继续生成会话密钥,通信双方根据会话密钥进行数据的加解密。
OpenSSH源代码中存在着一个名为sshenc
的结构体,该结构体存储着会话密钥信息,在运行期中可以在内存中找到它,找到它后就可以对数据进行加解密了。
1 2 3 4 5 6 7 8 9 10 | struct sshenc { char * name; const struct sshcipher * cipher; int enabled; u_int key_len; u_int iv_len; u_int block_size; u_char * key; u_char * iv; }; |
上面提到的远程桌面和远程命令行的连接方式,如果通信双方都位于同一局域网下,它们都是非常强大且好用的功能,但当通信双方不在同一局域网下,通信连接就比较难建立起来。
为了解决跨网段的数据通信问题,隧道技术就此诞生。
隧道技术是什么,“扎小人”是也!
在现实生活中,难免会四处树敌,少则百八十个敌人,多则与千千万万个人结下过梁子,有恩怨怎么办,当然是想方设法的将他们干掉,但是呢,直接对他们进行打击报复肯定违法乱纪的行为,身为一名共产主义接班人,这种行为是万万不能干的。
打击报复仇人的需求是一直在的,伟大的宗教就想出了针对这种需求的解决方案,宗教会使用某种秘法将仇人的灵魂拉到草人上,然后针对草人进行殴打,并且按照宗教的说法,真人会跟草人感同身受。
隧道使用的也是这个原理,当机器A无法访问机器C时,隧道技术会借助代理机器B在机器A和机器C间搭起一段通信通道,保障机器A和机器C的正常通信。
1 2 | box_A - > cannot access - > box_C | - - - - box_B - - can access - - - - | |
隧道技术技术的服务对象不会区分是客户端还是服务端,为客户端服务时被看作是正向的,反之为服务端服务时则看作是反向的。
隧道技术按照通信通道搭建的方式可以分成两大类,第一类是端口转发,第二类是完全代理,不过它们之间有什么区别呢?
当然啊,当你想要搭建隧道时,可能会发现SSH会是一个极为趁手的软件。
所谓正向端口转发,指的就是将客户端的某端口和服务端的某端口建立通信通道,当正向端口转发完成后,客户端访问本地端口,就可以实现对服务端端口的访问。
SSH通过-L
选项支持正向端口转发的功能。
1 2 3 4 5 | | - - can access - - - | [client:port] - > [agent:port] - > [target:port] | - - - - - - - - - cannot access - - - - - - - - - - - - - | using [client:port] produces the same effect as using [target:port] |
SSH与-L
选项最常见的组合命令行形式如下所示。
1 | client: / home$ ssh - p agent_port agent_user@agen_ip - L client_ip:client_port:server_ip:server_port |
SSH首先需要指定代理机器的IP地址、端口、用户,-L
中的target_ip:target_port
指的是服务端被转发的端口,至于local_ip:local_port
则就是与服务端通信的本地端口。
-L
选项设置端口时,本地端口不能与现有端口冲突,如果冲突会出现无法监听端口的情况。
···
bind [127.0.0.1]:3434: Address already in use
channel_setup_fwd_listener_tcpip: cannot listen to port: 3434
···
服务端端口需要处于对外开放的状态,如果不确定服务端中的哪些端口处于开放状态,可以通过nmap -Pn
命令扫描确认。当端口处于关闭状态时,连接行为会被阻止。
1 | channel 1 : open failed: connect failed: Connection refused |
在下方的示例中,选取了IP为192.168.18.177
作为代理机器,IP为192.168.18.157
是服务端机器,SSH将目标机器的22号端口通过代理机器绑定到本地的1234号端口上。
22号端口一般对应SSH服务,因此在本地机器上通过本地的1234号端口,就可以通过SSH登录到服务端机器192.168.18.157
上。
1 2 3 4 5 6 7 | agent: nyssa@ 192.168 . 18.177 server: tmp@ 192.168 . 18.157 client: / home$ ssh - p 22 nyssa@ 192.168 . 18.177 - L 127.0 . 0.1 : 1234 : 192.168 . 18.157 : 22 client: / home$ ssh - p 1234 tmp@ 127.0 . 0.1 - > server machine login succeed - > login IP from [agent] |
值得注意的是,server_ip:server_port
如果设置成了本地回环地址127.0.0.1
,那么此处的本地回环地址对应的就是代理机器的本地回环地址。
要知道不少服务绑定的都是本地回环地址,127.0.0.1:4444:127.0.0.1:5555
的写法可以将代理机器上5555号端口的服务转发到本地机器上的4444号端口,代理机器的5555号端口有没有开放并不重要,代理机器能操作5555好端口就可以了。
假如代理机器上存在着不对外开放的服务,通过这种方法就可以让本地机器访问到原本不该访问的服务。在服务端IP地址target_ip
不是本地回环地址时,本地机器只能使用目标机器上开放的端口,而本地回环地址的特殊性则完美规避了这一点,它可以指定任意端口。
1 2 3 4 | ssh - p agent_port agent_user@agen_ip - L client_ip:client_port:server_ip:server_port target_ip = localhost - > target_port ( any ports on agent) target_ip ! = localhost - > target_port (opened ports on server) |
反向端口转发和正向端口转发的差别并不明显,正向端口转发将远端机器端口转发到本地的端口上,而反向端口转发则是将远端A的端口转发到远端B的端口上。
1 2 3 4 5 | | - - - - can access - - - - | [target:port] < - [client:port] - > [remote:port] | - - - - - - - - - cannot access - - - - - - - - - - - - - | using [remote:port] produces the same effect as using [target:port] |
SSH通过-R
选项支持反向端口转发,SSH与-R
选项最常见的组合命令行形式如下所示。
本地客户端拥有远端机器和目标机器的访问权,但远端机器无法访问目标机器target
,于是SSH通过-R
选项让客户端将远端机器端口A和目标机器端口B建立连接,反向端口转发完成后,登录到远端机器,就可以使用本地端口A与目标机器端口B进行通信。
1 2 3 | client: / home$ ssh - p remote_port remote_user@remote_ip - R remote_ip:remote_port:target_ip:target_port - > on remote machine - > [remote:port] = [target:port] - > access [target:port] - > IP from [client] |
显然,如果想要通过正向端口转发的方式让远端机器访问目标机器也是可以的。
1 2 3 | remote: / home$ ssh - p client_port client_user@client_ip - L remote_ip:remote_port:target_ip:target_port - > still in [remote] machine - > [remote:port] = [target_ip:port] - > access [target_ip:port] - > IP from [client] |
对于机器remote
来讲,它是没有办法访问机器target
的,此时你正位于机器client
上,好在机器client
是可以访问机器target
,所以你可以通过反向端口转发的功能,将机器target
的22号端口转发到机器remote
的3333号端口上,完成后机器remote
就可以通过SSH登录到机器target
上。
1 2 3 4 5 6 | remote: ban@ 192.168 . 1.119 target: tmp@ 192.168 . 1.213 client: / home$ ssh - p 22 ban@ 192.168 . 1.119 - R 127.0 . 0.1 : 3333 : 192.168 . 1.213 : 22 ban: / home$ ssh - p 3333 tmp@ 127.0 . 0.1 - > target login succeed |
当你通过SSH建立好第一层隧道后,顺利的借助机器2从机器1抵达机器3后,还能不能顺着第一层隧道继续搭建隧道呢?
答案当然是可以的,只要机器3转发给机器1的端口,是SSH服务占用的22号端口就可以了。
1 2 3 4 5 6 7 8 9 10 11 | box1: / home$ layer 1 : ssh - p 22 box2_user@box2_ip - L localhost:box1_port_1:box3_ip: 22 layer 2 : ssh - p box1_port_1 box3_user@localhost - L localhost:box1_port_2:box4_ip: 22 ...... layer n: ssh - p box1_port_(n - 1 ) box(n + 1 )_user@localhost - L localhost:box1_port_n:box(n + 2 )_ip: 22 ssh - p box1_port_n box(n + 2 )_user@localhost - > login machine [box(n + 2 )] - > IP not from [box1], IP from [box(n + 1 )] | box(n + 2 ):port_n | - > | box(n + 1 ) | - > ... - > | box2 | - > | box1 | |
本地机器登录时目标机器时会通过最后一层跳板发出流量,目标机器感知到的流量也是最后一层跳板发出的,在目标机器上输入w命令,可以查看源IP地址确认这一点。
通过上面逐级写SSH命令的步骤难免有些繁琐,要建立几层隧道就要构造几条SSH命令,好在强大的SSH提前考虑到了这点,推出了-J
选项。
-J
选项后需要跟着多个跳板user@ip:port
,跳板间使用,
作为分隔符,-J
选项对应字符串中,机器的位置越靠后,其跳板的层级也越靠后,SSH最终登录的机器final
可以看作是最后一级的跳板。
如果final_ip
是本地回环地址,那么机器final
就对应机器jmpbox(n)]
。
1 2 3 | ssh - p final_port final_user@final_ip - J jmpbox1_user@jmpbox1_ip:jmpbox1_port,...,jmpbox(n)_user@jmpbox(n)_ip:jmpbox(n)_port - > login [final] - > IP from [jmpbox(n)] - > login [jmpbox(n)] - > IP from [jmpbox(n - 1 )] - > ... |
通过下面的示例命令将服务端192.168.1.119
与客户端的9798号端口建立连接,当客户端通过9798端口和SSH登录到服务端上,服务端此时会检测到登录IP是跳板192.168.1.204
。
1 2 3 4 5 6 7 8 9 10 | clent$: / home: ssh - p 22 duer@ 192.168 . 1.204 - J duer@ 192.168 . 1.210 ,duer@ 192.168 . 1.208 - L 127.0 . 0.1 : 9798 : 192.168 . 1.119 : 22 ssh - p 9798 ban@ 127.0 . 0.1 - > machine [ 192.168 . 1.119 ] login succeed - > IP from [ 192.168 . 1.204 ] | client | - > | 192.168 . 1.210 | - > | 192.168 . 1.208 | - > | 192.168 . 1.204 | | client: 9798 | < - > | 192.168 . 1.204 | < - > | 192.168 . 1.119 : 22 | |
反向端口转发当然也是支持-J
选项的,下方的示例中,机器192.168.1.119
的22号端口被转发到了192.168.1.204
的9798号端口上,由于使用-J
选项设置了跳板的缘故,所以登录到机器192.168.1.204
时,会发现登录IP是192.168.1.208
。
在机器192.168.1.204
上通过9798号端口和SSH登录到机器192.168.1.119
后,由于反向端口转发的缘故,所以登录IP是客户端client
的IP。
1 2 3 4 5 6 7 8 9 10 11 12 13 | clent: / home$ ssh - p 22 duer@ 192.168 . 1.204 - J duer@ 192.168 . 1.210 ,duer@ 192.168 . 1.208 - R 127.0 . 0.1 : 9798 : 192.168 . 1.119 : 22 [ 192.168 . 1.204 ] duer: / home$ - > IP from machine [ 192.168 . 1.208 ] ssh - p 9798 duer@ 127.0 . 0.1 - > login machine [ 192.168 . 1.119 ] [ 192.168 . 1.119 ] duer: / home$ - > IP from machine [client] | 192.168 . 1.210 | - > | 192.168 . 1.208 | - > | 192.168 . 1.204 | | 192.168 . 1.204 : 9798 | < - > | client | < - > | 192.168 . 1.119 : 22 | |
在使用浏览器时,经常可以看到HTTP代理、SockS代理这样的字眼,这些代理为什么存在,又会起到什么作用呢?
HTTP和SockS都是网络协议的一种,网络协议规定了通信双方的通信语言,避免一边讲中文另一边讲英语,导致两边都听不懂对方说话的情况。不管是客户端应用还是服务端应用,它们都会通过解析HTTP报文获取信息。
至于HTTP代理,主要就是HTTP代理服务器,在一般的场景下,客户端发送的HTTP报文是直接指向服务器的,添加HTTP代理后,客户端发送的HTTP报文会先交给HTTP代理服务器,最后HTTP代理服务器会再转发给服务端。
1 2 3 | client - > http message - > server | ^ | - - - - - http proxy server - - - - - | |
代理和端口转发的区别在于,端口转发是让代理机器建立机器A端口与机器C端口间的通信通道,这是一对一的关系,而代理是让代理机器建立机器A端口和其余机器的通信通道,这是一对多的关系。
1 2 | port forwarding: | client:port | < - > | agent | < - > | server:port | proxy : | client:port | < - > | agent | < - > | ...... | |
SockS协议与HTTP协议虽然都是网络协议,但是它们所属网络模型的层级是不同的,对于TCP/IP协议来讲,HTTP协议位于应用层,SockS协议则位于网络层,因此SockS协议只会参与数据包向目的地继续转发的流程。
下面是一个使用SSH的SockS代理示例,SSH使用了-D
选项,-D
选项用于在指定的地址上创建端口,该端口会通过SSH协议进行通信,此处-D
选项在本地创建了9050号端口,并让端口借助机器box2
进行通信。
本地端口的创建完成后,隧道box2
就可以传输数据了,此时服务端box3
通过netcat工具在9900号端口创建了一个聊天服务,客户端box1
也通过netcat工具创建了一个聊天客户端,这个聊天客户端有一点特殊之处,就是使用proxychains让客户端通过9050端口通信。
1 2 3 4 5 6 7 8 9 10 | box2: 192.168 . 18.157 box3: 192.168 . 18.177 box1: / home$ ssh - p 22 box2@ 192.168 . 18.157 - D 127.0 . 0.1 : 9050 box3: / home$ nc - nv - l 192.168 . 18.177 - p 9900 box1: / home$ proxychains nc 192.168 . 18.177 9900 | nc_box3: 9900 | < - > eth < - > ip < - > tcp < - > data < - > | sshd_box2: 35070 | | sshd_box2: 22 | < - > eth < - > ip < - > tcp < - > ssh < - > | win_box: 57291 | < - > | vbox_mgr | < - > | ssh_box1: 58256 | | ssh_box1: 9050 | < - data - > | nc_box1: 43806 | |
9050号端口是proxychains工具默认设置的SockS端口,所以proxychains运行后,box1
的聊天客户端收发数据时,都会先经过9050端口,9050端口会决定数据下一步向哪转发。
1 2 | cat / etc / proxychains.conf - > socks4 127.0 . 0.1 9050 |
当proxychains建立起联系后,聊天服务端和聊天客户端的通信,都会交给代理服务器box2
的sshd进行控制,聊天服务端会先将数据(data
协议)交给box2
的sshd,box2
的sshd会把加密好的数据传递给box1
的ssh,然后box1
的ssh会解密数据,最后在通过9050端口上的SockS服务(SSH维护)将数据转发给聊天客户端。
SSH隧道作为SockS服务并不会实际处理报文,只会对报文进行加密和转发。
上面的示例是一个正向SockS代理建立的例子,如果想要建立反向SockS代理建立,那么仍然需要借助SSH-R
选项。
下面是常见的利用SSH建立反向SockS代理的命令行示例,它与反向端口转发的区别在于,反向端口转发-R ${remote_ip}:${remote_port}:${target_ip}:${target_port}
中指定了目标机器(提供网络资源的机器),而反向SockS代理就只指定了远端的IP和端口。
1 | agent: / home$: ssh - R ${remote_ip}:${remote_port} remote_user@remote_ip |
反向SockS代理建立后,远端机器上的SockS服务器绑定到指定端口后,所有经过该端口的浏览,都会由SockS服务器转发到代理机器上,代理机器会代替远端机器发出请求。
SSH并不是一开始就支持反向SockS代理的,版本较低的SSH是只支持反向端口转发的,但是也可以利用端口转发功能委婉的实现反向SockS代理。
反向SockS代理有两大要求,一是存在SockS端口,二是远端机器要和SockS端口建立连接。
因此委婉的建立反向SockS代理可以分成两步,一是机器A上通过-D
建立SockS端口,二是通过-R
的反向端口转发功能将SockS端口转发到机器B上,完成之后经过机器B上的特定端口的流量,都会由机器A的SockS服务器转发。
1 2 | client: / home$ ssh - D ${loacl_ip}:${loacl_port} ${jmpbox_user}@${jmpbox_ip} client: / home$ ssh - R ${remote_ip}:${remote_port}:${loacl_ip}:${loacl_port} ${remote_user}@${remote_ip} |
隧道技术可以端口转发和完全代理两类,两者的区别在于,端口转发将被访问资源与本地请求端建立一对一的关系,本地请求端口无法访问服务端其余端口提供的资源,或其他服务器提供的网络资源,完全代理会将经过本地请求端口的所有流量转发,不会限制服务端的范围。
隧道技术在方向上的主要区别是网络环境的差异,正向隧道转发适用于本机访问外部网络资源(非局域网)受限的场景,反向隧道转发适用于外部机器难以访问局域网内部的场景。
不管是正向隧道还是反向隧道,都要求访问资源受限的机器可以与访问资源不受限机器的建立连接。大部分情况下,不受限机器不对外提供什么资源,但拥有另一种网络环境下的资源访问权,当然也有可能代理机器就是一个大礼包,本身就提供了丰富的网络资源。
1 2 3 | | network env A | | network env B | | [cannot access machine] - ) - - - - ) - [can access machine] - > [other machine] | | - - - - - - - - - - - - - - - - - - - - - - cannot access - - - - - - - - - - - - - - - - - - - - - - | |
使用隧道还有一个好处,就是被访问资源的机器识别到的流量来自于跳板机器,不会将发出请求的源机器识别出来。通过tcpdump和tshark工具配合,将使用端口转发时IP协议帧中的IP地址情况和不使用端口转发时的IP地址情况进行对比,就可以确认这一点。
至于组织SSH命令时主要还是要想清楚四大问题。
哪些机器可以作为代理主机。
缺少访问权限的主机应该由谁提供服务,正向建立隧道还是反向建立。
不对外开放的端口有没有借助本地回环地址访问的可能。
跳板机器结构应该如何设置。
经过上面的分析可以发现,SSH隧道可以帮助我们访问跨网段的内网机器,只不过需要借助代理服务器的帮助,经过SSH隧道的数据都会被加密。
1 | | machine_A | < - > | ssh tunnel | < - > | machine_B | |
虚拟私有网络VPN Virtual Private Network
与SSH隧道基本是一致的,它们都经过代理服务器传递报文,并且会将数据加密。
但要说VPN和SockS隧道是不是一样的,那答案就不一定是了,因为SockS隧道要求代理服务支持SockS协议提供转发的功能,至于会不会将报文加密,就要看代理服务的具体实现了,使用SSH构建隧道时,SSH会提供加密通信的支持。
VPN和普通隧道连接还有一个很重要,那就是普通隧道都是通过端口进行通信,而VPN则会借助虚拟网络接口设备TUN或TAP进行通信。
那么TUN和TAP这样的虚拟网络接口设备是怎么来的呢,VPN又为什么要使用它呢?
虚拟网络接口设备和物理网络接口设备的显著区别,就是物理网络接口设备通过硬件收发数据,而虚拟网络接口设备则借助用户态程序收发数据。
1 2 | app < - > kernel < - > hardware network interface device (hnid) < - > send / receive app < - > kernel < - > tun / tap < - > app_handle < - > hnid < - > send / receive |
用户态报文处理程序想要加入网络通信这一流程,首先要建立的就是内核和用户态报文处理程序间的通信方式,虚拟网络接口设备就是提供该功能的主要元素。
在Linux中存在着一个名为/dev/net/tun
的文件,虚拟网络接口设备需要通过该文件进行创建,基本流程是先根据/dev/net/tun
获取一个文件描述符,最后通过ioctl
系统调用注册虚拟网络接口设备。
1 2 | fd = open ( "/dev/net/tun" , O_RDWR); ioctl(fd, TUNSETIFF, struct * ifreq ifr); |
Linux中的也有像ip这样的命令,它们是支持通过非编程的方式创建虚拟网络接口设备的。
1 2 | example: ip tuntap add dev tun_test mode tun |
创建好虚拟网络设备后,设备中向IP地址、子网掩码等信息都是空的,所以需要还需要对虚拟网卡进行进一步的设置,在编程时需要通过struct *ifreq ifr
设置信息。
不管是虚拟网卡设备还是代理服务绑定的本地端口,它们的作用都是一样的,即拦截程序发出的报文,交给本地服务处理,本地服务可以选择如何处理报文,以及报文是直接发给最终目标端,还是交给代理服务器继续处理。
对于使用VPN的场景来讲,内核会借助虚拟网卡设备拦截报文,然后将拦截下来的报文发给文件/dev/net/tun
,报文处理程序需要根据/dev/net/tun
文件的文件描述符读取或写入数据,报文处理程序拿到报文后就可以随意处理了,那么VPN至少是要求加密的。
对于SSH隧道的端口转发场景来讲,客户端和服务端都会直接将数据发给SSH隧道绑定的端口,并不需要对报文进行拦截。
对于SSH隧道的SockS代理场景来讲,一般会要求使用类似于proxychains的工具,将客户端与SSH创建的Socks端口建立连接关系,客户端通过自己的端口发送数据时,会被转移到SockS端口上,SSH的Socks服务会对报文进行处理。
更多【WEB安全-Web安全入门-网络资源的访问-隧道】相关视频教程:www.yxfzedu.com