使用Java模拟登录KINGOSOFT青果教务系统(一)

分析 KINGOSOFT 青果教务系统登录逻辑

  • 前言
  • 分析登录流程
    • 0x1
    • 0x2
    • 0x3
    • 0x4
      • chapter 1
      • chapter 2
      • chapter 3
      • capter 4
      • chapter 5
        • chapter 5.1
        • chapter 5.2
        • chapter 5.3
      • chapter 6
      • chapter 7
      • chapter 8
  • 总结

前言

首先,我们学校的教务系统是青果的教务系统,KINGOSOFT 教务系统应该是青果的新版本的教务系统。不知道为什么是英文,也许是英文显得比较厉害一点。

其次,我们学校的教务系统长这个样子

使用Java模拟登录KINGOSOFT青果教务系统(一)_第1张图片

这个样子

使用Java模拟登录KINGOSOFT青果教务系统(一)_第2张图片

以及这个样子的

使用Java模拟登录KINGOSOFT青果教务系统(一)_第3张图片

分析登录流程

0x1

我们要做到模拟登录,那么就应该是模拟真实的用户来进行登录,那么我们就来先分析一下他的登录逻辑。

  • 通过元素审查,可以发现,在用户点击登录按钮之后,页面执行了这个 doLogon() 的方法,那么我们就进入这个方法看看,这个方法到底做了些什么事情

使用Java模拟登录KINGOSOFT青果教务系统(一)_第4张图片

  • 我们使用调试器查找到这个方法的具体位置

使用Java模拟登录KINGOSOFT青果教务系统(一)_第5张图片

使用Java模拟登录KINGOSOFT青果教务系统(一)_第6张图片

使用Java模拟登录KINGOSOFT青果教务系统(一)_第7张图片

这是 doLogon() 方法的 js 代码

function doLogon() {
	
	// 输入信息验证
	if (!validate()) {
		return false;
	}
	
	// 验证码正确性验证
	var username = j$("#yhmc").val();
	var password = j$("#yhmm").val();
	var randnumber = j$("#randnumber").val();
	var passwordPolicy = kutil.isPasswordPolicy(username, password);
	var url = _webRootPath + "cas/logon.action";
	password = hex_md5(hex_md5(password)+hex_md5(randnumber.toLowerCase()));
	/**
	var params = {
					"yhmc" : username,
					"yhmm" : password,
					"randnumber": randnumber,
					"isPasswordPolicy" : passwordPolicy
				};
	*/				
	var p_username = "_u"+randnumber;
	var p_password = "_p"+randnumber;
	username = base64encode(username+";;"+_sessionid);
	var params = p_username+"="+username+"&"+p_password+"="+password+"&randnumber="+randnumber+"&isPasswordPolicy="+passwordPolicy ;
	//alert("params="+params);
	params = getEncParams(params); 
	//alert("encparams="+params);
	doPreLogon();					
	kutil.doAjax(url, params, doPostLogon);					
	
	function doPreLogon(){
		j$("#msg").html("正在登录......");
		j$("#login").attr("disabled", true); 
		j$("#reset").attr("disabled", true);
	}

	function doPostLogon(response) {
		var data = JSON.parse(response); 
		var status = data.status ;
		var message = data.message ;
		if ("200" == status) {
			var result = data.result ;
			window.document.location.href = result ;
		} else {
			reloadScript("kingo_encypt",_webRootPath+"custom/js/SetKingoEncypt.jsp"); 		
			if("407" == status){
				alert(message);
				showMessage("");
			}else{
				showMessage(message);
			}
			j$("#login").attr("disabled", false); 
			j$("#reset").attr("disabled", false);
			refreshImg();
			if ("401"==status) {
				j$("#randnumber").val("");
				j$("#randnumber").focus();
			} else {
				j$("#yhmc").val("");
				j$("#yhmm").val("");
				j$("#randnumber").val("");
				j$("#yhmc").focus();
			}
		}
	}
}

function validate() {
	var username = j$("#yhmc").val();
	var password = j$("#yhmm").val();
	var randnumber = j$("#randnumber").val();
	if (kutil.isNull(username)) {
		showMessage("请输入用户名!");
		j$("#yhmc").focus();
		return false;
	}
	if (kutil.isNull(password)) {
		showMessage("请输入密码!");
		j$("#yhmm").focus();
		return false;
	}
	if (kutil.isNull(randnumber)) {
		showMessage("请输入验证码!");
		j$("#randnumber").focus();
		return false;
	}
	return true;
}

我们接下来,一句一句的慢慢分析

0x2

