返回

打开查分页面,F12 进入控制台,观察到一系列请求,但对我们重要的只有三个:

这些请求的内容和响应的内容目前都是经过加密的。比如 getUserMultiExamByStudentIdAndSchoolId 请求的内容就是这样:

ff860f6474312dcb44ea6756bc559db9b6e0316c8ceda5d250f9d0149faf9f1f7c7d1dbcbff5947ab69c26159c68a136

发回的响应是这样:

a0b554bf6acf7c6fbfa29cc211354c80a1e07f6479242fc87f6a94ead1154c4c6d8979f5a40f4bc8438d4f2455eeec2f39aa4cf4105d4187e266523cffa899c2781fa76585437f5e4d9b7b95fe5c50c0ea351623e161f4e732fcd93e9125abcc443321b2cf023940ebdb1d9750174ec49a27848ab490868a548f9fa0e9ebca318639f59f15e1804308453aa28d29399f489543067d2e332443fb8a2563cdad30df7190c6bf18ad1bd32ee9ce60dc7a65f1e30ddeb147b131ab20f383cd57ca07fa70210bc00a1e2abe31fb990a915ae581e2123f03414abc5b910f8669b8adfe38ba472accd9267fac42e4cda092a96c0c8f4fde596ac39063c9ae72dbc7ecdc58c6b4b98c13a9f6a5a6b17bc39815839670b3baa3fb3f37255a48112bacb26aafbbb2147e9e67e89c0c2b0091db1a0d10f3f6a365948997e214e6b4d09b46ff15ef1166a552d9dcab6bd43fb95a45ea545e187fe531c6776e2e57b5b775ca8b42c24c3c3efbe8bfbadc985587f3cecc8cdb623a387034e1e641749e38c13c29523515a8cdfb6723adb1dd2ef789455bcdc9e9c90736b4ae13d545c01325c691627ed9e79cb2844c96c522f7574e6087bacebaef40727d72f1e78af705b446096877be0464dd99d91476ed2ce95c2d55b3aa47b3a1aa3c493d4652540cd00c1ffbfb2506b376f943b70aebf9d389eabdf1f868ed4efbd40102c3e7aa2e5bc2ed60ac858f026548eafdf6121a27cb7c5fbc8291796a164bb003b6780ea8d68859e0c7de6d5050e94286cb3e9cb8ad2d9665c0a7dfd9f49d729b40d9d21a8187d73b0a26ababccad9f91b46351288c495edc7a133eabae8f5b474063f5add5ad5e8dfa220552e2ab8f96cbf63504d2e237b68f824e516a2374202030dbcec521e3b9be47a91081e492867ff8a2054780dab4a8590ad2e2d79003ec65e7876d588032c3c5ad99e0940c509b87604deb57c09e3127c152e65f99975628851662333e816c69727bede4cb26fa9752af44f25a9ae8b384e26edf5851a3a2bbb9eefe8f583d0f1037863f5d5bf6d7d16ebd49efad0c5d5db1e9bc12bd6cdc4be181f19a81d7f3e275fcf275aa582ac7a838126cd0cc62401f88dab73da2d603cb994bf395f875d1fd0f8715a3394ff8c7adc9b07313501aab64263c712fa3b65df2593e243ef98850ecd05699740fb8f1e08aa4ff140b730daf1c87e918b3ade7473554f4a67726b4f2e0c15aa7fa1d0737a7cfe4d39c389cd709323d3fd58b4e654d573b0432b53dd6470a9b4c06d1b49494f621e26c0a94a312978d59805fa350d9015379e8566ec5f27ba87e866dcb17b16f53810bbd9ef619cb655ad1c5643b007a1358b83daa454eb49ee6dea36a91b249f19916fd0e708d38dbf2a833f18c1ca086df73317b9c3435a43313f27470fad5e500bf48fff0c6684803fb3cc87957231c28e57ac8fd926c22b70fc7a01101758b103e77c88e05af6bae50e8aa626532442253648eb38d4ebf9c40c5a1ec8e6eb805cfab6d918b60281ba669f35d37caaa5ba44d281885dde2ee75db029774aaca5fb7d3ee1bd411b3de79941027e9ddfd20a7ee860e839fcf5f6c1683fdd72b29ada74bc7c1d64ebb8f0af7204c6fecb598f1db68e00dba1c4319613183a24cb1506f913e9d8b70573ee34cc23e9902529d1f7d16b26f90cbee8f364e2c7cbeecb30400eaa9498bdf07e70b51a821d62cc2437aa334c79daa73eba4af41837a03b21d6d5097d625f5f8c1ec75e755c0ba45511d548c2000617a9c3561fd3fd568ee141dccbea9ec28609d5738f2347f45bc6c9138d333c5a203cd8b4950c7e0c187b729c221a71b9bc2435203d18738ca81d29547f20fb9d62306deb4b819400e6d090dd3ded4da30336541c8d5f3d9c3bb034acf21fb13238d8147734187271ec177ab60a7537c691df6c08a94904867eb0d6deb01134132fd5ec40a0d151ccf2dd475a798c240426b0ef26f36e99c741a7ed7b72dcd33950cc89fdab76cda68f026a5f51a8b945b0dde85ad76685e6fc8e8f53eb6fc480162ff32051c532b2750e99531097835169846921f336cca0d5e8af467aad3b00a35f349a899b97fcdc5b1c1eb806d3601e1e7c9628ac04066b1289efc6861ebe996ff825478e6f77dc58c28278cc887e6f7af85798df3a4baaf45a23506b62c3b54167c7a73c87f225e91c63ea4801144c1392624da7e8b344753de09049e5a3903ae46b31a4b46a574d4de91510c62b0aed48e97fc211046913b61073d277344dac9c330a52d53b1aba58774100df4d336af3e727bde886bec18d3d9630fbc6150ee504064b8416af2853b3391825d323af2203236ff8ed9d06d8b50df682aae28f6af9df17377005764f1ae337462a27dc90d568e4af02fe25de845ea0cad71a1ccae3a114b84bc13f52e3ffde13e85892747abcc384946f0d06d348dde2c0895d89a1442b78bfa82deea4b980b47ceaf1947f783d156de626415a321874e57d852d025074402ad0c5d5db1e9bc12bd6cdc4be181f19ac07dee03025513f8f348e8a52a792840b06e1350a0299468f7bc36f9d5c5fab4d16cde55952e9265e2cbf248bbcac854fd8c023d1edeb34c0ef0075c17a3b6b0b2c350c370a1c53bd0f54a7d5077589b3e4cadcd2253afbb6773e5909be0feddaa8e711234111e7e748ca1fecf3f2c4362b7e19dae0a0eff3bd495bdb44948860258b96c3a8595ff6c8e10e3049ee156a03db66cd24a9405e57481b5b0a7aa7fba87972733b07e3e9220095306ab492c5ac8085000c640e14cec0bf7f57e49db5710b876712604158770d5170e3d7270232c786d1efe0fc141a4ced9f7d0f7544ac7af2785627a3a18436560cc4569b68e756320d9f003f4a4845542af00d08bd89ee5ad8755590d54fb3384d1e8f0da3a46381e1970015467c66fa64e94920e65ba7eaeb2a52bfee53f67d80fb5fab20f10cee1ee1b4dc839a0466e51a449f86702bd2a83bbb55e033da775ad60d02e15e0fb4750744befa7fedb222578e45093dceb13771ce50585442bdbd1001ff0f2d7199c8181f452d34fc3023559d1d88c41a0bbe6647a0933ad7b66e26035307d9459b7af7c2aa10844c10bf73f41eeeed7dcb59d1a7a9f3cba8114510155244a64801f20f01d3ee7a3d65fd08364df0c2e2b5c0ee811ee5b03e3e865dcd82ff681446a29a77c1859c688500663acb6fff400f06d3a1d23dcadd99331d702a2f172c56f918ec4569865267852c741e81bfe42556844c589ececd604874aff8e36171161d12f12f909792a4ad058cb80ac69998a24ffad895876fc4ab5ee67877d4fda0cac2194008e0ba7a2229680d82feb70ec6bc7fda477f738aef6e730e917ee8e84658355224323b18277b9a61d81e2123f03414abc5b910f8669b8adfe38ba472accd9267fac42e4cda092a96c0c8f4fde596ac39063c9ae72dbc7ecdc7b864ec333845fd1f3ba87eddc8f328e05062b3146d75bdcc2a13b57b3f35446cdac7e6cfd0693eb65739d4bd169f9ba0b60f50c18cbc2a2259b487c4df85e6eb1aba58774100df4d336af3e727bde886bec18d3d9630fbc6150ee504064b841b865e0cbb23ec0a4c78815377afc425eaea4e54868815bdb03b8e109e648c8a2cb1a57468fa75ac392973850ba28d7c9138a34d92d169ea8fd81dff1b79fcda51cf7d44706172f1ed8e8e93d89e77497bc13f52e3ffde13e85892747abcc384946f0d06d348dde2c0895d89a1442b78bfa82deea4b980b47ceaf1947f783d156be0086da599869ad30b36df716f3069f956aef5f0bd1195f1bac0454690a3e342548102ac87e6fc67affb1c2da42e38cd83c4b8ef0ee44b3472ee4a0d1b3864bbfd791cd66512ae6ea7a24b84d2d61f90f28691a65bdcdb5786178484aeb7c6faf2d504ba449bff02212cca1e19643bb6c09c488c98722ba1ff9c3bc63d5cd4df1fd8b3f16998ac8e3d971a1d6da455390871d561962cf95eec6cf5b711e68e5258d61952ae71aaee9ef6153b9bb3fdccf6ea80fbbb51161426fe0d3c089b06989e2747d52ede03f27e26cb0a44bb6f248c1f8398afeb281c14dce66d40eab47f0018d80b1a2dd57d993f02b96f7ff03ff07ce9077a013a244d008d47ba3437b7f98ea5266c689ac328bfb380cb0d15ef724739757bf0d658eeb74d0e193bcb8ef71e7c1f947a97e1d728e846d34c82912901af9a6f78337b5b89d9cbfaee3d9220e072821a1942eddf0bd08a9843deb0e97025fd40d691dbcca92352d79c51cce8b438d868bab9fdf609d43f855da838d8c47218a43d36078736cb9f2cae4034ceb600ca2b29c2210cd2ed61f849b7b2b7c26e53f6d7a29fb0f2c428ab965384b706e49086a3cf752e19957c374dff16d8979f5a40f4bc8438d4f2455eeec2f942e0dd46ab610332cf7ed70ba7fed2179f690b98c3455cc26bc8d34f43fceff10d063a66975c99d1865b10a50156ff2c923080ff31fc245e86b1231338ba409de38ca7e2661bf2909fabbf6f16d942ada08f1980dd11ded7fa555ee1c46880daeba158ba84337a4388aac7431b3d51216cdcec118267d224f77c3603960b6e997a75da761f7e0bd7354a0a045b7cbfeafa882125a56c01bd3d588cbf1e2c17b38ba472accd9267fac42e4cda092a96ca152a31c64477c9c823a7b16d67d2763b2264ff51a622ac6e4eddfd7d1486c94e662dca314190d14d30e3d9786a8af945dd297a4ed45ec3c69d7ce54ad14224ca0f0a39d048d1cb464114490e10f33c013666551e092b79ff588788f9c5e6f651ccf6b4ab506e09dde4f0f1cf651eda38be09288c7c908dff9dba4de3b36242af1321e54846f922f64a4d9be67d38c9d8ba6abbbfc12347904bfd3eb2bc32d3ca2d54a6678f0e4f06c74e08faf7f160f4861808c1089ff13b27bac7828c0ffa89dcf59b8915f146ddecba8b2396a26dea124690f4d0c66d88df5897bd9f193cd53bf06bc82a7667d281c4f0e4f2ec944de400afb92242caf1ccd06c2c8988b209aa023890915295f6be76903d2ee7587f687c37a1659a39c4283c3dcc0125d27a480bb2242ee2075b516379372a60cc7178b13a21e42515385ff55f9e18a1f69aaafa62aa9174ee6879f89c01328a8dd59f7ee87fd041d6f9d9f7e9aa838954b2e7970b3619ca87c39eeaaccfa67ac654b17ba3f39f0d7b1e971e21e8eb7e78e9b59ea2c3f6a9e8179203e568ce4a9a9f4eda59560f357099db1612ae9333e9e7ffb39b0da9ea7245537c05ffb627442

