免责声明:本文仅供技术研究和学习交流之用。请勿将文中所述技术用于非法用途,作者不对任何滥用行为承担责任。

需求

哆啦校圈是一个同济学生比较活跃的校内交流平台,然而里面鱼龙混杂,时不时乌烟瘴气。对获取校内资讯造成了很大障碍。目前来看,有一个恶心的问题,便是拉黑系统。

拉黑操作是这样的,找到一个帖子,点击发帖人的头像。如果发帖人是非匿名发帖,则出现一个拉黑按钮,点击后便可以把对方拉黑 1个月

短暂的分别

如果是匿名发帖,您还真就拿对方没办法了,因为如上操作对匿名发帖无效,并不会有拉黑按钮的出现。那么就只能看着对方宣泄情绪、带节奏而无可奈何。

这样不好!

因此,本文章要完成的主要任务是提供一些背景知识,使得如下目的能够但不一定便捷实现:

  1. 得以拉黑匿名发帖;
  2. 实现永久拉黑

我们的美好期望能够实现吗?可能会遇到一些问题吧?比如说,如果定位不到匿名发帖人,怎么办?如果拉黑了发帖人,只能屏蔽正常发帖,仍然无法屏蔽匿名发帖,怎么办…

谢谢这个草台班子一样的世界,这些问题都能够得到攻克。

太长不看版

问题 1 的初步解决

注意到在帖子详情页,如果在发帖人部分出现了 关注 按钮,点击关注,然后在 我的 -> 设置 -> 关注列表 就可以查看没有马甲的页面,从而可以进行拉黑。

有关注按钮

