260 lines
9.7 KiB
Markdown
260 lines
9.7 KiB
Markdown
|
---
|
|||
|
title: 模拟登录教务系统
|
|||
|
date: 2021-08-12 17:27:41
|
|||
|
tags: python
|
|||
|
---
|
|||
|
|
|||
|
+++
|
|||
|
|
|||
|
|
|||
|
# 1.分析协议
|
|||
|
|
|||
|
## 1.1. 使用fiddler进行抓包
|
|||
|
|
|||
|
首先尝试登录,登录网页为<http://jwc.scnucas.com/home.aspx>然后通过fiddler抓包获取传递的参数。
|
|||
|
|
|||
|

|
|||
|
|
|||
|
发现其中有两个参数乱码,通过fiddler的syntaview选项可以找到内容
|
|||
|
|
|||
|
```
|
|||
|
typeName=%D1%A7%C9%FA
|
|||
|
txt_psasas=%C7%EB%CA%E4%C8%EB%C3%DC%C2%EB
|
|||
|
```
|
|||
|
|
|||
|
通过GBK解码得知,typeName的值为‘学生’, txtpsasas的值为‘请输入密码’,可得知这两个参数并不是我们需要的,
|
|||
|
此时就只能通过分析‘dsdsdsdsdxcxdfgfg’和‘fgfggfdgtyuuyyuuckjg’参数了。可猜测这两个参数是加密后的验 证码和密码。然后通过正常解密手段尝试不通,就只能阅读javascript源码了。
|
|||
|
|
|||
|
## 1.2. 使用浏览器自带的网络分析工具
|
|||
|
|
|||
|
通过浏览器的检查工具发现,登录区域是一个iframe页面,链接为<http://jwc.scnucas.com/_data/login_home.aspx>, 我们讲该网页下载到本地后进行阅读。
|
|||
|
|
|||
|
```javascript
|
|||
|
<td align="left" valign="bottom">
|
|||
|
<input class="tx1" id="txt_pewerwedsdfsdff"
|
|||
|
style="display:none"
|
|||
|
type="password" maxLength="25" name="txt_pewerwedsdfsdff" autocomplete="off"
|
|||
|
onblur="shtitblur(this);chkpwd(this)" onkeyup="chkpwd(this)">
|
|||
|
<input class="tx1" id="txt_psasas" onfocus="shtitcus(this)"
|
|||
|
style=""
|
|||
|
maxLength="25" name="txt_psasas" value="请输入密码" autocomplete="off" onkeyup="shtitblur(this)">
|
|||
|
</td>
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
通过上面这段html发现关键函数‘shtitcus(this)’,然后通过notepad++的搜索功能找到了该函数
|
|||
|
|
|||
|
```javascript
|
|||
|
function shtitcus(obj) {
|
|||
|
if (obj.id == "txt_sdertfgsadscxcadsads") {
|
|||
|
if (obj.value == "请输入验证码") {
|
|||
|
obj.value = "";
|
|||
|
obj.style.color = "black";
|
|||
|
}
|
|||
|
}
|
|||
|
if (obj.id == "txt_psasas") {
|
|||
|
if (obj.value == "请输入密码") {
|
|||
|
obj.style.display = "none";
|
|||
|
document.getElementById("txt_pewerwedsdfsdff").style.display = "";
|
|||
|
document.getElementById("txt_pewerwedsdfsdff").focus();
|
|||
|
}
|
|||
|
}
|
|||
|
if (obj.id == "txt_asmcdefsddsd") {
|
|||
|
if (obj.value == "请输入帐号") {
|
|||
|
obj.value = "";
|
|||
|
obj.style.color = "black";
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
也就是该函数判断当id为txt_psasas的input输入框获得焦点时触发,然后通过设置display的值来隐藏该输入框,并且使id为txt_pewerwedsdfsdff的
|
|||
|
input输入框获得焦点。所以当我们正常通过检查得到的input是错误的输入框。然后我们发现真实输入框还有一个事件触发函数chkpwd函数。尝试搜索到该函数。
|
|||
|
|
|||
|
```javascript
|
|||
|
function chkpwd(obj) {
|
|||
|
if (obj.value != '') {
|
|||
|
var s = md5(document.all.txt_asmcdefsddsd.value + md5(obj.value).substring(0, 30).toUpperCase() + '13671').substring(0, 30).toUpperCase();
|
|||
|
document.all.dsdsdsdsdxcxdfgfg.value = s;
|
|||
|
} else {
|
|||
|
document.all.dsdsdsdsdxcxdfgfg.value = obj.value;
|
|||
|
}
|
|||
|
chkLxstr(obj.value);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
然后我在这里就找到了上文提到的dsdsdsdsdxcxdfgfg的值了,该值就是通过加密之后的密码。然后在一起还找到了chkyzm函数。
|
|||
|
|
|||
|
```javascript
|
|||
|
function chkyzm(obj) {
|
|||
|
if (obj.value != '') {
|
|||
|
var s = md5(md5(obj.value.toUpperCase()).substring(0, 30).toUpperCase() + '13671').substring(0, 30).toUpperCase();
|
|||
|
document.all.fgfggfdgtyuuyyuuckjg.value = s;
|
|||
|
} else {
|
|||
|
document.all.fgfggfdgtyuuyyuuckjg.value = obj.value.toUpperCase();
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
所以说fgfggfdgtyuuyyuuckjg就是验证码加密后的值。至此我们基本已经有了头绪,这时候就指向了md5这个加密函数了。
|
|||
|
通过浏览器的网络分析找到md5文件里面的md5函数。然后一部一部分析发现如果需要重构这个加密函数,那不过又是大废周折。
|
|||
|
正好,最近学了node.js的一点基础。不妨直接使用node.js来调用这个加密函数。然后通过api的形式来调用。
|
|||
|
|
|||
|
# 2.模拟登录
|
|||
|
|
|||
|
## 2.1. 使用node.js编写加密api
|
|||
|
|
|||
|
在这里使用了node.js的express框架。代码如下:
|
|||
|
|
|||
|
```javascript
|
|||
|
//将md5.js下载到同级目录,
|
|||
|
//使用npm安装express和bodu-parser npm install express , npm install body-parser
|
|||
|
|
|||
|
|
|||
|
var md5 = require("./md5");
|
|||
|
var express = require("express");
|
|||
|
var path = require("path")
|
|||
|
|
|||
|
var app = express();
|
|||
|
var bodyParser = require('body-parser');
|
|||
|
app.use(bodyParser.urlencoded({
|
|||
|
extended:true
|
|||
|
}));
|
|||
|
|
|||
|
function chkpwd(account, pass) {
|
|||
|
if (pass !== '') {
|
|||
|
return md5(account + md5(pass).substring(0, 30).toUpperCase() + '13671').substring(0, 30).toUpperCase();
|
|||
|
} else {
|
|||
|
return "";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function chkyzm(value) {
|
|||
|
if (value !== '') {
|
|||
|
return md5(md5(value.toUpperCase()).substring(0, 30).toUpperCase() + '13671').substring(0, 30).toUpperCase();
|
|||
|
} else {
|
|||
|
return ""
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
app.post("/login",function (req,res) {
|
|||
|
var account = req.body.account;
|
|||
|
console.log(account)
|
|||
|
var password = req.body.password;
|
|||
|
console.log(account+password)
|
|||
|
var data = chkpwd(account,password);
|
|||
|
res.send(data)
|
|||
|
})
|
|||
|
|
|||
|
app.post("/yzm",function (req,res) {
|
|||
|
var value = req.body.value;
|
|||
|
var yzm = chkyzm(value);
|
|||
|
res.send(yzm)
|
|||
|
})
|
|||
|
|
|||
|
app.listen(10001,"127.0.0.1")
|
|||
|
```
|
|||
|
|
|||
|
当我们此时使用post方法访问<http://127.0.0.1:10001/login>,然后加上需要加密账号和密码,就可以返回
|
|||
|
加密后的内容了。同理<http://127.0.0.1:10001/yzm>可以对验证码进行加密。
|
|||
|
|
|||
|
## 2.2. 使用python模拟登录
|
|||
|
|
|||
|
首先创建类和构造方法
|
|||
|
|
|||
|
```python
|
|||
|
class Education:
|
|||
|
|
|||
|
def __init__(self, account: str, password: str):
|
|||
|
self.account = account
|
|||
|
self.password = password
|
|||
|
self.client = httpx.Client()
|
|||
|
self.source = "http://jwc.scnucas.com"
|
|||
|
```
|
|||
|
|
|||
|
然后在类里面编写两个加密api调用的函数
|
|||
|
|
|||
|
```python
|
|||
|
//加密账户和密码
|
|||
|
def __md5Account(self) -> str:
|
|||
|
data = {
|
|||
|
"account": self.account,
|
|||
|
"password": self.password
|
|||
|
}
|
|||
|
return httpx.post("http://47.110.228.1:10001/login", data=data).text
|
|||
|
|
|||
|
//加密验证码
|
|||
|
def __md5_verification(self):
|
|||
|
content = input("请输入验证码:")
|
|||
|
data = {
|
|||
|
"value": content
|
|||
|
}
|
|||
|
result = httpx.post("http://47.110.228.1:10001/yzm", data=data)
|
|||
|
|
|||
|
return result
|
|||
|
```
|
|||
|
|
|||
|
然后编写发送请求的工具方法,因为教务系统偶尔会返回503服务器繁忙,所以我们对此进行判断并且重新进行请求,
|
|||
|
|
|||
|
```python
|
|||
|
def __ask(self, url: str, method: str, data=None) -> httpx.Response: //带上常用请求头 headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'jwc.scnucas.com', 'Referer': 'http://jwc.scnucas.com/_data/login_home.aspx', 'User-Agent': 'Mozilla / 5.0(Windows NT 10.0;Win64;x64) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / 88.0.4324.96Safari / 537.36Edg / 88.0.705.50' } if data is None: data = {} response = self.client.request(method=method, url=url, headers=headers, data=data, allow_redirects=False,) code = response.status_code while code >= 500: response = self.client.request(method=method, url=url, data=data, allow_redirects=False) code = response.status_code response.encoding = "GBK" return response
|
|||
|
```
|
|||
|
|
|||
|
最后就是调用该方法进行登录
|
|||
|
|
|||
|
```python
|
|||
|
def login(self):
|
|||
|
//这是模拟使用get方法获取登录页面
|
|||
|
self.__ask(self.source + "/_data/login_home.aspx", "GET", data=None)
|
|||
|
//该接口是获得验证码的接口
|
|||
|
a = self.__ask(self.source + "/sys/ValidateCode.aspx", "GET").content
|
|||
|
with open("code.png", "wb+") as f:
|
|||
|
f.write(a)
|
|||
|
//使用PIL库的Image方法打开验证码
|
|||
|
Image.open("code.png").show()
|
|||
|
data = {
|
|||
|
# "__VIEWSTATE": view,
|
|||
|
"pcInfo": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
|
|||
|
"Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50undefined5.0 (Windows NT 10.0; Win64; x64) "
|
|||
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50 "
|
|||
|
"SN:NULL",
|
|||
|
"txt_mm_expression": "",
|
|||
|
"txt_mm_length": "",
|
|||
|
"txt_mm_userzh": "",
|
|||
|
"typeName": "学生",
|
|||
|
"dsdsdsdsdxcxdfgfg": self.__md5Account(),
|
|||
|
"fgfggfdgtyuuyyuuckjg": self.__md5_verification().text,
|
|||
|
|
|||
|
"Sel_Type": "STU",
|
|||
|
"txt_asmcdefsddsd": "19104978",
|
|||
|
"txt_pewerwedsdfsdff": "",
|
|||
|
"txt_psasas": "请输入密码",
|
|||
|
"txt_sdertfgsadscxcadsads": ""
|
|||
|
}
|
|||
|
|
|||
|
result = self.__ask(self.source + "/_data/login_home.aspx", "POST", data=data).text
|
|||
|
```
|
|||
|
|
|||
|
通过发现当登录成功的时候,就会出现‘window.top.document.location.replace("../MAINFRM.aspx")’语句进行页面跳转。
|
|||
|
所以我们只需要判断返回的结果有没有该语句就可以确定是否登录成功。修改上面代码为:
|
|||
|
|
|||
|
```python
|
|||
|
result = self.__ask(self.source + "/_data/login_home.aspx", "POST", data=data).text.find(
|
|||
|
'window.top.document.location.replace("../MAINFRM.aspx")')
|
|||
|
if result != -1:
|
|||
|
print(f"登录成功\n{self.account}")
|
|||
|
else:
|
|||
|
print("登录失败")
|
|||
|
exit(3)
|
|||
|
```
|
|||
|
|
|||
|
然后尝试调用该类
|
|||
|
|
|||
|
```python
|
|||
|
if __name__ == '__main__':
|
|||
|
account = input("请输入你的教务系统账号:")
|
|||
|
password = input("请输入你的教务系统密码:")
|
|||
|
e = Education(account=account,password=password)
|
|||
|
e.login()
|
|||
|
```
|