看着让人很难以下手。

将页面用到的 js 文件下载下来仔细观察。代码进行了打包和混淆,但因为加密算法是引用的外部库,没有完全混淆,所以算法名字和密码都明文摆在那。

注意到这段代码多次出现,且这个 abcdefg 串疑似密码:

JSON.parse(Object(j.b)(n.data, "abcdefgabcdefg12"));

翻找 j.b 函数,很快找到:

j = e("POKb")

e 即为打包的特大函数,从里面找到编号为 POKb 的一项:

POKb: function(t, n, e) {
    "use strict";
    n.a = function(t, n) {
        if (void 0 == t)
            return "";
        var e = u.a.enc.Utf8.parse(n)
            , a = "";
        if ("string" == typeof t)
            a = u.a.AES.encrypt(t, e, {
                mode: u.a.mode.ECB,
                padding: u.a.pad.Pkcs7
            });
        else if ("object" === (void 0 === t ? "undefined" : i()(t))) {
            var c = o()(t);
            a = u.a.AES.encrypt(c, e, {
                mode: u.a.mode.ECB,
                padding: u.a.pad.Pkcs7
            })
        }
        return a.ciphertext.toString()
    }
    ,
    n.b = function(t, n) {
        var e = u.a.enc.Utf8.parse(n)
            , a = u.a.AES.decrypt(u.a.format.Hex.parse(t), e, {
            mode: u.a.mode.ECB,
            padding: u.a.pad.Pkcs7
        });
        return u.a.enc.Utf8.stringify(a)
    }
    ;
    var a = e("mvHQ")
        , o = e.n(a)
        , c = e("pFYg")
        , i = e.n(c)
        , r = e("Av7u")
        , u = e.n(r)
}

