关于制作考试（答题）系统的研究 | Mayx的博客
Author: mayx
Description: 在答题系统的安全性上，没有人胜利……
Date: 2020-04-19T00:00:00+08:00

19 April 2020 - 字数统计：3184 - 阅读大约需要11分钟 - Hits: Loading...

# 关于制作考试（答题）系统的研究

by mayx

这是一篇创建于 天前的文章，其中的信息可能已经有所发展或是发生改变。

AI摘要

这篇文章主要讲述了作者对一个学校的答题系统进行研究的经历。作者一开始利用网络考试中常见的将答案隐藏在源代码中的漏洞，在答题系统中找到了通过获取已知答案来作弊的方法，成功注册了花火学园，并因这个发现加入了运维团队。尽管作者随后指出系统的阅卷逻辑存在漏洞，可以通过无限重复已知答案的问题来获取满分，但问题并未被开发者完全修复。文章的最后提到，作者偶然间发现自己的修改并未解决问题，实际上漏洞仍然存在，被另一位测试者Sora Jin重新发现。作者反思认为，设计答题系统时应避免过于精密，以避免被轻易破解。

- 答题系统的来由
- 修复漏洞
- 参考代码
- 结尾
- 后记

在答题系统的安全性上，没有人胜利……

#
答题系统的来由

在我维护的花火学园上，有一套答题系统。另外我能成为花火学园的运维也是因为这个系统，这件事也是个很神奇的事情。

具体内容也充满了机遇性，在我上高中的一天，我偶然发现了花火学园论坛，发现了之后我就想注册，然而这个论坛注册要求挺高，需要邀请码，如果没有就要去答20道题来获得邀请码。

那个题很难，正常人会的可能只有不到一半，如果有兴趣也可以看看花火考场，对于这些题同样我也不会。不过不会没关系，我以前在做网上的考试时，从来都不会安心看题，总会想着按下F12看看有没有什么不答题也能通过的方法。

按正常来说，应该没有人会把答案放到网页源代码中，但是在我做的好多次网络考试中，他们都把答案放到了源代码之中。我想这也许是为了降低服务器的运算压力吧……但是这样考试有点计算机知识的人都能把答案找出来，那考试还有啥意义……

总之就是因为这样奇怪的程序员很多，所以我每次在网上答题前都会按下F12看看有没有什么更简单的通过方法。

当时我看了看花火考场的源代码，是打包后的vue代码，看来是没法从网页源代码这里入手了，那第二点就是查看网络请求的数据了。

我大概看了一下，这个答题系统的获得题目和提交都是由同一个php程序工作的，过程是答题之前获得一组题目，答完后将答案和一组表示题目的ID组合到一起提交到这个程序上，提交后如果分数超过70分就会生成一个邀请码。看着这个过程，我突然脑洞大开，想着既然这个程序阅卷时只用ID和答案，那么我如果会做其中一道题，让这个程序反复批已知答案的同一道题，那么我就可以得满分了吧。

结果还真是这样，有点不可思议，于是我就成功的注册了花火学园。

注册后我就给管理员说了这个问题，也是这样的一次机会我加入了花火学园的程序组当中。

这个答题系统是由you06大佬开发的，不过我反馈了这个问题以后，貌似问题仍然没有被解决……而我一般看代码也是有一定的机遇，只有心情适合写代码或看代码的时候才愿意去写，所以我成为管理员以后我也没有解决。

关于答题系统这个东西我一直觉得开发起来很简单，像客观题就是比对一下答案是不是一致就行了，所以说我上了大学以后，我一直想着和同学一起做个答题系统练练手，可惜现代大学生最喜欢干的事情就是摸鱼，所以一直都没有做答题系统。

不过前几天正好我的心情符合看代码的状态，然后就看了看我能不能解决这个问题。

#
修复漏洞

