Python安全之自動打卡程序(GUI)

前言:
某公司可以使用OA系統進行打卡,前提是要在公司內網,也就是必須要在公司才能打卡,實際工作中經常會遇到忘記打卡或者遲到早退的情況(嘿嘿你懂的),於是就想寫個小程序來實現自動打卡。其實這也可以視爲OA系統的一個跨站請求僞造(CSRF)漏洞。要堵住這個漏洞可以使用四種方法:
1、隨機TOKEN,打卡請求時攜帶一個隨機TOKEN
2、圖形驗證碼,打卡請求時輸入一個隨機驗證碼
3、Referrer頭驗證,打卡請求時驗證Referrer頭已判斷來源
4、輸入密碼,打卡請求時輸入自己的密碼
顯然,1、3、4都可以輕鬆繞過,最有效的辦法還是圖形驗證碼。不過會降低用戶體驗。安全和易用性總是矛盾的嘛。
在這裏插入圖片描述
代碼邏輯說明:

由於tkinter不能使用類似schedule的調度模塊,也不能使用time.sleep,主要原因是tkinter的GUI是靠window.mainloop()的循環實現,當使用schedule或time.sleep時,函數會暫停跳不到window.mainloop()導致界面會卡死。tkinter爲了實現定時任務提供了自己的after方法。其功能是指定一定的時間(毫秒)後執行某函數。因此這個定時打卡退卡程序反覆使用tkinter的after來實現。思路如下:
一、當點擊button按鈕進入start_time函數,主要功能是無論何時啓動打卡,都能在設置的打卡時間進行打卡,退卡時間進行退卡。實現如下:
1、 先獲取當前時間,再設置一個打卡時間workon_time和一個退卡時間workoff_time;
2、 分別計算打卡時間到當前時間之間的打卡毫秒數minsecond和退卡時間到當前時間的退卡毫秒數minsecond1;
3、 設置兩個window.after定時任務,一個是minsecond毫秒後啓動start_time1函數;另一個是minsecond1毫秒後啓動start_time1函數;
二、start_time1函數的作用是實現循環打卡,也就是本次執行後每24小時後再執行一次,實現如下:
1、 該函數啓動先執行一次randomtimes函數,24小時(86400000毫秒)後再執行一次自己(同時再次先執行一次randomtimes),以實現循環執行;
2、 start_time2和start_time1一樣,只不過一個在打卡時間時啓動循環,另一個在退卡時間啓動循環
三、randomtimes函數的主要功能是隨機時間打卡,以防止每次打開時間相同讓人起疑,實現如下:
1、 在一定的範圍內(這裏設置的是360秒)隨機一個秒數s;
2、 在s秒後開始執行真正的打卡函數work_on;
3、 randomtimes1函數和randomtimes功能相似;
四、work_on函數的作用是模擬登陸後打卡,都是由requests模塊實現,不再詳說;

舉例(以打卡爲例):
假如打卡時間是上午8:30,當前時間是上午8:00,我啓動了此程序,點擊了‘按鈕’,程序先執行了start_time函數;
start_time函數獲取了當前時間8:00,計算出30分鐘後(也就是8:30)開始執行start_time1函數;
start_time1函數先執行了randomtimes函數,然後再執行一個計劃任務——24小時後再執行一次自己;
randomtimes函數隨機了一個秒數60秒,並在60秒後執行work_on函數;
60秒後(8:31時)work_on正式啓動,第1次打卡;
24小時後(第二天8:30)…
start_time1再次啓動了自己,執行了randomtimes函數,同時又執行一個計劃任務——24小時後再執行一次自己;
randomtimes函數這次隨機120秒,並在120秒後執行work_on函數;
120秒後(8:32時)work_on正式啓動,第2次打卡;
24小時後(第三天8:30)…

代碼:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# @Time     :2020/2/8 14:53
# @Author   :Donvin.li
# @File     :autologin.py

import requests
import rsa
import time
import random
import tkinter as tk
import datetime

window=tk.Tk()
window.title('XXX自動打卡')
window.geometry('355x355')

