前言:
某公司可以使用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