我闲来无事打开看了看阅卷的这个小程序，整个代码很短，也很好理解。当时加入程序组后我和you06说用session解决这个问题也许不错，虽然问题没解决，但是能看到他已经写了一部分了，也许是因为调试时出问题了，所以写了一半之后就再没管。

我看了看之所以you06的代码出了问题，其实主要原因是因为环境变量上少加了下划线，所以没读到数据才导致程序出问题的，既然问题找到了，我很轻松的就改了过来。

#
参考代码

既然漏洞解决了，那我觉得即使大家看到程序的源代码也没关系，我检查了很多遍，也没看出问题，所以现在我将阅卷系统的核心代码展示出来：

    <?php
      Session_start();
      $filename = "problem.json";

      if ($_SERVER['REQUEST_METHOD'] === 'GET') {
        // query problem data
        $json_string = file_get_contents($filename);
        $questionSet = json_decode($json_string, true);
        $questiones = array();
        for ($i = 0; $i < 20; $i++) {
          $index = mt_rand(0, sizeof($questionSet) - 1);
          array_push($questiones, array(
            'title' => $questionSet[$index]['title'],
            'code' => $questionSet[$index]['code'],
            'options' => $questionSet[$index]['options'],
            'img' => $questionSet[$index]['img']
          ));
          array_splice($questionSet, $index, 1);
        }
        $_SESSION['questiones'] = $questiones;
        echo json_encode($questiones);
        die();
      }
      if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        // check result
        $answers = json_decode(file_get_contents("php://input"), true);
        $score = calcscore($answers);
        $pass = $score >= 70;
        $invitecode = '';
        if ($pass) {
          $invitecode = invite();
        }

        echo json_encode(array(
          'score' => $score,
          'pass' => $pass,
          'invitecode' => $invitecode
        ));

    if (isset($_SESSION['questiones'])) {
    unset($_SESSION['questiones']);
    }

        die();
      }

      function calcscore($answers) {
        $score = 0;
        $filename = "problem.json";

        $json_string = file_get_contents($filename);
        $questionSet = json_decode($json_string, true);

        for ($i = 0; $i < sizeof($answers); $i++) {
          if ($answers[$i]['answer']) {
            for ($j = 0; $j < sizeof($questionSet); $j++) {
              if ($answers[$i]['code'] == $questionSet[$j]['code'] &&
                $answers[$i]['answer'] == $questionSet[$j]['answer']) {
                  $score += 5;
              }
            }
          }
        }

        $matchCount = 0;
        if ($_SESSION && $_SESSION['questiones']) {
          for ($i = 0; $i < sizeof($answers); $i++) {
            for ($j = 0; $j < sizeof($_SESSION['questiones']); $j++) {
    	  if ($answers[$i]['code'] == $_SESSION['questiones'][$j]['code']) {
    	    $matchCount++;
    	  }
    	}
          }
        } else {
          return 0;
        }

        if ($matchCount == sizeof($answers)) {
          return $score;
        } else {
          return 0;
        }
      }

      function invite() {
        //generate invite code
      }

#
结尾

其实答题系统想让用户钻不了空还是相当简单的事情，不过我个人还是希望那些答题系统能不要写的太精密，这样谁都不会胜利，互相为难也不好嘛……

#
后记

我的愚蠢超乎我想象，这个代码的漏洞应该说完全没有解决，看似复杂的代码就如同破铜烂铁一般，轻易就能击碎QAQ。

我完全没有测试就胡乱改，改完之后除了让代码看起来更加复杂外没有解决任何问题。今天一位名叫Sora Jin的大佬像曾经的我一样测试了这个垃圾答题系统，重新发现了这个漏洞……

不过这次的修复不怎么美观，代码我就不放上来了，不是很难的问题，有兴趣的读者可以自己研究一下。

tags: 考试 - 答题 查看原始文件

#### 推荐文章

Loading...

上一篇：Mayx的自我探索之旅
下一篇：记一次买笔电的经历
想问这篇文章
召唤伊斯特瓦尔