成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

使用Go語(yǔ)言構(gòu)建網(wǎng)絡(luò)漏洞掃描程序

譯文 精選
開(kāi)發(fā) 前端
在本文中,我們將使用Go語(yǔ)言創(chuàng)建一個(gè)簡(jiǎn)易卻頗具健壯性的網(wǎng)絡(luò)漏洞掃描程序。Go是一種非常適合網(wǎng)絡(luò)編程的語(yǔ)言,這是因?yàn)槠湓谠O(shè)計(jì)之初就充分考量了并發(fā)性,而且擁有一套出色的標(biāo)準(zhǔn)庫(kù)。

譯者 | 劉濤

審校 | 重樓

滲透測(cè)試能夠幫助組織發(fā)現(xiàn)網(wǎng)絡(luò)中潛在的安全漏洞,明確了在這些漏洞被惡意行為者利用之前開(kāi)展修復(fù)工作的必要性。

在本文中,我們將使用Go語(yǔ)言創(chuàng)建一個(gè)簡(jiǎn)易卻頗具健壯性的網(wǎng)絡(luò)漏洞掃描程序。Go是一種非常適合網(wǎng)絡(luò)編程的語(yǔ)言,這是因?yàn)槠湓谠O(shè)計(jì)之初就充分考量了并發(fā)性,而且擁有一套出色的標(biāo)準(zhǔn)庫(kù)。

創(chuàng)建項(xiàng)目

創(chuàng)建漏洞掃描程序

首先,我們用Go語(yǔ)言編寫一個(gè)簡(jiǎn)易的命令行界面(CLI)工具,該工具能夠?qū)χ鳈C(jī)網(wǎng)絡(luò)進(jìn)行掃描,查找開(kāi)放端口、正在運(yùn)行的服務(wù),并發(fā)現(xiàn)潛在的漏洞。這個(gè)掃描工具啟動(dòng)方式簡(jiǎn)易,并且隨著我們逐步為其增添更多功能模塊,它的功能將愈發(fā)強(qiáng)大。

以下建立一個(gè)新的Go工程:

mkdir goscan
cd goscan
go mod init github.com/yourusername/goscan

這一步操作會(huì)為項(xiàng)目初始化一個(gè)全新的Go模塊。該模塊能夠幫助我們對(duì)項(xiàng)目所需的依賴項(xiàng)進(jìn)行有效管理。

配置包與環(huán)境

針對(duì)我們的掃描程序,我們將使用幾個(gè)Go包:

package main

import (
 "fmt"
 "net"
 "os"
 "strconv"
 "sync"
 "time"
)

func main() {
 fmt.Println("GoScan Network Vulnerability Scanner")
}

以上僅僅是初始設(shè)置。就一些初步功能而言,這些設(shè)置已然足夠。不過(guò),后續(xù)我們會(huì)依據(jù)實(shí)際需求添加更多導(dǎo)入內(nèi)容。目前,諸如net這類Go內(nèi)置的其他標(biāo)準(zhǔn)庫(kù)包將承擔(dān)起我們所需的大部分網(wǎng)絡(luò)相關(guān)工作,而sync包則會(huì)負(fù)責(zé)處理并發(fā)操作等等。

網(wǎng)絡(luò)掃描的倫理考量與風(fēng)險(xiǎn)

在開(kāi)始實(shí)施網(wǎng)絡(luò)掃描之前,有必要深入探討其中涉及的倫理問(wèn)題。在全球眾多地區(qū),未經(jīng)授權(quán)開(kāi)展網(wǎng)絡(luò)掃描或進(jìn)行網(wǎng)絡(luò)枚舉屬于違法行為,這類行為還會(huì)被視為網(wǎng)絡(luò)攻擊的一種形式。因此,在進(jìn)行網(wǎng)絡(luò)掃描時(shí),務(wù)必始終遵循以下規(guī)則:

  • 權(quán)限獲取:僅對(duì)自己有權(quán)限訪問(wèn)的網(wǎng)絡(luò)和系統(tǒng)進(jìn)行掃描,或者是在獲得明確的掃描授權(quán)之后開(kāi)展操作。
  • 掃描范圍界定:為掃描工作設(shè)定清晰明確的范圍,并嚴(yán)格將限定在該范圍內(nèi)。
  • 掃描時(shí)機(jī)選擇:要避免過(guò)度掃描,防止因頻繁掃描導(dǎo)致目標(biāo)系統(tǒng)服務(wù)中斷,或者觸發(fā)不必要的安全警報(bào)。
  • 漏洞披露原則:一旦發(fā)現(xiàn)漏洞,應(yīng)以負(fù)責(zé)任的態(tài)度將相關(guān)情況報(bào)告給對(duì)應(yīng)的系統(tǒng)所有者。
  • 法律合規(guī)遵循:充分了解并嚴(yán)格遵守當(dāng)?shù)赜嘘P(guān)網(wǎng)絡(luò)掃描的法律法規(guī)。

需要明確的是,濫用掃描工具可能會(huì)引發(fā)一系列嚴(yán)重后果,包括但不限于面臨法律訴訟、造成目標(biāo)系統(tǒng)損壞,甚至導(dǎo)致意外的拒絕服務(wù)情況。盡管我們開(kāi)發(fā)的掃描程序會(huì)設(shè)置諸如速率限制等防護(hù)機(jī)制,但最終以符合倫理道德和法律規(guī)范的方式使用該工具,仍是每位使用者應(yīng)盡的責(zé)任。

簡(jiǎn)易端口掃描程序

漏洞評(píng)估的基礎(chǔ)在于端口掃描。每個(gè)開(kāi)放端口所提供的、可能存在易受攻擊情況的服務(wù)信息,就是我們要探尋的關(guān)鍵內(nèi)容。接下來(lái),讓我們用Go語(yǔ)言編寫一個(gè)簡(jiǎn)易的端口掃描程序。

端口掃描的底層實(shí)現(xiàn)

端口掃描的原理是嘗試與目標(biāo)主機(jī)上的每個(gè)可能端口建立連接。若連接成功,這就意味著該端口處于開(kāi)放狀態(tài);若連接失敗,則表明該端口處于關(guān)閉狀態(tài)或者被設(shè)置了過(guò)濾規(guī)則。Go語(yǔ)言的“net”包為實(shí)現(xiàn)這一功能提供了必要的支持。

以下是我們實(shí)現(xiàn)的一個(gè)簡(jiǎn)易端口掃描程序:

package main

import (
 "fmt"
 "net"
 "time"
)

func scanPort(host string, port int, timeout time.Duration) bool {
 target := fmt.Sprintf("%s:%d", host, port)
 conn, err := net.DialTimeout("tcp", target, timeout)
 
 if err != nil {
 return false
 }
 
 conn.Close()
 return true
}

func main() {
 host := "localhost" // Change this to your target
 timeout := time.Second * 2
 
 fmt.Printf("Scanning host: %s\n", host)
 
 // Scan ports 1-1024 (well-known ports)
 for port := 1; port <= 1024; port++ {
 if scanPort(host, port, timeout) {
 fmt.Printf("Port %d is open\n", port)
 }
 }
 
 fmt.Println("Scan complete")
}

使用Net包