因此可以认为 j.b 等效于这段代码中的 n.b

这段代码写得很明白,先把字符串编码,然后调用了 AES.decrypt,调用的参数也都写好了,意思就是通过 AES 算法解密,密码为 abcdefgabcdefg12。同样地,这段代码上面的 n.a 实际是通过 AES 加密。复刻这里的加密解密函数是很容易的,这个加密算法就这样被轻松破解了。

我还做了一个页面专门负责加密解密心意答,方便调试。

于是我们就可以把几次请求的内容解密出来观察了。

首先是 getUserMultiExamByStudentIdAndSchoolId,请求解密后是这样:

{"schoolId":19707,"studentId":"20222446"}

惊讶地发现,这条请求里面居然没有携带任何身份验证的内容,直接发送校园号,也就是说我即使发送别人的校园号它也会正常返回结果。这是极其不规范的设计,根本没有为接口安全性做考虑。

继续看响应:

[{"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}]}]

嗯,很老实地返回了所有考试的信息和编号。

用同样的方式观察剩下两个请求,发现它们同样没有进行身份验证。

也就是说,我完全可以自己写一个页面,集成好加密解密,然后直接向心意答发送请求。比起原本的心意答,我可以显示更完整的数据,而且可以查询任何一个人的成绩。

你会发现我的查询页面直接输入姓名就能查到,但刚才请求发送的明明是数字校园号。我是怎么知道这个姓名对应的数字校园号的呢?

这件事其实只要你有足够的运气和敏锐的感知力,很快就能搞定。我在在线学习平台闲逛的时候,发现了一个显示所有人姓名、头像、数字校园号的大列表。这个列表背后的数据也是需要请求获取的,那么我直接把这个请求的响应下载下来,就有了所有人姓名和校园号的对应表了。当你输入姓名时,我就直接向这个表里查询(这也是为什么只支持 2025 届高考部)。