我被热情的邻居拉进小区团购群,群主是位和蔼的阿姨。
直到我错过一次团购,群公告@我:【未参与团购者,将收到社区警告。】
下一秒,我的房门被砸得震天响。
我惊恐地翻看群规:
【1.必须参与每日团购。】
【2.必须购买清单上评价最高的商品。】
【3.绝对不能质疑群主。】
这时,群主阿姨私信我,发来一张我父母在家做饭的照片:“小李啊,叔叔阿姨好像缺瓶酱油,下次可别买错了。”
“下次团购的是「家庭和谐套餐」,你不买,我就帮你家和谐一下。”
我的手指停在“海天金标生抽”的购买按钮上。
它的月销和好评率都是9999+,稳居“调味品”分区榜首。
手机屏幕的冷光映在我脸上。
团购小程序的倒计时显示着“距结束00:01:34”。
下面一行,是“珠江桥牌御品鲜”,评价9887,位列第二。
我深吸一口气,胸腔因为缺氧而微微作痛。
指尖向下滑动半厘米,点在了“珠江桥牌”的图片上。
屏幕跳转,橙色的“立即购买”按钮占据了视野中心。
我按下去。
【订单支付成功!】
手机震动了一下。
我切回“幸福一家人”小区团购群。
群里最后一条消息是群主张惠兰半小时前发的全员提醒。
【@所有人,「家庭和谐套餐」里的酱油,邻居们别忘了下单,还剩最后半小时。】
下面是一排“收到”“好的张阿姨”“已下单”的回复。
我找到输入框,开始打字。
指尖触碰玻璃键盘,发出轻微的哒哒声。
【@【群主】张惠兰 阿姨,规则说要买最高评价的商品,但这个牌子添加剂太多,为了大家健康,我选了另一个,您没意见吧?】
点击发送。
消息出现在屏幕上,那个金色的“群主”标识格外显眼。
群里瞬间安静下来。
没有任何新消息弹出。
时间一秒一秒流逝。
我能听见自己心脏在胸腔里撞击的声音。
手机屏幕上方,代表有人正在输入的提示反复出现又消失。
五分钟后,没有任何人回复。
那个“正在输入…”的提示属于住在对门的302室王哥,他家孩子和我一样大。
那个提示闪烁了至少三分钟,最后还是消失了。
我把手机放在桌上,走到窗边。
窗外,小区花园的路灯亮着,几个孩子在滑梯旁追逐。
一切看起来和往常一样。
手机屏幕亮起。
我快步走回桌边,拿起手机。
一条新消息。
来自张惠兰。
我点开。
是一个微笑的表情。
就是系统自带的那个,最普通,最没有含义的黄色圆形笑脸。
我把手机锁屏,放在一边。
双腿有些发软,我扶着墙壁坐到椅子上。
额头渗出一层细密的汗。
客厅的灯光开始不规律地闪烁。
电流的滋滋声在寂静的房间里被放大。
我抬头看了一眼天花板上的吸顶灯。
它最后闪烁了两下,彻底熄灭。
房间陷入黑暗。
只有窗外的路灯光,在地上投下一片昏黄的矩形。
手机屏幕再次亮起,震动声在死寂中格外刺耳。
是群消息。
【【群主】张惠兰:小李也是为了大家着想,心是好的。】
【【群主】张惠兰:下次注意就好。】
她还配上了一个“大拇指”的表情。
302的王哥立刻回复:【张阿姨说的是,小李也是好心。】
401的刘姐:【对对对,大家都是邻居。】
505的赵叔:【年轻人嘛,有自己的想法。】
群里的气氛瞬间恢复了正常。
一条条消息快速刷过屏幕。
我没有回复。
卫生间的方向传来一声奇怪的“咕噜”声。
像是下水道堵塞后,空气从水管里艰难挤出来的声音。
我站起身,走向卫生间。
地面冰冷。
赤脚踩上去,一股寒意从脚底升起。
“咕噜……咕噜……”
声音越来越密集。
我拧开卫生间的门把手。
一股潮湿发霉的气味扑面而来。
我伸手去摸墙上的开关。
手指触碰到冰冷的塑料面板,按下去。
灯没有亮。
我借着从客厅透进来的微光,看向天花板。
吊顶的PVC板接缝处,一滴水珠正在慢慢聚集。
它变得越来越大,最终脱离表面,砸在下方的地砖上。
啪。
清脆的一声。
紧接着,第二滴,第三滴。
滴水声越来越快,连成一片。
天花板的接缝处,水迹迅速扩大,变成一片深色的水渍。
那块PVC板的中央开始向下凸起,形成一个沉重的弧度。
水不是在滴,而是在流。
像一条细线,从天花板一直连接到地面。
我后退一步,脚踩在了一片冰冷的水洼里。
水已经从卫生间门缝蔓延出来。
手机再次震动。
这次是私信。
来自张惠兰。
我点开。
【年轻人不要总想着标新立异,社区是个大家庭,水火可是无情的。】
一条语音消息紧随其后。
我点开播放。
是我父母的声音。
“……这火怎么回事,打不着了?”
“燃气灶坏了吗?我看看……阀门是开着的啊……”
语音结束。
我站在客厅冰冷的水中,水已经淹没了我的脚踝。
天花板上,那块被水浸透的PVC板发出一声不堪重负的呻吟。
裂纹在PVC板上蔓延。
轰!
吊顶连同积水一起砸落,在卫生间里激起一人高的水花。
水流瞬间冲出卫生间,涌入客厅。
我的小腿被一股冰冷的水流包裹。
客厅里所有放在地上的东西都漂浮起来。
我踉跄着穿过积水,冲到玄关,拉开电闸箱的盖子。
里面一片漆黑,总开关已经跳闸。
我尝试将它推上去,但开关立刻又弹了回来,发出一声沉闷的“咔嗒”声。
线路里有短路。
水已经没过我的膝盖。
我放弃了电闸,转身拉开房门。
楼道里的声控灯应声亮起,惨白的光照亮了门外的一切。
走廊干干净净,没有一滴水。
对门302的红色地垫,邻居放在门口的鞋架,都安然无恙。
仿佛我的房子,处于另一个被水淹没的世界。
我回到屋内,从漂浮的杂物中捞起我的笔记本电脑和手机,把它们放在唯一还干燥的餐桌上。
我拨打了物业的电话。
“您好,您拨打的电话正在通话中,请稍后再拨。”
冰冷的系统女声重复了三遍。
我挂断,再次拨打。
结果一样。
我又拨打了小区公布的24小时紧急维修电话。
这次接通了。
“喂?”一个含混不清的男声。
“你好,我是8栋901的住户,我家水管爆了,整个房子都被淹了,电也断了。”
“8栋901?”对方顿了一下,“系统里没查到你报修啊。”
“我刚打物业电话打不通。”
“哦,这样。水管爆了是吧,你先去把入户总阀关了。”
“哪个是总阀?”
“就在你家水表旁边,楼道管道井里,你自己找不到吗?”
“我不知道管道井在哪里。”
“啧,行吧,我记下了,你等着,我派人过去。”
电话被挂断。
我站在餐桌旁,水已经快要漫到桌沿。
墙上的插座不时冒出电火花,发出“噼啪”的声响。
我打开笔记本电脑,连接手机热点。
网络连接成功。
我打开浏览器,输入了“幸福一家人团购”小程序的名称。
搜索结果寥寥无几,只有一个官方的介绍页面和一个下载链接。
我点开介绍页面。
开发商是一家名为“邻里智联”的科技公司。
网站做得很简陋,只有一些宣传语和功能介绍。
“打造新型智慧社区生态。”
“一键团购,便捷生活。”
“智能家居联动,安全有保障。”
我点击“关于我们”,页面跳转到一个地址和联系方式。
地址在城西的一个软件园。
我打开地图软件,搜索这个地址。
距离我们小区三十公里。
我回到团购群。
群里还在热火朝天地讨论着今天新上架的水果。
【这个新疆的哈密瓜看着不错啊,有人买吗?】
【我买了,上次买过,特别甜。】
【张阿姨选品,质量肯定没问题!】
张惠兰没有再说话。
我点开她的头像,进入她的朋友圈。
最新一条是十分钟前发的。
九张图片,都是各种美食和笑脸。
配文是:【邻里和睦,万事兴。】
下面一排点赞和评论。
302王哥:【张阿姨说得对!】
401刘姐:【没错,远亲不如近邻。】
我关掉朋友圈,视线回到笔记本屏幕上。
我需要找到这个小程序的后台。
或者说,找到张惠兰控制这一切的那个“系统”。
我打开了浏览器的开发者工具。
切换到网络监视标签。
在手机上,我重新打开了团购小程序。
笔记本屏幕上,一连串的数据请求开始滚动。
GET, POST, PUT…
各种发往服务器的请求日志,像瀑布一样刷新。
我找到了用户登录的API接口。
api.linlizhilian.com/v1/login
返回的数据包里,包含了我的用户信息。
userID: “u-1b9d7a8c”
username: “李明”
role: “user”
communityID: “CMT-0034”
这个CMT-0034应该就是我们小区的代号。
而我的角色,只是一个普通user。
我继续在手机上操作,点开团购清单,模拟下单。
一个新的API请求出现在日志里。
POST api.linlizhilian.com/v1/order/create
请求体里,是我刚刚选择的商品ID和数量。
我往下翻看日志,试图找到与“规则”或“惩罚”相关的接口。
一条不起眼的日志引起了我的注意。
GET api.linlizhilian.com/v1/community/rules?communityID=CMT-0034
我复制了这个URL,在浏览器里打开。
屏幕上显示出一串JSON格式的数据。
{ “rules”: [ {“id”: 1, “description”: “必须参与每日团购。”}, {“id”: 2, “description”: “必须购买清单上评价最高的商品。”}, {“id”: 3, “description”: “绝对不能质疑群主。”} ], “enforcement_module”: “smart-home-control-v2.3” }
执法模块:智能家居控制。
这就是答案。
这个小程序,连接着小区的智能家居中控系统。
张惠兰的权力,就来自于这个enforcement_module。
可她是怎么获得权限的?
我继续翻找网络请求日志。
当我点开群成员列表,查看张惠兰的个人资料时,一条新的请求出现了。
GET api.linlizhilian.com/v1/user/profile?userID=u-00000001
返回的数据里,她的role字段写着一个我从未见过的词。
role: “initial_administrator”
初始管理员。
不是admin,不是super_admin,而是initial。
这个词意味着,她的权限是系统被创建时就赋予的。
唯一的,最原始的管理员。
我尝试着构造一个请求,去修改我自己的role。
我打开一个API测试工具,输入了修改用户信息的接口地址。
PUT api.linlizhilian.com/v1/user/profile/update
请求体里,我填上了我的userID,然后把role字段修改为admin。
点击发送。
服务器返回了错误信息。
{ “error”: “Permission Denied”, “code”: 403 }
权限被拒绝。
很正常的防御机制。
普通用户不能修改自己的权限。
我陷入了沉思。
既然无法从外部提升自己的权限,那有没有可能,让系统本身出错?
或者说,找到一个不需要高权限就能操作的致命漏洞。
我重新梳理着刚才看到的所有API接口。
登录,获取信息,创建订单,查看规则……
我的目光停留在了一个非常常见的接口上。
POST api.linlizhilian.com/v1/user/register
用户注册。
任何一个开放系统,注册功能都是对所有人都开放的,不需要任何权限。
问题是,注册一个新用户对我毫无帮助。
我需要的是修改我现有账户的权限。
除非……
除非注册的过程中,能做点什么。
我点开注册接口的详细信息,查看它的请求参数。
username, password, phoneNumber……
都是些常规的参数。
我往下滚动,一个可选参数引起了我的注意。
invitationCode。
邀请码。
很多系统都有这个功能,通过邀请码注册可以获得一些奖励。
但在这里,它或许有别的用处。
一个念头在我脑中闪过。
如果……这个invitationCode不只是一个普通的字符串,而是与其他用户的userID相关联呢?
如果我用张惠兰的userID作为邀请码去注册一个新用户,会发生什么?
这是一种非常古老的系统漏洞,叫做“关联账户权限提升”。
如果程序员在设计时偷懒,可能会让被邀请的新账户,继承邀请者的一部分属性。
通常是积分、会员等级之类的东西。
但万一,它继承了role呢?
我感到心跳在加速。
这是一个疯狂的猜测,但值得一试。
我立刻打开API测试工具,填好注册接口的地址。
username我随便填了一个“testuser”。
password也是。
然后在invitationCode字段,我小心翼翼地填入了张惠兰的ID。
u-00000001
我的手指悬在“发送”按钮上。
门外传来一阵脚步声,由远及近,停在了我的门口。
然后是敲门声。
咚,咚,咚。
“小李啊,在家吗?阿姨过来看看你。”
是张惠兰的声音。