XDU_choose_course

本文最后更新于:1 年前

XDU 抢课脚本

项目地址

1.登录认证

1.1 密码加密

首先在浏览器中在同一身份的位置进行手动登录可以发现如下的请求

  • 输入的密码已经被加密
  • 表单参数中多了一个execution参数

image-20220909095403214

在浏览器拦截的请求中有一个encrypt.js的文件,猜测是用于加密密码的

image-20220909095645965

该文件的代码很长,但是核心的加密过程如下

function getAesString(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();
}
function encryptAES(data, aesKey) {
  if (!aesKey) {
    return data;
  }
  var encrypted = getAesString(
    randomString(64) + data,
    aesKey,
    randomString(16)
  );
  return encrypted;
}
function encryptPassword(pwd0, key) {
  try {
    return encryptAES(pwd0, key);
  } catch (e) {}
  return pwd0;
}
var $aes_chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
var aes_chars_len = $aes_chars.length;
function randomString(len) {
  var retStr = "";
  for (i = 0; i < len; i++) {
    retStr += $aes_chars.charAt(Math.floor(Math.random() * aes_chars_len));
  }
  return retStr;
}

可以看出使用的时AES的CBC模式进行加密,而AES在加密时需要一个密钥,这个密钥应该是隐藏在前端页面中,每次刷新都会更新。

果然在前端页面中可以找到pwdEncryptSalt字段,并把execution字段也找了出来

image-20220909100329173

下面就是写出AES加密的脚本(fe1w0写的)

def encryptPassword(password, key):
    # password 加密, 该段代码参考于 https://github.com/EdenLin-c/CPdaily/blob/master/Jin.py
    def randomString(len):
        retStr = ''
        i=0
        while i < len:
            retStr += aes_chars[(math.floor(random.random() * aes_chars_len))]
            i=i+1
        return retStr

    def getAesString(data,key,iv):
        key = re.sub('/(^\s+)|(\s+$)/g', '', key)
        aes = AES.new(str.encode(key),AES.MODE_CBC,str.encode(iv))
        pad_pkcs7 = pad(data.encode('utf-8'), AES.block_size, style='pkcs7')
        encrypted =aes.encrypt(pad_pkcs7)
        return str(base64.b64encode(encrypted),'utf-8')
    aes_chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
    aes_chars_len = len(aes_chars)
    encrypted = getAesString(randomString(64) + password, key, randomString(16))
    return encrypted

1.2 获取session

下面就是使用requese获取登录的session

设置请求头

headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
    'Cache-Control': 'max-age=0',
    'Connection': 'keep-alive',
    'Content-Length': '154',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Host': 'ids.xidian.edu.cn',
    'Origin': 'http://ids.xidian.edu.cn',
    'Referer': 'http://ids.xidian.edu.cn/authserver/login',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
}

获取前端页面的pwdEncryptSalt字段和execution字段,并对密码进行加密

session_client = requests.Session()
requests.packages.urllib3.disable_warnings()
response = session_client.get("https://ids.xidian.edu.cn/authserver/login")

parse_html = etree.HTML(response.text)
pwdEncryptSalt = parse_html.xpath('//div//input[@id="pwdEncryptSalt"]//@value')[0]
execution = parse_html.xpath('//div//input[@id="execution"]//@value')[0]
encrypt_passwrod = encryptPassword(password, pwdEncryptSalt)

设置post请求参数

post_data = {
"username": user_id,
"password": encrypt_passwrod,
"captcha": None,
"_eventId": "submit",
"cllt": "userNameLogin",
"dllt": "generalLogin",
"lt": None,
"execution": execution
}
sso_response = session_client.post("https://ids.xidian.edu.cn/authserver/login", headers=headers, data=post_data,verify=False)

2.获取课程列表

进入选课平台后,能够看到https://yjspt.xidian.edu.cn/yjsxkapp/sys/xsxkapp/xsxkCourse/loadJhnCourseInfo.do这样一条请求,查看数可知,是加载计划内选课的api

image-20220909101535786

再仔细看拦截的请求,可以看到很多请求参数

image-20220909101655048

第一个就是查询关键字参数,即将想抢课的课程代码或课程名放到此处即可获取到相应的课程信息。

def getCourseInfo(session_client, course_KCDM):
    """
    获取指定课程的详细信息
    :param session_client:
    :param course_KCDM:
    :return:
    """
    # course_KCDM = ""
    course_infos = session_client.post("https://yjspt.xidian.edu.cn/yjsxkapp/sys/xsxkapp/xsxkCourse/loadJhnCourseInfo.do?query_keyword=" + course_KCDM,  verify = False)
    course_json = json.loads(course_infos.text)["datas"]
    if len(course_json) == 0:

        return None
    course_info = ""
    for course in course_json:
        if course_KCDM == course["KCDM"]:
            course_info = course
            break
    return course_info

3.监控容量并抢课

3.1 选课函数

获取课程信息后,只要判断课程的容量与当前的选课人数的关系即可进行选课。点击选课按钮,发送的请求如下,可以看到加入csrfToken,那么就想办法找到它就能进行选课了。

image-20220909102145303

在刷新选课页面时,会出现https://yjspt.xidian.edu.cn/yjsxkapp/sys/xsxkapp/xsxkHome/loadPublicInfo_course.do,其返回的数据中就包含了csrfToken

image-20220909102441128

def chooseCourse(course_BJDM):
    """
    选课函数,根据course_BJDM进行选课
    :param course_BJDM:
    :return:
    """
    csrf_token_url = "https://yjspt.xidian.edu.cn/yjsxkapp/sys/xsxkapp/xsxkHome/loadPublicInfo_course.do"
    csrf_response = session_client.get(csrf_token_url).text
    json_csrf_res = json.loads(csrf_response)
    csrf_token = json_csrf_res.get("csrfToken")
    choose_course_url = "https://yjspt.xidian.edu.cn/yjsxkapp/sys/xsxkapp/xsxkCourse/choiceCourse.do?bjdm={0}&csrfToken={1}&lx=0".format(course_BJDM, csrf_token)
    choose_response = session_client.get(choose_course_url, verify = False).text
    # print(choose_response)
    return json.loads(choose_response)["code"]

3.2 监控容量并抢课

为了防止登录的session过期可以设置一个定时任务,每个十二小时启动一次该脚本。

if __name__ == '__main__':
    KCDM = course_KCDM
    session_client = getSession()
    course_info = getCourseInfo(session_client = session_client, course_KCDM = KCDM)
    if course_info == None:
        print("[+] 未查询到课程信息,请检查课程代码!")
        quit()
    course_KXRS = course_info["KXRS"]  # 课程总容量
    course_DQRS = course_info["DQRS"]  # 当前选课人数

    while True:
        if course_DQRS < course_KXRS:
            course_BJDM = course_info["BJDM"]
            code = chooseCourse(course_BJDM)
            if code == 1:
                print("[+] 选课成功!")
                print("[+] 课程信息如下:")
                printCourseInfo(course_info)
                quit()
            else:
                print("[+] 选课失败!")
        else:
            print("[+] 当前课程容量已满!")
            time.sleep(sleep_time)    # 每60s查询一次
            continue
        # 监控选课人数的变化
        course_info = getCourseInfo(session_client=session_client, course_KCDM = KCDM)
        course_KXRS = course_info["KXRS"]  # 课程总容量
        course_DQRS = course_info["DQRS"]  # 当前选课人数
    print("[+] Finish")

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

 目录

Copyright © 2020 my blog
载入天数... 载入时分秒...