当前位置: 首页 > article >正文

高校教务系统登录页面JS分析——安徽大学

高校教务系统密码加密逻辑及JS逆向

最近有粉丝说安徽大学的教务系统换了,之前用的是正方出品的系统,今天我来看看新版教务系统怎么模拟登录,总体来说,还是比较简单的,就是一个哈希加密了密码,其次就是一个滑块验证码,本文都会一一来介绍。

本文将介绍高校教务系统的密码加密逻辑以及使用JavaScript进行逆向分析的过程。通过本文,你将了解到密码加密的基本概念、常用加密算法以及如何通过逆向分析来破解密码。

本文仅供交流学习,勿用于非法用途。

一、密码加密基本概念

密码加密是一种保护信息安全的技术手段,它通过将明文(原始信息)转换为密文(加密后的信息),以防止未经授权的访问和篡改。常见的密码加密算法有MD5、SHA-1、SHA-256等。

1.1 加密过程

加密过程通常包括以下步骤:

  1. 密钥扩展:将密钥扩展为多个轮值,每个轮值都与明文的一部分有关。
  2. 初始轮值生成:将扩展后的密钥与轮常数进行某种运算,生成第一轮加密的密文。
  3. 多轮迭代:对密文进行多轮迭代操作,每轮操作都包括非线性函数、模运算和轮常数的变换。
  4. 最终密文:经过多轮迭代后,得到最终的密文。

1.2 解密过程

解密过程与加密过程相反,通过反向操作来恢复原始明文。通常需要知道加密时使用的密钥和算法。

二、高校教务系统密码加密逻辑分析

2.1 抓包

我们首先打开教务系统的登录页面,我们可以看到,只有学号和密码,安徽大学是错误一次密码,会验证验证码,我们后面再说。

ps:这里系统有个小问题,有个测试账号估计安徽大学忘记删了。

密码错误一次后会触发滑块验证码,都没用极验验证码等主流的滑块验证码,都没加密,好搞。

我们打开开发者工具,尝试登录抓包,网页会返回这样的数据接口。我们用户名和密码都是默认输入123456,你也可以输入其他的。

2.2 分析加密参数

我们接下来,就是来分析这个密码是怎么加密的。我们全局搜索password。定位到加密的位置。

发现上面有一行字符串,我们仔细一看,就是加密方法。这里很简单,就是普通的哈希加密。

三、JS逆向分析方法

逆向分析是指从已知的加密文本或程序中还原出原始信息的过程。在本例中,我们将使用JavaScript编写一个简单的逆向分析工具,用于逆向高校教务系统的密码。

环境使用

  • python 3.9
  • pycharm
  • node

代码实现

我们就不去扣js代码了,直接用python实现。这里的salt就是服务器返回的值,也就是这个接口返回的密钥:https://jw.ahu.edu.cn/student/login-salt 

import hashlib

def sha1_hash(text):
    # 创建一个sha1 hash对象
    sha1 = hashlib.sha1()

    # 使用输入的文本更新hash对象
    sha1.update(text.encode('utf-8'))

    # 得到哈希摘要,并转换为十六进制字符串
    hash_output = sha1.hexdigest()

    return hash_output
new_password = salt + '-' + str("密码")
h_password = sha1_hash(new_password)

滑块验证码

我们今天来讲一下这个验证码,其实很简单,我们先看接口,总共有两个接口,一个是获取验证码图片的,一个是提交验证的。

login-captcha

我们先来看第一个接口,传入了两个参数,一个是ts,也就是时间戳,第二个是clientId。这个clientId是本地生成的,不难找到生成算法,我直接给大家了,原文算法是这样的:

uuid: function () {
                var s = []
                var hexDigits = '0123456789abcdef'
                for (var i = 0; i < 36; i++) {
                    s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
                }
                s[14] = '4' // bits 12-15 of the time_hi_and_version field to 0010
                s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1) // bits 6-7 of the clock_seq_hi_and_reserved to 01
                s[8] = s[13] = s[18] = s[23] = '-'
                return s.join('')
            }

能用python实现,我们就不要用js,下面是我们的python代码:

def generate_uuid():
    hexDigits = '0123456789abcdef'
    s = [''] * 36

    for i in range(36):
        if i in [8, 13, 18, 23]:
            s[i] = '-'
        else:
            s[i] = hexDigits[random.randint(0, 0xf)]

    # 设置特定的位以匹配JavaScript代码
    s[14] = '4'  # 设置版本号为4(0100)
    # 设置时钟序列的变体部分
    variant = int(s[19], 16) & 0x3  # 获取当前s[19]的低两位
    s[19] = hexDigits[(variant | 0x8) & 0xf]  # 设置变体为RFC 4122(1000)

    return ''.join(s)

# 生成UUID
uuid = generate_uuid()
# print(uuid)

我们可以看到返回了验证码图片,这里是以base64数据返回的,我们保存到本地就可以。

 下面是保存验证图片的代码,以便于我们后续比较:


import base64