但是如果用户本身关闭了主页开放,这个按钮不会出现。这时候就只好抓包了,那就请看后面的内容(## 主观评价 => ### 问题 1 的最终解决)。

无关注按钮

总结一下,匿名 / 不匿名;主页开放 / 主页不开放 组成了四个状态,其中

发帖状态 主页状态 是否可查看用户信息 拉黑按钮 关注状态
不匿名 开放 可关注
不匿名 不开放 无法关注
匿名 开放 可关注,然后拉黑
匿名 不开放 无法关注

前三种隐私的设置情况,可以通过图形界面的迂回方案来解决,而第四种必须抓包。

问题 2 的解决

注意到在 我的 -> 设置 -> 黑名单 中可以查看到所有被拉黑的用户,就算拉黑的 1 个月期限过了,也只是解除拉黑,但拉黑记录还在,很方便就可以进行反复拉黑,结合后面提到的 API,可以进行自动化操作。

软件与环境

  • Windows 11 专业版 24H2
  • 微信电脑版 3.9.12.37
  • 校圈
  • Charles v4.6.7
  • Postman 11.31.1

熟悉接口…之前

在介绍接口之前,得讲讲校圈的特殊性。

因为校圈是微信小程序-only,因此抓取接口比纯粹的网页内容要多一步,便是进行抓包。

使用 Charles 来抓包

Charles官网如下描述它的功能:

Charles is an HTTP proxy / HTTP monitor / Reverse Proxy that enables a developer to view all of the HTTP and SSL / HTTPS traffic between their machine and the Internet. This includes requests, responses and the HTTP headers (which contain the cookies and caching information).

简而言之,是充当了中间人角色来监听本机的所有流量请求。

它的配置,网络上有不少帖子表述得很清楚了:

如这篇 CSDN 上的文章

这里要补充说明的是,PC 的配置按帖子中的描述就可以了,而移动端的配置略微复杂一些。

移动端的配置——没必要

  • 问题一:为什么要配置证书?按 ChatGPT 的解释:

当我们使用 Charles 或其他类似的抓包工具时,Charles 需要在客户端和服务器之间充当一个中间人,也就是中间人攻击(MITM,Man-in-the-Middle)的方式来解密和查看 HTTPS 的通信内容。这就要求 Charles 拿到传输过程中加密数据的明文内容。

没有证书的情况下:

当你没有安装 Charles 生成的证书 时,浏览器(或任何其他客户端,如微信小程序)会在访问 HTTPS 链接时,发现其证书是 由 Charles 代理生成的,而不是由一个受信任的证书颁发机构(CA)颁发的。
浏览器会认为这是一个不安全的链接,因为它无法验证该证书的合法性,通常会显示 “您的连接不是私密连接” 或类似的警告信息。

这里举一个例子,例如如果在没有打开 Charles的情况下,访问 www.baidu.com,证书颁发者的公用名是 GlobalSign RSA OV SSL CA 2018,而如果打开了 Charles,并且要代理 HTTPS 的请求时,就会发现颁发者的公用名为 Charles Proxy CA (7 Feb 2025, XIALING)。接下来会发生的事情,AI 已经解释得很清晰了,不再赘述。

  • 问题二:为什么要配置移动端的证书?

简而言之,因为微信小程序在手机上运行啊!

思考几秒钟,我们突然想到:

谁说微信小程序一定要在手机上运行啊?电脑版微信不是也有小程序吗?

此言得之。因此,配置移动端的证书不是必要的!

不过,如果要配置的话,in case you need it,有以下几个问题是我遇到过的:

  1. 证书的安装
    使用常见网络教程提供的 chls.pro/ssl 无法下载证书,返回的内容为空,chrome 浏览器的提示是:chls.pro 未发送任何数据,需要切换到 www.charlesproxy.com/getssl才能下载证书。

  2. 只能抓包浏览器应用,其他 APP 不可以
    问题的原因,官网已经解释的很清晰了,在 Android section 是这样说的:

As of Android N, you need to add configuration to your app in order to have it trust the SSL certificates generated by Charles SSL Proxying. This means that you can only use SSL Proxying with apps that you control.

我们只能代理我们控制的 APP,而微信是腾讯控制的,因此无法实现代理。不过有的解决方案提出,手机 ROOT 后经过一些设置就能够实现。我想想,太麻烦了,因此放弃,感兴趣的读者可以自行探索。

PC 的设置

安装好了软件,配置好了证书(参见上方 CSDN 的链接),时间来到抓包之前,现在要设置如下几个事项。

Proxy 页签中设置 Start RecodingStart SSL Proxying,懒得翻导航栏的话,快捷按钮是下图的两个:

快捷按钮

此外,在 Proxy > SSL Proxying Settings... > Include下的(Add) 中添加个通配符,懒人设置,代理所有连接。具体见下图:

代理设置

如果这样设置后,打开一些网络页面,左侧监听列表仍为空白,看看是不是在 Proxy 菜单勾选了 Windows Proxy 或者 macOS Proxy(上图的对应位置是)。如果电脑上运行了不止一个代理软件的话,可以尝试关闭其他代理后,重启 Charles。仅仅关闭某小猫是不行的,重启 Charles 后才好。

几个接口

可以右键感兴趣的链接,选择 Focus,这样就会把我们感兴趣的链接单独划分出来,剩下的其他链接归为一类(Other Hosts)。

Focus

这些接口都需要登录才能访问,不过认证方法也很简单。在请求头的 token 字段中,把值复制一下就可以了。值应该是长时间不变的。

我们发现,在浏览校圈时,有这样几个非平凡的接口,在这些非平凡的接口中更非平凡的,将用 标注出来。

获取最近发布的帖子列表

接口:https://cdn.dolacc.com + /api/Wxpostv2/getPostsCdnpw

方法:GET

Query String:

Parameter Value
toId 0
scId 24
pageSize 10
lastindex -1
keyword
freshKey 23333

这几个参数中:

scId 有用,sc 可能是 School Circle 的意思吧?24 表示同济大学的校圈编号;

lastIndex 也有用,表示的是上一次请求的最早时间的帖子。例如,下面返回格式中,data.posts[9].id 如果是 23333,那么在下一次请求的时候,这个值就会是 23333

pageSize 没用,默认是 10,但是修改为其他大小,也不会返回更多内容。

返回格式:

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
72
{
"code": 1,
"msg": "返回成功",
"time": "1739002893",
"data": {
"posts": [ // 存放了 10 个帖子
{
"id": 2071591, // 帖子 id
"user_id": 6, // 发布帖子的用户的 id
"name": "Q8lcO9y19WMqZAZ+1J3nr3rd+HMxixc93iGMHg78C8SndvS2/AWNDpkkYk3F5+YEJCS4RQwWGrBqw5h1XfyQSw==", // 帖子内容,base64 编码,AES 加密,utf-8 格式
"myimages": "https://ltadmin.rainsee.top/uploads/20241225/870d6c4f16612c88c6bf5e2d7f967ae7.jpg", // 帖子的附件图片
"myvideofiles": [],
"mymusicfiles": [],
"topic_id": 0,
"createtime": 1734587521, // 创建时间
"updatetime": 1734587521, // 更新时间
"pReviewStatus": "3",
"hot": 6601, // xx人围观
"top": "1", // 是否置顶
"user_ids": "[400482]",
"shorturl": null,
"zansum": 41, // 赞数和
"commentsum": 273, // 评论和
"jubaocount": 0,
"deleteuser": null,
"topEndTime": 0,
"dlz_count": 0,
"target": "0", // 是否需要学生认证后才能查看
"xuanshang": null,
"majia": "0", // 是否匿名发言(马甲)
"adlevel": 1,
"jinghua": "1", // 是否为精华
"origin": "0",
"keywords": "校圈、吃瓜、同步群、需要、进、群", // 关键词
"business_task_id": -1,
"freshkey": 356,
"business_plan_id": -1,
"settleed": "1",
"risky": "0",
"hotnews": null,
"isfee": "1",
"user": { // 发帖用户的信息
"id": 6, // 用户 id
"nickname": "哆啦", // 用户昵称
"avatar": "https://static.rainsee.cn/bdd88202206151049061619.jpg?imageslim", // 头像 url
"level": 4, // 等级
"gender": 0, // 性别
"block": "0",
"price": 200,
"eattip": "0",
"eatupdatetime": 1649840402,
"dlzs": 81888,
"verty": "0",
"xs_credit": 3,
"xs_price": 100,
"checktime": 1728057600,
"school_ids": "",
"remark": null,
"fire": 21,
"fuqicount": 513,
"likes": "951053,520636,1907893",
"ques": "0",
"nohidepermission": "0"
},
"topic": null
},

// 略去 9 个
],
"searchUse100": false
}
}

定期获取新帖子数量

接口:https://luntan.dolacc.com + /api/wxUser/countNewMessagev4

方法:GET

返回格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"code": 1,
"msg": "返回成功",
"time": "1739002045",
"data": {
"needAuth": 0,
"newMessageCount": 0,
"todaytime": 1738944000,
"newReadCount": 0,
"addLikeCacheNum": 0,
"xuanshang": 1,
"xuanshangArr": [2244299]
}
}

