最近疫情虽有好转趋势,仍不可放松,很多企业不得不减少人员密度,或者直接在家远程办公。

可是某些行业由于保密性,如果不能保证远程办公时数据的安全,远程办公也就无法实现。

最近测试了jumpserver和teleport这两款开源堡垒机系统,能够用一个有公网IP的linux机器做堡垒,通过网页登录连接中转ssh或者rdp远程桌面,同时他们的权限分配、运维管理、操作记录等等也都很完善。

如果只是运维人员通过ssh连接其实还好,可是中国大多数人还是使用windows进行工作。而windows的rdp远程图像质量不佳,还会消耗大量带宽,一旦使用人数较多,难免有卡顿和马赛克问题。

不过在这非常时期,也能作为一个备选的方案。

不过对于注重数据安全的企业来说,只是禁用rdp远程的上传下载和剪贴板等功能,并不能保证数据安全,仍然存在通过截图或拍照的方式导致内部数据泄露的问题。

因此,我开始研究通过python添加屏幕水印的功能,检测RDP连接开启就显示屏幕水印,如果在公司办公则不需要显示。

水印本身通过tkinter库来制作,参考了Python 实现计算机屏幕水印一文。

import tkinter, win32api, win32con, pywintypes

#生成重复文字,可更改间距
def get_replicate_text(text):
    i,space,str1,str2 = 0,70,"",""
    while (i <= 5):
        str1 = str1 + text + " "space         i = i + 1     str2 = " "space + str1 + "\n\n\n\n"
    str1 = str1 + "\n\n\n\n"
    str1 = (str1 + str2) * 5
    return str1

mytext = get_replicate_text('watermark')        #水印重复文字设置
root = tkinter.Tk() 
width = win32api.GetSystemMetrics(0)
height = win32api.GetSystemMetrics(1)

root.overrideredirect(True)                     #隐藏显示框
root.geometry("+0+0")                           #设置窗口位置或大小
root.lift()                                     #置顶层
root.attributes("-alpha",0.6)                   #全局透明度,用来设置水印文字透明 
root.wm_attributes("-topmost", True)            #始终置顶层
root.wm_attributes("-disabled", True)           #禁止鼠标等与水印窗口交互
root.wm_attributes("-transparentcolor", "white")#白色背景透明
hWindow = pywintypes.HANDLE(int(root.frame(), 16))
exStyle = win32con.WS_EX_COMPOSITED | win32con.WS_EX_LAYERED | win32con.WS_EX_NOACTIVATE | win32con.WS_EX_TOPMOST | win32con.WS_EX_TRANSPARENT
win32api.SetWindowLong(hWindow, win32con.GWL_EXSTYLE, exStyle)
label = tkinter.Label(text=mytext,compound = 'left',font=('Times New Roman','15'), fg='#d5d5d5', bg='white')
label.pack()                                     #显示

而判定当前账号是否是远程登录的方法是windows本身的quser命令,如果当前登录用户的会话名为rdp-tcp的话,就证明当前用户是通过RDP远程登录的,从而开启水印。

而只是能够自动开启水印显然是不够的,因为即使此服务在后台开机自启,仍然存在被人发现和关闭的风险,比如taskkill命令,或者在开始菜单中注销用户并在彻底注销前取消,后台水印进程都会被关闭。

因此有必要做成中心化的server/client服务,每个水印服务client启动时通过socket连接到server服务,在用户每次登陆(显示水印)、退出(关闭水印)时发送信息到server。

当某个socket连接断开时,就代表水印服务client被关闭,此时可以选择发送警告邮件到运维人员的邮箱,或者直接远程重启client服务,或者直接将该机器上的远程用户注销登录等。

下面先说说一对多的socket连接命令,首先是server端:

from socket import *
from threading import Thread

address = "127.0.0.1"
port = 5432
buffsize = 1024
Msock = socket(AF_INET,SOCK_STREAM)
Msock.bind((address,port))
Msock.listen(24)        #set max connect 24
conn_list = []         #save connecting connect
conn_dt = {}

def tcplink(sock,addr):
    while True:
        try:
            #accept message and save
            recvData = sock.recv(buffsize).decode("utf-8")
            print(recvData,addr)
            if not recvData:
                 break
        except: 
            #when break,remove connect 
            sock.close() 
            print(addr,"offline") 
            _index = conn_list.index(addr) 
            conn_dt.pop(addr) 
            conn_list.pop(_index) 
            break

def recs():
    while True:
        clientsock,clientaddress = Msock.accept()
        if clientaddress not in conn_list:
            conn_list.append(clientaddress)
            conn_dt[clientaddress] = clientsock
        print("connect from:",clientaddress)
    #start thread 
    t = Thread(target=tcplink,args=(clientsock,clientaddress)) 
    t.start()

if __name__  == __"main"__:
    recs()

然后是client端:

from socket import *
def run():
    ip = "127.0.0.1"
    port = "5432"
    buffsize = 1024
    conn = socket(AF_INET,SOCK_STREAM)
    conn.connect((ip,port))
    conn.send("hello,bitch")
if __name__ == __"main"__:
    run()

如上,因为是一对多的模式,所以server端需要通过多线程创建并保持连接,client端主线程为水印,也需要单独一个线程连接server通信。

当用户登录、退出、或者client服务被关闭时,server端可以发送邮件通知运维人员,邮件发送部分代码如下:

from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr,formataddr
import smtplib

acccount = "yumefx@yumefx.com"
password = "123456"   #部分邮箱(例如qq邮箱)这里需要填写授权码,在邮箱客户端设置那里可以生成
toMailAddrs = ["admin@yumefx.com","develope@yumefx.com"]
smtpServer = "smtp.yumefx.com"

def formatAddress(s):
    name,addr = parseaddr(s)
    return formataddr((Header(name,"utf-8").encode(),addr))

def sendmail(title,text):
    server = smtplib.SMTP(smtpServer,25)    #25是默认的smtp端口,有可能不同
    server.set_debuglevel(1)
    server.login(acccount,password)

    msg = MIMEText(text,"plain","utf-8") 
    msg["Form"] = formatAddress("Watermark Service<%s>" %acccount) 
    msg["Subject"] = Header(title,"utf-8").encode() 

    for ta in toMailAddrs: 
        msg["To"] = formatAddress("SiteGroup<%s>" % ta) 
        server.sendmail(acccount,ta,msg.as_string()) 
    server.quit()

sendmail("warning","watermark offline")

如果某个socket连接断开,那么可以由server运行下面的cmd命令重启client服务

wmic  /node:192.168.1.10 /user:administrator /password:123456  process where name="watermark.exe" call terminate 
wmic /node:192.168.1.10 /user:administrator /password:123456 process call create "watermark.exe" 

熟悉的wmic命令对吧,快去复习wmic命令详解及应用

为什么是exe不是py,当然要打包好再用啊。pyinstaller-py脚本打包成exe

至于怎么把这几块功能结合起来,请去全世界最大的直男交友平台github查看。

 


中国总是被他们最勇敢的人保护得很好。

《论中国》——基辛格