【=W=】这个答题系统怎么答案都在前端

一条求助信息

昨天朋友突然发来微信,说自己在帮父亲做一份每日考题,是强制要求完成的。她从朋友手中弄了一份 题号->答案 的表格,其中题号是 2-4 位的英文编码,但是线上小程序都是一道道题目,题号都是顺序的数字,于是问我怎么能看到题号?

编码大概如下:

1
2
3
4
5
6
7
8
|-------|-------|
| 题号 | 答案 |
|-------|-------|
| MSQ | B |
|-------|-------|
| AU | 对 |
|-------|-------|
| ... | ... |

嗯虽然有悖答题道德,我还是看了下这个网站 https://lf.health-china.com/ ,不看不知道,一看把我逗笑了,部分代码节选 (exam.html):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<div val="0" valtype="1" class="topic_page">
<div class="examBg">
<div class="type">
<span class="point"></span>
<div class="typeCon">
单选题 <span class="arrow"></span>
</div>
<span class="point"></span>
</div>
<div class="number">1 / 20</div>
<div class="questions">
第1题:<br> 疫苗存在供应短缺风险时,国务院卫生健康主管部门、国务院药品监督管理部门( ),国务院工业和信息化主管部门、国务院财政部门应当采取有效措施保障疫苗( )。 </div>
<!--<img src="/assets/index/img/exam/examBg.png">-->
<img class="bg1" src="/assets/index/img/exam/bg1.png">
<div class="bg2">
<img src="/assets/index/img/exam/bg2.png">
</div>
<img class="bg3" src="/assets/index/img/exam/bg3.png">
<input type="hidden" class="topic_id" value="799">
</div>
<div>
<!-- 选项 -->
<div class="selectCon">
<div class="answer_options answer_A">
<span>A、</span>
<p>做出供应决定;生产、供应</p>
<img src="/assets/index/img/exam/error.png" class="error_img" style="display: none;">
<img src="/assets/index/img/exam/correct.png" class="correct_img" style="display: none;">
</div>
<div class="answer_options answer_B">
<span>B、</span>
<p>做出供应决定;研发、供应</p>
<img src="/assets/index/img/exam/error.png" class="error_img" style="display: none;">
<img src="/assets/index/img/exam/correct.png" class="correct_img" style="display: none;">
</div>
<div class="answer_options answer_C">
<span>C、</span>
<p>提出建议;研发、供应</p>
<img src="/assets/index/img/exam/error.png" class="error_img" style="display: none;">
<img src="/assets/index/img/exam/correct.png" class="correct_img" style="display: none;">
</div>
<div class="answer_options answer_D">
<span>D、</span>
<p>提出建议;生产、供应</p>
<img src="/assets/index/img/exam/error.png" class="error_img" style="display: none;">
<img src="/assets/index/img/exam/correct.png" class="correct_img" style="display: none;">
</div>
</div>
<p class="showAnalysis" style="display: none;">查看题目解析</p>
<a href="javascript:;" class="startButton sureButton">确定</a>
<a href="javascript:;" class="startButton nextButton" style="display: none;">下一题</a>
<a href="javascript:;" class="startButton submitButton" style="display: none;">提交</a>
</div>
<!-- 答题解析 -->
<div class="analysisDiv">
<div class="analysisCon">
<div class="analysisContent">
<p class="analysisTitle">答题解析</p>
<p class="analysisDetail"><span>正确答案:</span>
<span class="ranswers">

</span>
<span class="hanswers" style="display: none" val="UWs4cUczRA==" vals="1">

</span>
<br> 答题解析:《疫苗管理法》第六十五条第二款 疫苗存在供应短缺风险时,国务院卫生健康主管部门、国务院药品监督管理部门提出建议,国务院工业和信息化主管部门、国务院财政部门应当采取有效措施,保障疫苗生产、供应。</p>
</div>
<a href="javascript:;" class="startButton closeButton">继续答题</a>
</div>
</div>
</div>

