原理
从源头思考测试方法是如何来的。 ------ sec875
JWT只是为了授权而不是身份验证
身份验证后用户访问特权资源通常是session ID机制,具体实施是使用会话cookie来授权
但JWT实际上没有使用cookie,而是使用json web token授权
传统的session ID授权机制如图所示
以前是别人告诉你cookie可能永不过期等测试方法。而现在可以基于原理图的观察与质疑来增加测试方法。
基于第一步:我可以高并发注册一个账户吗?它会生成多少session ID?有效期?是否都一致,都有效?
基于第二步:我注销后再登录,它下发一样的session ID吗?以前下发的session ID是否弃用了?
基于第三步:发送假session ID?空?多个session ID?
基于第四步:bypass 验证?弱比较验证?
基于第五步:响应包中有没有客户端hook重定向?OAUTH 2.0?
现今JWT授权机制如图所示
可以发现与session ID非常类似。但不是将信息存储在服务器的会话内存中。
服务器创建了一个json web token,它进行了编码,使用自己的key对其签名。如果您进行篡改,则它知道已无效。服务器上没有任何JWT的信息,而是将它发送给了浏览器。但是key本身保存于服务器中,确保密钥安全性。
浏览器可以选择存储它,比如使用cookie来存储。不管已什么方式,最终浏览器会发送含有jwt内容的请求给服务器,确保服务器知道用户正在使用什么进行授权。
服务器验证jwt的签名。要注意key在json web token中还是在服务器上面?记住哦,原则上是保存于服务器上确保安全性。至于具体实施就不清楚了哦 :)
举一反三,大家可以自己观察与质疑并测试。
为什么要如此?
session ID存储在服务器上,服务器必须根据ID来找到用户信息。
而json web token将用户信息存储在token中,这意味着它存储在客户端上,服务器不需要存储。去掉了服务器的依赖,就能引入多台服务器共用JWT。
JWT的签名以及它如何存储用户信息
https://jwt.io/ debug
红色部分的头部是base64编码,用于确定算法和JWT令牌类型,对最后蓝色的验证部分有用
紫色部分的数据payload一看就是用户信息
sub字段类似于session ID,只不过是存进了JWT中发送给客户端,而没有存储它。
iat字段是发布时间。name就不用讲了吧。有的会出现eat字段,是过期时间。
鼠标放上去可以看见时间。您看,您可能又有新的测试手法了。比如过期时间?你把着JWT拿走一直用?
蓝色部分是最重要的验证签名
签名验证用户是否篡改了它。
用户验证就靠这里的东西。如果这里出现问题,将你验证成管理员,那你就真的变成管理员了。
注意观察签名内部使用的函数。JWT为什么是base64编码?因为签名中使用的是这个函数。为什么JWT有点号?看看签名中用了什么符号进行了拼接。注意HMACSHA256函数是两个参数,因此参数1的头部与payload是定义用户的,而参数2 secret密钥,用于签名验证。
如果您篡改了header与payload部分。马上就签名报错。
因为参数1的值与参数2 secret的值不匹配了。无法通过签名验证。
验证函数大致类似如下所示,您修改了数据部分,但是密钥部分没变。这肯定无法通过验证函数
VerifyFile(secretkey, signedFile);
同理,如果您篡改secret的签名部分,那么header、payload和签名又不匹配了。
参数1的header与payload部分,和参数2的secret部分是同时生成的匹配值。它们相互验证彼此。secret本身的密钥key又保存于服务器中。
参数1与参数2的数据一旦变化,马上就能发现,无法通过验证。
总之一句话:参数1与参数2要互为匹配,整个JWT才会验证通过。如果您想篡改并通过验证,则需要secret密钥key。
签名函数大致如下所示,您会发现,要签名一个东西需要key,数据,和需要签名的东西(您不会只签名红色部分和紫色部分吧。。那谁来保证蓝色部分是否被篡改)。
SignFile(secretkey, dataFile, signedFile);
签名处的secret值保存于服务器中,确保了密钥本身的安全性。
为什么要用JWT?
同一个组织的,两个服务器:银行与社保服务器
用户希望登录银行账户的同时也能自动登录到社保账户,或者该银行的任何其他服务器
当用户的登录信息仅保存在银行服务器时,登录到社保服务器自然需要重新登录
出于体验,用户不希望登录银行服务器的用户切换到社保服务器时又重新登录
需要一个无缝衔接,让用户感觉就在用一个程序一样
如果两个服务器共享了key,那么JWT刚好满足这种需求。两个服务器需要做的就是给客户端发送一样的JWT
诸如负载均衡,微服务的API也可以使用JWT进行授权管理,只要它们共享了key
node.js与JWT
【以下代码仅用于这里的示例,不能直接用于实际开发】
在node.js服务器上有一个自动刷新key的机制来刷新JWT,大致情况如下:
node.js服务器上JWT的机制大致情况如下:
接收请求的账户身份验证,如果验证通过将创建一个JWT访问令牌,将用户信息保存在其中,下发给浏览器
node.js服务器上会存在一些逻辑来验证一下JWT,判断一下头部分,判断一下是否为空?来验证一下浏览器发送的JWT是否有效。大致情况如下:
服务器共享同一个key,这里是调用同一个变量来指定同一个地方的key,大致代码如下:
在服务器处通过验证(1),服务器下发JWT(3),复制JWT访问另一个服务器(3),一样可以授权与识别JWT(2)
一个服务器处理所有的令牌创建、刷新、删除与身份认证,其他服务器做别的事情。为啥需要刷新令牌就不需要再提了。会话永不过期在cookie时代老生常谈了。。
node.js服务器上会存在一些逻辑来验证一下JWT的令牌刷新,刷新令牌是否为空,是否有效等。大致代码如下:
服务器会下发两个token值,一个是前面的,一个是刷新的。如果前面的过期了,服务器将返回拒绝。您可以增加逻辑机制,比如快过期了再下发刷新值等等。大致代码如下:
如何让这些刷新与生成的令牌失效呢,同理,代码层引入删除令牌的机制。代码体现都差不多的,这里也不需要再放出来看。不同的函数命名,delete之类的东东。
JWT漏洞场景练习
漏洞场景环境部署
万事俱备,练练手?切记,万事都逃不掉大局观。全局观的原理很重要,能学到就先学了再回过头来看安全。攻击向量会变多,测试手法与思维也会变多。
有轮子的话尽量借鉴
https://www.freebuf.com/vuls/219056.html
请合法练习遵守法律与规则
https://www.onelogin.com/ 是在线已授权的JWT测试网站,有时候速度很慢。。
点击Developers,GET A DEVELOPER ACCOUNT获取一个账户,身份认证通过后,下发JWT
https://www.onelogin.com/developer-signup
burpsuite扩展应用 JSON Web Tokens,可以debug JWT。 JSON Web Tokens Attacker扩展也可以试试
JWT漏洞:算法None 和 CVE-2018-0114
参考资料:https://portswigger.net/bappstore/f923cbf91698420890354c1d8958fee6
参考资料:https://portswigger.net/bappstore/82d6c60490b540369d6d5d01822bdf61
JWT扩展使用
https://www.油兔比.com/watch?v=SuDN35-aefY
webgoat靶机也有JWT tokens 练习场景
https://github.com/WebGoat/WebGoat/releases 部署与安装
https://www.油兔比.com/watch?v=k94sct9FKw4 windows 10部署视频
https://www.oracle.com/java/technologies/javase-jdk16-downloads.html java 16版本下载
java -jar webgoat-server-8.2.1.jar [--server.port=8080] [--server.address=localhost] [--hsqldb.port=9001]
java -jar webwolf-8.2.1.jar [--server.port=9090] [--server.address=localhost] [--hsqldb.port=9001]
可以发现靶机对java版本的差异性还是依赖很强烈的,我这里是11 TLS版本,部署失败。你们可以自行安装16版本,我这里就不换版本了。
安装一下扩展
docker实例
ubuntu 20.04TLS部署webgoat
https://www.油兔比.com/watch?v=aMKUuaga85A 部署docker
sudo apt install docker.io # 对了,仓库中的docker版本够用了,无需去官网部署最新docker版本。
docker --version
sudo systemctl status docker
https://hub.docker.com/r/webgoat/goatandwolf
https://www.油兔比.com/watch?v=qSqXhBABxhU 靶机部署视频
docker语法这里就不讲了 --help对照看看
docker --help
sudo docker ps -a #列举实例
sudo docker pull webgoat/goatandwolf # 拉取镜像
注意是不是拉取成功了。
运行docker靶机
sudo docker run -p 127.0.0.1:8080:8080 -p 127.0.0.1:9090:9090 -e TZ=Europe/Amsterdam webgoat/goatandwolf
sudo docker run --help
访问靶机并注册一个账户
http://127.0.0.1:8080/WebGoat/
停止docker运行实例
停止后再起实例,将docker运行实例的端口转发到虚拟机IP上面,方便windows上的bp访问与测试
sudo docker run -p 192.168.236.130:8080:8080 -p 192.168.236.130:9090:9090 -e TZ=Europe/Amsterdam webgoat/goatandwolf
访问,注册并登录JWT场景
第一页是教学
解码JWT
这里是让我们用jwt调试一下,找username
这里的任务是让我们改jwt成管理员权限,然后重置投票
它说guest不能投票,叫我们登录
切换一下用户看看数据包
用插件解码一下。看见字典admin为false值
修改false值为true的话,签名验证就没法通过。这里验证一下后端是否存在签名验证机制。
复制header部分到编解码器,base64解码,篡改为none,再编码回去。none表示不进行签名验证。
将数据包发到重放器中,将篡改后的header部分替换好,再复制出数据payload部分进行字段admin的值篡改,再替换。
注意观察解码后的base64是不是解码正确了。如果缺少闭合,请手动修改
以上属于乱改的,是为了和有意识的测试形成鲜明的对比。
因为,不基于功能点的观察,您就算篡改了jwt,那它这个数据包发送后去做什么事情?
我这里是已投票这个功能点为例子,您可以找一找重置的功能点在哪里?
找到投票功能点,然后抓取数据包
这个包才是基于功能点触发的投票包
发送到重放器中
header部分篡改为none,不进行签名验证。重放器中记得替换header。
数据payload部分篡改admin字段为true
篡改,替换完了之后重放数据包发现 401 权限拒绝
思考:算法改成了none,数据payload也改了admin,不进行签名。那再改改签名部分?删除它看看?
base64编码的header头部分中,存在特殊字符 = ,值中含有特殊字符肯定是需要百分号编码,为%3d。将签名部分的值删除掉,发现请求成功。
多发几次,投票数字一直在涨,疯狂刷票
注意算法none的其他变体都试试:none,None,nOnE,NONE。
JWT还有一个算法替换攻击 CVE-2015-9235
阅读理解部分:https://www.chosenplaintext.ca/2015/03/31/jwt-algorithm-confusion.html
简而言之:服务器本来部署了RSA验证,但是攻击者篡改算法为HS256,导致RAS的公钥变成了HS256验证签名的私钥了。拿着这个公钥就能给JWT签名。
走到第六页,发现这后面就有教学。大家可以对照一下自己的测试姿势已用于工作中SDL的web渗透阶段。
结尾
到此为止,有原理有练习场景还有例子,已经可以运用到工作的实施阶段了。
我鼓励您将这个靶机的其他内容都看一看,将这些验证方法运用到SDL中来。这个靶场也比较新,在不停的维护中。
助力各位师傅们在SDL建设中快速的成长。
感谢师傅们很有耐心的阅读到这里。
我们还会再见面的。
共勉。