补充说明:15s 调用一次,非平凡的数据是 data.newMessageCount,这个数量统计的应该是最近一次请求 posts 后的新帖子数量。

获取所有主题

接口:https://www.dolacc.cn + /api/wxPost/getAllTopic

方法:GET

返回格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"code": 1,
"msg": "返回成功",
"time": "1739006113",
"data": {
"topics": [{
"name": "投稿",
"order": 1,
"id": 1
}, {
"name": "求助",
"order": 2,
"id": 2
}, {
"name": "💧漫金山",
"order": 2,
"id": 24
},

// 略去几个

]
}
}

获取十大热帖

接口:https://www.dolacc.cn + /api/wxPostv2/getTop10v3

方法:GET

Query String:

Parameter Value
scid 24

返回格式:

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
72
73
74
75
76
77
78
79
80
81
82
{
"code": 1,
"msg": "返回成功",
"time": "1739006113",
"data": {
"top10": [{
"hots": 2569, // xx围观
"type": "post",
"id": 2243410, // 帖子 id
"name": "好冷冻得我想跳楼,上海是好地方😭", // 帖子内容
"bg": "http:\/\/www.dolacc.cn\/imgs\/hot1.png?t=123" // 热 | 爆
}, {
"hots": 2530,
"type": "post",
"id": 2243481,
"name": "老天奶啊,我喜欢那种有点自卑感破碎感敏感的好看的小男生,但是,好看的人是不会这样的基本上,无解了",
"bg": "http:\/\/www.dolacc.cn\/imgs\/hot2.png?t=123"
}, {
"hots": 3679,
"type": "post",
"id": 2244168,
"name": "男朋友天天打游戏到凌晨,大三了四级还没过,催他学习无数遍就是不学。今天晚上好不容易跟我说看会教资的课,我还怕打扰他一直没发消息。过了好久给他打了个视频发现忙线中,结果和兄弟打语音聊游戏呢🤣🤣",
"bg": "http:\/\/www.dolacc.cn\/imgs\/hot3.png?t=123"
}, {
"hots": 3574,
"type": "post",
"id": 2243314,
"name": "总感觉身边的世界蛮割裂的……之前有同省的学长问我要不要一起去滑雪(报考的时候聊过几句,开学之后也没什么交流),后面直接跟我说如果我接受的话可以两个人拼一间房来省钱,我感觉自己接受程度已经蛮良好了还是被吓得连夜扛火车跑路\n今天来校圈一看,原来还有些男生觉得异性两个人吃顿饭就很暧昧了\n两极分化好严重",
"bg": "http:\/\/www.dolacc.cn\/imgs\/hot4.png?t=123"
}, {
"hots": 2032,
"type": "post",
"id": 2243618,
"name": "唐探1900好看不 跟唐探2或者3比",
"bg": "http:\/\/www.dolacc.cn\/imgs\/hot5.png?t=123"
},
// 略去 5 个
],
"scId": "24", // 校圈 id
"scName": "同济校圈", // 校圈名
"openAd": 1,
"schoolInfo": {
"id": 24,
"name": "同济大学",
"deletetime": null,
"bindurl": "",
"msgnum": 10,
"top": "老师评价选课指导\r\n#小程序:\/\/校圈\/tX9sDT2zjjhcVdK",
"lastId": 2244859, // 最新的帖子
"simpName": "同济校圈",
"lastMateId": 0,
"openswitch": 0,
"openAd": 1,
"adtrade": 1,
"level": "2",
"postAuth": 1,
"commentAuth": 1,
"publichSm": 1,
"h5link": 1,
"homeSm": 1,
"que": 1,
"ptfee": 15,
"qunqrcode": "system",
"nohidepermission": 0,
"homemenu": "0,2,4,3,1,5,6",
"blackword": "校园跑,代课,dai做,代做", // 黑名单词汇,这下知道为什么有一次我帖子发不出去了。老师代课也不行啊23333
"freshKey": 29262
},
"adBoxs": [{
"id": 0,
"title": "AI助手",
"desc": "免费好用",
"img": "https:\/\/luntanpic.yujianpay.com\/upload\/static\/static\/meta.png",
"class": "meta"
},

// 略去了 7 个

],
"from": "cache"
}
}