每道题目的 div 都包含 class="topic_page" 的 attr。在 class="analysisDiv" 的 div 中,完整包含了答案解析,只不过其中 class 样式默认 display:none。其中像编码的一段东西: UWs4cUczRA== 大概就是加密过的答案。

于是我说告诉她,这完全可以直接看着解析做啊。但这还不是结束……

解码

继续看这个页面的 js 代码:

1
2
3
4
5
6
 var topics = '[{"answer":"c2Zva1NURA==","class":1},{"answer":"ejJhU3NEQw==","class":1},{"answer":"UUFYdnRJQg==","class":1},{"answer":"NVhxakNMQg==","class":1},{"answer":"aUdJS1p1RA==","class":1},{"answer":"amV0WnZxQUJDRA==","class":2},{"answer":"TVZMMWtUQUJDRA==","class":2},{"answer":"UlpXQVlmQUI=","class":2},{"answer":"ZUJ5cEpUQUJDRA==","class":2},{"answer":"b0o5OFd1QkM=","class":2},{"answer":"a0Y4UjFwMQ==","class":5},{"answer":"aVBCalRFMA==","class":5},{"answer":"Z0tmcjZQMQ==","class":5},{"answer":"ejhTSGVEMA==","class":5},{"answer":"UkYzNmZzMA==","class":5},{"answer":"RE5Vb21iMA==","class":5},{"answer":"VmxKUUtiMQ==","class":5},{"answer":"UkFneWVGMQ==","class":5},{"answer":"WW9EdUs1MQ==","class":5},{"answer":"SmdScW9BMA==","class":5}]';
if (topics) {
topics = JSON.parse(topics)
}
var info = new Array();
var grade = 0;

这个 topics = JSON.parse(topics) 操作显然证明是后端使用模板页面技术,直接把字符串数据塞到了静态页面里。这个估计就是解码的关键了,继续向下看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$(".sureButton").on("click", function() {
//...省略部分代码
var ranswers = topics[$(this).parents(".topic_page").attr("val")]["answer"];
//...省略部分代码
if (answer == ranswer) {
grade += 0.5;
$(this).siblings(".selectCon").children(".answer_" + ranswer).removeClass("check_answer").addClass("correct").children(".correct_img").show()
} else {
$(this).siblings(".selectCon").children(".answer_" + answer).removeClass("check_answer").addClass("error").children(".error_img").show();
if (now_class == 5) {
ranswer = ranswer == 0 ? "对" : "错"
}
$(this).parents(".topic_page").children(".analysisDiv").find(".ranswers").html(ranswer);
$(this).parents(".topic_page").children(".analysisDiv").show();
$(this).parents(".topic_page").children(".analysisDiv").find(".analysisCon").animate({
bottom: ".5rem"
}, 800)
}
//...省略部分代码
});

这个从 topics 数组中解码得到的 ranswers,又与 answer 进行了相等比较,很可能就是答案了。打开浏览器,从控制台输入以下命令验证猜想:

1
2
3
4
 var topics = '[{"answer":"c2Zva1NURA==","class":1},{"answer":"ejJhU3NEQw==","class":1},{"answer":"UUFYdnRJQg==","class":1},{"answer":"NVhxakNMQg==","class":1},{"answer":"aUdJS1p1RA==","class":1},{"answer":"amV0WnZxQUJDRA==","class":2},{"answer":"TVZMMWtUQUJDRA==","class":2},{"answer":"UlpXQVlmQUI=","class":2},{"answer":"ZUJ5cEpUQUJDRA==","class":2},{"answer":"b0o5OFd1QkM=","class":2},{"answer":"a0Y4UjFwMQ==","class":5},{"answer":"aVBCalRFMA==","class":5},{"answer":"Z0tmcjZQMQ==","class":5},{"answer":"ejhTSGVEMA==","class":5},{"answer":"UkYzNmZzMA==","class":5},{"answer":"RE5Vb21iMA==","class":5},{"answer":"VmxKUUtiMQ==","class":5},{"answer":"UkFneWVGMQ==","class":5},{"answer":"WW9EdUs1MQ==","class":5},{"answer":"SmdScW9BMA==","class":5}]';
