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" }