array(5) { ["chapterid"]=> string(8) "42521650" ["articleid"]=> string(7) "6096345" ["chaptername"]=> string(7) "第3章" ["content"]=> string(8757) "

她的声音透过厚重的防盗门传进来,带着一贯的和善。

“你家怎么不开灯啊?是不是跳闸了?”

她轻轻拧动门把手。

门锁发出“咔哒”一声,纹丝未动。

“哎呀,这孩子,怎么把门反锁了。”

她的语气里带着嗔怪。

“快开门让阿姨进去,我听物业说你家漏水了,严不严重啊?”

我没有回应。

我的眼睛紧盯着笔记本屏幕。

水已经漫上了椅子腿,冰冷的液体浸湿了我的裤子。

我必须在她进来之前完成。

我深吸一口气,按下了API测试工具上的“发送”按钮。

请求已发送。

屏幕上开始转圈。

一秒。‌⁡⁡

两秒。

门外的敲门声变得急促起来。

“小李?你在不在里面?回句话呀!”

“再不开门阿姨可要叫保安了啊!”

笔记本屏幕上,服务器返回了响应数据。

{ “message”: “Registration successful”, “newUser”: { “userID”: “u-8f3e4b9a”, “username”: “testuser”, “role”: “user”, “communityID”: “CMT-0034” } }

注册成功了。

但是,新用户的role依然是user。

我的猜测失败了。

权限并没有被继承。

门外传来钥匙插入锁孔的声音。

是金属摩擦的刺耳声响。

她有我家的钥匙。

或者说,她有小区的万能钥匙。

锁芯转动了半圈,然后被反锁的旋钮卡住了。

“小李!你到底在搞什么鬼!把门打开!”

她的声音失去了伪装,变得尖利而凶狠。

紧接着是更加用力的撞门声。

“砰!”

“砰!”‌⁡⁡

整扇门都在震动。

我感到一阵绝望。

电脑屏幕上的代码和数据流在我眼前变得模糊。

失败了。

我重新看向那条注册成功的返回信息。

userID: “u--8f3e4b9a”

等等。

这个新生成的用户ID,格式不对。

我自己的ID是 u-1b9d7a8c,张惠兰的是 u-00000001。

都是u-加上8位十六进制字符。

而这个新ID,是u-后面跟了9位。

8f3e4b9a是8位,但前面多了一个 -。

这看起来像是一个字符串拼接错误。

程序员在拼接u-和生成的ID时,可能把某个变量也一起拼了进来。

会是什么变量?

我再次审视u--8f3e4b9a这个字符串。

两个横杠。

这通常意味着一个空值或者错误值被转换成了字符串。

我忽然想到了另一个常见的漏洞。

SQL注入。‌⁡⁡

如果后端的数据库查询语句,是直接用字符串拼接而成的,那么恶意的输入就有可能改变查询的逻辑。

比如,登录验证时,查询语句可能是:

SELECT * FROM users WHERE username = {$username} AND password = {$password}

如果我输入的用户名是 admin --,那么语句就会变成:

SELECT * FROM users WHERE username = admin -- AND password = xxx

在SQL里,--是注释符,后面的所有内容都会被忽略。

于是查询就变成了无视密码、直接查询admin用户。

这能用在注册上吗?

注册的SQL语句通常是INSERT。

INSERT INTO users (username, password, invitationCode) VALUES ({$username}, {$password}, {$invitationCode})

如果我在invitationCode里做手脚……

比如,我输入test, role = admin) --

那么语句就会变成:

INSERT INTO users (username, password, invitationCode) VALUES (testuser, pw123, test, role = admin) --)

这会导致语法错误。

INSERT语句的注入比SELECT要困难得多。

“砰!!”

一声巨响,门框上方的墙皮簌簌落下。

她似乎找来了工具。

我没有时间了。‌⁡⁡

必须想一个更直接的办法。

既然PUT更新不了权限,INSERT注入不了权限,那么还有什么办法?

我的目光回到了那个让我产生怀疑的、错误的userID上。