上述代碼使用了Go語(yǔ)言的net包,該包提供了豐富的網(wǎng)絡(luò)輸入/輸出接口(I/O interfaces)以及相關(guān)函數(shù)。具體而言,主要涉及以下幾個(gè)關(guān)鍵部分:

  • net.DialTimeout函數(shù):此函數(shù)的功能是嘗試在預(yù)先設(shè)定的超時(shí)時(shí)間內(nèi),與指定的TCP網(wǎng)絡(luò)地址建立連接。在連接過(guò)程中,如果操作成功,它將返回一個(gè)連接對(duì)象;若出現(xiàn)連接錯(cuò)誤,函數(shù)則會(huì)返回相應(yīng)的錯(cuò)誤信息。
  • 連接處理機(jī)制:當(dāng)連接成功建立時(shí),也就意味著對(duì)應(yīng)的端口處于開(kāi)放狀態(tài)。為了避免資源的浪費(fèi)和不必要的占用,我們會(huì)在確認(rèn)端口開(kāi)放后,立即關(guān)閉該連接,以釋放相關(guān)資源,確保系統(tǒng)的高效運(yùn)行。
  • 超時(shí)參數(shù)設(shè)定:我們?yōu)檫B接操作指定了一個(gè)超時(shí)時(shí)間。這一設(shè)置的目的在于防止程序在遇到被過(guò)濾的開(kāi)放端口時(shí),出現(xiàn)長(zhǎng)時(shí)間等待(掛起)的情況。通常來(lái)說(shuō),將初始超時(shí)時(shí)間設(shè)置為2秒是一個(gè)較為合適的選擇。不過(guò),實(shí)際應(yīng)用中可根據(jù)具體的網(wǎng)絡(luò)狀況和需求進(jìn)行靈活調(diào)整。

測(cè)試我們的首次掃描

接下來(lái),讓我們針對(duì)本地主機(jī)運(yùn)行這個(gè)簡(jiǎn)易掃描程序。在本地主機(jī)上,通常會(huì)運(yùn)行著一些服務(wù)。具體操作步驟如下:

  • 將代碼保存為名為 main.go 的文件
  • 使用 go run main.go 命令運(yùn)行該文件執(zhí)行上述操作后,程序?qū)@示哪些本地端口處于開(kāi)放狀態(tài)。在一般的開(kāi)發(fā)計(jì)算機(jī)上,依據(jù)所運(yùn)行的服務(wù)不同,可能會(huì)出現(xiàn) 80 端口(用于 HTTP 服務(wù))、443 端口(用于 HTTPS 服務(wù)),或者其他正在使用的數(shù)據(jù)庫(kù)端口等。

以下是可能得到的一些輸出結(jié)果

Scanning host: localhost
Port 22 is open
Port 80 is open
Port 443 is open
Scan complete

使用該簡(jiǎn)單掃描程序,盡管能夠?qū)崿F(xiàn)基本的端口掃描功能,但存在一些較為嚴(yán)重的不足:

  • 掃描速度問(wèn)題:掃描程序按照順序逐個(gè)掃描端口,這種方式導(dǎo)致掃描速度極為緩慢。在面對(duì)大量端口需要掃描的場(chǎng)景時(shí),會(huì)耗費(fèi)大量時(shí)間。
  • 信息獲取不足:它僅僅能夠告知我們端口處于開(kāi)放還是關(guān)閉狀態(tài),并沒(méi)有提供任何與端口所對(duì)應(yīng)服務(wù)相關(guān)的信息。這使得我們難以進(jìn)一步了解目標(biāo)主機(jī)的詳細(xì)情況。
  • 掃描范圍受限:目前我們僅對(duì)前 1024 個(gè)端口進(jìn)行掃描。然而,在實(shí)際的網(wǎng)絡(luò)環(huán)境中,許多服務(wù)可能會(huì)使用 1024 以上的端口,這就導(dǎo)致該掃描程序無(wú)法全面檢測(cè)到所有可能存在的開(kāi)放端口及相關(guān)服務(wù)。

鑒于上述這些限制因素,我們的掃描程序在實(shí)際應(yīng)用場(chǎng)景中的實(shí)用性大打折扣。

從這里開(kāi)始改進(jìn):多線程掃描

為何第一版速度慢

我們的第一個(gè)端口掃描程序雖能運(yùn)行,但速度慢得讓人難以接受。問(wèn)題在于它使用順序掃描的方式——一次僅能探測(cè)一個(gè)端口。當(dāng)主機(jī)有大量關(guān)閉或過(guò)濾的端口時(shí),在轉(zhuǎn)向下一個(gè)端口之前,我們會(huì)浪費(fèi)時(shí)間等待每個(gè)端口的連接超時(shí)。

為了讓了解這個(gè)問(wèn)題,我們來(lái)看一下簡(jiǎn)易掃描程序用時(shí)情況:

  • 掃描前1024個(gè)端口,在最壞的情況下,若每個(gè)端口超時(shí)時(shí)間設(shè)為2秒,最多需要2048秒(超過(guò)34分鐘)。
  • 但即使到關(guān)閉端口的連接能立即失敗,由于網(wǎng)絡(luò)延遲,這種方法效率依然低下。
    這種逐個(gè)掃描的方式是任何真正的漏洞掃描工具的瓶頸。

添加多線程支持

Go語(yǔ)言在使用goroutine(協(xié)程)和channel(通道)并發(fā)處理方面表現(xiàn)尤為出色。因此,我們利用這些功能嘗試一次同時(shí)掃描多個(gè)端口,這將顯著提高性能。

現(xiàn)在,讓我們創(chuàng)建一個(gè)多線程端口掃描程序

package main

import (
 "fmt"
 "net"
 "sync"
 "time"
)

type Result struct {
 Port int
 State bool
}

func scanPort(host string, port int, timeout time.Duration) Result {
 target := fmt.Sprintf("%s:%d", host, port)
 conn, err := net.DialTimeout("tcp", target, timeout)
 
 if err != nil {
 return Result{Port: port, State: false}
 }
 
 conn.Close()
 return Result{Port: port, State: true}
}

func scanPorts(host string, start, end int, timeout time.Duration) []Result {
 var results []Result
 var wg sync.WaitGroup
 
 // Create a buffered channel to collect results
 resultChan := make(chan Result, end-start+1)
 
 // Create a semaphore to limit concurrent goroutines
 // This prevents us from opening too many connections at once
 semaphore := make(chan struct{}, 100) // Limit to 100 concurrent scans
 
 // Launch goroutines for each port
 for port := start; port <= end; port++ {
 wg.Add(1)
 go func(p int) {
 defer wg.Done()
 
 // Acquire semaphore
 semaphore <struct{}{}
 defer func() { <-semaphore }() // Release semaphore
 
 result := scanPort(host, p, timeout)
 resultChan <result
 }(port)
 }
 
 // Close channel when all goroutines complete
 go func() {
 wg.Wait()
 close(resultChan)
 }()
 
 // Collect results from channel
 for result := range resultChan {
 if result.State {
 results = append(results, result)
 }
 }
 
 return results
}

func main() {
 host := "localhost" // Change this to your target
 startPort := 1
 endPort := 1024
 timeout := time.Millisecond * 500 
 
 fmt.Printf("Scanning %s from port %d to %d\n", host, startPort, endPort)
 startTime := time.Now()
 
 results := scanPorts(host, startPort, endPort, timeout)
 
 elapsed := time.Since(startTime)
 
 fmt.Printf("\nScan completed in %s\n", elapsed)
 fmt.Printf("Found %d open ports:\n", len(results))
 
 for _, result := range results {
 fmt.Printf("Port %d is open\n", result.Port)
 }
}

多線程結(jié)果

