譯者 | 劉濤
審校 | 重樓
滲透測試能夠幫助組織發(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