某大学登陆流程分析

31

动手!

首先登录、抓包,查看所提交的参数,password被加密并经过base64编码。
image-1699706889481

F12审查元素,查看登陆表单以及所绑定事件。
image-1699706898925

passwordpwdDefaultEncryptSalt传入_etd2函数,pwdDefaultEncryptSalt(下文中简称key)可在表单中获取。

function doLogin() {
    var username = casLoginForm.find("#username");
    var password = casLoginForm.find("#password");
    if (!checkRequired(username, "usernameError")) {
        username.focus();
        return false;
    }

    if (!checkRequired(password, "passwordError")) {
        password.focus();
        return false;
    }

    var captchaResponse = casLoginForm.find("#captchaResponse");
    if (!checkRequired(captchaResponse, "cpatchaError")) {
        captchaResponse.focus();
        return false;
    }

    _etd2(password.val(), casLoginForm.find("#pwdDefaultEncryptSalt").val());
}

passwordkey传入encryptAES,可知加密算法为AES。

function _etd2(_p0, _p1) {
    try {
        var _p2 = encryptAES(_p0, _p1);
        $("#casLoginForm").find("#passwordEncrypt").val(_p2);
    } catch (e) {
        $("#casLoginForm").find("#passwordEncrypt").val(_p0);
    }
}

_rds函数为产生指定长度随机字符串,encryptAES函数将64位的随机字符串拼接上password作为第一个参数,key和16位随机字符串作为后两个参数传入_gas函数。

function encryptAES(data, _p1) {
    if (!_p1) {
        return data;
    }
    var encrypted = _gas(_rds(64) + data, _p1, _rds(16));
    return encrypted;
}

var $_chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
var _chars_len = $_chars.length;
function _rds(len) {
    var retStr = '';
    for (i = 0; i < len; i++) {
        retStr += $_chars.charAt(Math.floor(Math.random() * _chars_len));
    }
    return retStr;
}

可见,加密算法为AES,待加密字符为64位的随机数拼接上password,密钥为key,初始向量IV为16位随机字符串,CBC模式,填充方式为PKCS7。

function _gas(data, key0, iv0) {
    key0 = key0.replace(/(^\s+)|(\s+$)/g, "");
    var key = CryptoJS.enc.Utf8.parse(key0);
    var iv = CryptoJS.enc.Utf8.parse(iv0);
    var encrypted = CryptoJS.AES.encrypt(data, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    return encrypted.toString();
}

base64编码

function() {
    var u = CryptoJS
      , p = u.lib.WordArray;
    u.enc.Base64 = {
        stringify: function(d) {
            var l = d.words
              , p = d.sigBytes
              , t = this._map;
            d.clamp();
            d = [];
            for (var r = 0; r < p; r += 3)
                for (var w = (l[r >>> 2] >>> 24 - 8 * (r % 4) & 255) << 16 | (l[r + 1 >>> 2] >>> 24 - 8 * ((r + 1) % 4) & 255) << 8 | l[r + 2 >>> 2] >>> 24 - 8 * ((r + 2) % 4) & 255, v = 0; 4 > v && r + 0.75 * v < p; v++)
                    d.push(t.charAt(w >>> 6 * (3 - v) & 63));
            if (l = t.charAt(64))
                for (; d.length % 4; )
                    d.push(l);
            return d.join("")
        },
        parse: function(d) {
            var l = d.length
              , s = this._map
              , t = s.charAt(64);
            t && (t = d.indexOf(t),
            -1 != t && (l = t));
            for (var t = [], r = 0, w = 0; w < l; w++)
                if (w % 4) {
                    var v = s.indexOf(d.charAt(w - 1)) << 2 * (w % 4)
                      , b = s.indexOf(d.charAt(w)) >>> 6 - 2 * (w % 4);
                    t[r >>> 2] |= (v | b) << 24 - 8 * (r % 4);
                    r++
                }
            return p.create(t, r)
        },
        _map: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
    }
}

登陆复现

解密验证,发现末尾为密码。(推荐工具:cyberchef)
image-1699706918152

CBC模式

image-1699706949220
可见,初始向量的不同只影响第一块的明文,所以初始向量可自定义。

复现代码

import base64
import random
import requests
import re
from Crypto.Cipher import AES
import json

#随机字符串产生
def getRandomN(N):
    s = ''
    for _ in range(N):
        s += str(random.randint(0,9))
    return s

#PKCS7填充
def pad(data):
    bs = AES.block_size
    data += (bs - len(data)%bs) * chr(bs - len(data)%bs)
    return data

#加密函数
def encrypt(data,key,iv):
    iv = iv.encode('utf-8')
    cipher = AES.new(key.encode('utf-8'),AES.MODE_CBC,iv)
    data = cipher.encrypt(pad(data).encode('utf-8'))
    return data

#解密函数
def decrypt(data,key):
    iv = '0000000000000000'.encode('utf-8')
    cipher = AES.new(key,AES.MODE_CBC,iv)
    data = cipher.decrypt(data)
    return (data[64:])

#base64编码
def getPwd(data,key):
    iv = getRandomN(16)
    data  = getRandomN(64) + data
    return (base64.b64encode(encrypt(data,key,iv)).decode('utf-8'))

#登陆函数
def login(username,password):
    url = 'https://xxx.edu.cn'
    r = ses.get(url,headers=header)
    text = r.text
    l = re.findall(r'<input type=\"hidden\" .*?=\".*?\" value=\"(.*?)\"',text)
    password = getPwd(password,l[5])
    data = {
        'username':username,
        'password':password,
        'lt':l[0],
        'dllt':l[1],
        'execution':l[2],
        '_eventId':l[3],
        'rmShown':l[4],
    }
    r = ses.post(url,data=data,allow_redirects=False)

header = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
}