現(xiàn)在,讓我們一同審視改進(jìn)后的掃描程序在性能提升以及并發(fā)機(jī)制運(yùn)方面的具體情況:

  • 協(xié)程(Goroutines):為有效提升掃描效率,針對(duì)每個(gè)待掃描的端口,我們都會(huì)啟動(dòng)一個(gè)獨(dú)立的協(xié)程。通過(guò)這種方式,系統(tǒng)能夠在檢查某一個(gè)端口狀態(tài)的同時(shí),并行地對(duì)其他端口展開(kāi)檢查,極大地提高了掃描的并行度。
  • 等待組(WaitGroup):隨著協(xié)程的大量引入,我們需要一種機(jī)制來(lái)確保所有協(xié)程都能順利完成任務(wù)。sync.WaitGroup在此發(fā)揮了關(guān)鍵作用,它能夠幫助我們精準(zhǔn)跟蹤所有正在運(yùn)行的協(xié)程,并使程序在所有協(xié)程執(zhí)行完畢之后再繼續(xù)后續(xù)操作,保證了掃描任務(wù)的完整性和準(zhǔn)確性。
  • 結(jié)果通道(Result Channel):我們專門創(chuàng)建了一個(gè)帶有緩沖的通道,其作用是按順序接收所有協(xié)程返回的掃描結(jié)果。這種設(shè)計(jì)使得掃描結(jié)果能夠有序地進(jìn)行收集和處理,方便我們對(duì)掃描結(jié)果進(jìn)行統(tǒng)一管理和分析。
  • 信號(hào)量模式(Semaphore Pattern):借助通道,我們成功實(shí)現(xiàn)了信號(hào)量模式。通過(guò)這一模式,我們能夠?qū)υ试S并行進(jìn)行的掃描數(shù)量進(jìn)行有效限制。這一舉措至關(guān)重要,它可以避免因同時(shí)打開(kāi)過(guò)多連接,給目標(biāo)系統(tǒng)甚至自身運(yùn)行的機(jī)器帶來(lái)過(guò)大的負(fù)載壓力,確保掃描過(guò)程的穩(wěn)定性和安全性。
  • 減少超時(shí)(Reduced Timeout)鑒于我們使用并行方式運(yùn)行大量的端口掃描任務(wù),為了提高整體掃描效率并合理利用資源,我們使用相對(duì)較短的超時(shí)。

通過(guò)上述一系列改進(jìn)措施,性能差距對(duì)比表現(xiàn)十分明顯。在實(shí)際應(yīng)用中,當(dāng)我們成功實(shí)現(xiàn)此方法后,掃描1024個(gè)端口所需的時(shí)間大幅縮短,僅僅只需幾分鐘,肯定不會(huì)超過(guò)半小時(shí)。

以下為示例輸出:

Scanning localhost from port 1 to 1024
Scan completed in 3.2s
Found 3 open ports:
Port 22 is open
Port 80 is open
Port 443 is open

多線程方法具備出色的擴(kuò)展性,能夠很好地適用于更大的端口掃描范圍以及多個(gè)主機(jī)的掃描任務(wù)。其中,信號(hào)量模式發(fā)揮了關(guān)鍵作用,它確保即便掃描的端口數(shù)量超過(guò)1000個(gè),系統(tǒng)資源也不會(huì)被耗盡。

添加服務(wù)檢測(cè)

既然我們已經(jīng)構(gòu)建了一個(gè)快速且高效的端口掃描程序,接下來(lái)的關(guān)鍵步驟便是弄清楚在那些處于開(kāi)放狀態(tài)的端口上究竟運(yùn)行著何種服務(wù)。這一過(guò)程在業(yè)內(nèi)通常被稱作 “服務(wù)指紋識(shí)別(service fingerprinting)” 或者 “橫幅抓取(banner grabbing)”,簡(jiǎn)單來(lái)講,就是連接到開(kāi)放端口,并對(duì)返回的數(shù)據(jù)展開(kāi)檢測(cè)。

橫幅抓取的實(shí)現(xiàn)

所謂橫幅抓取,指的是我們與一個(gè)服務(wù)建立連接后,讀取該服務(wù)發(fā)送給我們的響應(yīng)信息(即橫幅)。這是一種非常有效的識(shí)別服務(wù)是否正在運(yùn)行的方法,因?yàn)樵S多服務(wù)會(huì)在這些橫幅信息中明確表明自身的身份。

接下來(lái),讓我們?cè)?/span>掃描程序中增添橫幅抓取功能:

package main

import (
 "bufio"
 "fmt"
 "net"
 "strings"
 "sync"
 "time"
)

type ScanResult struct {
 Port int
 State bool
 Service string
 Banner string
 Version string
}

func grabBanner(host string, port int, timeout time.Duration) (string, error) {
 target := fmt.Sprintf("%s:%d", host, port)
 conn, err := net.DialTimeout("tcp", target, timeout)
 if err != nil {
 return "", err
 }
 defer conn.Close()
 
 conn.SetReadDeadline(time.Now().Add(timeout))
 
 // Some services need a trigger to send data
 // Send a simple HTTP request for web services
 if port == 80 || port == 443 || port == 8080 || port == 8443 {
 fmt.Fprintf(conn, "HEAD / HTTP/1.0\r\n\r\n")
 } else {
 // For other services, just wait for the banner
 // Some services may require specific triggers
 }
 
 // Read the response
 reader := bufio.NewReader(conn)
 banner, err := reader.ReadString('\n')
 if err != nil {
 return "", err
 }
 
 return strings.TrimSpace(banner), nil
}

func identifyService(port int, banner string) (string, string) {
 commonPorts := map[int]string{
 21: "FTP",
 22: "SSH",
 23: "Telnet",
 25: "SMTP",
 53: "DNS",
 80: "HTTP",
 110: "POP3",
 143: "IMAP",
 443: "HTTPS",
 3306: "MySQL",
 5432: "PostgreSQL",
 6379: "Redis",
 8080: "HTTP-Proxy",
 27017: "MongoDB",
 }
 
 // Try to identify service from common ports
 service := "Unknown"
 if s, exists := commonPorts[port]; exists {
 service = s
 }
 
 version := "Unknown"
 
 lowerBanner := strings.ToLower(banner)
 
 // SSH version detection
 if strings.Contains(lowerBanner, "ssh") {
 service = "SSH"
 parts := strings.Split(banner, " ")
 if len(parts) >= 2 {
 version = parts[1]
 }
 }
 
 // HTTP server detection
 if strings.Contains(lowerBanner, "http") || strings.Contains(lowerBanner, "apache") || 
 strings.Contains(lowerBanner, "nginx") {
 if port == 443 {
 service = "HTTPS"
 } else {
 service = "HTTP"
 }
 
 // Try to find server info in format "Server: Apache/2.4.29"
 if strings.Contains(banner, "Server:") {
 parts := strings.Split(banner, "Server:")
 if len(parts) >= 2 {
 version = strings.TrimSpace(parts[1])
 }
 }
 }
 
 return service, version
}

func scanPort(host string, port int, timeout time.Duration) ScanResult {
 target := fmt.Sprintf("%s:%d", host, port)
 conn, err := net.DialTimeout("tcp", target, timeout)
 
 if err != nil {
 return ScanResult{Port: port, State: false}
 }
 
 conn.Close()
 
 banner, err := grabBanner(host, port, timeout)
 
 service := "Unknown"
 version := "Unknown"
 
 if err == nil && banner != "" {
 service, version = identifyService(port, banner)
 }
 
 return ScanResult{
 Port: port,
 State: true,
 Service: service,
 Banner: banner,
 Version: version,
 }
}

func scanPorts(host string, start, end int, timeout time.Duration) []ScanResult {
 var results []ScanResult
 var wg sync.WaitGroup
 
 resultChan := make(chan ScanResult, end-start+1)
 
 semaphore := make(chan struct{}, 100)
 
 for port := start; port <= end; port++ {
 wg.Add(1)
 go func(p int) {
 defer wg.Done()
 
 semaphore <struct{}{}
 defer func() { <-semaphore }()
 
 result := scanPort(host, p, timeout)
 resultChan <result
 }(port)
 }
 
 go func() {
 wg.Wait()
 close(resultChan)
 }()
 
 for result := range resultChan {
 if result.State {
 results = append(results, result)
 }
 }
 
 return results
}