def save_base64_image(base64_string, file_path):
    """
    将 Base64 编码的图片保存到本地文件

    :param base64_string: Base64 编码的图片字符串
    :param file_path: 保存图片的文件路径
    """

    # 将 Base64 字符串解码为二进制数据
    image_data = base64.b64decode(base64_string)

    # 将二进制数据写入文件
    with open(file_path, "wb") as file:
        file.write(image_data)

    print(f"图片已保存到 {file_path}")


# 调用函数保存图片
save_base64_image(jigsawImageBase64, "jsgsaw.jpg")
save_base64_image(originalImageBase64, "originalImage.jpg")

login-captcha-check

我们看看第二个接口,这里传入了四个参数,一个是ts,一个是clientid,前面提及到过,token的值是上一个接口返回的,都不需要出,第四个参数,也就是我们要处理的值,首先,我们知道X是我们移动的距离,Y是0~100的随机值,其次,这些数据都没有加密,那就很简单了,我们直接算出X移动的距离就好了,简而言之,就是计算缺口图片缺口的位置。

 我们这里用cv2库,直接实现,直接调用,没啥难度。


from PIL import Image
import cv2
import numpy as np

def pilImgToCv2(img: Image.Image, flag=cv2.COLOR_RGB2BGR):
    return cv2.cvtColor(np.asarray(img), flag)



def getDistance(imgpath, sliceimgpath):
    img = Image.open(imgpath)
    slice = Image.open(sliceimgpath)
    # 通过 pilImgToCv2 将图片置灰
    # 背景图和滑块图都需要做相同处理
    grayImg = pilImgToCv2(img, cv2.COLOR_BGR2GRAY)
    # img.show(grayImg)
    # showImg(grayImg) # 可以通过它来看处理后的图片效果
    graySlice = pilImgToCv2(slice, cv2.COLOR_BGR2GRAY)
    # 做边缘检测进一步降低干扰,阈值可以自行调整
    grayImg = cv2.Canny(grayImg, 255, 255)
    # showImg(grayImg) # 可以通过它来看处理后的图片效果
    graySlice = cv2.Canny(graySlice, 255, 255)
    # 通过模板匹配两张图片,找出缺口的位置
    result = cv2.matchTemplate(grayImg, graySlice, cv2.TM_CCOEFF_NORMED)
    maxLoc = cv2.minMaxLoc(result)[3]
    # 匹配出来的滑动距离
    distance = maxLoc[0]
    # print(distance)
    return distance
sliceimgpath = 'jsgsaw.jpg'
imgpath = 'originalImage.jpg'
x_dis= getDistance(imgpath, sliceimgpath)

 验证码通过验证之后,会返回这样的结果:

{
    "code": "0000",
    "message": "成功",
    "originalImageBase64": null,
    "jigsawImageBase64": null,
    "token": null,
    "originalImageWidth": 0,
    "success": true
}

全部代码 

下面是这个模拟登录的全部代码,不排除后期这个系统维护升级导致代码失效。

#!/usr/bin/python3.9.6
# _*_ coding: utf-8 _*_
#
# Copyright (C) 2022 - 2024 BROKEN, Inc. All Rights Reserved 
#
# @Time    : 2025/1/5 16:34
# @Author  : 爱吃饼干的小白鼠
# @File    : 安徽大学教务系统(新).py
# @IDE     : PyCharm
# @Blog    :https://broken.blog.csdn.net/

import requests
import json
import time
import random
import hashlib
import base64
from PIL import Image
import cv2
import numpy as np

def sha1_hash(text):
    # 创建一个sha1 hash对象
    sha1 = hashlib.sha1()
    # 使用输入的文本更新hash对象
    sha1.update(text.encode('utf-8'))
    # 得到哈希摘要,并转换为十六进制字符串
    hash_output = sha1.hexdigest()
    return hash_output

session = requests.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;q=0.7",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Cache-Control": "no-cache",
    "Connection": "keep-alive",
    "Pragma": "no-cache",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "same-origin",
    "Sec-Fetch-User": "?1",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.62",
}
url = "https://jw.ahu.edu.cn/student/login"
response = session.get(url, headers=headers)
url = "http://jw.ahu.edu.cn/student/login-salt"
salt = session.get(url, headers=headers).text
new_password = salt + '-' + str("123456")
h_password = sha1_hash(new_password)


def generate_uuid():
    hexDigits = '0123456789abcdef'
    s = [''] * 36

    for i in range(36):
        if i in [8, 13, 18, 23]:
            s[i] = '-'
        else:
            s[i] = hexDigits[random.randint(0, 0xf)]

    # 设置特定的位以匹配JavaScript代码
    s[14] = '4'  # 设置版本号为4(0100)
    # 设置时钟序列的变体部分
    variant = int(s[19], 16) & 0x3  # 获取当前s[19]的低两位
    s[19] = hexDigits[(variant | 0x8) & 0xf]  # 设置变体为RFC 4122(1000)

    return ''.join(s)

# 生成UUID
uuid = generate_uuid()

timetemp = int(time.time()*1000)

url = "https://jw.ahu.edu.cn/student/login-captcha"
data = {
    "ts": timetemp,
    "clientId": uuid
}
response = session.post(url, json=data)

jigsawImageBase64 = response.json()['jigsawImageBase64']
originalImageBase64 = response.json()['originalImageBase64']
token = response.json()['token']