※获取帖子详情

接口:https://cdn.dolacc.com + /api/wxPostv2/visitPostCdnpw

方法:GET

Query String:

Parameter Value
pId 2244815
nocache -1
orderType 1
freshkey 3

pId 是帖子的 id,非平凡。剩下的带不带都可以。

返回格式:

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
{
"code": 1,
"msg": "返回成功",
"time": "1739008503",
"data": {
"cachename": "postmillv42244815",
"postDtl": {
"id": 2244815, // 帖子 id
"user_id": 145164, // 发帖用户的 id
"name": "n0gKQU2McCfEXG2g17KqDQ==", // 帖子内容,仍然是 base64 + AES 加密后的内容
"introduction": "",
"myimages": [],
"myvideofiles": [],
"mymusicfiles": [],
"topic_id": 5, // 话题 id,这里的话题是 #帮转
"createtime": 1739001708, // 创建时间
"deletetime": null,
"updatetime": 1739001708075, // 更新时间,毫秒级
"school_id": 24, // 学校校圈的 id
"pReviewStatus": "3",
"hot": 178, // xx围观
"top": "0",
"pReviewComment": "",
"referuser_id": 0,
"referstatus": "0",
"user_ids": "",
"shorturl": null,
"zansum": 0, // 赞和
"commentsum": 3, // 评论和
"jubaocount": 0,
"deleteuser": null,
"topEndTime": 0,
"dlz_count": 0,
"target": "1", // 是否需要学生认证后才能观看,按理来说我应该不能看到这种帖子的详情,但是..
"xuanshang": null,
"majia": "0", // 是否为匿名
"adlevel": 1,
"jinghua": "0",
"origin": "0",
"keywords": "",
"business_task_id": -1,
"freshkey": 3,
"business_plan_id": -1,
"settleed": "1",
"risky": "0",
"hotnews": "",
"isfee": "1",
"user": {
"id": 145164, // 用户 id,同上
"nickname": "赵四", // 用户昵称
"avatar": "https://img2.woyaogexing.com/2022/11/01/2eedc496a2817541!400x400.jpg",
"level": 1,
"gender": 0,
"scId": 24,
"latepos": 2244299,
"needread": 0,
"admType": "normal",
"admUname": 0,
"block": "0",
"eattip": "0",
"ques": "0",
"nohidepermission": "0"
},
"topic": { // 话题
"id": 5,
"name": "帮转",
"createtime": 0,
"hot": "0",
"school_ids": "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,34,35,36,37,38,39,40,41,45,46,47,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,111,112,113", // 哪些学校有这些话题
"hots": 0,
"order": 6
},
"dunnum": 0,
"comments": [ // 帖子下的评论
{
"id": 6958633, // 评论 id
"post_id": 2244815, // 帖子 id
"user_id": 145164, // 发帖人 id
"name": "四平线下,二年级女孩,每周一三五晚上辅导作业(无需备课),每次1.5小时,100/h。要求女大学生。", // 帖子正文,最近的行情如何呢...
"zan": 0, // 赞数
"createtime": 1739001770, // 发布时间
"coStatus": "0",
"username": "张三", // 用户名
"userimage": "https://img2.woyaogexing.com/2022/11/01/2eedc496a2817541!400x400.jpg",
"user_gender": 0,
"touser_id": 145164, // 回复给谁
"comments_id": 0,
"readc": "0",
"myimages": [],
"level": 1,
"justown": null,
"origin": "0",
"candelete": false,
"majia": 0, // 是否匿名
"comments": [], // 是否有楼中楼
"iclick": false
},
{
"id": 6958636,
"post_id": 2244815,
"user_id": 145164,
"name": "有意➕v xxx", // 这里的内容我应该看不见的,但是...(敏感信息已略去)
"zan": 0,
"createtime": 1739001806,
"coStatus": "0",
"username": "张三",
"userimage": "https://img2.woyaogexing.com/2022/11/01/2eedc496a2817541!400x400.jpg",
"user_gender": 0,
"touser_id": 145164,
"comments_id": 0,
"readc": "0",
"myimages": [],
"level": 1,
"justown": null,
"origin": "0",
"candelete": false,
"majia": 0,
"comments": [],
"iclick": false
},
{
"id": 6958670,
"post_id": 2244815,
"user_id": 244,
"name": "地址在哪里呀",
"zan": 0,
"createtime": 1739002342,
"coStatus": "0",
"username": "李四",
"userimage": "https://static.rainsee.cn/upload/2024/06/22/eb5c7202406221031036225.jpg?imageslim",
"user_gender": 1,
"touser_id": 145164,
"comments_id": 0,
"readc": "0",
"myimages": [],
"level": 3,
"justown": null,
"origin": "0",
"candelete": false,
"majia": 0,
"comments": [],
"iclick": false
}
],
"porder": null
},
"nocache": false
}
}