func main() {
 host := "localhost"
 startPort := 1
 endPort := 1024
 timeout := time.Millisecond * 800 
 
 fmt.Printf("Scanning %s from port %d to %d\n", host, startPort, endPort)
 startTime := time.Now()
 
 results := scanPorts(host, startPort, endPort, timeout)
 
 elapsed := time.Since(startTime)
 
 fmt.Printf("\nScan completed in %s\n", elapsed)
 fmt.Printf("Found %d open ports:\n\n", len(results))
 
 fmt.Println("PORT\tSERVICE\tVERSION\tBANNER")
 fmt.Println("----\t-------\t-------\t------")
 for _, result := range results {
 bannerPreview := ""
 if len(result.Banner) > 30 {
 bannerPreview = result.Banner[:30] + "..."
 } else {
 bannerPreview = result.Banner
 }
 
 fmt.Printf("%d\t%s\t%s\t%s\n", 
 result.Port, 
 result.Service, 
 result.Version, 
 bannerPreview)

識(shí)別正在運(yùn)行的服務(wù)

我們主要使用兩種策略來(lái)檢測(cè)正在運(yùn)行的服務(wù):

  • 基于端口的識(shí)別:通過(guò)將常見(jiàn)的端口號(hào)進(jìn)行映射(例如,80端口通常對(duì)應(yīng) HTTP服務(wù)),我們能夠?qū)φ谶\(yùn)行的服務(wù)做出合理的推測(cè)。
  • 橫幅分析:我們對(duì)獲取到的橫幅文本進(jìn)行提取操作,從中查找能夠標(biāo)識(shí)服務(wù)的標(biāo)識(shí)符以及版本信息。
    其中,第一個(gè)函數(shù)grabBanner的作用是嘗試從服務(wù)端獲取首個(gè)響應(yīng)。對(duì)于某些特定的服務(wù),例如 HTTP 服務(wù),由于我們需要先發(fā)送請(qǐng)求然后才能接收回復(fù),針對(duì)此類情況,我們會(huì)添加專門的處理邏輯或測(cè)試用例

基礎(chǔ)版本檢測(cè)

版本檢測(cè)在識(shí)別潛在漏洞的過(guò)程中具有重要意義。只要條件允許,我們的掃描程序就會(huì)對(duì)服務(wù)橫幅進(jìn)行解析,以此來(lái)提取其中的版本信息:

  • SSH服務(wù):其版本信息通常以 “SSH-2.0-OpenSSH_7.4” 這樣的格式呈現(xiàn)。
  • HTTP服務(wù)器:一般會(huì)在響應(yīng)頭返回版本信息,例如 “Server: Apache/2.4.29”。
  • 數(shù)據(jù)庫(kù)服務(wù)器:可能會(huì)在歡迎消息中透露出版本相關(guān)信息。

經(jīng)過(guò)上述功能添加后,現(xiàn)在對(duì)于每個(gè)開(kāi)放端口,掃描程序的輸出將會(huì)返回更為豐富的信息:

Scanning localhost from port 1 to 1024
Scan completed in 5.4s
Found 3 open ports:

PORT SERVICE VERSION BANNER
--- ------------------
22 SSH 2.0 SSH-2.0-OpenSSH_8.4p1 Ubuntu-6
80 HTTP Apache/2.4.41 Server: Apache/2.4.41 (Ubuntu)
443 HTTPS Unknown Connection closed by foreign...

這些經(jīng)過(guò)增強(qiáng)的信息,對(duì)于開(kāi)展漏洞評(píng)估而言,具備更高的價(jià)值。

漏洞檢測(cè)實(shí)施

既然我們已經(jīng)能夠枚舉正在運(yùn)行的服務(wù)及其版本,接下來(lái)就需要實(shí)現(xiàn)漏洞檢測(cè)功能。我們將對(duì)獲取到的服務(wù)信息進(jìn)行深入分析,并與已知的漏洞信息進(jìn)行比對(duì)。

編寫簡(jiǎn)單的漏洞測(cè)試

我們基于常見(jiàn)服務(wù)及其版本信息,構(gòu)建一個(gè)已知漏洞的數(shù)據(jù)庫(kù)。為了簡(jiǎn)化操作流程,我們創(chuàng)建一個(gè)代碼內(nèi)漏洞數(shù)據(jù)庫(kù)。不過(guò)在實(shí)際應(yīng)用場(chǎng)景中,掃描程序通常會(huì)查詢外部的專業(yè)漏洞數(shù)據(jù)庫(kù),比如 CVE(通用漏洞披露)或 NVD(國(guó)家漏洞數(shù)據(jù)庫(kù))。

下面,讓我們對(duì)現(xiàn)有代碼進(jìn)行擴(kuò)展,以實(shí)現(xiàn)漏洞檢測(cè)功能:

package main

import (
 "bufio"
 "fmt"
 "net"
 "strings"
 "sync"
 "time"
)
type ScanResult struct {
 Port int
 State bool
 Service string
 Banner string
 Version string
 Vulnerabilities []Vulnerability
}
type Vulnerability struct {
 ID string
 Description string
 Severity string
 Reference string
}
var vulnerabilityDB = []struct {
 Service string
 Version string
 Vulnerability Vulnerability
}{
 {
 Service: "SSH",
 Version: "OpenSSH_7.4",
 Vulnerability: Vulnerability{
 ID: "CVE-2017-15906",
 Description: "The process_open function in sftp-server.c in OpenSSH before 7.6 does not properly prevent write operations in read-only mode",
 Severity: "Medium",
 Reference: "https://nvd.nist.gov/vuln/detail/CVE-2017-15906",
 },
 },
 {
 Service: "HTTP",
 Version: "Apache/2.4.29",
 Vulnerability: Vulnerability{
 ID: "CVE-2019-0211",
 Description: "Apache HTTP Server 2.4.17 to 2.4.38 Local privilege escalation through mod_prefork and mod_http2",
 Severity: "High",
 Reference: "https://nvd.nist.gov/vuln/detail/CVE-2019-0211",
 },
 },
 {
 Service: "HTTP",
 Version: "Apache/2.4.41",
 Vulnerability: Vulnerability{
 ID: "CVE-2020-9490",
 Description: "A specially crafted value for the 'Cache-Digest' header can cause a heap overflow in Apache HTTP Server 2.4.0-2.4.41",
 Severity: "High",
 Reference: "https://nvd.nist.gov/vuln/detail/CVE-2020-9490",
 },
 },
 {
 Service: "MySQL",
 Version: "5.7",
 Vulnerability: Vulnerability{
 ID: "CVE-2020-2922",
 Description: "Vulnerability in MySQL Server allows unauthorized users to obtain sensitive information",
 Severity: "Medium",
 Reference: "https://nvd.nist.gov/vuln/detail/CVE-2020-2922",
 },
 },
 // Add more known vulnerabilities here
}
// checkVulnerabilities checks if a service/version combination has known vulnerabilities
func checkVulnerabilities(service, version string) []Vulnerability {
 var vulnerabilities []Vulnerability
 
 for _, vuln := range vulnerabilityDB {
 // Simple matching in a real scanner, this would be more sophisticated
 if vuln.Service == service && strings.Contains(version, vuln.Version) {
 vulnerabilities = append(vulnerabilities, vuln.Vulnerability)
 }
 }
 
 return vulnerabilities
}
// grabBanner attempts to read the banner from an open port
func grabBanner(host string, port int, timeout time.Duration) (string, error) {
 target := fmt.Sprintf("%s:%d", host, port)
 conn, err := net.DialTimeout("tcp", target, timeout)
 if err != nil {
 return "", err
 }
 defer conn.Close()
 
 conn.SetReadDeadline(time.Now().Add(timeout))
 
 if port == 80 || port == 443 || port == 8080 || port == 8443 {
 fmt.Fprintf(conn, "HEAD / HTTP/1.0\r\nHost: %s\r\n\r\n", host)
 } else {
 }
 
 reader := bufio.NewReader(conn)
 banner, err := reader.ReadString('\n')
 if err != nil {
 return "", err
 }
 
 return strings.TrimSpace(banner), nil
}
func identifyService(port int, banner string) (string, string) {
 commonPorts := map[int]string{
 21: "FTP",
 22: "SSH",
 23: "Telnet",
 25: "SMTP",
 53: "DNS",
 80: "HTTP",
 110: "POP3",
 143: "IMAP",
 443: "HTTPS",
 3306: "MySQL",
 5432: "PostgreSQL",
 6379: "Redis",
 8080: "HTTP-Proxy",
 27017: "MongoDB",
 }
 
 service := "Unknown"
 if s, exists := commonPorts[port]; exists {
 service = s
 }
 
 version := "Unknown"
 
 lowerBanner := strings.ToLower(banner)
 
 if strings.Contains(lowerBanner, "ssh") {
 service = "SSH"
 parts := strings.Split(banner, " ")
 if len(parts) >= 2 {
 version = parts[1]
 }
 }
 
 if strings.Contains(lowerBanner, "http") || strings.Contains(lowerBanner, "apache") || 
 strings.Contains(lowerBanner, "nginx") {
 if port == 443 {
 service = "HTTPS"
 } else {
 service = "HTTP"
 }
 
 if strings.Contains(banner, "Server:") {
 parts := strings.Split(banner, "Server:")
 if len(parts) >= 2 {
 version = strings.TrimSpace(parts[1])
 }
 }
 }
 
 return service, version
}
func scanPort(host string, port int, timeout time.Duration) ScanResult {
 target := fmt.Sprintf("%s:%d", host, port)
 conn, err := net.DialTimeout("tcp", target, timeout)
 
 if err != nil {
 return ScanResult{Port: port, State: false}
 }
 
 conn.Close()
 
 banner, err := grabBanner(host, port, timeout)
 
 service := "Unknown"
 version := "Unknown"
 
 if err == nil && banner != "" {
 service, version = identifyService(port, banner)
 }
 
 vulnerabilities := checkVulnerabilities(service, version)
 
 return ScanResult{
 Port: port,
 State: true,
 Service: service,
 Banner: banner,
 Version: version,
 Vulnerabilities: vulnerabilities,
 }
}
func scanPorts(host string, start, end int, timeout time.Duration) []ScanResult {
 var results []ScanResult
 var wg sync.WaitGroup
 
 resultChan := make(chan ScanResult, end-start+1)
 
 semaphore := make(chan struct{}, 100)
 
 for port := start; port <= end; port++ {
 wg.Add(1)
 go func(p int) {
 defer wg.Done()
 
 semaphore <struct{}{}
 defer func() { <-semaphore }()
 
 result := scanPort(host, p, timeout)
 resultChan <result
 }(port)
 }
 
 go func() {
 wg.Wait()
 close(resultChan)
 }()
 
 for result := range resultChan {
 if result.State {
 results = append(results, result)
 }
 }
 
 return results
}
func main() {
 host := "localhost"
 startPort := 1
 endPort := 1024
 timeout := time.Second * 1 
 
 fmt.Printf("Scanning %s from port %d to %d\n", host, startPort, endPort)
 startTime := time.Now()
 
 results := scanPorts(host, startPort, endPort, timeout)
 
 elapsed := time.Since(startTime)
 
 fmt.Printf("\nScan completed in %s\n", elapsed)
 fmt.Printf("Found %d open ports:\n\n", len(results))
 
 fmt.Println("PORT\tSERVICE\tVERSION")
 fmt.Println("----\t-------\t-------")
 for _, result := range results {
 fmt.Printf("%d\t%s\t%s\n", 
 result.Port, 
 result.Service, 
 result.Version)
 
 if len(result.Vulnerabilities) > 0 {
 fmt.Println(" Vulnerabilities:")
 for _, vuln := range result.Vulnerabilities {
 fmt.Printf(" [%s] %s %s\n", 
 vuln.Severity, 
 vuln.ID, 
 vuln.Description)
 fmt.Printf(" Reference: %s\n\n", vuln.Reference)
 }
 }
 }
}package main
import (
 "bufio"
 "fmt"
 "net"
 "strings"
 "sync"
 "time"
)
type ScanResult struct {
 Port int
 State bool
 Service string
 Banner string
 Version string
 Vulnerabilities []Vulnerability
}
type Vulnerability struct {
 ID string
 Description string
 Severity string
 Reference string
}
var vulnerabilityDB = []struct {
 Service string
 Version string
 Vulnerability Vulnerability
}{
 {
 Service: "SSH",
 Version: "OpenSSH_7.4",
 Vulnerability: Vulnerability{
 ID: "CVE-2017-15906",
 Description: "The process_open function in sftp-server.c in OpenSSH before 7.6 does not properly prevent write operations in read-only mode",
 Severity: "Medium",
 Reference: "https://nvd.nist.gov/vuln/detail/CVE-2017-15906",
 },
 },
 {
 Service: "HTTP",
 Version: "Apache/2.4.29",
 Vulnerability: Vulnerability{
 ID: "CVE-2019-0211",
 Description: "Apache HTTP Server 2.4.17 to 2.4.38 Local privilege escalation through mod_prefork and mod_http2",
 Severity: "High",
 Reference: "https://nvd.nist.gov/vuln/detail/CVE-2019-0211",
 },
 },
 {
 Service: "HTTP",
 Version: "Apache/2.4.41",
 Vulnerability: Vulnerability{
 ID: "CVE-2020-9490",
 Description: "A specially crafted value for the 'Cache-Digest' header can cause a heap overflow in Apache HTTP Server 2.4.0-2.4.41",
 Severity: "High",
 Reference: "https://nvd.nist.gov/vuln/detail/CVE-2020-9490",
 },
 },
 {
 Service: "MySQL",
 Version: "5.7",
 Vulnerability: Vulnerability{
 ID: "CVE-2020-2922",
 Description: "Vulnerability in MySQL Server allows unauthorized users to obtain sensitive information",
 Severity: "Medium",
 Reference: "https://nvd.nist.gov/vuln/detail/CVE-2020-2922",
 },
 },
 // Add more known vulnerabilities here
}
// checkVulnerabilities checks if a service/version combination has known vulnerabilities
func checkVulnerabilities(service, version string) []Vulnerability {
 var vulnerabilities []Vulnerability
 
 for _, vuln := range vulnerabilityDB {
 // Simple matching in a real scanner, this would be more sophisticated
 if vuln.Service == service && strings.Contains(version, vuln.Version) {
 vulnerabilities = append(vulnerabilities, vuln.Vulnerability)
 }
 }
 
 return vulnerabilities
}
// grabBanner attempts to read the banner from an open port
func grabBanner(host string, port int, timeout time.Duration) (string, error) {
 target := fmt.Sprintf("%s:%d", host, port)
 conn, err := net.DialTimeout("tcp", target, timeout)
 if err != nil {
 return "", err
 }
 defer conn.Close()
 
 conn.SetReadDeadline(time.Now().Add(timeout))
 
 if port == 80 || port == 443 || port == 8080 || port == 8443 {
 fmt.Fprintf(conn, "HEAD / HTTP/1.0\r\nHost: %s\r\n\r\n", host)
 } else {
 }
 
 reader := bufio.NewReader(conn)
 banner, err := reader.ReadString('\n')
 if err != nil {
 return "", err
 }
 
 return strings.TrimSpace(banner), nil
}
func identifyService(port int, banner string) (string, string) {
 commonPorts := map[int]string{
 21: "FTP",
 22: "SSH",
 23: "Telnet",
 25: "SMTP",
 53: "DNS",
 80: "HTTP",
 110: "POP3",
 143: "IMAP",
 443: "HTTPS",
 3306: "MySQL",
 5432: "PostgreSQL",
 6379: "Redis",
 8080: "HTTP-Proxy",
 27017: "MongoDB",
 }
 
 service := "Unknown"
 if s, exists := commonPorts[port]; exists {
 service = s
 }
 
 version := "Unknown"
 
 lowerBanner := strings.ToLower(banner)
 
 if strings.Contains(lowerBanner, "ssh") {
 service = "SSH"
 parts := strings.Split(banner, " ")
 if len(parts) >= 2 {
 version = parts[1]
 }
 }
 
 if strings.Contains(lowerBanner, "http") || strings.Contains(lowerBanner, "apache") || 
 strings.Contains(lowerBanner, "nginx") {
 if port == 443 {
 service = "HTTPS"
 } else {
 service = "HTTP"
 }
 
 if strings.Contains(banner, "Server:") {
 parts := strings.Split(banner, "Server:")
 if len(parts) >= 2 {
 version = strings.TrimSpace(parts[1])
 }
 }
 }
 
 return service, version
}
func scanPort(host string, port int, timeout time.Duration) ScanResult {
 target := fmt.Sprintf("%s:%d", host, port)
 conn, err := net.DialTimeout("tcp", target, timeout)
 
 if err != nil {
 return ScanResult{Port: port, State: false}
 }
 
 conn.Close()
 
 banner, err := grabBanner(host, port, timeout)
 
 service := "Unknown"
 version := "Unknown"
 
 if err == nil && banner != "" {
 service, version = identifyService(port, banner)
 }
 
 vulnerabilities := checkVulnerabilities(service, version)
 
 return ScanResult{
 Port: port,
 State: true,
 Service: service,
 Banner: banner,
 Version: version,
 Vulnerabilities: vulnerabilities,
 }
}
func scanPorts(host string, start, end int, timeout time.Duration) []ScanResult {
 var results []ScanResult
 var wg sync.WaitGroup
 
 resultChan := make(chan ScanResult, end-start+1)
 
 semaphore := make(chan struct{}, 100)
 
 for port := start; port <= end; port++ {
 wg.Add(1)
 go func(p int) {
 defer wg.Done()
 
 semaphore <struct{}{}
 defer func() { <-semaphore }()
 
 result := scanPort(host, p, timeout)
 resultChan <result
 }(port)
 }
 
 go func() {
 wg.Wait()
 close(resultChan)
 }()
 
 for result := range resultChan {
 if result.State {
 results = append(results, result)
 }
 }
 
 return results
}
func main() {
 host := "localhost"
 startPort := 1
 endPort := 1024
 timeout := time.Second * 1 
 
 fmt.Printf("Scanning %s from port %d to %d\n", host, startPort, endPort)
 startTime := time.Now()
 
 results := scanPorts(host, startPort, endPort, timeout)
 
 elapsed := time.Since(startTime)
 
 fmt.Printf("\nScan completed in %s\n", elapsed)
 fmt.Printf("Found %d open ports:\n\n", len(results))
 
 fmt.Println("PORT\tSERVICE\tVERSION")
 fmt.Println("----\t-------\t-------")
 for _, result := range results {
 fmt.Printf("%d\t%s\t%s\n", 
 result.Port, 
 result.Service, 
 result.Version)
 
 if len(result.Vulnerabilities) > 0 {
 fmt.Println(" Vulnerabilities:")
 for _, vuln := range result.Vulnerabilities {
 fmt.Printf(" [%s] %s %s\n", 
 vuln.Severity, 
 vuln.ID, 
 vuln.Description)
 fmt.Printf(" Reference: %s\n\n", vuln.Reference)
 }
 }
 }
}

基于版本的漏洞匹配

我們使用一種相對(duì)簡(jiǎn)單的基于版本匹配的漏洞檢測(cè)方式:

  • 直接匹配:里,我們會(huì)將檢測(cè)到的服務(wù)類型和版本信息,直接與漏洞數(shù)據(jù)庫(kù)中的記錄進(jìn)行匹配。若匹配成功可確定存在相應(yīng)漏洞。
  • 部分匹配:針對(duì)易受攻擊的版本匹配情況,我們會(huì)對(duì)版本字符串執(zhí)行包含性驗(yàn)證(containment checks)。這種方式具有較高的靈活性,即便版本字符串中包含一些額外信息,也能夠準(zhǔn)確識(shí)別出存在漏洞的系統(tǒng)。
    需要注意的是,在實(shí)際掃描過(guò)程中,這種匹配過(guò)程會(huì)更加復(fù)雜,需要綜合考慮以下多種因素:
  • 版本范圍:例如明確指出某個(gè)軟件版本號(hào)在2.4.0至2.4.38區(qū)間內(nèi)存在受影響的情況。
  • 特定配置的漏洞:不同的系統(tǒng)配置可能會(huì)導(dǎo)致特定的漏洞出現(xiàn),這部分因素也需要納入考量。
  • 特定操作系統(tǒng)的問(wèn)題:某些漏洞可能是特定操作系統(tǒng)環(huán)境下才會(huì)出現(xiàn)的,掃描時(shí)需要結(jié)合操作系統(tǒng)信息進(jìn)行判斷。
  • 更細(xì)致的版本比較:除了簡(jiǎn)單的版本號(hào)比對(duì),還需要進(jìn)行更精細(xì)的版本比較邏輯,以確保漏洞判斷的準(zhǔn)確性。

報(bào)告檢測(cè)結(jié)果

報(bào)告檢測(cè)結(jié)果是整個(gè)漏洞檢測(cè)流程的最后一步,需要以簡(jiǎn)潔且操作執(zhí)行的格式呈現(xiàn)出來(lái)。目前,我們的掃描程序具備以下功能:

  • 詳細(xì)列出所有開(kāi)放端口,并附帶相應(yīng)的服務(wù)名稱以及版本信息。
  • 針對(duì)每個(gè)檢測(cè)出存在漏洞的服務(wù),展示以下關(guān)鍵信息:

A.漏洞 ID:例如常見(jiàn)的 CVE 編號(hào),用于唯一標(biāo)識(shí)該漏洞。

B.漏洞描述:清晰闡述該漏洞的具體情況和影響。

C.嚴(yán)重程度評(píng)級(jí):對(duì)漏洞的嚴(yán)重程度進(jìn)行評(píng)估和分級(jí),方便用戶快速了解風(fēng)險(xiǎn)程度。

D.更多相關(guān)信息的參考鏈接:提供指向更多關(guān)于該漏洞詳細(xì)信息的參考鏈接,以便用戶進(jìn)一步深入了解和采取應(yīng)對(duì)措施。

以下為示例輸出:

Scanning localhost from port 1 to 1024
Scan completed in 6.2s
Found 3 open ports:

PORT SERVICE VERSION
--- -------------
22 SSH OpenSSH_7.4p1
 Vulnerabilities:
 [Medium] CVE-2017-15906 The process_open function in sftp-server.c in OpenSSH before 7.6 does not properly prevent write operations in read-only mode
 Reference: https://nvd.nist.gov/vuln/detail/CVE-2017-15906

80 HTTP Apache/2.4.41
 Vulnerabilities:
 [High] CVE-2020-9490 A specially crafted value for the 'Cache-Digest' header can cause a heap overflow in Apache HTTP Server 2.4.0-2.4.41
 Reference: https://nvd.nist.gov/vuln/detail/CVE-2020-9490

443 HTTPS Unknown

這些全面的漏洞數(shù)據(jù)能夠?yàn)榫W(wǎng)絡(luò)安全專家提供有力支持,幫助他們及時(shí)排查出需要處理的安全問(wèn)題,并依據(jù)相關(guān)情況對(duì)這些問(wèn)題進(jìn)行優(yōu)先級(jí)排序。

