西安外事学院CAS系统模拟登陆

前言

学校的教务认证系统从老式方正换到了新版 老方法不好用了 而且网上没发现轮子..
只能自己造了 Python开搞

先放老代码

import requests, re, json, time
username = ""
password = ""
session = requests.session()
url = "https://ca.xaiu.edu.cn:8443/zfca/login?service=http%3A%2F%2Fi.xaiu.edu.cn%2FEIP%2Fuser%2Findex.htm"
UA = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400'
header = {"User-Agent": UA}
try:
   lt_for_html = session.get(url, headers=header, verify=False, timeout=(5, 5))
except requests.exceptions.RequestException as e:
   print("Login lt TimeOut")
   lt = re.findall(r'<input type="hidden" name="lt" value="(.*?)" />', lt_for_html.text, re.S)
   data = {
      'useValidateCode': '0',
      'isremenberme': '0',
      'ip': '',
      'username': username,
      'password': password,
      'losetime': '30',
      'lt': lt,
      '_eventId': 'submit',
      'submit1': ''
   }
try:
   r = session.post(url, data=data, headers=header, verify=False, timeout=(5, 5))
except requests.exceptions.RequestException:
   print("Login Post TimeOut")

老的教务系统 https://ca.xaiu.edu.cn:8443/zfca/login 停用了
改用为 https://ca.xaiu.edu.cn/cas/login

全站使用HTTPS传输 稍微造成了一些麻烦 但是问题不大 上Fiddler 对登陆情况进行检查

一个POST请求 地址为https://ca.xaiu.edu.cn/cas/login 5个请求Body

第一个username 学号 第二个mobileCode 猜测为手机识别 第三个是password 可以看到不是明文发送 第四个execution猜测为执行参数 第五个_eventID 一个提交

在上个版本的教务中 username password均为明文发送 这个版本换成了密文发送 暂时不清楚加密算法 但是肯定的是 是通过浏览器JS执行替换的

现在使用F12 审查登陆流程