// 输入信息验证
if (!validate()) {
    return false;
}

这里调用了一个 validate() 方法,可以看到,如果想要继续执行下去,这个方法就应该返回 true ,否则的话,就会直接 return 结束这个 doLogon() 方法那么我们就看下这个 validate() 方法里面做了些什么事情。

function validate() {
	var username = j$("#yhmc").val();
	var password = j$("#yhmm").val();
	var randnumber = j$("#randnumber").val();
	if (kutil.isNull(username)) {
		showMessage("请输入用户名!");
		j$("#yhmc").focus();
		return false;
	}
	if (kutil.isNull(password)) {
		showMessage("请输入密码!");
		j$("#yhmm").focus();
		return false;
	}
	if (kutil.isNull(randnumber)) {
		showMessage("请输入验证码!");
		j$("#randnumber").focus();
		return false;
	}
	return true;
}

到这里可以看到 validate() 方法显示获取了用户输入的用户名、密码和验证码,然后使用封装好的 kutil 对象中的 isNull 方法进行了验证,只有当得到的值都为 false 的时候,才能执行最后的 return true

那我们就来看看,这个 isNull 方法到底是怎么验证的。我们继续使用调试器中的全局搜索找到这个方法。

/**
 * 判断某个变量值是否为空值
 * 
 */
my.isNull = function(initValue){
    if (initValue == null || initValue.length == 0) {
        return true;
    } else {
        return false;
    }
}

可以看见这个方法只是一个单纯的判断字符串是否为空,如果为空则返回 true,否则返回 false。

到这里,这个就很明确了,当三个输入框(用户名、密码、验证码)中得任何一个没有输入内容的时候,都会结束这个 doLogon() 方法,并且给出相应的提示。

0x3

我们接着分析后面的代码,在用户输入的用户名、密码、验证码都不为空的情况下,就会执行后面的代码

// 验证码正确性验证
var username = j$("#yhmc").val();
var password = j$("#yhmm").val();
var randnumber = j$("#randnumber").val();
var passwordPolicy = kutil.isPasswordPolicy(username, password);
var url = _webRootPath + "cas/logon.action";

可以看见,这里又在一次获取到了三个输入框的值,并赋值给 usernamepasswordrandnumber,代表的意思就是用户名、密码、验证码。

然后 passwordPolicy 这个变量的值是通过执行了 kutil 对象中的 isPasswordPolicy 方法获取到的,我们在全局中搜索这个方法,然后找到了下面的代码

/**
 * 检测密码是否符合密码策略
 * 1、用户密码不能与登陆帐号一致。
 * 2、用户密码必须为6位或6位以上字符长度,并且包含有字符和数字。
 * return 1-符合; 0-不符合
 */
my.isPasswordPolicy = function(username, password){
    if (password == "" || password == null || username == password){
        return "0" ;
    }
    var passwordlen = new String(password).length ;
    if (passwordlen < 6){
        return "0" ;
    }
    /* 2019.2.16 因密码复杂度要求可以设置不需要包括字母和数字而去掉
		var letter = new String(password).replace(/[^A-Za-z]/g, ""); // 保留字母
		var letterlen = letter.length ;
		if (letterlen == passwordlen) { 
			return "0";
		}
		var digit = new String(password).replace(/[^0-9]/g, ""); // 保留数字
		var digitlen = digit.length ;
		if (digitlen == passwordlen) { 
			return "0";
		}
		*/
    return "1" ;
}

可以看见,这个方法中,主要是校验密码是否符合要求的。包括密码不能为空、密码不能和用户名相同以及密码的长度不能小于 6。如果符合的话就返回 1, 否则就返回 0。如果我们想要模拟登录的话,这个值就应该为 1

这样在前端校验密码是否符合要求,应该是为了减轻后台服务器的压力。毕竟密码都符合要求,那么密码肯定是错的。

0x4

那么,接下来,我们就来看看,在获取到了输入框中用户输入的值之后,做了些什么事情吧。

password = hex_md5(hex_md5(password)+hex_md5(randnumber.toLowerCase()));
/**
	var params = {
					"yhmc" : username,
					"yhmm" : password,
					"randnumber": randnumber,
					"isPasswordPolicy" : passwordPolicy
				};
	*/				
var p_username = "_u"+randnumber;
var p_password = "_p"+randnumber;
username = base64encode(username+";;"+_sessionid);
var params = p_username+"="+username+"&"+p_password+"="+password+"&randnumber="+randnumber+"&isPasswordPolicy="+passwordPolicy ;
//alert("params="+params);
params = getEncParams(params); 
//alert("encparams="+params);
doPreLogon();					
kutil.doAjax(url, params, doPostLogon);