topics = JSON.parse(topics);
//浏览器自带的 base64 解码方法
atob(topics[0]["answer"]).substring(6);

可以看到控制台输出了 D,成功!下一步编写批量输出的指令,顺带将 0、1 转意为 对、错:

1
topics.forEach((e,i) => {var a=atob(e.answer).substring(6);console.log(i+1,a=='0'?'对':a=='1'?'错':a)})

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1 "D"
2 "C"
3 "B"
4 "B"
5 "D"
6 "ABCD"
7 "ABCD"
8 "AB"
9 "ABCD"
10 "BC"
11 "错"
12 "对"
13 "错"
14 "对"
15 "对"
16 "对"
17 "错"
18 "错"
19 "错"
20 "对"

更进一步!

事已至此,其实完全足够了;但既然已经得到答案,再照一道一道地点一遍题目,未免也太无聊。为了节省这没意义的消磨,我继续看了看原页面的 js,直到看到提交操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function submit_exam() {
if (info.length == 0) {
// 用户未答题,获取试题信息
for (var q = 0; q < $('.topic_id').length; q++) {
info[q] = {
'topic_id': $('.topic_id').eq(q).val(),
'user_answer': ''
}
}
}
$.post("https://lf.health-china.com/exam/submit_topic.html", {
info: info,
grade: grade
}, function(msg) {
if (msg.code != 1000) {
layer.msg(msg.message);
return
} else {
setTimeout(function() {
layer.msg(msg.message);
}, 1000);
setTimeout(function() {
location.href = "https://lf.health-china.com/exam/exam_result.html"
}, 2000);
}
});
}

其中 info 便是直接从页面各个 topic 下加载的题目信息数组,其中 topic_id 是真实题号(题库中的题号,而非页面上的题号),user_answer 是用户回答。

而 grade 成绩满分就是十分,于是乎,可以进入页面后直接运行命令填充答案,并提交表单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if (info.length == 0) {
for (var q = 0; q < $('.topic_id').length; q++) {
info[q] = {
'topic_id': $('.topic_id').eq(q).val(),
'user_answer': ''
}
}
}
topics.forEach((e,  i)  =>  {
info[i].user_answer = atob(e.answer).substring(6);
});
$.post("https://lf.health-china.com/exam/submit_topic.html",   {
info:  info,
grade:  10
},  function(msg{
if  (msg.code  !=  1000)  {
layer.msg(msg.message);
return

else  {
setTimeout(function({
layer.msg(msg.message);
},  1000);
setTimeout(function({
location.href  =  "https://lf.health-china.com/exam/exam_result.html"
},  2000);
}
});

压缩后的代码:

1
if (info.length == 0) {for (var q = 0; q < $('.topic_id').length; q++) {info[q] = {'topic_id': $('.topic_id').eq(q).val(),'user_answer': ''}}};topics.forEach((e, i) => {var a = $.base64.decode(e.answer).substring(6);info[i].user_answer =  a;});$.post("https://lf.health-china.com/exam/submit_topic.html", {info: info,grade10}, function(msg{if (msg.code != 1000) {layer.msg(msg.message);returnelse {setTimeout(function({layer.msg(msg.message);}, 1000);setTimeout(function({location.href = "https://lf.health-china.com/exam/exam_result.html"}, 2000);}});

总结【请勿模仿 PAP】

  • 既然都有后端生成随机题目了,为什么不把判断答案放在后端操作?第一锅由该小程序外包来背;
  • 如果说安全锁可防君子不防小人,但如果没有锁,无成本投机取巧过于挑战人性;
  • 虽然这个小程序可能没多少人研究,但网络安全意识普及仍然有很长的路要走;
  • Anyway 仅供参考,不宜模仿