获取悬赏详情(接口同上)

接口:cdn.dolacc.com + /api/wxPostv2/visitPostCdnpw

方法:GET

Query String:

Parameter Value
pId 2244299
nocache -1
orderType 1
freshkey 2

返回格式:

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
{
"code": 1,
"msg": "返回成功",
"time": "1739007940",
"data": {
"cachename": "postmillv42244299",
"postDtl": {
"id": 2244299,
"user_id": 244331,
"name": "1tQTvw48u+9XhpkEsW\/eT4YayiZ+rMR+mlKubsmR21lm0oEK70EEA81u\/kuFmU+pm5WGqGtdZcsUpA6Wckj\/\/imyDxzqYoGhUErQXoDCT6Vr4zZMPy\/8SqLP7cnzTJ0r", // 加密的正文
"introduction": "",
"myimages": [],
"myvideofiles": [],
"mymusicfiles": [],
"topic_id": 14,
"createtime": 1738977878,
"deletetime": null,
"updatetime": 1738977878207,
"school_id": 24,
"pReviewStatus": "3",
"hot": 22,
"top": "0",
"pReviewComment": "",
"referuser_id": 0,
"referstatus": "0",
"user_ids": "",
"shorturl": null,
"zansum": 0,
"commentsum": 0,
"jubaocount": 0,
"deleteuser": null,
"topEndTime": 0,
"dlz_count": 0,
"target": "0",
"xuanshang": {
"id": 9820,
"post_id": 2244299,
"createtime": 1738977878,
"status": "2",
"dlz": 0,
"price": 600,
"wxorderid": "17389778789953",
"user_id": 244331,
"outime": 1739150678,
"updatetime": 1739006890,
"kpmsg": "有两个快递,一个是顺丰取件码:xxxx,一个是在....", // 隐去敏感信息,这部分信息应该只有接单用户才能看见,但是...
"bind_user_id": 239845,
"finishtime": null,
"finishowntime": null,
"zmimages": null,
"kpimages": "",
"output": "0"
},
"majia": "0",
"adlevel": 1,
"jinghua": "0",
"origin": "0",
"keywords": "",
"business_task_id": -1,
"freshkey": 2,
"business_plan_id": -1,
"settleed": "1",
"risky": "0",
"hotnews": "",
"isfee": "1",
"user": { // 发布悬赏的用户
"id": 244331,
"nickname": "王五",
"avatar": "https:\/\/gimg2.baidu.com\/image_search\/src=http%3A%2F%2Fpic2.zhimg.com%2F50%2Fv2-9eae8c8afdfac62b987a9f99280442a5_hd.jpg&refer=http%3A%2F%2Fpic2.zhimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1625914275&t=9e697bc8b095d12837b0bd5aee5858ed",
"level": 1,
"gender": 0,
"scId": 24,
"latepos": 2244378,
"needread": 0,
"admType": "normal",
"admUname": 0,
"block": "0",
"eattip": "0",
"ques": "0",
"nohidepermission": "0"
},
"topic": {
"id": 14,
"name": "悬赏",
"createtime": 0,
"hot": "0",
"school_ids": "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,34,35,36,37,38,39,40,41,45,46,47,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,111,112,113",
"hots": 0,
"order": 4
},
"dunnum": 0,
"comments": [],
"porder": {
"id": 45402,
"order_id": "17389778789953",
"money": "6", // 6 块钱
"createtime": 1738977878,
"type": "3",
"user_id": 244331,
"post_id": 2244299,
"status": "1",
"data": "[]",
"school_id": 24,
"quitemsg": null,
"quit_notify_time": null,
"quit_notify_msg": null,
"checktime": -1
}
},
"nocache": false
}
}

※查成分(点击发帖人头像)

接口:luntan.dolacc.com + /api/wxUser/getUserMsg

方法:POST

Payload:

1
2
3
{
"id": "169161" // 用户 id
}