u--8f3e4b9a

双横杠。

系统在某个环节出错了,把一个空值拼了进来。

哪个环节?

邀请码。

我用张惠兰的ID作为邀请码,触发了这个错误。

如果我不用她的ID,用一个不存在的ID呢?比如u-ffffffff。

或者,用一个格式不正确的ID?比如12345。

我回到API测试工具,准备再次尝试注册。

username: testuser2

password: pw123

invitationCode: 12345

发送!

返回结果:

{ “error”: “Invalid invitation code format”, “code”: 400 }

邀请码格式无效。

系统对邀请码的格式做了校验。必须是u-开头。‌⁡⁡

好,那我再试。

invitationCode: u-

只输入一个前缀。

发送!

返回结果:

{ “message”: “Registration successful”, “newUser”: { “userID”: “u-a1b2c3d4”, “username”: “testuser2”, “role”: “user”, “communityID”: “CMT-0034” } }

这次的userID是正常的。

看起来,只有用一个真实存在的userID作为邀请码,才会触发那个双横杠的BUG。

这说明,系统在处理真实存在的邀请码时,走了另一套逻辑。

它会去查询邀请者的信息。

然后,在某个步骤中,一个预期的值没有被正确赋上,导致了空值,最后拼接成了双横杠。

等一下。

既然它会去查询邀请者的信息……

那它查询的是什么信息?

role?communityID?

我看着张惠兰的role:initial_administrator。

这个词很奇怪。

initial,初始。

它不像是一个权限等级,更像是一个状态标签。

也许,系统里真正的权限划分,不是靠role这个字段。‌⁡⁡

也许还有一个隐藏的字段,或者一个独立的权限表。

而initial_administrator这个角色,拥有某种特殊的、可以传递的属性。

我刚才的继承权限猜想,大方向可能是对的,但细节错了。

它继承的不是role,而是别的什么。

门锁的位置传来刺耳的电钻声。

她在破坏锁芯。

我全身的血液仿佛都凝固了。

我必须立刻找到那个被继承的“属性”,并且利用它。

我的目光死死地盯住屏幕。

数据流,API,JSON……

我脑中飞速地回想着所有关于这个小程序的信息。

邻里智联,智慧社区,智能家居联动……

联动。

一个账户,如何与一套房子里的所有智能设备联动?

通过communityID和房号?

还是通过一个唯一的设备绑定码?

我突然想起了什么。

我回到我的用户信息里,仔细检查每一个字段。

userID: “u-1b9d7a8c”

username: “李明”‌⁡⁡

role: “user”

communityID: “CMT-0034”

binding_key: “bk-xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx”

一个我之前忽略的字段。

binding_key,绑定密钥。

这是一个很长的、UUID格式的字符串。

张惠兰的用户信息里,有这个字段吗?

我快速翻找刚才的日志。

找到了。

GET api.linlizhilian.com/v1/user/profile?userID=u-00000001

返回的数据里,赫然写着:

binding_key: null

她的绑定密钥是空的。

这很合理,她作为管理员,不需要绑定到某一个具体的房间。

她的权限是针对整个社区的。

那我的权限提升失败,是不是因为我注册的新账户,自动生成了一个新的binding_key,从而被限制在了“普通用户”的范畴里?

如果我能在创建用户的时候,就让这个binding_key变成和她一样,也就是null呢?

一个没有绑定任何房间,但存在于我们小区系统里的“幽灵”账户。

它会拥有什么权限?

我再次打开API测试工具,来到注册接口。‌⁡⁡

这次,我在请求体里手动增加了一个字段。

binding_key: null

然后,在invitationCode里,再次填入张惠兰的ID。

u-00000001

电钻声停了。

门外传来金属零件掉在地上的声音。

锁芯被钻开了。

她要进来了。

我的心脏狂跳,几乎要从喉咙里蹦出来。

我最后看了一眼屏幕上的代码,闭上眼睛,按下了发送键。

" ["create_time"]=> string(10) "1759280433" }