发现访问控制缺陷的测试矩阵
目标
编写负面测试,把“未认证”“无权限”“他人所有”和“正常请求”分开。
为什么重要
只确认成功响应的访问控制测试一直是绿灯。即使在路径中漏掉了 Header 注入,或者删掉了 scope 检查,测试也不会失败。验证者不能只罗列允许的用例,而必须分别观察每一种拒绝的原因。
步骤
- 在
/root/work/test-auth-matrix-lab/test_service.py中测试所提供 service.py 的下列公开契约:bearer(header) 在请求头恰好以 'Bearer ' 开头、后面只有一个不含空白的令牌时返回该令牌。None、空令牌、其他 scheme 和多余的空白都会抛出 ValueError。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
先做一次准备。已有的文件不会被覆盖。
mkdir -p /root/work/test-auth-matrix-lab
test -e /root/work/test-auth-matrix-lab/service.py || cp /opt/fixtures/ten_labs/test-auth-matrix-lab/service.py /root/work/test-auth-matrix-lab/service.py
test -e /root/work/test-auth-matrix-lab/test_service.py || cp /opt/fixtures/ten_labs/test-auth-matrix-lab/test_service.py /root/work/test-auth-matrix-lab/test_service.py
cd /root/work/test-auth-matrix-lab
-
在
/root/work/test-auth-matrix-lab/test_service.py中测试所提供 service.py 的下列公开契约:principal(token, users) 返回令牌字典中的用户 {id, scopes},并且连 scopes 列表也一并复制。未知令牌会抛出 ValueError。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。 -
在
/root/work/test-auth-matrix-lab/test_service.py中测试所提供 service.py 的下列公开契约:require_scope(user, scope) 在 scopes 中恰好存在该 scope 字符串时返回 None,否则抛出 PermissionError。read-all 不等于 read。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。 -
在
/root/work/test-auth-matrix-lab/test_service.py中测试所提供 service.py 的下列公开契约:visible(user, document) 仅当 document 不是 None,且 owner 与 user 的 id 完全相同时才为 True。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。 -
在
/root/work/test-auth-matrix-lab/test_service.py中测试所提供 service.py 的下列公开契约:public_document(document) 返回一个只含 id 和 title 的新字典。不包含 owner 或 internal_cost。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。 -
在
/root/work/test-auth-matrix-lab/test_service.py中测试所提供 service.py 的下列公开契约:authenticate(header, users) 把 bearer 与 principal 连接起来。ValueError 要变成 HTTPException(401),且 headers 中 WWW-Authenticate 的值为 Bearer。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。 -
在
/root/work/test-auth-matrix-lab/test_service.py中测试所提供 service.py 的下列公开契约:read_document(user, documents, document_id) 在没有 read scope 时抛出 HTTPException(403),文档不存在或属于他人时抛出 HTTPException(404),否则返回 public_document 的结果。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。 -
在
/root/work/test-auth-matrix-lab/test_service.py中测试所提供 service.py 的下列公开契约:create_app(users, documents) 返回一个 FastAPI 应用,它在 GET /documents/{document_id} 上接收 Authorization 请求头,并调用 authenticate 和 read_document。通过真实请求验证 200、401、403、404 以及非公开字段的移除。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
参考
- 无需联网,也无需安装软件包,在现有的 lab-dev 环境中进行。
- 每一步都在 45 秒的评分预算内运行。不要添加真实的 sleep 或网络调用。
- 提交的测试会在另外的临时文件夹中,分别在正确实现和有缺陷的实现上运行。在正确实现上,实际运行的测试必须全部通过;在有缺陷的实现上,测试本体必须失败。收集错误、运行 0 个、全部跳过、被强制终止都不算通过。只使用 pytest 的基本功能和提供的库。
- FastAPI 官方文档 · pytest 官方文档 · Python sqlite3
- 局限:固定的令牌字典只是教学用的输入。生产环境的认证还需要过期、签名、撤销和安全保存。仅仅让 404 保持一致,也不会消除通过响应时间或访问日志得出的所有推断。还要同时检查被拒绝的请求是否连原始数据也改动了。提供的实现可以阅读,但评分使用的是另外的副本。不要通过检查源码措辞或修改文件来绕过缺陷,而要检查公开接口的实际运行结果。
拆分 Bearer 请求头——测试
在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:bearer(header) 在请求头恰好以 'Bearer ' 开头、后面只有一个不含空白的令牌时返回该令牌。None、空令牌、其他 scheme 和多余的空白都会抛出 ValueError。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
先做一次准备。已有的文件不会被覆盖。
mkdir -p /root/work/test-auth-matrix-lab
test -e /root/work/test-auth-matrix-lab/service.py || cp /opt/fixtures/ten_labs/test-auth-matrix-lab/service.py /root/work/test-auth-matrix-lab/service.py
test -e /root/work/test-auth-matrix-lab/test_service.py || cp /opt/fixtures/ten_labs/test-auth-matrix-lab/test_service.py /root/work/test-auth-matrix-lab/test_service.py
cd /root/work/test-auth-matrix-lab
如果随意把请求头切成多段,就可能把空白错误当成正常令牌接受。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-auth-matrix-lab/01-contract.sh 确认。
复制身份后再返回——测试
在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:principal(token, users) 返回令牌字典中的用户 {id, scopes},并且连 scopes 列表也一并复制。未知令牌会抛出 ValueError。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
修改返回的 scopes 时,如果连原始用户的权限也被改变,不同请求之间的权限就会混在一起。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-auth-matrix-lab/02-contract.sh 确认。
精确比较权限——测试
在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:require_scope(user, scope) 在 scopes 中恰好存在该 scope 字符串时返回 None,否则抛出 PermissionError。read-all 不等于 read。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
子串比较会把更长的权限名误认为另一个权限。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-auth-matrix-lab/03-contract.sh 确认。
单独确认所有权——测试
在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:visible(user, document) 仅当 document 不是 None,且 owner 与 user 的 id 完全相同时才为 True。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
把资源不存在与他人所有归为同一个判定。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-auth-matrix-lab/04-contract.sh 确认。
用白名单挑选响应字段——测试
在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:public_document(document) 返回一个只含 id 和 title 的新字典。不包含 owner 或 internal_cost。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
不要从原始数据中删除字段,而是组装一个新的响应。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-auth-matrix-lab/05-contract.sh 确认。
让错误符合 HTTP 契约——测试
在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:authenticate(header, users) 把 bearer 与 principal 连接起来。ValueError 要变成 HTTPException(401),且 headers 中 WWW-Authenticate 的值为 Bearer。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
不要把认证失败和应用错误笼统地合成一个 500。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-auth-matrix-lab/06-contract.sh 确认。
固定拒绝的顺序——测试
在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:read_document(user, documents, document_id) 在没有 read scope 时抛出 HTTPException(403),文档不存在或属于他人时抛出 HTTPException(404),否则返回 public_document 的结果。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
即使认证通过之后,也必须分别确认 scope 和所有权。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-auth-matrix-lab/07-contract.sh 确认。
在真实请求中封住边界——测试
在 /root/work/test-auth-matrix-lab/test_service.py 中测试所提供 service.py 的下列公开契约:create_app(users, documents) 返回一个 FastAPI 应用,它在 GET /documents/{document_id} 上接收 Authorization 请求头,并调用 authenticate 和 read_document。通过真实请求验证 200、401、403、404 以及非公开字段的移除。在正确实现上应当通过,而在违反该契约的实现上,必须由测试本体的实际失败将其检出。保留前面步骤的测试,并添加 test_ 函数。
即使各个函数单独都是对的,只要在路径里漏掉了调用,访问控制就不会生效。不要修改实现文件。用 pytest.raises 确认预期的异常,对正常结果则断言具体的预期值。
保存后用 bash /opt/lab/checks/test-auth-matrix-lab/08-contract.sh 确认。