自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

譯者 | 劉濤

審校 | 重樓

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

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

創(chuàng)建項目

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

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

以下建立一個新的Go工程:

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

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

配置包與環(huán)境

針對我們的掃描程序,我們將使用幾個Go包:

package main

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

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

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

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

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

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

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

簡易端口掃描程序

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

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

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

以下是我們實現(xià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語言的net包,該包提供了豐富的網(wǎng)絡(luò)輸入/輸出接口(I/O interfaces)以及相關(guān)函數(shù)。具體而言,主要涉及以下幾個關(guān)鍵部分:

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

測試我們的首次掃描

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

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

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

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

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

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

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

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

為何第一版速度慢

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

為了讓了解這個問題,我們來看一下簡易掃描程序用時情況:

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

添加多線程支持

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

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

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

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

以下為示例輸出:

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ò)展性,能夠很好地適用于更大的端口掃描范圍以及多個主機(jī)的掃描任務(wù)。其中,信號量模式發(fā)揮了關(guān)鍵作用,它確保即便掃描的端口數(shù)量超過1000個,系統(tǒng)資源也不會被耗盡。

添加服務(wù)檢測

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

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

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

接下來,讓我們在掃描程序中增添橫幅抓取功能:

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)

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

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

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

基礎(chǔ)版本檢測

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

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

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

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)過增強(qiáng)的信息,對于開展漏洞評估而言,具備更高的價值。

漏洞檢測實施

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

編寫簡單的漏洞測試

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

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

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)
 }
 }
 }
}

基于版本的漏洞匹配

我們使用一種相對簡單的基于版本匹配的漏洞檢測方式:

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

報告檢測結(jié)果

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

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

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

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

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

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

以下為示例輸出:

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ù)能夠為網(wǎng)絡(luò)安全專家提供有力支持,幫助他們及時排查出需要處理的安全問題,并依據(jù)相關(guān)情況對這些問題進(jìn)行優(yōu)先級排序。

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

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

命令行參數(shù)

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

下面,我們開始添加命令行參數(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)前,我們的掃描程序支持三種輸出格式,以滿足不同用戶在不同場景下的多樣化需求:

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

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

示例用法與結(jié)果

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

對單個主機(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

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

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

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

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

譯者介紹

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

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

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

2011-03-03 15:08:48

2013-01-11 09:41:34

2023-12-26 00:58:53

Web應(yīng)用Go語言

2025-02-04 13:53:18

NixGogRPC

2019-07-22 13:11:39

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

2013-05-20 11:54:55

2013-03-12 09:50:45

GoRESTful Web

2014-12-08 09:01:53

2021-02-03 15:10:38

GoKubernetesLinux

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

2012-11-20 10:20:57

Go

2009-11-26 10:13:54

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

2024-01-15 00:42:55

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

2023-11-06 13:32:38

Go編程

2010-03-15 16:27:59

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

2025-01-26 17:00:46

2012-08-20 09:16:15

Go語言

2010-02-02 10:16:16

軟交換系統(tǒng)
點贊
收藏

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