長鏈接與短鏈接的安全差別討論

1、長鏈接與短鏈接的定義

1.1 定義

在接解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

 

1.2 http等協議使用短鏈接的緣由

從前面討論能夠看到短鏈接要頻繁地創建和斷開鏈接,每多一組請求就比長鏈接多一次握手和揮手,直覺上長鏈接比短鏈接有優點。但現實是衆多應用層協議使用的是短鏈接而不是長鏈接,咱們以http爲例來分析其緣由。ide

咱們以訪問和查看一個連接爲一個時間單位----好比你查看這篇博客----從點擊連接到如今這段時間其實也就只有加載頁面那一組請求,查看內容這段時間是沒有任何請求的,也就是說在這段時間中長鏈接確實比短鏈接節省了一個握手揮手過程,但也在整段時間內比短鏈接多耗保持鏈接須要的系統資源,並且用戶查看內容的時間越長長鏈接耗費的資源就越大。性能

訪問整個網站能夠分拆成訪問和查看一個個連接,從單個時間單位上看http使用長鏈接其實並不比短鏈接節省資源,因此整個來看http使用長鏈接也不會比短鏈接節省資源。

從上面討論中,主要就是看是長鏈接減小的握手揮手過程節省的資源多,仍是短鏈接不須要保存會話節省的資源多;或者叫,長鏈接的優點與服務時間內的請求組數成正比。

 

2、長鏈接與短鏈接的安全差別

在平常web滲透中咱們的經驗是,若是一個包返回了某個結果那咱們用burpsuite再次發送時仍會獲得一樣的結果(不考慮防重放不考慮會話超時不考慮刪除操做不要鑽牛角尖)。

直到有一天我截獲了一個沒有鑑權的數據包,而後編寫腳本重放時獲得了迵然不一樣的結果,才意識到這個經驗並不能用到長鏈接上。下面舉例說明。

2.1 代碼

服務端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()
View Code

客戶端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()
View Code

 

2.2 運行過程

操做步驟以下:

第一步,運行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使用的是短鏈接)。

 

3、滲透長鏈接系統的注意事項

如今隨着計算機性能的長足進步,性能已逐漸被安全性易用性等超越淪爲系統設計中的次要矛盾,在一些私有系統(相對百度等公共能夠訪問的系統)中尤其明顯。因爲長鏈接的有狀態特性直接節省了會話保持設計,有不少的私有協議(相對http等標準協議)直接採用長鏈接,也所以滲透測試者也就不免會趕上長鏈接系統

而長鏈接的有狀態特性,就要求對於習慣於滲透短連接的滲透測試者,在滲透長鏈接系統時須要注意如下兩點:

一是在長鏈接系統中,若是截獲一個危險操做的數據包發現裏面沒有任何鑑權字段,那也不能就認定該系統存在越權漏洞,須要查看鏈接剛創建時有沒有登陸認證機制,若是有登陸認證機制且該機制沒問題那是不存在越權漏洞的。

二是在長鏈接系統中,若是有登陸認證機制那麼若是想直接對服務端口重放從別的鏈接截獲的數據包那是不可能成功的,須要先完成鏈接開頭的登陸認證。

相關文章
相關標籤/搜索