def save_base64_image(base64_string, file_path):
    """
    将 Base64 编码的图片保存到本地文件
    :param base64_string: Base64 编码的图片字符串
    :param file_path: 保存图片的文件路径
    """
    # 将 Base64 字符串解码为二进制数据
    image_data = base64.b64decode(base64_string)

    # 将二进制数据写入文件
    with open(file_path, "wb") as file:
        file.write(image_data)

# 调用函数保存图片
save_base64_image(jigsawImageBase64, "jsgsaw.jpg")
save_base64_image(originalImageBase64, "originalImage.jpg")

def pilImgToCv2(img: Image.Image, flag=cv2.COLOR_RGB2BGR):
    return cv2.cvtColor(np.asarray(img), flag)

def getDistance(imgpath, sliceimgpath):
    img = Image.open(imgpath)
    slice = Image.open(sliceimgpath)
    # 通过 pilImgToCv2 将图片置灰
    # 背景图和滑块图都需要做相同处理
    grayImg = pilImgToCv2(img, cv2.COLOR_BGR2GRAY)
    # img.show(grayImg)
    # showImg(grayImg) # 可以通过它来看处理后的图片效果
    graySlice = pilImgToCv2(slice, cv2.COLOR_BGR2GRAY)
    # 做边缘检测进一步降低干扰,阈值可以自行调整
    grayImg = cv2.Canny(grayImg, 255, 255)
    # showImg(grayImg) # 可以通过它来看处理后的图片效果
    graySlice = cv2.Canny(graySlice, 255, 255)
    # 通过模板匹配两张图片,找出缺口的位置
    # 通过模板匹配两张图片,找出缺口的位置
    result = cv2.matchTemplate(grayImg, graySlice, cv2.TM_CCOEFF_NORMED)
    maxLoc = cv2.minMaxLoc(result)[3]
    # 匹配出来的滑动距离
    distance = maxLoc[0]
    return distance
sliceimgpath = 'jsgsaw.jpg'
imgpath = 'originalImage.jpg'
x_dis= getDistance(imgpath, sliceimgpath)

url = "https://jw.ahu.edu.cn/student/login-captcha-check"
data = {
    "point": {
        "x": x_dis,
        "y": random.randint(0, 99)# 生成一个0到99之间的随机整数

    },
    "token": token,
    "ts": timetemp+23253,
    "clientId": uuid
}
response = session.post(url, json=data)

print(response.text)

url = "https://jw.ahu.edu.cn/student/login"
data = {
    "username": "123456",
    "password": h_password,
    "captchaToken": token
}

response = session.post(url,headers=headers, json=data)

print(response.text)

由于我们账号和密码是错误的(大家也可以用测试账号12345模拟登录,密码也是12345,看看效果吧),所以返回密码错误,但是会提示验证码正确("needCaptcha":true),就说明我们滑块验证码破解成功了,下面是运行结果:
 

四、总结

本文介绍了高校教务系统的密码加密逻辑以及使用JavaScript进行逆向分析的方法。通过学习这些知识,你可以更好地理解密码加密技术的原理,并掌握一定的逆向分析技巧。请注意,逆向分析可能涉及到法律问题,请在合法范围内进行研究和实践。

五、累计更新

争取到到底早日更新30所高校,大家可以在评论区留言。前期更的可能会多一点,有的学校教务系统都没有加密,我这里就不写了,还有,部分学校的教务系统已经和我之前写的是一样的,我也不重复赘述了。

往期作品可以查看专栏👇👇👇

全国高校教务系统登录页面JS分析_爱吃饼干的小白鼠的博客-CSDN博客

6adf31c8c5dd4e6a83314f4805b30bc1.jpg


http://www.kler.cn/a/468646.html

相关文章:

  • 计算机网络:网络层知识点及习题(一)
  • Excel | 空格分隔的行怎么导入excel?
  • GWAS数据和软件下载
  • 前端小案例——520表白信封
  • 【2025年最新】OpenWrt 更换国内源的指南(图形界面版)
  • 关于 webservice 日志中 源IP是node IP的问题,是否能解决换成 真实的客户端IP呢
  • Level DB --- BloomFilterPolicy
  • List-顺序表--2
  • Go语言的 的泛型(Generics)核心知识
  • Eclipse Memory Analyzer (MAT)
  • MongoDB基本操作
  • 常见的反规范化技术
  • 在大型语言模型LLM中使用私有数据
  • Ansible之批量管理服务器
  • 高效撰写文献综述的指南:利用ChatGPT提升研究能力
  • CPU 100% 优化排查实战
  • Maven的依赖管理
  • 深入理解卷积神经网络(CNN):图像识别的强大工具
  • R语言安装教程与常见问题
  • 第P4周-Pytorch实现猴痘病识别
  • leetcode(hot100)4
  • C++编程等级认证学习计划
  • 一种可复用的AI提效方案:AI点灯
  • Springboot项目部署以及jar包属性配置
  • 分类、聚类与回归的评价指标
  • 【NLP高频面题 - 分布式训练篇】ZeRO主要为了解决什么问题?