可以看见,这里就开始对获取到的数据进行加密了。那么我们就来逐行看看这里的数据是如何加密的吧。

chapter 1

password = hex_md5(hex_md5(password)+hex_md5(randnumber.toLowerCase()));

这里先将密码(password)使用 hex_md5 方法进行加密,然后将验证码(randnumber)转换成了小写(这里其实没有必要,因为青果的验证码是都是数字,但是我们现在是在分析青果是怎么做的)。接着将这两个加密后的字符串拼接在一起之后,再使用 hex_md5 方法进行了一次加密。

可以看到,在这一句中 hex_md5 方法是关键,那么这个方法到底是什么呢,看名字我觉得应该是 md5 加密,那么我们就查找到这个方法,看看究竟是不是 md5 加密吧。

使用Java模拟登录KINGOSOFT青果教务系统(一)_第8张图片

emmm~~~看不懂。那么就是当作他是 MD5 加密了(敷衍了····)。因为我之前没有接触 MD5加密,所以我是通过百度了 MD5 加密,然后使用 java 实现 MD5 加密。然后我通过自己写 html 调用这个这个方法和 java 写的方法得到的加密结果是否一致。然后确定了这确实就是 MD5加密。

那么这句代码就很明显了。总共进行了三次 MD5加密。

1、使用 MD5加密用户输入的密码

2、将用户输入的验证码转成小写之后,使用了 MD5 进行了加密

3、上面两个加密的结果进行拼接,然后再进行一次 MD5 加密

这时,我们就得到了加密后的密码(password)

chapter 2

var p_username = "_u"+randnumber;
var p_password = "_p"+randnumber;

这里,声明了两个变量 p_username、p_password。他们的值是使用的是字符串 _u_p 分别拼接上验证码(randnumber)得到的。

chapter 3

username = base64encode(username+";;"+_sessionid);

这里开始对用户名进行加密了,这里先在用户输入的用户名后面拼接了两个字符串 ;;_sessionid,这个 _sessionid 就是 session,我们之后在模拟登录的时候,再说说这个值是怎么获取到的。

然后将拼接好的字符串使用 base64encode 这个方法进行了加密,看名字,这个方法应该是 base64 加密。我们看看这个方法是不是如我们想的一样,是 base64 加密呢。

使用Java模拟登录KINGOSOFT青果教务系统(一)_第9张图片

可以看见,这个方法确实如猜想的一样,是 base64 加密

capter 4

var params = p_username+"="+username+"&"+p_password+"="+password+"&randnumber="+randnumber+"&isPasswordPolicy="+passwordPolicy ;
//alert("params="+params);
params = getEncParams(params); 

这里就是将我们得到的加密后的数据进行了拼接成了上面的格式,需要注意的是。isPasswordPolicy 这个字段是校验密码是否符合条件的,如果符合的话,这个值应该是 1,我们在模拟登录的时候,这个值可以直接设置为 1

拿到拼接好之后的 params,使用了 getEncParams 方法,再进行了一次加密。那~~这个方法是怎么加密的呢。我们再进去看看。

chapter 5

使用Java模拟登录KINGOSOFT青果教务系统(一)_第10张图片

这里我把这里用到的主要方法提取到下面进行分析

var _deskey = '6531555511500043279';
var _nowtime = '2019-04-17 22:31:40';
function b64_encode(data) { return base64encode(utf16to8(data)); } 
function md5(data) { return hex_md5(data); } 
function des_encode(data) { return strEnc(data, _deskey, null, null); } 

function getEncParams(params) { 
 var timestamp = _nowtime; 
 var token = md5(md5(params)+md5(timestamp)); 
 var _params = b64_encode(des_encode(params)); 
 _params = "params=" + _params + "&token="+token+"×tamp="+timestamp; 
 return _params; 
 } 

chapter 5.1

function md5(data) { return hex_md5(data); } 

var timestamp = _nowtime; 
var token = md5(md5(params)+md5(timestamp));

这里需要注意的是:_nowtime 这个变量是当前时间的,_deskey 这个变量我没有完全懂是怎么生成的,只知道中间的13位是 _nowtime 这个时间转成到毫秒的时间戳的结果。前后的三位数字没有找到到底是什么。

所以在模拟登录的时候,这两个变量的值,只能从这个jsp页面中获取。这个页面的地址是 http://jw.tgc.edu.cn/custom/js/SetKingoEncypt.jsp