# 首次打卡函數,無論何時啓動打卡,都能在設置的打卡時間進行打卡,退卡時間進行退卡
def start_time():
    test=logintest()    #執行登錄測試函數,獲取返回值
    if test=='OK':  #如果返回OK,則代表測試成功,進入打卡退卡
        now=datetime.datetime.now() #當前時間,即:程序啓動的時間
        workon_time='14:05' #打卡時間字符串
        workon_time1=datetime.datetime.strptime(workon_time,'%H:%M')    #將打卡時間字符串轉換成時間格式
        second=(workon_time1-now).seconds   #計算現在到打卡時間之間的秒數
        minsecond=second*1000   #轉換成毫秒,供window.after使用
        h=minsecond/3600000 #轉換成小時,供輸出
        ts=str(h)+'小時後開始打卡\n'
        t1.insert(tk.INSERT, ts)    #輸出到t1

        workoff_time = '14:10'
        workoff_time1 = datetime.datetime.strptime(workoff_time, '%H:%M')
        second1 = (workoff_time1 - now).seconds  # 現在到打卡時間之間的秒數
        minsecond1 = second1 * 1000
        h1 = minsecond1 / 3600000
        ts1 = str(h1) + '小時後開始退卡\n'
        t1.insert(tk.INSERT, ts1)

        window.after(minsecond,start_time1)  #second秒後執行循環打卡函數
        window.after(minsecond1, start_time2)   #second1秒後執行循環退卡函數

# 循環打卡函數,每24小時執行一次
def start_time1():
    ts='打卡啓動\n'
    t1.insert(tk.INSERT, ts)
    randomtimes()   #執行一次隨機打卡函數
    window.after(86400000, start_time1) #每24小時執行一次自己,實現循環

# 循環退卡函數,每24小時執行一次
def start_time2():
    ts='退卡啓動\n'
    t1.insert(tk.INSERT, ts)
    randomtimes1()  #執行一次隨機退卡函數
    window.after(86400000, start_time2) #每24小時執行一次自己,實現循環

# 隨機打卡函數,用於讓打卡時間隨機
def randomtimes():
    s=random.randint(0,360) #隨機一個360秒內的秒數
    t1.insert(tk.INSERT,str(s)+'秒後開始打卡......\n')
    mins=s*1000 #轉換成毫秒
    window.after(mins,work_on)  #mins毫秒後執行打卡函數

# 隨機退卡函數,用於讓退卡時間隨機
def randomtimes1():
    s=random.randint(0,360)
    t1.insert(tk.INSERT, str(s)+'秒後開始退卡......\n')
    mins=s*1000
    window.after(mins,work_off)  # mins毫秒後執行退卡函數

# 打卡函數,用於打卡
def work_on():
    usr=e1.get()    #從e1(Enrty)控件獲取值作爲用戶名
    psd=e2.get()    #從e2(Enrty)控件獲取值作爲密碼
    passd=get_rsa_result(psd)   #執行RSA加密函數,將密碼加密
    s=requests.session()
    headers={'Host': 'e.XXXX.com',
             'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0',
             'Accept': 'application/json, text/javascript, */*; q=0.01',
             'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
             'Accept-Encoding': 'gzip, deflate',
             'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
             'X-Requested-With': 'XMLHttpRequest',
             'Origin': 'http://e.XXXX.com',
             'Connection': 'close',
             'Referer': 'http://e.XXXX.com/portal/',
             }
    postdata={'lang':'cn','userid':usr,'pwd':passd,'cmd':'CLIENT_USER_LOGIN','sid':'','deviceType':'pc','_CACHE_LOGIN_TIME_':'1579162050273','pwdEncode':'RSA','timeZone':'-8'}
    url='http://e.XXXX.com/portal/r/jd'
    rs=s.post(url,postdata,headers=headers)
    result=rs.text
    if 'error' not in result:
        print('Login sucessful:'+usr+':'+psd)
        work_url='http://e.XXXX.com/portal/r/jd?sid=0e65fa9f-1970-42d5-9053-fa380d52f05d&cmd=com.hrpaas.apps.atnd.sign'
        work_postdata={'signtype':1,'type':1}
        work_rs=s.post(work_url,work_postdata,headers=headers)
        now= time.strftime("%Y-%m-%d %H:%M:%S")
        ts='打卡成功\n'
        t1.insert(tk.INSERT, str(now)+':')
        t1.insert(tk.INSERT, ts)
    else:
        now = time.strftime("%Y-%m-%d %H:%M:%S")
        ts = '打卡失敗\n'
        t1.insert(tk.INSERT, str(now) + ':')
        t1.insert(tk.INSERT, ts)