返回格式:

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
{
"code": 1,
"msg": "返回成功",
"time": "1739009248",
"data": {
"rebot": "ozQHs4p_z0SbIWHa8cDdIa5HnqTs",
"user": {
"username": "同学4k2a9w", // 用户名,不带马甲。这位自己的名字就是这样,取得和马甲似的。和 momo 啥的异曲同工
"level": 5, // 等级
"authtype": "学生认证 ·本科", // 认证
"id": "169161", // 用户 id
"sex": -1, // 没选性别
"blocked": false,
"year": 1.3240813673262304, // 注册时长
"avatar": "https:\/\/file.yujianweb.cn\/upload\/2024\/01\/09\/db539202401090953263164.jpg?imageslim", // 头像 url
"postcount": 421, // 发帖数
"commentcount": 526, // 评论数
"nohidepermission": "0"
},
"post": [{ // 最近 5 条帖子
"id": 2244800,
"user_id": 169161,
"name": "你们能接受和你暧昧的人同时也在和别人接触吗,甚至你看得到他们在互相接触的那种?但好像也并不能说些什么?是否应该允许别人挑选你?也允许你挑选别人?但挑来挑去也并不一定能挑对眼对吗?",
"introduction": "",
"myimages": "",
"myvideofiles": "",
"mymusicfiles": "",
"topic_id": 0,
"createtime": 1739001063,
"deletetime": null,
"updatetime": 1739001063995,
"school_id": 24,
"pReviewStatus": "3",
"hot": 287,
"top": "0",
"pReviewComment": "",
"referuser_id": 0,
"referstatus": "0",
"user_ids": "[175157][352293]", // 没明白这个字段是干啥的
"shorturl": null,
"zansum": 2,
"commentsum": 8,
"jubaocount": 0,
"deleteuser": null,
"topEndTime": 0,
"dlz_count": 0,
"target": "0",
"xuanshang": null,
"majia": "1",
"adlevel": 1,
"jinghua": "0",
"origin": "0",
"keywords": null,
"business_task_id": -1,
"freshkey": 10,
"business_plan_id": -1,
"settleed": "1",
"risky": "0",
"hotnews": "",
"isfee": "1"
}, {
"id": 2244718,
"user_id": 169161,
"name": "个人认为被不喜欢的人经常性地“拍一拍”就像是一种赛博“性骚扰”,明明实体并没有触碰到彼此,但对方有意无意地“拍一拍”就像是在提醒你“I touched you and I am watching you.(尽管是线上的,但其实也很想试试线下的)”而被“拍”的人只会觉得边界被冒犯且有些恶心🤮。",
"introduction": "",
"myimages": "",
"myvideofiles": "",
"mymusicfiles": "",
"topic_id": 0,
"createtime": 1738997776,
"deletetime": null,
"updatetime": 1738997776447,
"school_id": 24,
"pReviewStatus": "3",
"hot": 408,
"top": "0",
"pReviewComment": "",
"referuser_id": 0,
"referstatus": "0",
"user_ids": "[64515][175157][318087][352293]",
"shorturl": null,
"zansum": 3,
"commentsum": 10,
"jubaocount": 0,
"deleteuser": null,
"topEndTime": 0,
"dlz_count": 0,
"target": "0",
"xuanshang": null,
"majia": "1",
"adlevel": 1,
"jinghua": "0",
"origin": "0",
"keywords": null,
"business_task_id": -1,
"freshkey": 13,
"business_plan_id": -1,
"settleed": "1",
"risky": "0",
"hotnews": "",
"isfee": "1"
},

// 略去了 3 个

],
"close": true, // **重要** 主页是否关闭
"scId": 24, // 校圈 id
"mylike": false,
"fanNum": 0, // 粉丝数
"znNum": 509 // 赞总数
}
}

※用户登录

接口:luntan.dolacc.com + /api/wxUser/fetch

方法:GET

返回格式:

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
{
"code": 1,
"msg": "登陆成功",
"time": "1739009641",
"data": {
"id": 65472, // 用户 id
"school_count": 1, // 学校数量
"likes": [],
"authtype": -1,
"hasmobile": true,
"commentphone": true,
"realphone": true,
"verty": "0",
"vertykey": "foo",
"blockuser": "[114495][149385][133815][196795][232828][343217][302802][184315][389031][233566][106632][229005][522][104727][396863]", // 屏蔽用户的列表
"token": "bar", // **重要**认证凭证,后续的请求都要带上它。不过好像有了 token 才能请求这个页面...先有鸡?先有蛋?
"dlzs": 3710,
"nickName": "バカ", // 昵称
"avatarUrl": "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic2.zhimg.com%2F50%2Fv2-5bcf79fce14ac8c7ed2e1584bb7015b0_hd.jpg&refer=http%3A%2F%2Fpic2.zhimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1625914138&t=48850fe2af2862a1c9d5a165a03a2701", // 头像 url
"scId": 24,
"gender": 0,
"admType": "normal",
"rq": 0,
"scName": "同济大学",
"scSmallName": "同济校圈",
"level": 1,
"leName": "Lv2",
"admUname": 0,
"overdue": 1741526214,
"ad": [ // 广告
{
"id": 2,
"name": "外卖红包",
"path": "/pages/waimai/waimai",
"image": "http://file.yujianweb.cn/duolaqiandao.jpg",
"binduser": false,
"deletetime": null
},
{
"id": 3,
"name": "ershou",
"path": "/pages/webview/webview?url=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2FnnFFmUm3rKNO9vMzkSi0xg",
"image": "https://file.yujianpay.com/duola.jpg",
"binduser": false,
"deletetime": null
}
],
"adnum": 0,
"isbind": false,
"user": {
// 重复信息的嵌套
},
"yujiankey": "baz"
}
}

※获取拉黑列表

接口:luntan.dolacc.com + /api/wxUser/getMyBlock

方法:GET