这里,和加密用户名密码的时候差不多,也是进行了三次 MD5 加密。将 params 和 timestamp 分别进行MD5加密,然后将拼接后的字符串再进行一次 MD5 加密。得到了 token

chapter 5.2

function b64_encode(data) { return base64encode(utf16to8(data)); } 
function des_encode(data) { return strEnc(data, _deskey, null, null); } 

var _params = b64_encode(des_encode(params));

可以看到这里将传递进来的 params 进行了两次加密,显示使用 des_encode 进行加密。然后再进行一次 b64_encode 加密。

b64_encode 我们已经知道这个是 base64 加密了。那么 des_encode 是什么呢。我们查找到这个方法看看是什么。

function des_encode(data) { return strEnc(data, _deskey, null, null); } 

这个方法又调用了 strEnc() 这个方法,传递了总共传递了 4 个参数。第一个是传递过来的 params 参数,第二个参数是 _deskey,这个值在前面分析了,是服务器动态生成的,只能从这个 jsp 页面获取。

那么这个 strEnc 方法是什么呢,我们继续全局搜索看看这个方法是什么。

使用Java模拟登录KINGOSOFT青果教务系统(一)_第11张图片

可以看到这个方法起始是 Des 加密。那么来总结一下这个 传递进来的 params 是怎么加密的:

  1. 使用 Des 加密,加密参数总共是4个,第一个为 params,第二个为 _deskey,第三个和第四个都为 null
  2. 将第一次加密后的结果进行 base64 加密

这样就得到了加密后的 params

chapter 5.3

 _params = "params=" + _params + "&token="+token+"×tamp="+timestamp; 
 return _params; 

这里开始将这些值进行拼接,这里的作用应该是为了拼接登录请求。因为在登录的时候,发送的表单数据格式是下面这个样子

使用Java模拟登录KINGOSOFT青果教务系统(一)_第12张图片

chapter 6

doPreLogon();		

function doPreLogon(){
    j$("#msg").html("正在登录......");
    j$("#login").attr("disabled", true); 
    j$("#reset").attr("disabled", true);
}

这里这是做的只是一个提示的功能

chapter 7

kutil.doAjax(url, params, doPostLogon);	

可以看见这里开始发送请求了,这个会发送携带之前拼接好的参数Post到 cas/logon.action 这个地址。

在执行之后,有这个 doPostLogon 回调函数会被调用。

chapter 8

到这里我们的登录逻辑就分析完成了,我们在看看这个回调函数会做些什么事情。

function doPostLogon(response) {
    var data = JSON.parse(response); 
    var status = data.status ;
    var message = data.message ;
    if ("200" == status) {
        var result = data.result ;
        window.document.location.href = result ;
    } else {
        reloadScript("kingo_encypt",_webRootPath+"custom/js/SetKingoEncypt.jsp"); 		
        if("407" == status){
            alert(message);
            showMessage("");
        }else{
            showMessage(message);
        }
        j$("#login").attr("disabled", false); 
        j$("#reset").attr("disabled", false);
        refreshImg();
        if ("401"==status) {
            j$("#randnumber").val("");
            j$("#randnumber").focus();
        } else {
            j$("#yhmc").val("");
            j$("#yhmm").val("");
            j$("#randnumber").val("");
            j$("#yhmc").focus();
        }
    }
}

首先在发送请求之后,我们会得到这样的一条 JSON 数据,如果状态码是 200 ,说明登录成功,那么就会跳转到 result 中的地址去。

{"message":"操作成功!","result":"\/MainFrm.html?random=0.8587982073702812","status":"200"}

如果状态码是 402,说明密码错误,会得到这样的一条数据

{"message":"您24小时内已输错1次密码,若超过5次,您的帐号将被锁定!","result":null,"status":"402"}

如果状态码是 401,说明验证码错误,会得到这样的一条数据

{"message":"验证码有误!","result":null,"status":"401"}

总结

那么到这里,整个登录的逻辑就已经分析完成。第一次学习分析,还是很难受,虽然他的所有 js 都是明文的,但是在看到加密的时候还是会很懵逼,因为之前没有做过在前端对数据进行加密。在看到 MD5 加密和 Des 加密的时候有点懵逼,不知道怎么去实现这样的一个加密方法。以及这两种加密在前端和后端加密的过程是不一样的。

之前也有记笔记的习惯。但是一直都是放在本地的。这次分析青果的登录逻辑,虽然算不得什么,但是对于我来时,是之前没有做过的事情,所以把他放在博客上面。

下面我准备使用 Java 实现这样的一个模拟用户登录的过程,然后再获取成绩之类的数据。

你可能感兴趣的:(青果教务系统)