適合有一點(diǎn)Python編程基礎(chǔ)的學(xué)員學(xué)習(xí)
實(shí)現(xiàn)的原理
最簡(jiǎn)單的端口掃描工具使用TCP連接掃描的方式,即利用操作系統(tǒng)原生的網(wǎng)絡(luò)功能,且通常作為SYN掃描的替代選項(xiàng)。Nmap將這種模式稱(chēng)為連接掃描,因?yàn)槭褂昧祟?lèi)似Unix系統(tǒng)的connect()命令腾务。如果該端口是開(kāi)放的,操作系統(tǒng)就能完成TCP三次握手削饵,然后端口掃描工具會(huì)立即關(guān)閉剛建立的該連接岩瘦,防止拒絕服務(wù)攻擊。這種掃描模式的優(yōu)勢(shì)是用戶(hù)無(wú)需特殊權(quán)限窿撬。但使用操作系統(tǒng)原生網(wǎng)絡(luò)功能不能實(shí)現(xiàn)底層控制启昧,因此這種掃描方式并不流行。并且TCP掃描很容易被發(fā)現(xiàn)尤仍,尤其作為端口清掃的手段:這些服務(wù)會(huì)記錄發(fā)送者的IP地址箫津,入侵檢測(cè)系統(tǒng)可能觸發(fā)警報(bào)。
還有另外一種掃描方式是SYN掃描宰啦,端口掃描工具不使用操作系統(tǒng)原生網(wǎng)絡(luò)功能苏遥,而是自行生成、發(fā)送IP數(shù)據(jù)包赡模,并監(jiān)控其回應(yīng)田炭。這種掃描模式被稱(chēng)為“半開(kāi)放掃描”,因?yàn)樗鼜牟唤⑼暾腡CP連接漓柑。端口掃描工具生成一個(gè)SYN包教硫,如果目標(biāo)端口開(kāi)放叨吮,則會(huì)返回SYN-ACK包。掃描端回應(yīng)一個(gè)RST包瞬矩,然后在握手完成前關(guān)閉連接茶鉴。如果端口關(guān)閉了但未使用過(guò)濾,目標(biāo)端口應(yīng)該會(huì)持續(xù)返回RST包景用。這種粗略的網(wǎng)絡(luò)利用方式有幾個(gè)優(yōu)點(diǎn):給掃描工具全權(quán)控制數(shù)據(jù)包發(fā)送和等待回應(yīng)時(shí)長(zhǎng)的權(quán)力涵叮,允許更詳細(xì)的回應(yīng)分析。關(guān)于哪一種對(duì)目標(biāo)主機(jī)的掃描方式更不具備入侵性存在一些爭(zhēng)議伞插,但SYN掃描的優(yōu)勢(shì)是從不會(huì)建立完整的連接割粮。然而,RST包可能導(dǎo)致網(wǎng)絡(luò)堵塞媚污,尤其是一些簡(jiǎn)單如打印機(jī)之類(lèi)的網(wǎng)絡(luò)設(shè)備舀瓢。
實(shí)例中采用的是第一種掃描方式,直接利用操作系統(tǒng)的socket連接接口耗美,初步測(cè)試目標(biāo)服務(wù)器的端口是否可以連接京髓,如果可以則返回端口打開(kāi)狀態(tài)。
實(shí)現(xiàn)單線程掃描功能
主要實(shí)現(xiàn)這個(gè)簡(jiǎn)單的掃描器為單線程掃描商架,具體步驟如下:
獲取端口及目標(biāo)服務(wù)器
新建代碼如下:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from socket import *
# port_scan.py <host> <start_port>-<end_port>
host = sys.argv[1]
protstrs = sys.argv[2].splist('-')
start_port = int(portstrs[0])
end_port = int(portstrs[1])
target_ip = gethostbyname(host)
opened_ports = []
for port in range(start_port, end_port):
sock = socket(AF_INET, SOCK_STREAM)
sock.settimeout(10)
result = sock.connect_ex((target_ip, port))
if result == 0:
opened_ports.append(port)
print("Opened ports:")
for i in opened_ports:
print(i)
代碼解析:
獲取目標(biāo)ip地址:
target_ip = gethostbyname(host)
進(jìn)入循環(huán)連接:
opened_ports = []
for port in range(start_port, end_port):
sock = socket(AF_INET, SOCK_STREAM)
sock.settimeout(10)
result = sock.connect_ex((target_ip, port))
if result == 0:
opened_ports.append(port)
打印opened_ports
列表
print i in opened_ports:
print(i)
測(cè)試掃描10-200端口情況
>> python3 scanning_demo.py 127.0.0.1 10-200
Opened ports:
53
80
我們可以看到 53 與 80端口正處于開(kāi)啟的狀態(tài)朵锣,你可以使用127.0.0.1:80 查看開(kāi)啟了什么類(lèi)型的服務(wù)
多線程掃描
上面代碼實(shí)現(xiàn)了單線程掃描端口的測(cè)試,但是正常的程序在執(zhí)行中我們需要考慮執(zhí)行效率和提升性能甸私,所以需要實(shí)現(xiàn)多線程程序:
新建代碼如下:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import thread
from socket import *
def tcp_test(port):
sock = socket(AF_INET, SOCK_STREAM)
sock.settimeout(10)
result = sock.connect_ex((target_ip, port))
if result == 0:
lock.acquire()
print "Opened Port:", port
lock.release()
if __name__=='__main__':
# portscan.py <host> <start_port>-<end_port>
post = sys.argv[1]
portstrs = sys.argv[2].split('_')
start_port = int(portstrs[0])
end_port = int(portstrsp[1])
target_ip = gethostbyname(host)
lock = thread.allocate_lock()
for port in range(start_port, end_port):
thread.start_new_thread(tcp_test, (port,))
代碼解析
引入代碼包 thread ,這個(gè)是實(shí)現(xiàn)多線程必須要的:
import thread
實(shí)現(xiàn)TCP測(cè)試函數(shù)
需要注意print輸出時(shí)候需要加鎖飞傀,如果不加鎖可能會(huì)出現(xiàn)多個(gè)輸出混合在一起的錯(cuò)誤狀態(tài)皇型,而鎖需要在程序啟動(dòng)時(shí)創(chuàng)建,從而能讓新建的線程共享這個(gè)鎖
def tcp_test(port):
sock = socket(AF_INET, SOCK_STREAM)
sock.settimeout(10)
result = sock.connect_ex((target_ip, port))
if result == 0:
lock.acquire()
print "Opened Port:", port
lock.release()
當(dāng)代碼執(zhí)行完之后要把鎖釋放掉(釋放lock)
輸入的處理及l(fā)ock的創(chuàng)建可以放在main函數(shù)中:
if __name__=='__main__':
# portscan.py <host> <start_port>-<end_port>
host = sys.argv[1]
portstrs = sys.argv[2].split('-')
start_port = int(portstrs[0])
end_port = int(portstrs[1])
target_ip = gethostbyname(host)
lock = thread.allocate_lock()
然后修改for循環(huán):
for port in range(start_port, end_port):
thread.start_new_thread(tcp_test, (port,))
thread.start_new_thread
用來(lái)創(chuàng)建一個(gè)線程砸烦,該函數(shù)的第一個(gè)參數(shù)是一個(gè)線程中執(zhí)行的函數(shù)弃鸦,第二個(gè)參數(shù)必須是個(gè)元組,作為函數(shù)的輸入幢痘,由于 tcp_test
函數(shù)只有一個(gè)參數(shù)唬格,所以我們使用(port,)這種形式表示這個(gè)參數(shù)為元組。
最后去掉上一節(jié)中的輸出代碼后我們的多線程改造就已經(jīng)完成了颜说。
測(cè)試結(jié)果如下:
>> python3 all_scanning_demo.py 127.0.0.1 80-200
Opened ports:80
python-nmap 包
學(xué)習(xí)Python端口掃描我們必須要接觸的一個(gè)非常強(qiáng)大的Python端口掃描包 pyton-nmap
這是一款很有名的安全工具购岗,開(kāi)源的。它可以在python程序中使用nmap端口掃描的Python包门粪,可以允許開(kāi)發(fā)者對(duì)nmap掃描結(jié)果進(jìn)行解析并實(shí)現(xiàn)自動(dòng)化掃描的任務(wù)喊积,并輸出報(bào)告。還有牛B的是可以支持異步操作玄妈,當(dāng)執(zhí)行掃描完成之后調(diào)用用戶(hù)自定義的回調(diào)函數(shù)乾吻。
install
執(zhí)行安裝命令
pip install pyton-nmap
Collecting python-nmap
Downloading python-nmap-0.6.1.tar.gz (41kB)
100% |████████████████████████████████| 51kB 65kB/s
Building wheels for collected packages: python-nmap
Running setup.py bdist_wheel for python-nmap ... done
Stored in directory: /Users/devon/Library/Caches/pip/wheels/d2/20/17/8eb9401fb0fa5ffbd0394c44d9d1c743036896c86029b0a613
Successfully built python-nmap
Installing collected packages: python-nmap
Successfully installed python-nmap-0.6.1
進(jìn)入到python shell 操作:
加載nmap包
import nmap
創(chuàng)建PortScanner對(duì)象
nm = nmap.PortScanner()
掃描 127.0.0.1的 80-200端口:
nm.scan('127.0.0.1','22-100')
查看使用的命令行和掃描信息:
nm.command_line()
Nm.scaninfo()
查看掃描的目標(biāo)主機(jī)信息:
nm.all_hosts()
nm['127.0.0.1'].hostname()
nm['127.0.0.1'].state()
nm['127.0.0.1'].all_protocols()
nm['127.0.0.1']['tcp'].keys()
擴(kuò)展
通過(guò)nmap我們可以實(shí)現(xiàn)比較復(fù)雜的一些掃描程序髓梅,你可以給予我們上面寫(xiě)的程序嘗試引入python-nmap
包并將其拓展改版,實(shí)現(xiàn)一些有用的功能:
- 1.增加GUI绎签,手動(dòng)添加掃描的端口范圍和主機(jī)
- 2.生成csv格式的掃描報(bào)告
- 3.后臺(tái)進(jìn)行掃描枯饿,完成后吧掃描報(bào)告已郵件的形式發(fā)送給管理員
常動(dòng)手,常思考 祝進(jìn)步诡必!