返回格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"code": 1,
"msg": "返回成功",
"time": "1739022545",
"data": {
"lists": [
{
"id": 29060, // 黑名单 table 中的主键 id
"user_id": 65472, // 用户 id,我的 id
"close_id": 941, // 被我拉黑用户的 id
"createtime": 1722937038,
"deletetime": null,
"detail": {
"nickname": "张三", // 昵称
"avatar": "https://file.yujianweb.cn/upload/2024/02/02/1fd45202402020105135635.jpg?imageslim" // 头像
}
},

// 略去一些

]
}
}

※拉黑/取消拉黑用户

接口:luntan.dolacc.com + /api/wxUser/blockMyuser

方法:POST

Payload:

1
2
3
4
{
"block": 0, // 0 拉黑 | 1 取消拉黑
"userid": "6"
}

返回格式:

1
2
3
4
5
6
{
"code": 1,
"msg": "返回成功",
"time": "1738998015",
"data": []
}

获取关注列表

接口:www.dolacc.cn + /api/Wxpostv3/getMyLike

方法:GET

返回格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"code": 1,
"msg": "返回成功",
"time": "1739023216",
"data": {
"users": [{
"nickname": "小明",
"avatar": "https:\/\/gimg2.baidu.com\/image_search\/src=http%3A%2F%2Fpic2.zhimg.com%2F50%2Fv2-5bcf79fce14ac8c7ed2e1584bb7015b0_hd.jpg&refer=http%3A%2F%2Fpic2.zhimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1625914138&t=48850fe2af2862a1c9d5a165a03a2701",
"level": 1,
"id": 347868
}]
}
}

关注某个用户

接口:luntan.dolacc.com + /api/Wxuserv2/addUserLike

方法:POST

Payload:

1
2
3
{
"beLikeUser": "347868"
}

返回格式:

1
2
3
4
5
6
7
8
{
"code": 1,
"msg": "创建成功",
"time": "1739023041",
"data": {
"logs": "33706"
}
}

取关某个用户(在关注了的用户主页取消)

接口:luntan.dolacc.com + /api/Wxuserv2/cancelLike

方法:POST

Payload:

1
2
3
{
"beLikeUser": "347868"
}

返回格式:

1
2
3
4
5
6
{
"code": 1,
"msg": "创建成功",
"time": "1739023142",
"data": []
}

取关某个用户(在自己的关注列表取消)

接口:www.dolacc.cn + /api/Wxuserv2/cancleLike

方法:GET

Query String:

Parameter Value
beLikeUser 347868

返回格式:

1
2
3
4
5
6
{
"code": 1,
"msg": "创建成功",
"time": "1739023422",
"data": []
}

name 字段的破解

在上面的接口中,查看最近 10 条帖子、查看帖子/悬赏的详细内容需要使用到 name 字段,然而它是被编码的,无法获得明文,如何对敌?

显然,这是一个 base64 编码后的内容,看看能不能直接转换为 utf-8?

n0gKQU2McCfEXG2g17KqDQ== 为例。

运行如下的 python 脚本:

1
2
3
4
5
6
7
8
9
10
import base64

foo = "n0gKQU2McCfEXG2g17KqDQ=="

_dec = base64.b64decode(foo) # base64 => binary

_dec = _dec.decode() # binary => utf-8

print(_dec)

报错:

UnicodeDecodeError: ‘utf-8’ codec can’t decode byte 0x9f in position 0: invalid start byte

推测进行了加密。如何加密的呢?

思路一:抓包

顺着 web 逆向的思路,看看小程序有没有向哪里请求 js 文件?如果有的话,全文检索 decrypt 不就好了?找到 ivkey 就行了。为什么推测是 AES 而不是 RSA,因为把私钥放到客户端很奇怪呀。

结果走不通。

因为微信小程序的原理是,js 在本地,而不需要向哪个服务器请求。

思路二:反编译微信小程序

首先,定位微信小程序的位置。它们被存储在 C:\Users\<username>\Documents\WeChat Files\Applet 下,文件夹以 wx打头,形如 wx9ddd73d26fdbacba ,这个就是校圈的文件夹,而打包后的文件名是 __APP__.wxapkg,目录结构是:Applet/wx9ddd73d26fdbacba/315/__APP__.wxapkg

咋就定死了这是校圈小程序呢?有两种确认方案:

  1. Applet 下的所有小程序文件夹都删除,打开校圈,看多了哪个文件夹;
  2. 看看 Charles 请求头中的 Referer,形如 https://servicewechat.com/wx9ddd73d26fdbacba/315/page-frame.html315 露出了鸡脚(看看上文的目录结构)。

找到了 __APP__.wxapkg,需要先解密。有一个 Github 项目,使用方法很清晰。不妨设解密后的文件是 dec.wxapkg

之后,在这个 Github 项目 中,把 dec.wxapkg 进行解包,虽然最后会报错,不过我们已经得到足够的文件了。

报错信息:

1
2
3
4
5
6
7
8
9
C:\wxappUnpacker-main\node_modules\vm2\lib\transformer.js:86
throw e;
^