最后的優(yōu)化與使用方法

目前,已經(jīng)擁有了一個(gè)基礎(chǔ)的漏洞掃描程序,它具備服務(wù)檢測(cè)和漏洞匹配功能。接下來(lái),我們將對(duì)其進(jìn)行一些優(yōu)化,以提升該掃描程序在實(shí)際應(yīng)用中的實(shí)用性。

命令行參數(shù)

我們希望掃描程序能夠通過(guò)命令行標(biāo)志進(jìn)行靈活配置,以此來(lái)設(shè)置掃描目標(biāo)、端口范圍以及其他掃描選項(xiàng)。借助Go語(yǔ)言的flag包,實(shí)現(xiàn)這一功能并非難事。

下面,我們開(kāi)始添加命令行參數(shù):

package main

import (
 "bufio"
 "encoding/json"
 "flag"
 "fmt"
 "net"
 "os"
 "strings"
 "sync"
 "time"
)

type ScanResult struct {
 Port int
 State bool
 Service string
 Banner string
 Version string
 Vulnerabilities []Vulnerability
}

type Vulnerability struct {
 ID string
 Description string
 Severity string
 Reference string
}

var vulnerabilityDB = []struct {
 Service string
 Version string
 Vulnerability Vulnerability
}{
 // ... (same as before)
}


