记一次在线学习平台的漏洞利用
注:此漏洞已汇报给IT,已被修复
目录
- 漏洞介绍
- 具体实现
- 整体实现代码
漏洞介绍
在线学习平台study.test***.cn中SAT题卡允许用户重复提交,且提交后可获得错题报告,因此可以通过填3次题卡通过报告推出正确选项然后填第4次提交。但由于在提交一次后系统不允许再从主页面创建题卡的句柄,只有创建多个句柄后依次填卡提交才能利用此漏洞。此漏洞可以人为在浏览器中操作利用,但由于人工填4张卡和推到正确选项过于费时,利用脚本模拟浏览器操作更高效。
具体实现
使用了Python + Selenium模拟浏览器操作,xpath解析HTML
获取页面
对应代码如下:
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--disable-gpu')
browzer = webdriver.Chrome(options=chrome_options)
login_url = 'https://study.test***.cn'
browzer.get(login_url)
登陆
找到用户名输入框 -> 输入用户名 -> 找到密码框 -> 输入密码 -> 回车
对应代码如下:
user_name = '******'
password = '******'
browzer.find_element_by_id('username').send_keys(user_name)
browzer.find_element_by_id('password').send_keys(password)
browzer.find_element_by_xpath('//body').send_keys(Keys.ENTER)
找到题卡
系统提供了搜索功能,所以可以 找到搜索框 -> 输入题卡名称
对应代码如下:
search_kw = input('Task name: ')
browzer.find_element_by_xpath('//input[@placeholder="Search…"]').send_keys(search_kw)
创建四个题卡句柄
点击“开始”按钮为创建一个题卡句柄
加上等待以防阻塞
对应代码如下:
browzer.find_element_by_xpath('//span[@title="开始任务"]').click()
time.sleep(4)
browzer.find_element_by_xpath('//span[@title="开始任务"]').click()
time.sleep(4)
browzer.find_element_by_xpath('//span[@title="开始任务"]').click()
time.sleep(4)
browzer.find_element_by_xpath('//span[@title="开始任务"]').click()
time.sleep(4)
填卡推答案然后填第四张卡
此时一共有五个句柄。第0个是主页面,1-4个是题卡句柄。
切换到句柄1 -> 全填A -> 提交 -> 分析报告 -> 结果存入字典
*填卡是通过找到选项单元格元素然后点击
*提交是指找到提交按钮并且点击
*分析报告是找出正确(没有红叉)的题目
*存入字典是指把正确的答案存入字典
切换到句柄2 -> 全填B -> 提交 -> 分析报告 -> 结果存入字典
切换到句柄3 -> 全填C -> 提交 -> 分析报告 -> 结果存入字典
整理字典,字典中没有答案的题答案为D (因为前面A,B,C都试过了)
切换到句柄4 ,用字典中的答案填卡 -> 提交 -> 满分SAT模考诞生;但很明显真实的做满分不太可能(对我来说),因此为了成功欺骗老师可以通过一些社工手段,即部分随机错误答案来达到自己想要的正确率。
对应代码如下:
CorrectAnswerlist = {}
for trial_t in range(3):
print(CorrectAnswerlist)
browzer.switch_to.window(browzer.window_handles[1 + trial_t])
time.sleep(2)
if trial_t == 0:
card_body = browzer.find_element_by_xpath('//div[@class="ant-card-body"]').get_attribute('innerHTML')
# Get number range
html = etree.HTML(card_body)
etree.tostring(html, encoding= 'utf-8')
q_nums = html.xpath('//div[contains(@class,"ant-col-4")]/text()')
q_nums = [int(i) for i in q_nums]
# print(q_nums)
choice = {'1' : 'A', '2' : 'B', '3' : 'C', '4' : 'D'}[str(trial_t + 1)]
for q_ind in q_nums:
browzer.find_element_by_xpath('//div[@class="ant-card-body"]//div[contains(@class, "ant-col-4") and text()="' + str(q_ind) + '"]/../div[contains(@class, "ant-col-16")]//span[text()="' + choice + '"]').click()
browzer.find_element_by_xpath('//span[text()="提交答题卡"]/..').click()
time.sleep(4)
browzer.find_element_by_xpath('//div[@class="ant-modal-confirm-btns"]//span[text()="提交答题卡"]/../..').click()
time.sleep(4)
card_body = browzer.find_element_by_xpath('html').get_attribute('innerHTML')
# 找 class="ant-col ant-col-xs-4 ant-col-sm-3 ant-col-md-3" 的div的个数,那么(n/2)个到最后就是
print(card_body)
html = etree.HTML(card_body)
etree.tostring(html, encoding= 'utf-8')
roots = html.xpath('//div[@class="ant-col ant-col-xs-4 ant-col-sm-3 ant-col-md-3"]')
for i in range(len(q_nums)):
ToF = roots[len(q_nums) + i].xpath('div[2]//svg/@height')
if len(ToF) == 0:
CorrectAnswerlist.update({str(q_nums[i]) : choice})
for q_ind in q_nums:
if str(q_ind) not in CorrectAnswerlist:
CorrectAnswerlist.update({str(q_ind) : 'D'})
browzer.switch_to.window(browzer.window_handles[4])
time.sleep(4)
print(CorrectAnswerlist)
# Fill the card, with expected error rate
# Here is 0.25.
for q_ind in q_nums:
if random.random() > 0.25: # To make it 100% correct, use -1
choice = CorrectAnswerlist[str(q_ind)]
else:
aaa = random.random()
if aaa < 0.25:
choice = 'C'
elif aaa < 0.5 and aaa > 0.25:
choice = 'D'
elif aaa < 0.75 and aaa > 0.5:
choice = 'A'
else:
choice = 'B'
browzer.find_element_by_xpath('//div[@class="ant-card-body"]//div[contains(@class, "ant-col-4") and text()="' + str(q_ind) + '"]/../div[contains(@class, "ant-col-16")]//span[text()="' + choice + '"]').click()
browzer.find_element_by_xpath('//span[text()="提交答题卡"]/..').click()
time.sleep(4)
browzer.find_element_by_xpath('//div[@class="ant-modal-confirm-btns"]//span[text()="提交答题卡"]/../..').click()
time.sleep(6)
整体实现代码
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
from lxml import etree
import random
chrome_options = webdriver.ChromeOptions()
# chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
browzer = webdriver.Chrome(options=chrome_options)
login_url = 'https://study.test***.cn'
user_name = '******'
password = '******'
browzer.get(login_url)
browzer.find_element_by_id('username').send_keys(user_name)
browzer.find_element_by_id('password').send_keys(password)
browzer.find_element_by_xpath('//body').send_keys(Keys.ENTER)
time.sleep(4)
search_kw = input('Task name: ')
browzer.find_element_by_xpath('//input[@placeholder="Search…"]').send_keys(search_kw)
time.sleep(4)
browzer.find_element_by_xpath('//span[@title="开始任务"]').click()
time.sleep(4)
browzer.find_element_by_xpath('//span[@title="开始任务"]').click()
time.sleep(4)
browzer.find_element_by_xpath('//span[@title="开始任务"]').click()
time.sleep(4)
browzer.find_element_by_xpath('//span[@title="开始任务"]').click()
time.sleep(4)
time.sleep(2)
print(browzer.window_handles)
CorrectAnswerlist = {}
for trial_t in range(3):
print(CorrectAnswerlist)
browzer.switch_to.window(browzer.window_handles[1 + trial_t])
time.sleep(2)
if trial_t == 0:
card_body = browzer.find_element_by_xpath('//div[@class="ant-card-body"]').get_attribute('innerHTML')
# Get number range
html = etree.HTML(card_body)
etree.tostring(html, encoding= 'utf-8')
q_nums = html.xpath('//div[contains(@class,"ant-col-4")]/text()')
q_nums = [int(i) for i in q_nums]
# print(q_nums)
choice = {'1' : 'A', '2' : 'B', '3' : 'C', '4' : 'D'}[str(trial_t + 1)]
for q_ind in q_nums:
browzer.find_element_by_xpath('//div[@class="ant-card-body"]//div[contains(@class, "ant-col-4") and text()="' + str(q_ind) + '"]/../div[contains(@class, "ant-col-16")]//span[text()="' + choice + '"]').click()
browzer.find_element_by_xpath('//span[text()="提交答题卡"]/..').click()
time.sleep(4)
browzer.find_element_by_xpath('//div[@class="ant-modal-confirm-btns"]//span[text()="提交答题卡"]/../..').click()
time.sleep(4)
card_body = browzer.find_element_by_xpath('html').get_attribute('innerHTML')
# 找 class="ant-col ant-col-xs-4 ant-col-sm-3 ant-col-md-3" 的div的个数,那么(n/2)个到最后就是
print(card_body)
html = etree.HTML(card_body)
etree.tostring(html, encoding= 'utf-8')
roots = html.xpath('//div[@class="ant-col ant-col-xs-4 ant-col-sm-3 ant-col-md-3"]')
for i in range(len(q_nums)):
ToF = roots[len(q_nums) + i].xpath('div[2]//svg/@height')
if len(ToF) == 0:
CorrectAnswerlist.update({str(q_nums[i]) : choice})
print(CorrectAnswerlist)
for q_ind in q_nums:
if str(q_ind) not in CorrectAnswerlist:
CorrectAnswerlist.update({str(q_ind) : 'D'})
browzer.switch_to.window(browzer.window_handles[4])
time.sleep(4)
print(CorrectAnswerlist)
for q_ind in q_nums:
if random.random() > 0.25:
choice = CorrectAnswerlist[str(q_ind)]
else:
aaa = random.random()
if aaa < 0.25:
choice = 'C'
elif aaa < 0.5 and aaa > 0.25:
choice = 'D'
elif aaa < 0.75 and aaa > 0.5:
choice = 'A'
else:
choice = 'B'
browzer.find_element_by_xpath('//div[@class="ant-card-body"]//div[contains(@class, "ant-col-4") and text()="' + str(q_ind) + '"]/../div[contains(@class, "ant-col-16")]//span[text()="' + choice + '"]').click()
browzer.find_element_by_xpath('//span[text()="提交答题卡"]/..').click()
time.sleep(4)
browzer.find_element_by_xpath('//div[@class="ant-modal-confirm-btns"]//span[text()="提交答题卡"]/../..').click()
time.sleep(6)