在接解http的时候,咱们或多或少都会据说过长链接和短链接的概念,有心点还会知道http默认是短链接若是要长链接可带上以下头,什么长链接节省性能历来都是无论的。css
Connection: keep-alive
但其实除了性能,长链接和短链接在安全方面其实存在差别,这就使得渗透测试人员不得不认真了解什么是长链接什么是短链接。web
而第一步就是要明确什么是长链接什么是短链接,学院派流行把简单的东西自我感受良好地说到让人听不懂,学术派流行能力不足强行解释把简单的东西自我都感受不良好地说到让人听不懂。apache
反正就是听不懂,因此仍是得本身来理解一下。先举个例子,好比如今有url1和url2两个页面:安全
短链接的访问模式是:三次握手---url1----四次挥手,三次握手----url2----四次挥手。一次链接只承载一组http请求(一组而不是一个,是由于请求和响应确定要在一个链接中完成;一组而不是一对,是由于当前页面经过url导入的其余元素如js文件css文件图片等的请求响应也是在同一个链接中完成)。服务器
长链接的访问模式是:三次握手----url1----url2----urlx----四次挥手。一次链接承载多组http请求。cookie
而后咱们能够下定义:一个链接以三次握手开始四次挥手结束;若是在这个链接只传输一组应用层数据包那他就是短链接,若是能传输多组应用层数据包那他就是长链接。app
(不过严谨地讲,如今的apache等http服务器都支持设置keep-alive的时长,因此时间长短仍是传输多少组应用层数据包都比较难准确划分长链接短链接,但如今的系统都讲登陆咱们能够从登陆角度去下一个定义:若是一个链接传输从在用户登陆到用户退出中全部请求那他就是长链接,反之则是短链接。)socket
从前面讨论能够看到短链接要频繁地创建和断开链接,每多一组请求就比长链接多一次握手和挥手,直觉上长链接比短链接有优点。但现实是众多应用层协议使用的是短链接而不是长链接,咱们以http为例来分析其缘由。ide
咱们以访问和查看一个连接为一个时间单位----好比你查看这篇博客----从点击连接到如今这段时间其实也就只有加载页面那一组请求,查看内容这段时间是没有任何请求的,也就是说在这段时间中长链接确实比短链接节省了一个握手挥手过程,但也在整段时间内比短链接多耗保持链接须要的系统资源,并且用户查看内容的时间越长长链接耗费的资源就越大。性能
访问整个网站能够分拆成访问和查看一个个连接,从单个时间单位上看http使用长链接其实并不比短链接节省资源,因此整个来看http使用长链接也不会比短链接节省资源。
从上面讨论中,主要就是看是长链接减小的握手挥手过程节省的资源多,仍是短链接不须要保存会话节省的资源多;或者叫,长链接的优点与服务时间内的请求组数成正比。
在平常web渗透中咱们的经验是,若是一个包返回了某个结果那咱们用burpsuite再次发送时仍会获得一样的结果(不考虑防重放不考虑会话超时不考虑删除操做不要钻牛角尖)。
直到有一天我截获了一个没有鉴权的数据包,而后编写脚本重放时获得了迵然不一样的结果,才意识到这个经验并不能用到长链接上。下面举例说明。
服务端server_keep_alive.py代码以下:
import socket import threading # 线程实现类 class thread_socket (threading.Thread): def __init__(self, thread_id, client_addr, client_socket): threading.Thread.__init__(self) self.thread_id = thread_id self.client_addr = client_addr self.client_socket = client_socket def run(self): msg = self.client_socket.recv(1024).decode("utf-8") while msg != "exit": print(f"receive msg from client {self.thread_id}-{self.client_addr}:{msg}") msg_dict = msg.split(":") # 若是客户传过来的内容不能以:切分红2份那必然不是用户名密码,继续要求用户输入用户名密码 if len(msg_dict) != 2: msg = f"{self.thread_id}-{self.client_addr},sorry please enter correct username and password at first".encode("utf-8") self.client_socket.send(msg) # 若是是正确的用户名密码,那么容许客户端执行命令 elif msg_dict[0] == "admin" and msg_dict[1] == "password": msg = f"{self.thread_id}-{self.client_addr},congratulation, you have connect with server\r\nnow, you can execute your command".encode("utf-8") self.client_socket.send(msg) msg = self.client_socket.recv(1024).decode("utf-8") while msg != 'exit': print(f"receive msg from client {self.thread_id}-{self.client_addr}: command: {msg}") msg = f"{msg} execute finished".encode("utf-8") self.client_socket.send(msg) msg = self.client_socket.recv(1024).decode("utf-8") print(f'{self.thread_id}-{self.client_addr} now close connect') self.client_socket.close() # 若是是正确的用户名密码,继续要求用户输入用户名密码 else: msg = f"{self.thread_id}-{self.client_addr},sorry,please enter correct username and password at first".encode("utf-8") self.client_socket.send(msg) # self.client_socket.close() msg = self.client_socket.recv(1024).decode("utf-8") self.client_socket.close() # 服务端主类 class server_class : def build_listen(self): # 监听端口 server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server_socket.bind(('10.10.6.91',9999)) server_socket.listen(5) print(f"now server listen at 10.10.6.91:9999") thread_count = 0 threads = [] while True: # 每接收一个客户端链接,就新启动一个线程去交互 client_socket, client_addr = server_socket.accept() thread_name = thread_socket(thread_count, client_addr, client_socket) threads.append(thread_name) threads[thread_count].start() # threads[thread_count].join() thread_count += 1 if __name__ == "__main__": server = server_class() server.build_listen()
客户端client_keep_alive.py代码以下:
import socket class client_class: def send_hello(self): # 与服务端创建链接 client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client_socket.connect(('10.10.6.91',9999)) # 向服务器发送消息,打印服务器返回消息 msg = input("please enter your msg for send to server:") while msg != "close": # 向服务端发送消息 client_socket.send(msg.encode("utf-8")) # 接收服务端返回的消息 msg = client_socket.recv(1024).decode('utf-8') print(f"receive msg from server : {msg}\r\n") msg = input("please enter your msg for send to server:") client_socket.close() if __name__ == "__main__": client = client_class() client.send_hello()
操做步骤以下:
第一步,运行server_keep_alive.py
第二步,运行client_keep_alive.py两次,实例化出两个客户端client0和client1
第三步,client0输入client0,client1输入client1;返回结果都是要求输入用户名密码
第四步,client0输入admin:password登陆成功,client1输入admin:password未登陆成功继续被要求输入用户名密码
第五步,client0输入whoami命令执行成功,client1输入whoami命令执行不成功继续被要求输入用户名密码
client0运行截图:
client1运行截图:
server运行截图:
总的意思就是长链接中client0登陆成功,成功执行命令;client1登陆未成功,企图直接模仿client0执行命令被拒绝了。
也就是说,在长链接中若是有登陆认证机制,那么全部链接都须要独自完成这个认证过程,直接构造发送认证以后才接收的数据包服务端是不认的;或者说长链接可以记录每一个链接是否已经过认证;或者说长链接是有状态的(http没有状态根本缘由就是http使用的是短链接,http须要cookie的根本缘由也是http使用的是短链接)。
如今随着计算机性能的长足进步,性能已逐渐被安全性易用性等超越沦为系统设计中的次要矛盾,在一些私有系统(相对百度等公共能够访问的系统)中尤其明显。因为长链接的有状态特性直接节省了会话保持设计,有不少的私有协议(相对http等标准协议)直接采用长链接,也所以渗透测试者也就不免会赶上长链接系统。
而长链接的有状态特性,就要求对于习惯于渗透短连接的渗透测试者,在渗透长链接系统时须要注意如下两点:
一是在长链接系统中,若是截获一个危险操做的数据包发现里面没有任何鉴权字段,那也不能就认定该系统存在越权漏洞,须要查看链接刚创建时有没有登陆认证机制,若是有登陆认证机制且该机制没问题那是不存在越权漏洞的。
二是在长链接系统中,若是有登陆认证机制那么若是想直接对服务端口重放从别的链接截获的数据包那是不可能成功的,须要先完成链接开头的登陆认证。