func main() {
 hostPtr := flag.String("host", "", "Target host to scan (required)")
 startPortPtr := flag.Int("start", 1, "Starting port number")
 endPortPtr := flag.Int("end", 1024, "Ending port number")
 timeoutPtr := flag.Int("timeout", 1000, "Timeout in milliseconds")
 concurrencyPtr := flag.Int("concurrency", 100, "Number of concurrent scans")
 formatPtr := flag.String("format", "text", "Output format: text, json, or csv")
 verbosePtr := flag.Bool("verbose", false, "Show verbose output including banners")
 outputFilePtr := flag.String("output", "", "Output file (default is stdout)")
 
 flag.Parse()
 
 if *hostPtr == "" {
 fmt.Println("Error: host is required")
 flag.Usage()
 os.Exit(1)
 }
 
 if *startPortPtr < 1 || *startPortPtr > 65535 {
 fmt.Println("Error: starting port must be between 1 and 65535")
 os.Exit(1)
 }
 if *endPortPtr < 1 || *endPortPtr > 65535 {
 fmt.Println("Error: ending port must be between 1 and 65535")
 os.Exit(1)
 }
 if *startPortPtr > *endPortPtr {
 fmt.Println("Error: starting port must be less than or equal to ending port")
 os.Exit(1)
 }
 
 timeout := time.Duration(*timeoutPtr) * time.Millisecond
 
 var outputFile *os.File
 var err error
 
 if *outputFilePtr != "" {
 outputFile, err = os.Create(*outputFilePtr)
 if err != nil {
 fmt.Printf("Error creating output file: %v\n", err)
 os.Exit(1)
 }
 defer outputFile.Close()
 } else {
 outputFile = os.Stdout
 }
 
 fmt.Fprintf(outputFile, "Scanning %s from port %d to %d\n", *hostPtr, *startPortPtr, *endPortPtr)
 startTime := time.Now()
 
 var results []ScanResult
 var wg sync.WaitGroup
 
 resultChan := make(chan ScanResult, *endPortPtr-*startPortPtr+1)
 
 semaphore := make(chan struct{}, *concurrencyPtr)
 
 for port := *startPortPtr; port <= *endPortPtr; port++ {
 wg.Add(1)
 go func(p int) {
 defer wg.Done()
 
 semaphore <struct{}{}
 defer func() { <-semaphore }()
 
 result := scanPort(*hostPtr, p, timeout)
 resultChan <result
 }(port)
 }
 
 go func() {
 wg.Wait()
 close(resultChan)
 }()
 
 for result := range resultChan {
 if result.State {
 results = append(results, result)
 }
 }
 
 elapsed := time.Since(startTime)
 
 switch *formatPtr {
 case "json":
 outputJSON(outputFile, results, elapsed)
 case "csv":
 outputCSV(outputFile, results, elapsed, *verbosePtr)
 default:
 outputText(outputFile, results, elapsed, *verbosePtr)
 }
}

