index.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. <head>
  2. <script type="text/javascript" src="/js/jquery.min.js"></script>
  3. <link rel="stylesheet" href="/css/bootstrap.min.css">
  4. <script type="text/javascript" src="/js/bootstrap.min.js"></script>
  5. <link rel="preconnect" href="https://fonts.googleapis.com">
  6. <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  7. <link href="https://fonts.googleapis.com/css2?family=Anonymous+Pro:ital,wght@0,400;0,700;1,400;1,700
  8. &family=Noto+Serif+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
  9. <link rel="stylesheet" href="/css/style.css">
  10. <title>心意答</title>
  11. </head>
  12. <body>
  13. <a class="btn btn-info" href="/score"><span class="glyphicon glyphicon-home" aria-hidden="true"></span>&nbsp;返回</a><br>
  14. <div class="page-header">
  15. <h2>
  16. 如何破解心意答接口
  17. <small></small>
  18. </h2>
  19. </div>
  20. <div class="page-header">
  21. <h3>
  22. 一、观察浏览器请求
  23. <small></small>
  24. </h3>
  25. </div>
  26. <p>打开查分页面,F12 进入控制台,观察到一系列请求,但对我们重要的只有三个:</p>
  27. <ul>
  28. <li><code>getUserMultiExamByStudentIdAndSchoolId</code><br>根据个人的数字校园号确定他参加了哪些考试,考了哪些科目</li>
  29. <li><code>getStudentReportMEVO</code><br>每科具体成绩、平均分、排名信息</li>
  30. <li><code>getStudentReportSEVO</code><br>小题信息,针对它的功能我还没开发</li>
  31. </ul>
  32. <p>这些请求的内容和响应的内容目前都是经过加密的。比如 <code>getUserMultiExamByStudentIdAndSchoolId</code> 请求的内容就是这样:</p>
  33. <pre>ff860f6474312dcb44ea6756bc559db9b6e0316c8ceda5d250f9d0149faf9f1f7c7d1dbcbff5947ab69c26159c68a136</pre>
  34. <p>发回的响应是这样:</p>
  35. <pre style="height:150px;white-space:pre-wrap">a0b554bf6acf7c6fbfa29cc211354c80a1e07f6479242fc87f6a94ead1154c4c6d8979f5a40f4bc8438d4f2455eeec2f39aa4cf4105d4187e266523cffa899c2781fa76585437f5e4d9b7b95fe5c50c0ea351623e161f4e732fcd93e9125abcc443321b2cf023940ebdb1d9750174ec49a27848ab490868a548f9fa0e9ebca318639f59f15e1804308453aa28d29399f489543067d2e332443fb8a2563cdad30df7190c6bf18ad1bd32ee9ce60dc7a65f1e30ddeb147b131ab20f383cd57ca07fa70210bc00a1e2abe31fb990a915ae581e2123f03414abc5b910f8669b8adfe38ba472accd9267fac42e4cda092a96c0c8f4fde596ac39063c9ae72dbc7ecdc58c6b4b98c13a9f6a5a6b17bc39815839670b3baa3fb3f37255a48112bacb26aafbbb2147e9e67e89c0c2b0091db1a0d10f3f6a365948997e214e6b4d09b46ff15ef1166a552d9dcab6bd43fb95a45ea545e187fe531c6776e2e57b5b775ca8b42c24c3c3efbe8bfbadc985587f3cecc8cdb623a387034e1e641749e38c13c29523515a8cdfb6723adb1dd2ef789455bcdc9e9c90736b4ae13d545c01325c691627ed9e79cb2844c96c522f7574e6087bacebaef40727d72f1e78af705b446096877be0464dd99d91476ed2ce95c2d55b3aa47b3a1aa3c493d4652540cd00c1ffbfb2506b376f943b70aebf9d389eabdf1f868ed4efbd40102c3e7aa2e5bc2ed60ac858f026548eafdf6121a27cb7c5fbc8291796a164bb003b6780ea8d68859e0c7de6d5050e94286cb3e9cb8ad2d9665c0a7dfd9f49d729b40d9d21a8187d73b0a26ababccad9f91b46351288c495edc7a133eabae8f5b474063f5add5ad5e8dfa220552e2ab8f96cbf63504d2e237b68f824e516a2374202030dbcec521e3b9be47a91081e492867ff8a2054780dab4a8590ad2e2d79003ec65e7876d588032c3c5ad99e0940c509b87604deb57c09e3127c152e65f99975628851662333e816c69727bede4cb26fa9752af44f25a9ae8b384e26edf5851a3a2bbb9eefe8f583d0f1037863f5d5bf6d7d16ebd49efad0c5d5db1e9bc12bd6cdc4be181f19a81d7f3e275fcf275aa582ac7a838126cd0cc62401f88dab73da2d603cb994bf395f875d1fd0f8715a3394ff8c7adc9b07313501aab64263c712fa3b65df2593e243ef98850ecd05699740fb8f1e08aa4ff140b730daf1c87e918b3ade7473554f4a67726b4f2e0c15aa7fa1d0737a7cfe4d39c389cd709323d3fd58b4e654d573b0432b53dd6470a9b4c06d1b49494f621e26c0a94a312978d59805fa350d9015379e8566ec5f27ba87e866dcb17b16f53810bbd9ef619cb655ad1c5643b007a1358b83daa454eb49ee6dea36a91b249f19916fd0e708d38dbf2a833f18c1ca086df73317b9c3435a43313f27470fad5e500bf48fff0c6684803fb3cc87957231c28e57ac8fd926c22b70fc7a01101758b103e77c88e05af6bae50e8aa626532442253648eb38d4ebf9c40c5a1ec8e6eb805cfab6d918b60281ba669f35d37caaa5ba44d281885dde2ee75db029774aaca5fb7d3ee1bd411b3de79941027e9ddfd20a7ee860e839fcf5f6c1683fdd72b29ada74bc7c1d64ebb8f0af7204c6fecb598f1db68e00dba1c4319613183a24cb1506f913e9d8b70573ee34cc23e9902529d1f7d16b26f90cbee8f364e2c7cbeecb30400eaa9498bdf07e70b51a821d62cc2437aa334c79daa73eba4af41837a03b21d6d5097d625f5f8c1ec75e755c0ba45511d548c2000617a9c3561fd3fd568ee141dccbea9ec28609d5738f2347f45bc6c9138d333c5a203cd8b4950c7e0c187b729c221a71b9bc2435203d18738ca81d29547f20fb9d62306deb4b819400e6d090dd3ded4da30336541c8d5f3d9c3bb034acf21fb13238d8147734187271ec177ab60a7537c691df6c08a94904867eb0d6deb01134132fd5ec40a0d151ccf2dd475a798c240426b0ef26f36e99c741a7ed7b72dcd33950cc89fdab76cda68f026a5f51a8b945b0dde85ad76685e6fc8e8f53eb6fc480162ff32051c532b2750e99531097835169846921f336cca0d5e8af467aad3b00a35f349a899b97fcdc5b1c1eb806d3601e1e7c9628ac04066b1289efc6861ebe996ff825478e6f77dc58c28278cc887e6f7af85798df3a4baaf45a23506b62c3b54167c7a73c87f225e91c63ea4801144c1392624da7e8b344753de09049e5a3903ae46b31a4b46a574d4de91510c62b0aed48e97fc211046913b61073d277344dac9c330a52d53b1aba58774100df4d336af3e727bde886bec18d3d9630fbc6150ee504064b8416af2853b3391825d323af2203236ff8ed9d06d8b50df682aae28f6af9df17377005764f1ae337462a27dc90d568e4af02fe25de845ea0cad71a1ccae3a114b84bc13f52e3ffde13e85892747abcc384946f0d06d348dde2c0895d89a1442b78bfa82deea4b980b47ceaf1947f783d156de626415a321874e57d852d025074402ad0c5d5db1e9bc12bd6cdc4be181f19ac07dee03025513f8f348e8a52a792840b06e1350a0299468f7bc36f9d5c5fab4d16cde55952e9265e2cbf248bbcac854fd8c023d1edeb34c0ef0075c17a3b6b0b2c350c370a1c53bd0f54a7d5077589b3e4cadcd2253afbb6773e5909be0feddaa8e711234111e7e748ca1fecf3f2c4362b7e19dae0a0eff3bd495bdb44948860258b96c3a8595ff6c8e10e3049ee156a03db66cd24a9405e57481b5b0a7aa7fba87972733b07e3e9220095306ab492c5ac8085000c640e14cec0bf7f57e49db5710b876712604158770d5170e3d7270232c786d1efe0fc141a4ced9f7d0f7544ac7af2785627a3a18436560cc4569b68e756320d9f003f4a4845542af00d08bd89ee5ad8755590d54fb3384d1e8f0da3a46381e1970015467c66fa64e94920e65ba7eaeb2a52bfee53f67d80fb5fab20f10cee1ee1b4dc839a0466e51a449f86702bd2a83bbb55e033da775ad60d02e15e0fb4750744befa7fedb222578e45093dceb13771ce50585442bdbd1001ff0f2d7199c8181f452d34fc3023559d1d88c41a0bbe6647a0933ad7b66e26035307d9459b7af7c2aa10844c10bf73f41eeeed7dcb59d1a7a9f3cba8114510155244a64801f20f01d3ee7a3d65fd08364df0c2e2b5c0ee811ee5b03e3e865dcd82ff681446a29a77c1859c688500663acb6fff400f06d3a1d23dcadd99331d702a2f172c56f918ec4569865267852c741e81bfe42556844c589ececd604874aff8e36171161d12f12f909792a4ad058cb80ac69998a24ffad895876fc4ab5ee67877d4fda0cac2194008e0ba7a2229680d82feb70ec6bc7fda477f738aef6e730e917ee8e84658355224323b18277b9a61d81e2123f03414abc5b910f8669b8adfe38ba472accd9267fac42e4cda092a96c0c8f4fde596ac39063c9ae72dbc7ecdc7b864ec333845fd1f3ba87eddc8f328e05062b3146d75bdcc2a13b57b3f35446cdac7e6cfd0693eb65739d4bd169f9ba0b60f50c18cbc2a2259b487c4df85e6eb1aba58774100df4d336af3e727bde886bec18d3d9630fbc6150ee504064b841b865e0cbb23ec0a4c78815377afc425eaea4e54868815bdb03b8e109e648c8a2cb1a57468fa75ac392973850ba28d7c9138a34d92d169ea8fd81dff1b79fcda51cf7d44706172f1ed8e8e93d89e77497bc13f52e3ffde13e85892747abcc384946f0d06d348dde2c0895d89a1442b78bfa82deea4b980b47ceaf1947f783d156be0086da599869ad30b36df716f3069f956aef5f0bd1195f1bac0454690a3e342548102ac87e6fc67affb1c2da42e38cd83c4b8ef0ee44b3472ee4a0d1b3864bbfd791cd66512ae6ea7a24b84d2d61f90f28691a65bdcdb5786178484aeb7c6faf2d504ba449bff02212cca1e19643bb6c09c488c98722ba1ff9c3bc63d5cd4df1fd8b3f16998ac8e3d971a1d6da455390871d561962cf95eec6cf5b711e68e5258d61952ae71aaee9ef6153b9bb3fdccf6ea80fbbb51161426fe0d3c089b06989e2747d52ede03f27e26cb0a44bb6f248c1f8398afeb281c14dce66d40eab47f0018d80b1a2dd57d993f02b96f7ff03ff07ce9077a013a244d008d47ba3437b7f98ea5266c689ac328bfb380cb0d15ef724739757bf0d658eeb74d0e193bcb8ef71e7c1f947a97e1d728e846d34c82912901af9a6f78337b5b89d9cbfaee3d9220e072821a1942eddf0bd08a9843deb0e97025fd40d691dbcca92352d79c51cce8b438d868bab9fdf609d43f855da838d8c47218a43d36078736cb9f2cae4034ceb600ca2b29c2210cd2ed61f849b7b2b7c26e53f6d7a29fb0f2c428ab965384b706e49086a3cf752e19957c374dff16d8979f5a40f4bc8438d4f2455eeec2f942e0dd46ab610332cf7ed70ba7fed2179f690b98c3455cc26bc8d34f43fceff10d063a66975c99d1865b10a50156ff2c923080ff31fc245e86b1231338ba409de38ca7e2661bf2909fabbf6f16d942ada08f1980dd11ded7fa555ee1c46880daeba158ba84337a4388aac7431b3d51216cdcec118267d224f77c3603960b6e997a75da761f7e0bd7354a0a045b7cbfeafa882125a56c01bd3d588cbf1e2c17b38ba472accd9267fac42e4cda092a96ca152a31c64477c9c823a7b16d67d2763b2264ff51a622ac6e4eddfd7d1486c94e662dca314190d14d30e3d9786a8af945dd297a4ed45ec3c69d7ce54ad14224ca0f0a39d048d1cb464114490e10f33c013666551e092b79ff588788f9c5e6f651ccf6b4ab506e09dde4f0f1cf651eda38be09288c7c908dff9dba4de3b36242af1321e54846f922f64a4d9be67d38c9d8ba6abbbfc12347904bfd3eb2bc32d3ca2d54a6678f0e4f06c74e08faf7f160f4861808c1089ff13b27bac7828c0ffa89dcf59b8915f146ddecba8b2396a26dea124690f4d0c66d88df5897bd9f193cd53bf06bc82a7667d281c4f0e4f2ec944de400afb92242caf1ccd06c2c8988b209aa023890915295f6be76903d2ee7587f687c37a1659a39c4283c3dcc0125d27a480bb2242ee2075b516379372a60cc7178b13a21e42515385ff55f9e18a1f69aaafa62aa9174ee6879f89c01328a8dd59f7ee87fd041d6f9d9f7e9aa838954b2e7970b3619ca87c39eeaaccfa67ac654b17ba3f39f0d7b1e971e21e8eb7e78e9b59ea2c3f6a9e8179203e568ce4a9a9f4eda59560f357099db1612ae9333e9e7ffb39b0da9ea7245537c05ffb627442</pre>
  36. <p>看着让人很难以下手。</p>
  37. <div class="page-header">
  38. <h3>
  39. 二、破解加密算法
  40. <small></small>
  41. </h3>
  42. </div>
  43. <p>将页面用到的 js 文件下载下来仔细观察。代码进行了打包和混淆,但因为加密算法是引用的外部库,没有完全混淆,所以算法名字和密码都明文摆在那。</p>
  44. <p>注意到这段代码多次出现,且这个 abcdefg 串疑似密码:</p>
  45. <pre>JSON.parse(Object(j.b)(n.data, "abcdefgabcdefg12"));</pre>
  46. <p>翻找 <code>j.b</code> 函数,很快找到:</p>
  47. <pre>j = e("POKb")</pre>
  48. <p><code>e</code> 即为打包的特大函数,从里面找到编号为 POKb 的一项:</p>
  49. <pre style="height:150px">POKb: function(t, n, e) {
  50. "use strict";
  51. n.a = function(t, n) {
  52. if (void 0 == t)
  53. return "";
  54. var e = u.a.enc.Utf8.parse(n)
  55. , a = "";
  56. if ("string" == typeof t)
  57. a = u.a.AES.encrypt(t, e, {
  58. mode: u.a.mode.ECB,
  59. padding: u.a.pad.Pkcs7
  60. });
  61. else if ("object" === (void 0 === t ? "undefined" : i()(t))) {
  62. var c = o()(t);
  63. a = u.a.AES.encrypt(c, e, {
  64. mode: u.a.mode.ECB,
  65. padding: u.a.pad.Pkcs7
  66. })
  67. }
  68. return a.ciphertext.toString()
  69. }
  70. ,
  71. n.b = function(t, n) {
  72. var e = u.a.enc.Utf8.parse(n)
  73. , a = u.a.AES.decrypt(u.a.format.Hex.parse(t), e, {
  74. mode: u.a.mode.ECB,
  75. padding: u.a.pad.Pkcs7
  76. });
  77. return u.a.enc.Utf8.stringify(a)
  78. }
  79. ;
  80. var a = e("mvHQ")
  81. , o = e.n(a)
  82. , c = e("pFYg")
  83. , i = e.n(c)
  84. , r = e("Av7u")
  85. , u = e.n(r)
  86. }</pre>
  87. <p>因此可以认为 <code>j.b</code> 等效于这段代码中的 <code>n.b</code>。</p>
  88. <p>这段代码写得很明白,先把字符串编码,然后调用了 <code>AES.decrypt</code>,调用的参数也都写好了,意思就是通过 AES 算法解密,密码为
  89. <code>abcdefgabcdefg12</code>。同样地,这段代码上面的 <code>n.a</code> 实际是通过 AES 加密。复刻这里的加密解密函数是很容易的,这个加密算法就这样被轻松破解了。
  90. </p>
  91. <p>我还做了一个<a href="/crypty/2" target="_blank">页面</a>专门负责加密解密心意答,方便调试。</p>
  92. <div class="page-header">
  93. <h3>
  94. 三、观察请求内容
  95. <small></small>
  96. </h3>
  97. </div>
  98. <p>于是我们就可以把几次请求的内容解密出来观察了。</p>
  99. <p>首先是 <code>getUserMultiExamByStudentIdAndSchoolId</code>,请求解密后是这样:</p>
  100. <pre>{"schoolId":19707,"studentId":"20222446"}</pre>
  101. <p>惊讶地发现,这条请求里面居然没有携带任何身份验证的内容,直接发送校园号,也就是说我即使发送别人的校园号它也会正常返回结果。这是极其不规范的设计,根本没有为接口安全性做考虑。</p>
  102. <p>继续看响应:</p>
  103. <pre style="height:150px;white-space:pre-wrap">[{"meId":1044,"examDate":1672934400000,"examName":"2022-2023学年高一上学期期末测试","studentReportInfos":[{"meId":1044,"seCourseId":-1,"seName":"","seCourseName":"","seId":-1},{"meId":1044,"reportName":"物理成绩","seCourseId":204,"seName":"2022-2023学年高一上学期期末测试_物理","seCourseName":"物理","seId":3381},{"meId":1044,"reportName":"化学成绩","seCourseId":205,"seName":"2022-2023学年高一上学期期末测试_化学","seCourseName":"化学","seId":3382},{"meId":1044,"reportName":"生物成绩","seCourseId":206,"seName":"2022-2023学年高一上学期期末测试_生物","seCourseName":"生物","seId":3383}]},{"meId":1028,"examDate":1671724800000,"examName":"12月23日高一高考部化学测试","studentReportInfos":[{"meId":1028,"seCourseId":-1,"seName":"","seCourseName":"","seId":-1},{"meId":1028,"reportName":"化学成绩","seCourseId":205,"seName":"12月23日高一高考部化学测试_化学","seCourseName":"化学","seId":3303}]},{"meId":1023,"examDate":1671465600000,"examName":"12月20日高一高考部数学测试","studentReportInfos":[{"meId":1023,"seCourseId":-1,"seName":"","seCourseName":"","seId":-1},{"meId":1023,"reportName":"数学成绩","seCourseId":202,"seName":"12月20日高一高考部数学测试_数学","seCourseName":"数学","seId":3298}]},{"meId":1021,"examDate":1671120000000,"examName":"12月16日高一高考部物理测试","studentReportInfos":[{"meId":1021,"seCourseId":-1,"seName":"","seCourseName":"","seId":-1},{"meId":1021,"reportName":"物理成绩","seCourseId":204,"seName":"12月16日高一高考部物理测试_物理","seCourseName":"物理","seId":3296}]},{"meId":972,"examDate":1666627200000,"examName":"2022-2023学年第一学期高一年级高考部期中测试","studentReportInfos":[{"meId":972,"seCourseId":-1,"seName":"","seCourseName":"","seId":-1},{"meId":972,"reportName":"语文成绩","seCourseId":201,"seName":"2022-2023学年第一学期高一年级高考部期中测试_语文","seCourseName":"语文","seId":3129},{"meId":972,"reportName":"数学成绩","seCourseId":202,"seName":"2022-2023学年第一学期高一年级高考部期中测试_数学","seCourseName":"数学","seId":3096},{"meId":972,"reportName":"英语成绩","seCourseId":203,"seName":"2022-2023学年第一学期高一年级高考部期中测试_英语","seCourseName":"英语","seId":3132},{"meId":972,"reportName":"物理成绩","seCourseId":204,"seName":"2022-2023学年第一学期高一年级高考部期中测试_物理","seCourseName":"物理","seId":3101},{"meId":972,"reportName":"化学成绩","seCourseId":205,"seName":"2022-2023学年第一学期高一年级高考部期中测试_化学","seCourseName":"化学","seId":3104},{"meId":972,"reportName":"生物成绩","seCourseId":206,"seName":"2022-2023学年第一学期高一年级高考部期中测试_生物","seCourseName":"生物","seId":3087},{"meId":972,"reportName":"政治成绩","seCourseId":207,"seName":"2022-2023学年第一学期高一年级高考部期中测试_政治","seCourseName":"政治","seId":3116}]},{"meId":957,"examDate":1665590400000,"examName":"10月13日高一化学测练","studentReportInfos":[{"meId":957,"seCourseId":-1,"seName":"","seCourseName":"","seId":-1},{"meId":957,"reportName":"化学成绩","seCourseId":205,"seName":"10月13日高一化学测练_化学","seCourseName":"化学","seId":3067}]},{"meId":951,"examDate":1665244800000,"examName":"10月9日高一高考部生物测试","studentReportInfos":[{"meId":951,"seCourseId":-1,"seName":"","seCourseName":"","seId":-1},{"meId":951,"reportName":"生物成绩","seCourseId":206,"seName":"10月9日高一高考部生物测试_生物","seCourseName":"生物","seId":3061}]}]</pre>
  104. <p>嗯,很老实地返回了所有考试的信息和编号。</p>
  105. <p>用同样的方式观察剩下两个请求,发现它们同样没有进行身份验证。</p>
  106. <p>也就是说,我完全可以自己写一个页面,集成好加密解密,然后直接向心意答发送请求。比起原本的心意答,我可以显示更完整的数据,而且可以查询任何一个人的成绩。</p>
  107. <div class="page-header">
  108. <h3>
  109. 四、对应姓名与校园号
  110. <small></small>
  111. </h3>
  112. </div>
  113. <p>你会发现我的查询页面直接输入姓名就能查到,但刚才请求发送的明明是数字校园号。我是怎么知道这个姓名对应的数字校园号的呢?</p>
  114. <p>这件事其实只要你有足够的运气和敏锐的感知力,很快就能搞定。我在在线学习平台闲逛的时候,发现了一个显示所有人姓名、头像、数字校园号的大列表。这个列表背后的数据也是需要请求获取的,那么我直接把这个请求的响应下载下来,就有了所有人姓名和校园号的对应表了。当你输入姓名时,我就直接向这个表里查询(这也是为什么只支持 2025 届高考部)。</p>
  115. </body>