获取到对应ID为 "ppassword" (为啥是pp?迷惑行为

切换到源代码视图 找有关login的js和ppassword关键字

看了一下 一个login.js 一个pwdYZ.js 还有一个security.js 从login.sh下手 得到代码

var password = $("#ppassword").val(); /*先获取ppassword给password*/
var key = new RSAUtils.getKeyPair(public_exponent, "", Modulus); /*使用RSA计算密钥*/
var reversedPwd = password.split("").reverse().join(""); /*把password的值倒序*/
var encrypedPwd = RSAUtils.encryptedString(key,reversedPwd); /*使用key和倒序的密码计算得到结果*/
$("#password").val(encrypedPwd);

开始下断点检查密码变成密文的过程

var password = $("#ppassword").val();  /*password="shizuku"*/
var key = new RSAUtils.getKeyPair(public_exponent, "", Modulus); /*key = RSAKeyPair {e: $w.BigInt, d: $w.BigInt, m: $w.BigInt, chunkSize: 62, radix: 16, …}*/
var reversedPwd = password.split("").reverse().join(""); /*reversedPwd = "ukuzihs" password = "shizuku"*/
var encrypedPwd = RSAUtils.encryptedString(key,reversedPwd); /*encrypedPwd = "cbf0fc033160e3dc5d640db9ea5d0cf0fd5cfd7551e0cdd06a3537c76d5780b6ee3f9b62d4af582cb7a717fd7f70090b18a001147f95e816ef9c4e87435a1960"*/

这里补一些RSA加密的知识 加密需要得到两个值 一个exponent 和modulus

其中exponent 为指数 modulus为RSA算法中的N值 大数乘积

区分为 n,e (公钥) n,d(私钥)

n值和e值 通过访问https://ca.xaiu.edu.cn/cas/v2/getPubKey 得到

{"modulus":"a901b99486caae96170d28b486d5803cdaff23048187e54cc82dacd156d2607b39c0dc3130e800fee4ee16e8b535c8c0f90e5284e577ff41169a18f4f1912391","exponent":"10001"}

得到一组Json 这里使用Python的RSA库对密码RSA加密  代码如下

import rsa password = "shizuku" 
password = password[::-1] 
key = ("10001","","b8e2c1fa3ad7a96a2f053b9152de9bff1113e2254129444f52087101afc78672eeedcd0177ee636d3097055206b44affd519af0ca216ccacf7ddd4d53effb501") 
e = key[0] 
e = int(e, 16) 
n = key[-1] 
n = int(n, 16) 
pub_key = rsa.PublicKey(e=e, n=n) 
passwd = rsa.encrypt(password.encode(),pub_key) 
passwd = passwd.hex() 
printf (passwd) 
/*5db37615f1751bb0a1687643951c9fdd186d14f194965f24a7247269759fd30938c2f33e036e3230f1e76113526c8ad6b6210d79c633b2d24bed30ae8f5062d1*/

测试均失败 查验资料得知 RSA有多种填充方式 如 "PKCS1", "PKCS1_OAEP" 等

测试多种 RSA库 还是失败 目光转到security.js 使用Python模拟Javascript执行

因为使用的是Python3 PyV8库无法使用 转道使用PyExecJS

坏消息一个接着一个 因为是模拟环境 没有$w这些参数 无法执行下去

使用 ExecJS带着运行环境去执行

os.environ["EXECJS_RUNTIME"] = "PhantomJS"

传参得到PublicKey正常 到第二个encrypt时JS卡死 无法执行 看来环境依然有问题 后续换了几个环境 例如chrome firefox edge 均出现卡死或者非预期结果

放弃JS模拟这条路 继续回到Python上 后经过查证 security.js使用 "No Padding"填充 使用F12断点调试模拟 使用相同modulus 得到密文相同

又是一番Baidu Google Github Bing 得到一个Python RSA使用No Padding填充的算法 代码如下

class Encrypt(object):
    def __init__(self,e,m):
        self.e = e
        self.m = m

    def encrypt(self,message):
        mm = int(self.m, 16)
        ee = int(self.e, 16)
        rsa_pubkey = rsa.PublicKey(mm, ee)
        crypto = self._encrypt(message.encode(), rsa_pubkey)
        return crypto.hex()

    def _pad_for_encryption(self, message, target_length):
        message = message[::-1]
        max_msglength = target_length - 11
        msglength = len(message)
        padding = b''
        padding_length = target_length - msglength - 3
        for i in range(padding_length):
            padding += b'\x00'
        return b''.join([b'\x00\x00',padding,b'\x00',message])

    def _encrypt(self, message, pub_key):
        keylength = rsa.common.byte_size(pub_key.n)
        padded = self._pad_for_encryption(message, keylength)
        payload = rsa.transform.bytes2int(padded)
        encrypted = rsa.core.encrypt_int(payload, pub_key.e, pub_key.n)
        block = rsa.transform.int2bytes(encrypted, keylength)

return block

 

经过验证 RSA密文与Javascript RSA密文算法结果一致 开始写登陆模拟本来在一开始的时候就直接

import requests, json
headers = {
    'Content-Type': 'application/json;charset=UTF-8',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400'
}
url = "https://ca.xaiu.edu.cn/cas/v2/getPubKey"
res = requests.get(url, headers)
c = json.loads(res.text)
n = c["modulus"]

结果登陆模拟还是提示错误 后续检查得到getPubKey页面进行了set-cookie
解决方法使用session带参数模拟请求
正式代码如下

requests.packages.urllib3.disable_warnings()
def xaiu():
    session = requests.session()
    url = "https://ca.xaiu.edu.cn/cas/login?service=http%3A%2F%2Fi.xaiu.edu.cn%2FEIP%2Fuser%2Findex.htm"
    header = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400',
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    headers = {
            'Content-Type': 'application/json;charset=UTF-8',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400'
    }
    execution = session.get(url, headers=header, verify=False)
    #execution = session.get(url, headers=header, proxies=proxies, verify=False)
    execution = re.findall(r'<input type="hidden" name="execution" value="(.*?)" />', execution.text, re.S)
    print(execution)
    username = ""
    password = ""
    password = password[::-1]
    url_token = "https://ca.xaiu.edu.cn/cas/v2/getPubKey"
    res = session.get(url=url_token, headers=headers, verify=False)
    c = json.loads(res.text)
    m = c["modulus"]
    e = "10001"
    en = Encrypt(e, m)
    password = en.encrypt(password)
    #password=js_rsa(password)
    print(password)
    data = {
        'username': username,
        'mobileCode': '',
        'password': password,
        'execution': execution,
        '_eventId': 'submit'
    }
    r = session.post(url, data=data, headers=header, verify=False)
    print(r.text)

if __name__=="__main__":
    xaiu()

 

总结时间

收获如下:
1. 掌握RSA算法的利用 公钥 私钥 各种填充算法
2. Python 模拟JS执行传参(并不完美
3. 浏览器F12断点调试
4. 学习了很多JS技巧
5. 时间管理技巧
6.模拟登陆成功 又可以抓数据了

 

发表评论

电子邮件地址不会被公开。必填项已用 * 标注