func outputText(w *os.File, results []ScanResult, elapsed time.Duration, verbose bool) {
 fmt.Fprintf(w, "\nScan completed in %s\n", elapsed)
 fmt.Fprintf(w, "Found %d open ports:\n\n", len(results))
 
 if len(results) == 0 {
 fmt.Fprintf(w, "No open ports found.\n")
 return
 }
 
 fmt.Fprintf(w, "PORT\tSERVICE\tVERSION\n")
 fmt.Fprintf(w, "----\t-------\t-------\n")
 
 for _, result := range results {
 fmt.Fprintf(w, "%d\t%s\t%s\n", 
 result.Port, 
 result.Service, 
 result.Version)
 
 if verbose {
 fmt.Fprintf(w, " Banner: %s\n", result.Banner)
 }
 
 if len(result.Vulnerabilities) > 0 {
 fmt.Fprintf(w, " Vulnerabilities:\n")
 for _, vuln := range result.Vulnerabilities {
 fmt.Fprintf(w, " [%s] %s %s\n", 
 vuln.Severity, 
 vuln.ID, 
 vuln.Description)
 fmt.Fprintf(w, " Reference: %s\n\n", vuln.Reference)
 }
 }
 }
}

func outputJSON(w *os.File, results []ScanResult, elapsed time.Duration) {
 output := struct {
 ScanTime string `json:"scan_time"`
 ElapsedTime string `json:"elapsed_time"`
 TotalPorts int `json:"total_ports"`
 OpenPorts int `json:"open_ports"`
 Results []ScanResult `json:"results"`
 }{
 ScanTime: time.Now().Format(time.RFC3339),
 ElapsedTime: elapsed.String(),
 TotalPorts: 0, 
 OpenPorts: len(results),
 Results: results,
 }
 
 encoder := json.NewEncoder(w)
 encoder.SetIndent("", " ")
 encoder.Encode(output)
}

func outputCSV(w *os.File, results []ScanResult, elapsed time.Duration, verbose bool) {
 fmt.Fprintf(w, "Port,Service,Version,Vulnerability ID,Severity,Description\n")
 
 for _, result := range results {
 if len(result.Vulnerabilities) == 0 {
 fmt.Fprintf(w, "%d,%s,%s,,,\n", 
 result.Port, 
 escapeCSV(result.Service), 
 escapeCSV(result.Version))
 } else {
 for _, vuln := range result.Vulnerabilities {
 fmt.Fprintf(w, "%d,%s,%s,%s,%s,%s\n", 
 result.Port, 
 escapeCSV(result.Service), 
 escapeCSV(result.Version),
 escapeCSV(vuln.ID),
 escapeCSV(vuln.Severity),
 escapeCSV(vuln.Description))
 }
 }
 }
 
 fmt.Fprintf(w, "\n# Scan completed in %s, found %d open ports\n", 
 elapsed, len(results))
}

func escapeCSV(s string) string {
 if strings.Contains(s, ",") || strings.Contains(s, "\"") || strings.Contains(s, "\n") {
 return "\"" + strings.ReplaceAll(s, "\"", "\"\"") + "\""
 }
 return s
}

輸出格式

當(dāng)前,我們的掃描程序支持三種輸出格式,以滿足不同用戶在不同場(chǎng)景下的多樣化需求:

  • 文本格式:這種格式具有易讀易寫的特點(diǎn),極大地方便了用戶在交互式環(huán)境中的使用體驗(yàn)。用戶可以直觀地查看掃描結(jié)果,快速獲取關(guān)鍵信息。
  • JSON格式:該格式提供了結(jié)構(gòu)化的輸出方式,非常有利于機(jī)器進(jìn)行處理。同時(shí),它也便于與其他各類工具進(jìn)行集成,能夠?qū)崿F(xiàn)數(shù)據(jù)在不同系統(tǒng)間的高效流轉(zhuǎn)與共享。
  • CSV格式:作為一種與電子表格兼容的格式,CSV 格式特別適用于對(duì)掃描結(jié)果進(jìn)行分析和生成報(bào)告。用戶可以方便地將數(shù)據(jù)導(dǎo)入到電子表格軟件中,進(jìn)行進(jìn)一步的數(shù)據(jù)處理和可視化操作。

