任何一個靠譜的網(wǎng)絡攻擊都是起步于偵察的。
攻擊者必須在挑選并確定利用目標中的漏洞之前找到目標在哪里有漏洞氏涩。
編寫一個掃描目標主機開放的TCP端口的偵察小腳本腕扶。
為了與TCP端口進行交互啥寇,我們要先建立TCP套接字笨篷。
Python提供了BSD套接字的接口。
BSD套接字提供了一個應用編程接口联予,使程序員能編寫在主機之間進行網(wǎng)絡通信的應用程序啼县。
通過一系列套接字API函數(shù)材原,我們可以創(chuàng)建、綁定監(jiān)聽季眷、連接余蟹,或在TCP/IP套接字上發(fā)送數(shù)據(jù)。
大多數(shù)能訪問互聯(lián)網(wǎng)的應用使用的都是TCP協(xié)議子刮。
例如威酒,在目標組織中,Web服務器可能位于TCP80端口挺峡,電子郵件服務器在TCP25端口葵孤,F(xiàn)TP服務器在TCP21端口。
要連接目標組織中的任一服務器沙郭,攻擊者必須知道與服務器相關(guān)聯(lián)的IP地址和TCP端口佛呻。
所有成功的網(wǎng)絡攻擊一般都是從端口掃描來開序幕的。
有一種類型的端口掃描會向一系列常用的端口發(fā)送TCP SYN數(shù)據(jù)包病线,并等待TCP ACK響應——這能讓我們確定這個端口是開放的。
與此相反鲤嫡,TCP連接掃描是使用完整的三次握手來確定服務器或端口是否可用的送挑。
TCP全連接掃描
開始編寫自己用的TCP全連接掃描來識別主機的TCP端口掃描器。
要導入Python的BSD套接字API實現(xiàn)暖眼。
套接字API會為我們提供一些在實現(xiàn)TCP端口掃描程序時有用的函數(shù)惕耕。
要深入的了解請查看文檔.
為了更好的了解TCP端口掃描器的工作原理,我們將腳本分成五個獨立的步驟诫肠,分別為它們編寫Python代碼司澎。
首先輸入一個主機名和用逗號分割的端口列表,并予以掃描栋豫。
接下來將主機轉(zhuǎn)換成IPv4互聯(lián)網(wǎng)地址挤安。對列表中的每個端口,我們都會鏈接目標地址和該端口丧鸯。
最后蛤铜,為了確定在該端口上運行什么服務,我們將發(fā)送垃圾數(shù)據(jù)并讀取由具體應用發(fā)回的Banner丛肢。
在第一步中围肥,從用戶那里獲得主機名和端口。
為了做到這一點蜂怎,我們在程序中使用optparse庫解析命令行參數(shù)穆刻。
調(diào)用optparse.OptionParser([usage message])會生成一個參數(shù)解析器(option parser)類的實例。
接著杠步,在parser.add_option中指定這個腳本具體要解析哪個命令行參數(shù)氢伟。
e.g. 一個快速解析要掃描的目標主機名和端口的方法
import optparse
parser = optparse.OptionParser('usage %prog -H <target host> -p <target port>')
parser.add_option('-H', dest='tgtHost', type='string', help='specify target host')
parser.add_option('-p', dest='tgtPort', type='string', help='specify target port')
(options, args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPort = options.tgtPort
if tgtHost == None or tgtPort == None:
print(parser.usage)
exit(0)
else:
print(tgtHost)
print(tgtPort)
接下來我么要生成倆個個函數(shù): connScan和portScan.
portScan函數(shù)以參數(shù)的形式接受主機名和目標端口列表榜轿。
它首先會嘗試用gethostbyname()函數(shù)確定主機名對應的IP地址。
接下來腐芍,它會使用connScan函數(shù)輸出主機名字(或IP地址)差导,并使用connScan()函數(shù)嘗試逐個連接我們要連接的每個端口。
connScan函數(shù)接受兩個參數(shù):tgtHost和tgtPort猪勇,它會去嘗試建立與目標主機端口的連接设褐。
如果成功,connScan將打印出一個端口開放的消息泣刹。如果不成功助析,它會打印出端口關(guān)閉的消息。
from socket import *
def connScan(tgtHost, tgtPort):
try:
connSkt = socket(AF_INET, SOCK_STREAM)
connSkt.connect((tgtHost, tgtPort))
print('[+] %d/tcp open' % tgtPort)
connSkt.close()
except Exception:
print('[-] %d/tcp closed' % tgtPort)
def portScan(tgtHost, tgtPorts):
try:
tgtIP = gethostbyname(tgtHost)
except:
print("[-] Cannot resolve '%s': Unknown host" % tgtHost)
return
try:
tgtName = gethostbyaddr(tgtHost)
print("\n[+] Scan Results for: " + tgtName[0])
except:
print("\n[-] Scan Results for: " + tgtIP)
setdefaulttimeout(1)
for tgtPost in tgtPorts:
print("Scannint port " + tgtPost)
connScan(tgtHost, tgtPost)
抓取應用的Banner
為了抓取目標主機上應用的Banner椅您,必須現(xiàn)在connScan函數(shù)中插入一些新增的代碼外冀。
找到開放端口后,我們向它發(fā)送一個數(shù)據(jù)傳并等待響應掀泳。
根據(jù)收集到的響應雪隧,就能推斷出目標主機和端口上運行的應用。
from socket import *
def connScan(tgtHost, tgtPort):
try:
connSkt = socket(AF_INET, SOCK_STREAM)
connSkt.connect((tgtHost, tgtPort))
connSkt.send('Hello Python\r\n')
results = connSkt.recv(1024)
print('[+] {}/tcp open'.format(tgtPort))
print('[+] ' + str(results))
connSkt.close()
except:
print('[-] {}/tcp closed'.format(tgtPort))
def portScan(tgtHost, tgtPorts):
try:
tgtIP = gethostbyname(tgtHost)
except:
print("[-] Cannot resolve '{}': Unknown host".format(tgtHost))
return
try:
tgtName = gethostbyaddr(tgtIP)
print("\n[+] Scan Results for: " + tgtName[0])
except:
print('\n[+] Scan Results for: ' + tgtIP)
setdefaulttimeout(1)
for tgtPort in tgtPorts:
print('Scanning port ' + tgtPort)
connScan(tgtHost, tgtPort)
def main():
import optparse
parser = optparse.OptionParser('usage %prog -H <target host> -p <target port>')
parser.add_option('-H', dest='tgtHost', type='string', help='specify target host')
parser.add_option('-p', dest='tgtPort', type='string', help='specify target port')
(options, args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPorts = (options.tgtPort).split(' ')
if tgtPorts[0] == None or tgtHost == None:
print('[-] You must specify a target host and port[s].')
exit(0)
portScan(tgtHost, tgtPorts)
if __name__ == '__main__':
main()
<<<<<<< HEAD
=======
>>>>>>> origin/master
線程掃描
根據(jù)套接字中timeout變量的值员舵,每掃描一個套接字都會花費幾秒鐘脑沿。
這看上去微不足道,但如果我們要掃描多個主機端口马僻,時間總量就會成倍增加庄拇。
理想情況下,我們希望能同時掃描多個套接字韭邓,而不是一個一個地進行掃描措近。
這時,我們必須引入Python線程女淑,線程是一種能提供這類同時執(zhí)行多項任務的方法瞭郑。
具體到我們這個掃描器,我們要修改的是portScann()函數(shù)中迭代循環(huán)里的代碼诗力。
from threading import *
for tgtPort in tgtPorts:
t = Thread(target=connScan, args=(tgtHost, int(tgtPort)))
t.start()
這讓我們的速度有了顯著的提升凰浮,但這又有一個缺點。connScan()函數(shù)會在屏幕上打印一個輸出苇本。
如果多個線程同時打印輸出袜茧,就可能會出現(xiàn)亂碼和失序。
為了讓一個函數(shù)獲得完整的屏幕控制前瓣窄,我們需要使用一個信號量(semaphore)笛厦。
一個簡單的信號量就能阻止其他線程的運行。
注意俺夕,在打印輸出前裳凸,我們用screenLock贱鄙。acquire()執(zhí)行一個加鎖操作。
如果信號量還沒被鎖上姨谷,線程就有權(quán)繼續(xù)運行逗宁,并輸出到屏幕上。
如果信號量已經(jīng)被鎖定梦湘,我們只能等待瞎颗,直到持有信號量的線程釋放信號量。
通過利用信號量捌议,我們現(xiàn)在能夠確保在任何給定時間上只有一個線程可以打印屏幕哼拔。
在異常處理代碼中,位于finally關(guān)鍵字的是在終止阻塞(其他線程)之前需要執(zhí)行的代碼瓣颅。
from threading import *
from socket import *
screenLock = Semaphore(value=1)
def connScan(tgtHost, tgtPOrt):
try:
connSkt = socket(AF_INET, SOCK_STREAM)
connSkt.connect((tgtHost, tgtPOrt))
connSkt.send('something')
results = connSkt.recv(1024)
screenLock.acquire()
print('[+] {}/tcp open'.format(tgtPOrt))
print('[-] ' + str(results))
except:
screenLock.acquire()
print('[-] {}/tcp closed'.format(tgtPOrt))
finally:
screenLock.release()
<<<<<<< HEAD
connSkt.close()
=======
connSkt.close()
>>>>>>> origin/master
把其他所有的函數(shù)放入同一個腳本中倦逐,并添加一些參數(shù)解析代碼,就有了最終的端口掃描腳本宫补。
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from socket import *
from threading import *
screenLock = Semaphore(value=1)
def connScan(tgtHost, tgtPort):
try:
connSkt = socket(AF_INET, SOCK_STREAM)
connSkt.connect((tgtHost, tgtPort))
connSkt.send('some information\r\n')
results = connSkt.recv(1024)
screenLock.acquire()
print('[+] {}/tcp open'.format(tgtPort))
print('[+] ' + str(results))
except:
screenLock.acquire()
print('[-] {}/tcp closed'.format(tgtPort))
finally:
screenLock.release()
connSkt.close()
def portScan(tgtHost, tgtPorts):
try:
tgtIP = gethostbyname(tgtHost)
except:
print("[-] Cannot resolve '{}': Unknown host".format(tgtHost))
return
try:
tgtName = gethostbyaddr(tgtIP)
print("\n[+] Scan Results for: " + tgtName[0])
except:
print('\n[+] Scan Results for: ' + tgtIP)
setdefaulttimeout(1)
for tgtPort in tgtPorts:
t = Thread(target=connScan, args=(tgtHost, tgtPort))
t.start()
def main():
import optparse
parser = optparse.OptionParser('usage %prog -H <target host> -p <target port>')
parser.add_option('-H', dest='tgtHost', type='string', help='specify target host')
parser.add_option('-p', dest='tgtPort', type='string', help='specify target port[s] separated by comma')
(options, args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPorts = str(options.tgtPort).split(', ')
if tgtPorts[0] == None or tgtHost == None:
print(parser.usage)
exit(0)
portScan(tgtHost, tgtPorts)
if __name__ == '__main__':
main()
使用NMAP端口掃描代碼
前面的例子是快速編寫能進行TCP鏈接掃描的一個腳本檬姥。
這可能還不能夠用,因為我們可能還要進行其他類型的掃描粉怕,例如穿铆,由Nmap工具包提供的ACK、RST斋荞、FIN或SYN-ACK掃描等。
實際的工業(yè)標準——Namp端口掃描工具包提供了大量功能虐秦。
這也引出一個問題平酿,為什么不直接使用Nmap?這就是Python真正美妙的地方悦陋。
雖然Fyodor Vaskovich編寫的Nmap中也能使用C和Lua編寫的腳本蜈彼,但是Nmap還能被非常好的很合到Python中。
Nmap可以生成基于XML的輸出俺驶。這讓我們能在Python腳本中使用Nmap的全部功能幸逆。在編寫代碼之前,你必須安裝Python-Nmap暮现。
安裝好Python-Nmap之后还绘,我們就可以將Nmap導入到現(xiàn)有的腳本中。
創(chuàng)建一個PortScanner()類對象栖袋,這使我們能用這個對象完成掃描操作拍顷。
PortScanner類有一個scan()函數(shù),它可將目標和端口的列表作為參數(shù)輸入塘幅,并對它們進行基本的Nmap掃描昔案。
另外還可以把目標主機的地址/端口放入數(shù)組中備查尿贫,并打印出端口狀態(tài)。
def nmapScan(tgtHost, tgtPort):
import nmap
nmScan = nmap.PortScanner()
nmScan.scan(tgtHost, tgtPort)
state = nmScan[tgtHost]['tcp'][int(tgtPort)]['state']
print('[+] ' + tgtHost + ' tcp/' + tgtPort + " " + state)
def main():
import optparse
parser = optparse.OptionParser('usage %prog -H <target host> -p <target port>')
parser.add_option('-H', dest='tgtHost', type='string', help='specify target host')
parser.add_option('-p', dest='tgtPort', type='string', help='specify target port[s] separated by comma')
(options, args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPorts = str(options.tgtPort).split(', ')
if tgtPorts[0] == None or tgtHost == None:
print(parser.usage)
exit(0)
for tgtPort in tgtPorts:
nmapScan(tgtHost, tgtPort)
if __name__ == '__main__':
main()
more
- TCP SYN SCAN —— 也稱為半開放掃描踏揣,這種類型的掃描發(fā)送一個SYN包庆亡,啟動一個TCP會話,并等待響應的數(shù)據(jù)包捞稿。
如果收到的是一個reset包又谋,表明端口是關(guān)閉的,而如果收到的是一個SYN/ACK包括享,則表示響應的端口是開放的搂根。 - TCP NULL SCAN —— NULL掃描把TCP頭中的所有標志位都設為NULL。如果受到的是一個RST包铃辖,則表示響應的端口是關(guān)閉的剩愧。
- TCP FIN SCAN —— TCP FIN 掃描發(fā)送一個表示拆除一個活動的TCP連接的FIN包,讓對方關(guān)閉連接娇斩。
如果收到了一個RST包仁卷,則表示相應的端口是關(guān)閉的。 - TCP XMAS SCAN —— TCP XMAS掃描發(fā)送PSH犬第、FIN锦积、URG和TCP標志為被設為1的數(shù)據(jù)包。
如果受到了一個RST包歉嗓,則表示響應的端口是關(guān)閉的丰介。