# 退卡函數,用於退卡
def work_off():
    usr=e1.get()
    psd=e2.get()
    passd = get_rsa_result(psd)
    s = requests.session()
    headers = {'Host': 'e.XXXX.com',
               'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0',
               'Accept': 'application/json, text/javascript, */*; q=0.01',
               'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
               'Accept-Encoding': 'gzip, deflate',
               'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
               'X-Requested-With': 'XMLHttpRequest',
               'Origin': 'http://e.XXXX.com',
               'Connection': 'close',
               'Referer': 'http://e.XXXX.com/portal/',
               }
    postdata = {'lang': 'cn', 'userid': usr, 'pwd': passd, 'cmd': 'CLIENT_USER_LOGIN', 'sid': '', 'deviceType': 'pc',
                '_CACHE_LOGIN_TIME_': '1579162050273', 'pwdEncode': 'RSA', 'timeZone': '-8'}
    url = 'http://e.XXXX.com/portal/r/jd'
    rs = s.post(url, postdata, headers=headers)
    result = rs.text
    if 'error' not in result:
        print('Login sucessful:' + usr)
        work_url = 'http://e.XXXX.com/portal/r/jd?sid=0e65fa9f-1970-42d5-9053-fa380d52f05d&cmd=com.hrpaas.apps.atnd.sign'
        work_postdata = {'signtype': 1, 'type': 2}
        work_rs = s.post(work_url, work_postdata, headers=headers)
        now= time.strftime("%Y-%m-%d %H:%M:%S")
        ts='退卡成功\n'
        t1.insert(tk.INSERT, str(now)+':')
        t1.insert(tk.INSERT, ts)
    else:
        now = time.strftime("%Y-%m-%d %H:%M:%S")
        ts1 = '退卡失敗\n'
        t1.insert(tk.INSERT, str(now)+ ':')
        t1.insert(tk.INSERT, ts1)

# RSA加密函數,用於將密碼加密
def get_rsa_result(content):
    """
    根據 模量與指數 生成公鑰,並利用公鑰對內容 rsa 加密返回結果
    :param e:指數
    :param n: 模量
    :param content:待加密字符串
    :return: 加密後結果
    """
    n = "8bcbceb956d3d6c0da8cd8847e50796eac0fb3d67d4901820fa85dcd8edbb30bd25966eb18223e1ace1308da181897df4559bf97cca6ae9a33a0baf6f53324334a385d2a7cbc186fb5070045080b6c948423e7ddcd795ac9eaa438317772f4a948409ecec92dfe222a10b4c327e8d0e494cc0aa42ebc786030a105da0637049d"
    e = "10001"
    e = int(e, 16)
    n = int(n, 16)

    pub_key = rsa.PublicKey(e=e, n=n)
    m = rsa.encrypt(content.encode(), pub_key)
    #print(m.hex())
    return m.hex()

# 登錄測試函數,用戶在登錄前測試用戶名密碼是否正確
def logintest():
    usr=e1.get()
    psd=e2.get()
    passd=get_rsa_result(psd)
    s=requests.session()
    headers={'Host': 'e.XXXX.com',
             'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0',
             'Accept': 'application/json, text/javascript, */*; q=0.01',
             'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
             'Accept-Encoding': 'gzip, deflate',
             'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
             'X-Requested-With': 'XMLHttpRequest',
             'Origin': 'http://e.XXXX.com',
             'Connection': 'close',
             'Referer': 'http://e.XXXX.com/portal/',
             }
    postdata={'lang':'cn','userid':usr,'pwd':passd,'cmd':'CLIENT_USER_LOGIN','sid':'','deviceType':'pc','_CACHE_LOGIN_TIME_':'1579162050273','pwdEncode':'RSA','timeZone':'-8'}
    url='http://e.XXXX.com/portal/r/jd'
    rs=s.post(url,postdata,headers=headers)
    result=rs.text
    if 'error' not in result:
        ts='登錄成功,開始自動打卡守護,請勿關閉程序\n'
        t1.insert(tk.INSERT, ts)
        return 'OK'
    else:
        tishi='登錄失敗,請重新登錄\n'
        t1.insert(tk.INSERT, tishi)

l1=tk.Label(window,text='用戶名:',font=('Arial',10))
l1.place(x=50,y=50)
e1=tk.Entry(window,width=20,show = None)  #顯示成明文形式
e1.place(x=130,y=50)

l2=tk.Label(window,text='密  碼:',font=('Arial',10))
l2.place(x=50,y=80)
e2=tk.Entry(window,width=20,show ='*')  #顯示成密文形式
e2.place(x=130,y=80)

b1=tk.Button(window, text='開始', width=10,height=1, command=start_time)
b1.place(x=130,y=120)

t1=tk.Text(window,bg='green',fg='white',width=47, height=9)
# 說明: bg爲背景,fg爲字體顏色,font爲字體,width爲長,height爲高,這裏的長和高是字符的長和高,比如height=2,就是標籤有2個字符這麼高
t1.place(x=10,y=180)

l3=tk.Label(window,text='作者:李東鋒',font=('Arial',10))
l3.place(x=130,y=320)

window.mainloop()

使用:
使用pyinstaller打包成exe直接雙擊執行即可使用:

pyinstaller -F -w autologin.py
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章