此外,如果用戶設(shè)置了詳細(xì)模式標(biāo)志,掃描程序輸出的文本內(nèi)容將包含更多詳細(xì)信息,例如原始橫幅信息。這些額外信息對(duì)于調(diào)試掃描過(guò)程中的問(wèn)題或進(jìn)行深入的安全分析都具有重要的輔助作用。

示例用法與結(jié)果

接下來(lái),為展示在不同場(chǎng)景下使用我們掃描程序時(shí),可能出現(xiàn)的一些情況:

對(duì)單個(gè)主機(jī)進(jìn)行基礎(chǔ)掃描:

$ go run main.go -host example.com

掃描特定端口范圍:

$ go run main.go -host example.com -start 80 -end 443

將結(jié)果保存到JSON文件

$ go run main.go -host example.com -format json -output results.json

增加超時(shí)的詳細(xì)掃描:

$ go run main.go -host example.com -verbose -timeout 2000

以更高的并發(fā)性進(jìn)行掃描以獲得更快的結(jié)果:

$ go run main.go -host example.com -concurrency 200

文本輸出示例:

Scanning example.com from port 1 to 1024
Scan completed in 12.6s
Found 3 open ports:

PORT SERVICE VERSION
---- ------- -------
22 SSH OpenSSH_7.4p1
 Vulnerabilities:
 [Medium] CVE-2017-15906 - The process_open function in sftp-server.c in OpenSSH before 7.6 does not properly prevent write operations in read-only mode
 Reference: https://nvd.nist.gov/vuln/detail/CVE-2017-15906

80 HTTP Apache/2.4.41
 Vulnerabilities:
 [High] CVE-2020-9490 - A specially crafted value for the 'Cache-Digest' header can cause a heap overflow in Apache HTTP Server 2.4.0-2.4.41
 Reference: https://nvd.nist.gov/vuln/detail/CVE-2020-9490

443 HTTPS nginx/1.18.0

JSON 輸出示例:

{
 "scan_time": "2025-03-18T14:30:00Z",
 "elapsed_time": "12.6s",
 "total_ports": 1024,
 "open_ports": 3,
 "results": [
 {
 "Port": 22,
 "State": true,
 "Service": "SSH",
 "Banner": "SSH-2.0-OpenSSH_7.4p1",
 "Version": "OpenSSH_7.4p1",
 "Vulnerabilities": [
 {
 "ID": "CVE-2017-15906",
 "Description": "The process_open function in sftp-server.c in OpenSSH before 7.6 does not properly prevent write operations in read-only mode",
 "Severity": "Medium",
 "Reference": "https://nvd.nist.gov/vuln/detail/CVE-2017-15906"
 }
 ]
 },
 {
 "Port": 80,
 "State": true,
 "Service": "HTTP",
 "Banner": "HTTP/1.1 200 OK\r\nServer: Apache/2.4.41",
 "Version": "Apache/2.4.41",
 "Vulnerabilities": [
 {
 "ID": "CVE-2020-9490",
 "Description": "A specially crafted value for the 'Cache-Digest' header can cause a heap overflow in Apache HTTP Server 2.4.0-2.4.41",
 "Severity": "High",
 "Reference": "https://nvd.nist.gov/vuln/detail/CVE-2020-9490"
 }
 ]
 },
 {
 "Port": 443,
 "State": true,
 "Service": "HTTPS",
 "Banner": "HTTP/1.1 200 OK\r\nServer: nginx/1.18.0",
 "Version": "nginx/1.18.0",
 "Vulnerabilities": []
 }
 ]
}

我們運(yùn)用Go語(yǔ)言成功構(gòu)建了一個(gè)功能強(qiáng)大的網(wǎng)絡(luò)漏洞掃描程序,這一成果充分彰顯了Go語(yǔ)言在開(kāi)發(fā)安全工具領(lǐng)域的卓越適用性。此掃描程序具備諸多出色能力,能夠迅速掃描開(kāi)放端口,精準(zhǔn)識(shí)別端口上正在運(yùn)行的服務(wù),并準(zhǔn)確判斷是否存在已知漏洞。

該掃描程序不僅能夠提供有關(guān)網(wǎng)絡(luò)中運(yùn)行服務(wù)的實(shí)用信息,還集成了多線程處理機(jī)制、先進(jìn)的服務(wù)指紋識(shí)別功能,并且支持多種輸出格式,以滿足不同用戶在多樣化場(chǎng)景下的需求。

在此特別提醒,類似這樣的掃描工具,務(wù)必在嚴(yán)格遵循道德和法律規(guī)范的前提下使用,并且必須事先獲取對(duì)掃描目標(biāo)系統(tǒng)的適當(dāng)授權(quán)。當(dāng)以負(fù)責(zé)任的態(tài)度開(kāi)展操作時(shí),定期進(jìn)行漏洞掃描是維持良好安全態(tài)勢(shì)的關(guān)鍵因素,能夠切實(shí)幫助保護(hù)的系統(tǒng)免受潛在威脅的侵害。

如果希望進(jìn)一步了解該項(xiàng)目,可在GitHub上找到此項(xiàng)目的完整源代碼

譯者介紹

劉濤,51CTO社區(qū)編輯,某大型央企系統(tǒng)上線檢測(cè)管控負(fù)責(zé)人。

原文標(biāo)題:Building a Network Vulnerability Scanner with Go,作者:Rez Moss

責(zé)任編輯:姜華 來(lái)源: 51CTO
相關(guān)推薦

2011-03-03 15:08:48

2013-01-11 09:41:34

2019-07-22 13:11:39

網(wǎng)絡(luò)安全信息安全網(wǎng)絡(luò)威脅

2025-02-04 13:53:18

NixGogRPC

2023-12-26 00:58:53

Web應(yīng)用Go語(yǔ)言

2013-05-20 11:54:55

2014-12-08 09:01:53

2013-03-12 09:50:45

GoRESTful Web

2018-05-04 06:00:10

2015-06-02 11:37:58

算法結(jié)構(gòu)網(wǎng)絡(luò)安全

2021-02-21 22:21:46

網(wǎng)絡(luò)安全IT安全NSA

2021-02-03 15:10:38

GoKubernetesLinux

2009-11-26 10:13:54

無(wú)線網(wǎng)絡(luò)路由器

2024-01-15 00:42:55

Go語(yǔ)言應(yīng)用程序

2012-11-20 10:20:57

Go

2023-11-06 13:32:38

Go編程

2010-03-15 16:27:59

無(wú)線網(wǎng)絡(luò)設(shè)備

2010-02-02 10:16:16

軟交換系統(tǒng)

2011-09-02 16:12:48

ubuntuskipfish

2018-09-10 11:07:19

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 久久综合入口 | 欧美日韩黄色一级片 | 四虎最新 | 亚洲成人蜜桃 | 日韩一二三区视频 | 日韩一区二区三区在线观看 | 午夜专区 | 国产一区二区在线视频 | 91在线免费观看网站 | 久久爱黑人激情av摘花 | 亚洲精品电影网在线观看 | 天天爽综合网 | 99成人精品| 日韩一区精品 | a毛片| 国产精品久久久久久福利一牛影视 | 午夜影院在线播放 | 国产yw851.c免费观看网站 | 91麻豆精品国产91久久久久久久久 | 一区免费观看 | 日韩视频1 | 国产第一亚洲 | 日韩午夜影院 | 日韩精品在线网站 | 久久av一区二区三区 | 在线观看中文视频 | 观看毛片| 在线色网| 欧美色影院 | 亚洲激情在线视频 | 久久男人 | 91在线资源 | 亚洲aⅴ| 一区二区在线不卡 | 亚洲一区二区精品视频在线观看 | 亚洲欧美在线免费观看 | 亚洲一区二区三区四区五区午夜 | 中文字幕不卡 | 热99视频 | 一级毛片中国 | 亚洲欧美日韩系列 |