vm.js:122
});
^

SyntaxError: Unexpected token '}'

在解包后的 dec/ 文件夹中,找 js 文件,发现在 app-service.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
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
define("utils/ase.js", function(
require,
module,
exports,
window,
document,
frames,
self,
location,
navigator,
localStorage,
history,
Caches,
screen,
alert,
confirm,
prompt,
XMLHttpRequest,
WebSocket,
Reporter,
webkit,
WeixinJSCore
) {
"use strict";

Object.defineProperty(exports, "__esModule", { value: true });

exports.decrypt = function(e) {
var a = t.default.enc.Utf8.parse("IVIVIV_LUYILAFEI"), // a 是 key
d = t.default.enc.Utf8.parse("XDXDXU_LUYILAFEI"), // d 是 iv
r = t.default.enc.Base64.parse(e),
f = t.default.enc.Base64.stringify(r);

return t.default.AES.decrypt(f, a, {
iv: d,
mode: t.default.mode.CBC,
padding: t.default.pad.Pkcs7
}).toString(t.default.enc.Utf8).toString();
};

exports.encrypt = function(e) {
var a = t.default.enc.Utf8.parse("IVIVIV_LUYILAFEI"),
d = t.default.enc.Utf8.parse("XDXDXU_LUYILAFEI");

return t.default.AES.encrypt(e, a, {
iv: d,
mode: t.default.mode.CBC,
padding: t.default.pad.Pkcs7
}).toString();
};

var e,
t = (e = require("crypto-js")) && e.__esModule ? e : { default: e };
});

有了找到了 iv 和 key,就可以在 python 中进行破译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 需要安装依赖:pip install pyCryptodome
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

AES_KEY = "IVIVIV_LUYILAFEI"
AES_IV = "XDXDXU_LUYILAFEI"

_enc = "n0gKQU2McCfEXG2g17KqDQ=="

# base64 => binary
_dec = base64.b64decode(_enc)

# decrypt
cipher = AES.new(AES_KEY.encode(), AES.MODE_CBC, AES_IV.encode())
_dec = cipher.decrypt(_dec)

# PKCS7 unpad
_dec = unpad(_dec, 16)

# decrypted bytes => utf-8
_dec = _dec.decode()

print(_dec) // 得到 `转 ➕🦶`

主观评价

总的来说,校圈的安全性还是比较低的。根源在于其后端返回的内容太多了,仅仅使用纯前端来阻止敏感信息的查看。这样的应用实例在浏览器的界面下很容易被破解,然而在微信小程序的外壳下,安全了一点,但不多。部分内容使用了 AES 加密,然而 inconsistent——不连贯。有的正文信息加密了,有的又没有加密:帖子的正文,没啥敏感的,需要额外一步破解;然而悬赏中只能让接单人看到的信息不根据用户身份来区分,随意从后端返回也就罢了,居然还是明文显示..信息的敏感程度和获得的加密水平不一致。

我想到的漏洞的利用有:

  1. 悬赏中的敏感信息,包括但不限于快递取件码、手机号码、微信等联系方式,存在被其他用户读取的可能,而且是明文传送,很简单就能读取;
  2. 仅限认证用户查看的内容,未认证用户也可以查看;
  3. 主页已关闭,还是能查看到昵称和发帖历史。

不过后端返回的内容丰富对爬虫来说是好事情,可以获取到更丰富的信息。得益于一些接口的设置,我们可以查找到匿名发言用户的 id,进而通过 id 定位到真实昵称和最近的几条发帖。拉黑接口仅仅接受 id,也让匿名拉黑成为了可能。

问题 1 的最终解决

如何拉黑一个匿名用户呢?首先,要么找到 TA 发的帖子,要么点 TA 的头像,从这两个包中可以定位到用户的 idid 有了,通过已经存在的接口便能够实现拉黑的功能。这一方法是万能的,在关注按钮没有出现的情况下,仍然可行。

这里需要说明,模拟 HTTP 请求,推荐使用 Postman,上手很简单。

个人收获

这次本来是想爬校圈的帖子和评论的,正好用来熟悉数据库,但是想想没啥必要,不如把接口分析分析,故有了本文。这次分析 APi 的经历让我体会到:

漏洞是常常存在的,网络并非十分安全。一些简单的应用压根没有做好前后端的沟通以及数据的保护;

一些 API 的设计是重复冗余的,可能和一开始没有定好文档有关,最后积累成了屎山;

有时候为了达成自己的目的,不一定需要用大炮轰蚊子的技术。这次为了拉黑一些用户,在没有充分考察校圈本身的功能之前,就决定使用压箱底的技术——抓包+逆向。其实没必要,以校圈的设计水平,多少会有一些可能出现的漏洞存在。还是应该循序渐进,切记切记。不过多熟悉熟悉大炮也是好事情。

这文章看似很长,实际上多数是 API 的罗列,我自己也不愿意看完。可以当个厕纸读物,得意而忘言吧!