WebGoat-JWT
JWT Tokens 01
概念
本课程将介绍如何使用JSON Web Token(JWT)进行身份验证,以及在使用JWT时需要注意的常见陷阱。
目标
教授如何安全地实现令牌的使用和这些令牌的验证。
介绍
许多应用程序使用JSON Web令牌(JWT)来允许客户机指示身份,以便在身份验证后进行进一步交换
JWT Tokens 02
JWT TOKEN的结构
让我们看看JWT令牌的结构。
令牌是base64-url编码的,由header.claims.signature三部分组成。
基于该算法,将签名添加到令牌中。通过这种方式,您可以验证某人没有修改令牌(对令牌的一次更改将使签名无效)。
您可以使用下面的表单作为编码和解码JWT令牌的简单方法。或者你可以在jwt.io上使用更广泛的选项
JWT Tokens 03
身份验证和获得JWT令牌
获取令牌的基本顺序如下:
在此流程中,您可以看到在服务器返回的成功身份验证中,用户使用用户名和密码登录。服务器创建一个新令牌并将其返回给客户端。当客户端对服务器进行连续调用时,它将新令牌附加到“Authorization”头中。服务器读取令牌,并在成功验证后首先验证签名,服务器使用令牌中的信息来识别用户。
Claims
令牌包含标识用户的声明和服务器满足请求所需的所有其他信息。请注意不要将敏感信息存储在令牌中,并始终通过安全通道发送它们。
JWT Tokens 04
JWT签署
在将每个JWT令牌发送到客户机之前,至少应该对其进行签名,如果没有对令牌进行签名,客户机应用程序将能够更改令牌的内容。签名规范在这里定义,你可以使用的具体算法在这里描述。基本上,你使用“HMAC with SHA-2 Functions”或“Digital Signature with rassa - pkcs1 -v1_5/ECDSA/ rassa - pss”函数来签名令牌。
检查签名
一个重要的步骤是在执行任何其他操作之前验证签名,让我们看看在验证令牌之前需要注意的一些事情。
任务
尝试更改您收到的令牌,并通过更改令牌成为管理员用户,一旦您是管理员,重置投票
查看投票部分的源码 /JWT/votings
直接判断token中的admin对应值是否为true,没有验证签名。
随笔选一个用户,将jwt进行解码:
这里我们直接将admin改为true,算法改为none,然后把签名部分删除掉,再提交即可
JWT Tokens 05
JWT 爆破
使用带有SHA-2函数的HMAC,您可以使用一个秘密密钥来签名和验证令牌。一旦我们弄清楚了这个密钥,我们就可以创建一个新令牌并对它进行签名。因此,关键字要足够强,这样暴力破解或字典攻击就不可行了。一旦有了令牌,就可以启动离线暴力攻击或字典攻击。
任务
假设我们有以下令牌,尝试找出密钥并提交一个新密钥,将用户名更改为WebGoat。
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTYyOTg1ODIzMywiZXhwIjoxNjI5ODU4MjkzLCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.YRlFG7xUEjsdLoOiwu6LbocK7u0h8G-r13FvwweJdNI
查看源码,发现secret就是随机选择了字典里的一个
把这几个字符串放入字典,编写脚本爆破
JWT Tokens 06
刷新令牌
介绍
在本节中,我们将讨论刷新访问令牌。
令牌的类型
通常有两种类型的令牌:访问令牌和刷新令牌。访问令牌用于对服务器进行API调用。访问令牌的生命周期有限,因此需要使用刷新令牌。一旦访问令牌不再有效,就可以向服务器发出请求,通过呈现刷新令牌来获得新的访问令牌。刷新令牌可能会过期,但它们的生命周期要长得多。这解决了用户必须再次使用其凭证进行身份验证的问题。是否应该使用刷新令牌和访问令牌取决于情况,下面是在选择使用哪个令牌时需要记住的几点。
一个正常的流程可以是这样的:
服务器返回:
如你所见,刷新令牌是一个随机字符串,服务器可以跟踪(在内存中或存储在数据库中),以便将刷新令牌与被授予刷新令牌的用户匹配。因此,在这种情况下,只要访问令牌仍然有效,我们就可以称之为“无状态”会话,服务器端没有设置用户会话的负担,令牌是自包含的。当访问令牌不再有效时,服务器需要查询存储的刷新令牌,以确保令牌不会以任何方式被阻塞。
每当攻击者获得访问令牌时,它只在一定的时间内有效(比如10分钟)。然后,攻击者需要刷新令牌来获得新的访问令牌。这就是刷新令牌需要更好保护的原因。也可以使刷新令牌无状态,但这意味着要查看用户是否撤销令牌将变得更加困难。在服务器完成所有验证之后,它必须向客户机返回一个新的刷新令牌和一个新的访问令牌。客户端可以使用新的访问令牌进行API调用。
你应该检查什么?
无论选择何种解决方案,都应该在服务器端存储足够的信息,以验证用户是否仍然受信任。你能想到的很多事情,就像存储的ip地址,跟踪刷新令牌使用的次数(在访问令牌有效的时间窗口内,使用多次刷新令牌可能表示奇怪的行为,你可以撤销所有的token,让用户再次进行身份验证)。也要跟踪访问令牌属于哪个刷新令牌,否则攻击者可以通过自己的刷新令牌得到不同用户的新的访问令牌(详细见https://emtunc.org/blog/11/2017/jwt-refresh-token-manipulation/ )。同时,检查用户的ip地址或地理位置也是有用的。如果您需要给出一个新的令牌,首先检查位置是否仍然相同,如果不是,撤销所有令牌,并让用户再次进行身份验证。
Refresh Tokens的必要性
在现代单页应用程序(SPA)中使用刷新令牌有意义吗?正如我们在关于存储令牌的章节中所看到的,有两种选择:web存储或cookie,这意味着刷新令牌就在访问令牌旁边,所以如果访问令牌泄漏,刷新令牌也有可能被泄露。当然,大多数时候是有区别的。访问令牌在您进行API调用时发送,刷新令牌仅在应该获得新访问令牌时发送,而在大多数情况下,新访问令牌是不同的终端。如果您最终在同一台服务器上,您可以选择只使用访问令牌。
如上所述,使用访问令牌和单独的刷新令牌可以让服务器避免反复检查访问令牌。只在用户需要新的访问令牌时执行检查。当然可以只使用访问令牌。在服务器上存储与为刷新令牌存储的信息完全相同的信息,请参见前面的段落。通过这种方式,您需要每次检查令牌,但这可能是合适的,取决于应用程序。在存储刷新令牌用于验证的情况下,保护这些令牌也很重要(至少使用散列函数将它们存储在数据库中)。
JWT是个好主意吗?
有很多文章质疑使用JWT令牌进行客户端到服务器的cookie身份验证的用例。使用JWT令牌的最佳位置是服务器与服务器之间的通信。在一个普通的web应用程序中,你最好使用普通的旧cookie。
JWT Tokens 07
刷新令牌
实现刷新访问令牌的良好策略非常重要。这个任务是基于Bugcrowd上一个私人漏洞悬赏程序发现的漏洞,你可以在这里阅读完整的Write Up
任务
查看日志文件,找到让Tom为这些书买单的方法
token是jwt形式的,进行解密
发现这是tom的token,但是是18年的,已经过期。
查看源代码webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java:
refresh token是通过RandomStringUtils.randomAlphabetic(20)获取的随机值,用于刷新过期的access token,但是没有绑定用户信息
下面的校验也是仅校验是否存在user和refreshToken,未校验两者对应关系,存在漏洞
所以可以用来刷新任何用户的过期token。
那么,怎么来获得一个refresh token呢
查看源代码,我们发现编码了一个静态的password,并且存在一个登录页面/JWT/refresh/login,如果用户为Jerry,密码为bm5nhSkxCXZkKRy4的话就会登录成功,然后新的token。
根据responsebody,我们构造请求包,这里知道是json格式:
通过checkout的请求包,将conten-type改为json,然后构造参数,即可获得refresh token:
现在通过Jerry的refresh token来得到tom的新的access_token。
接口也在源码中:
在/JWT/refresh/newToken接口,首先接收Authorization的值,进行jwt解码,得到user,然后再从json中得到refresh_token,如果refresh_token有效,则刷新access_token:
现在就可以拿着刷新后的access_token 结账了:
eyJhbGciOiJIUzUxMiJ9.eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IlRvbSJ9.a4yUoDOuv6L7ICs-HsE6craLHG_u6YDTkmXiGHjF7GdJZVZWCTurWBBunW9ujab8f4vNG31XAEvWYUEmAt0SGg
JWT Tokens 08
最后的挑战
下面你会看到两个账户,一个是Jerry,一个是Tome。Jerry想从Twitter上删除Tom的账户,但是他的token只能删除他自己的账户。你能帮他删除Tom的账号吗?
点击delete tom
我们是Jerry的token,没有办法删除Tom
jwt解密后,发现header头的kid参数,kid是jwt header中的一个可选参数,全称是key ID,它用于指定加密算法的密钥,通过修改它的值,我们可能可以进行任意文件读取、SQL注入、命令执行等操作。
现在看一下源码:
webgoat/jwt/JWTFinalEndpoint.java
/JWT/final/delete
首先得到请求参数里的token,如果token不为空的话,进行解析:
从Header中获取“kid”直接插入sql查询语句中,存在SQL注入,将返回结果进行了base64解码作为KEY,然后进行解析。
然后解析后 ,username参数等于”Tom”,则删除
PS:
主要是利用注入,关注点有以下几个:
第一, select的值要是编码之后的,因为源码里取得值之后,有进行了base64解码。
第二, username改为Tom
第三, exp过期时间一定要注意,我前面很快就改好了,但是提示“Not a valid JWT token, please try again",最后发现到过过期时间了,修改之后就可以了。
WebGoat-XXE
XXE-01
Concept:
This lesson teaches how to perform a XML External Entity attack is and how it can be abused and protected against.
Goals:
The user should have basic knowledge of XML
The user will understand how XML parsers work
The user will learn to perform a XXE attack and how to protected against it.
XXE-02
什么是XML实体:
XML Entity允许定义标记,这些标记将在解析XML文档时被内容替换。 通常有三种类型的实体: 内部实体、外部实体、参数实体。
实体必须在文档类型定义(DTD)中创建,例如:
正如可以看到的,一旦XML文档被解析器处理,它将用已定义的常量“Jo Smith”替换已定义的实体js。这有很多好处,因为你可以在一个地方更改js,例如“John Smith”。
在Java应用程序中,XML可以用来从客户端获取数据到服务器,我们都熟悉JSON api,我们也可以使用XML来获取信息。 大多数情况下,框架会基于xml结构自动填充Java对象,例如:
什么是XXE注入?
XML外部实体攻击是针对解析XML输入的应用程序的一种攻击。当包含对外部实体引用的XML输入由配置较弱的XML解析器处理时,就会发生这种攻击。这种攻击可能导致泄露机密数据、拒绝服务、伪造服务器端请求、从解析器所在机器的角度扫描端口,以及其他系统影响。
攻击者可以使用file: 协议和系统标识符中的相对路径,来包含本地文件,这些文件可能包含密码或私人用户数据等敏感数据。由于攻击是相对于处理XML文档的应用程序发生的,攻击者可能使用这个受信任的应用程序来转移到其他内部系统,可能通过http请求公开其他内部内容,或者对任何不受保护的内部服务发起CSRF攻击。在某些情况下,容易出现客户端内存损坏问题的XML处理器库可能会被解除对恶意URI的引用所利用,可能允许在应用程序帐户下执行任意代码。其他攻击可以访问本地资源,这些资源可能不会停止返回数据,如果太多的线程或进程没有被释放,可能会影响应用程序的可用性。
一般来说,我们分为以下几种XXE攻击:
经典:在这种情况下,外部实体包含在本地DTD中;
盲:响应中没有输出或错误显示;
错误:尝试在错误消息中获取资源的内容
XXE-03
XXE例子
让我们看一个XXE注入的例子,在前一节中,我们看到XML实体可以如下使用:
外部DTD声明:
定义这些实体还可以在外部文件中定义另一个DTD,例如:
email.dtd可被定义如下:
XXE
如果XML解析器被配置为允许外部DTD或实体,我们可以用下面的代码修改以下XML片段:
现在发生了什么?我们从本地文件系统定义了一个include, XML解析器将加载该文件,并在引用实体的地方添加内容。让我们假设XML消息被返回给用户,消息将是:
额外的文档类型定义(DOCTYPE)总是可以添加到xml文档中,如果启用了解析器设置以允许处理外部实体,那么就为寻找XXE注入提供了一个良好的开端。
XXE-04
在这个作业中,您将向照片添加评论,当提交表单时,尝试执行带有XXE注入的评论。尝试列出文件系统的根目录。
解题:
源码分析:
webgoat/xxe/SimpleXXE.java
createNewComment中,接收POST请求正文中的内容赋值给commentStr这个字符串对象。
调用 Comment 的parseXml(commentStr, secure)方法进行xml解析。
查看源码文件Comments.java:
如下代码,描述了parseXml如何处理commentStr。
可以通过设置XMLConstants的两个属性来禁用外部实体解析,默认的空字符串就是禁用,但是这里secure值为true,所以没有禁用(SimpleXXE.java)
最后创建一个Unmarshaller对象。返回的值是xml经过unmarshal方法处理的值。由于unmarshal在执行过程中解析了XML,导致XXE注入
XXE-05
参考解决方案
本练习的目标是列出文件系统的根目录。如果我们首先尝试一个正常的帖子,我们看到以下请求:
这个网页发出一个xhr请求来发布一个xml消息,之后评论就会显示在评论部分。现在,让我们试着像前一节中所示的那样稍微更改一下请求:
因此,我们使用file:///来引用文件系统的根,而不是包含一个特定的文件。如果我们只是将其复制并粘贴到注释文本框中,您将在响应正文中得到一个错误
这是由于JavaScript正在接收输入。
创建以下请求:
第7行包含要使用注释表单时在文本框中输入的输入。
要解决这个问题,您必须拦截完整的传出请求,并用解决方案替换完整的正文
XXE-06
通过代码审计找到XXE漏洞
现在我们知道了注入是如何工作的,让我们看看为什么会发生这种情况。在Java应用程序中,默认情况下XML库配置是不安全的,您必须更改设置。假设在代码审计期间发现以下代码片段:
问题:解析器易受攻击吗?
这段代码定义了一个新的XmlMapper (ObjectMapper),它是一个用于读写xml和json的流行框架。如果我们再深入一层,我们会发现:
① 这是我们从上面的代码(1)中调用的'构造函数'。
② 调用另一个“构造函数”并初始化XmlFactory的新实例
让我们看一下XMLFactory的源代码:
① 这是3中创建的新实例的“构造函数”定义
② 调用3中定义的另一个“构造函数”
在4这里,我们知道if (xmlIn==null)将不为真,因为如果我们查看顶部的声明,我们创建了自己的实例XMLInputFactory.newInstance(),它不为空。这意味着我们有一个XML解析器,它在默认情况下不受XXE注入的保护。5和6中有趣的部分是嵌套在if语句中的额外保护,它们将起不到作用。
如果我们看看Spring Boot框架,例如它们是如何初始化相同的解析器的:
① 调用了一个可以安全的初始化解析器的方法
正如我们看到的,通过私有方法XMLInputFactory()显式地定义了XMLInputFactory,该方法实际上为解析器设置了与最上面代码中所示相同的属性。
正如你所看到的,要确定解析器对注入是否安全并不容易,您必须深入研究代码和库,以确定解析器设置是什么。
XXE-07
Modern REST framework
在现代REST框架中,服务器可能能够接受您作为开发人员没有考虑过的数据格式。 因此,这可能会导致JSON端点容易受到XXE攻击。
同样的练习,但尝试执行与第一次赋值中相同的XML注入。
解题:
改为xml格式
源码分析:
webgoat/xxe/ContentTypeAssignment.java
代码根据contentType判断数据格式,之后xml的解析和XXE-04一样,所以同样存在XXE
XXE-08
参考解决方案
这个赋值背后的想法是,虽然应用程序看起来只接受JSON,但如果我们将消息体更改为XML,框架可能会处理它。 当你尝试输入评论时,请求体将是:
这是一个普通的json消息,让我们试着改变请求的内容类型
这将导致以下异常:
根据XML解析器的不同,您可能会得到更详细的错误消息,在这种情况下,消息有点神秘,这意味着我们没有发送有效的XML。 例如,Jackson库给出以下信息:
这个错误消息出现是因为我们请求体的内容仍然是json格式,所以如果我们拦截并更改json消息为xml消息:
返回错误消息:
解析器抱怨消息不是有效的xml消息,需要嵌入到comment标签中:
现在不再报错,如果在WebGoat中刷新页面,发布的评论就会出现。
为了攻击工作,我们需要发布:
在一些公司的网络中,如果通过HTTP发送,一些网络设备可能会完全丢弃这个payload。 在这种情况下,POST不会返回响应,并且终端永远不会接收请求。 然而,这种保护的作用是有限的,因为相同的请求将在HTTPS设置中成功通过,而负载将被加密。
XXE-09
XXE DOS attack
使用同样的XXE攻击,我们可以对服务器执行DOS服务攻击。 这种攻击的一个例子是:
十亿笑脸DOS攻击
当XML解析器加载该文档时,它看到它包含一个根元素“lolz”,其中包含文本“&lol9;”。然而,“&lol9;”是一个定义的实体,它扩展为一个包含十个“&lol8;”字符串。 每个“&lol8;”字符串都是一个被定义的实体,扩展为10个“&lol7;”字符串,以此类推。 在处理完所有实体扩展之后,这个小的(< 1 KB) XML块实际上将占用几乎3g的内存。
XXE-10
Blind XXE
在某些情况下,您将看不到输出,因为尽管您的攻击可能有效,但该字段不会反映在页面的输出中。 或者您试图读取的资源包含导致解析器失败的非法XML字符。让我们从一个例子开始,在这个例子中,我们引用一个外部DTD,我们在自己的服务器上控制它。
作为一个攻击者,你可以控制WebWolf(这可以是你控制的任何服务器),例如,你可以使用这个服务器使用http://127.0.0.1:9090/landing来ping它
我们如何使用这个终端来验证是否可以执行XXE?
我们可以再次使用WebWolf来创建一个名为attack.dtd的文件,包含以下内容:
现在提交表单,将xml改为:
现在在WebWolf浏览到“传入请求”,你会看到:
所以有了XXE,我们可以ping我们自己的服务器,这意味着XXE注入是可能的。 因此,通过使用XXE注入,我们基本上能够达到与一开始使用curl命令时相同的效果。
XXE-11
Blind XXE assignment
在前面的页面中,我们展示了如何用XXE攻击ping服务器,在这个任务中,尝试创建一个DTD,它将从WebGoat服务器上传文件secret.txt的内容到我们的WebWolf服务器。 您可以使用WebWolf来提供DTD。txt文件位于WebGoat服务器的这个位置,所以你不需要扫描所有的目录和文件:
尝试使用WebWolf登陆页面上传这个文件,例如:http://127.0.0.1:9090/landing?text=contents_file(注意:这个终端在你的完全控制之下)。一旦你获得了文件的内容,将其作为一个新的评论发布在页面上,你将解决这一任务。
源码分析:
webgoat/xxe/BlindSendFileAssignment.java
源码层面还是一样的,只不过不返回我们的信息了。
这里有个if判断不让我们直接使用file协议进行直接读取,所以要读取只能借助dtd
解题:
test.dtd
遇到的问题:
第一次写的时候是这种:
一直觉得没啥问题,但是一直不成功,报错说实体名词必须紧跟在%后面
但是看了几遍都没啥问题,卡了挺长时间。
最后,看之前的笔记,说”实体的值中不能有 %, 所以将其转成html实体编码 % “
然后就可以成功了。
XXE-12
XXE 防御
为了防止XXE攻击,您需要确保验证从不受信任的客户机接收到的输入。在Java世界中,您还可以指示您的解析器完全忽略DTD,例如:
如果您不能完全关闭DTD支持,您也可以指示XML解析器忽略外部实体,例如:
验证
为Content-type和Accept报头实现正确的验证,不要简单地依赖框架来处理传入请求。如果客户端指定了一个合适的accept报头,返回一个' 406/Not Acceptable。
各语言中推荐的禁用外部实体的方法:
永远相信 永远热爱
分类: 代码审计
0
0
» 下一篇: 通过WebGoat学习java反序列化漏洞
posted @ 2021-08-29 12:46 yokan 阅读(863) 评论(0) 编辑 收藏 举报
WebGoat-JWT
JWT Tokens 01
概念
本课程将介绍如何使用JSON Web Token(JWT)进行身份验证,以及在使用JWT时需要注意的常见陷阱。
目标
教授如何安全地实现令牌的使用和这些令牌的验证。
介绍
许多应用程序使用JSON Web令牌(JWT)来允许客户机指示身份,以便在身份验证后进行进一步交换
JWT Tokens 02
JWT TOKEN的结构
让我们看看JWT令牌的结构。
令牌是base64-url编码的,由header.claims.signature三部分组成。
基于该算法,将签名添加到令牌中。通过这种方式,您可以验证某人没有修改令牌(对令牌的一次更改将使签名无效)。
您可以使用下面的表单作为编码和解码JWT令牌的简单方法。或者你可以在jwt.io上使用更广泛的选项
JWT Tokens 03
身份验证和获得JWT令牌
获取令牌的基本顺序如下:
在此流程中,您可以看到在服务器返回的成功身份验证中,用户使用用户名和密码登录。服务器创建一个新令牌并将其返回给客户端。当客户端对服务器进行连续调用时,它将新令牌附加到“Authorization”头中。服务器读取令牌,并在成功验证后首先验证签名,服务器使用令牌中的信息来识别用户。
Claims
令牌包含标识用户的声明和服务器满足请求所需的所有其他信息。请注意不要将敏感信息存储在令牌中,并始终通过安全通道发送它们。
JWT Tokens 04
JWT签署
在将每个JWT令牌发送到客户机之前,至少应该对其进行签名,如果没有对令牌进行签名,客户机应用程序将能够更改令牌的内容。签名规范在这里定义,你可以使用的具体算法在这里描述。基本上,你使用“HMAC with SHA-2 Functions”或“Digital Signature with rassa - pkcs1 -v1_5/ECDSA/ rassa - pss”函数来签名令牌。
检查签名
一个重要的步骤是在执行任何其他操作之前验证签名,让我们看看在验证令牌之前需要注意的一些事情。
任务
尝试更改您收到的令牌,并通过更改令牌成为管理员用户,一旦您是管理员,重置投票
查看投票部分的源码 /JWT/votings
直接判断token中的admin对应值是否为true,没有验证签名。
随笔选一个用户,将jwt进行解码:
这里我们直接将admin改为true,算法改为none,然后把签名部分删除掉,再提交即可
JWT Tokens 05
JWT 爆破
使用带有SHA-2函数的HMAC,您可以使用一个秘密密钥来签名和验证令牌。一旦我们弄清楚了这个密钥,我们就可以创建一个新令牌并对它进行签名。因此,关键字要足够强,这样暴力破解或字典攻击就不可行了。一旦有了令牌,就可以启动离线暴力攻击或字典攻击。
任务
假设我们有以下令牌,尝试找出密钥并提交一个新密钥,将用户名更改为WebGoat。
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImlhdCI6MTYyOTg1ODIzMywiZXhwIjoxNjI5ODU4MjkzLCJzdWIiOiJ0b21Ad2ViZ29hdC5vcmciLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQub3JnIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.YRlFG7xUEjsdLoOiwu6LbocK7u0h8G-r13FvwweJdNI
查看源码,发现secret就是随机选择了字典里的一个
把这几个字符串放入字典,编写脚本爆破
JWT Tokens 06
刷新令牌
介绍
在本节中,我们将讨论刷新访问令牌。
令牌的类型
通常有两种类型的令牌:访问令牌和刷新令牌。访问令牌用于对服务器进行API调用。访问令牌的生命周期有限,因此需要使用刷新令牌。一旦访问令牌不再有效,就可以向服务器发出请求,通过呈现刷新令牌来获得新的访问令牌。刷新令牌可能会过期,但它们的生命周期要长得多。这解决了用户必须再次使用其凭证进行身份验证的问题。是否应该使用刷新令牌和访问令牌取决于情况,下面是在选择使用哪个令牌时需要记住的几点。
一个正常的流程可以是这样的:
服务器返回:
如你所见,刷新令牌是一个随机字符串,服务器可以跟踪(在内存中或存储在数据库中),以便将刷新令牌与被授予刷新令牌的用户匹配。因此,在这种情况下,只要访问令牌仍然有效,我们就可以称之为“无状态”会话,服务器端没有设置用户会话的负担,令牌是自包含的。当访问令牌不再有效时,服务器需要查询存储的刷新令牌,以确保令牌不会以任何方式被阻塞。
每当攻击者获得访问令牌时,它只在一定的时间内有效(比如10分钟)。然后,攻击者需要刷新令牌来获得新的访问令牌。这就是刷新令牌需要更好保护的原因。也可以使刷新令牌无状态,但这意味着要查看用户是否撤销令牌将变得更加困难。在服务器完成所有验证之后,它必须向客户机返回一个新的刷新令牌和一个新的访问令牌。客户端可以使用新的访问令牌进行API调用。
你应该检查什么?
无论选择何种解决方案,都应该在服务器端存储足够的信息,以验证用户是否仍然受信任。你能想到的很多事情,就像存储的ip地址,跟踪刷新令牌使用的次数(在访问令牌有效的时间窗口内,使用多次刷新令牌可能表示奇怪的行为,你可以撤销所有的token,让用户再次进行身份验证)。也要跟踪访问令牌属于哪个刷新令牌,否则攻击者可以通过自己的刷新令牌得到不同用户的新的访问令牌(详细见https://emtunc.org/blog/11/2017/jwt-refresh-token-manipulation/ )。同时,检查用户的ip地址或地理位置也是有用的。如果您需要给出一个新的令牌,首先检查位置是否仍然相同,如果不是,撤销所有令牌,并让用户再次进行身份验证。
Refresh Tokens的必要性
在现代单页应用程序(SPA)中使用刷新令牌有意义吗?正如我们在关于存储令牌的章节中所看到的,有两种选择:web存储或cookie,这意味着刷新令牌就在访问令牌旁边,所以如果访问令牌泄漏,刷新令牌也有可能被泄露。当然,大多数时候是有区别的。访问令牌在您进行API调用时发送,刷新令牌仅在应该获得新访问令牌时发送,而在大多数情况下,新访问令牌是不同的终端。如果您最终在同一台服务器上,您可以选择只使用访问令牌。
如上所述,使用访问令牌和单独的刷新令牌可以让服务器避免反复检查访问令牌。只在用户需要新的访问令牌时执行检查。当然可以只使用访问令牌。在服务器上存储与为刷新令牌存储的信息完全相同的信息,请参见前面的段落。通过这种方式,您需要每次检查令牌,但这可能是合适的,取决于应用程序。在存储刷新令牌用于验证的情况下,保护这些令牌也很重要(至少使用散列函数将它们存储在数据库中)。
JWT是个好主意吗?
有很多文章质疑使用JWT令牌进行客户端到服务器的cookie身份验证的用例。使用JWT令牌的最佳位置是服务器与服务器之间的通信。在一个普通的web应用程序中,你最好使用普通的旧cookie。
JWT Tokens 07
刷新令牌
实现刷新访问令牌的良好策略非常重要。这个任务是基于Bugcrowd上一个私人漏洞悬赏程序发现的漏洞,你可以在这里阅读完整的Write Up
任务
查看日志文件,找到让Tom为这些书买单的方法
token是jwt形式的,进行解密
发现这是tom的token,但是是18年的,已经过期。
查看源代码webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java:
refresh token是通过RandomStringUtils.randomAlphabetic(20)获取的随机值,用于刷新过期的access token,但是没有绑定用户信息
下面的校验也是仅校验是否存在user和refreshToken,未校验两者对应关系,存在漏洞
所以可以用来刷新任何用户的过期token。
那么,怎么来获得一个refresh token呢
查看源代码,我们发现编码了一个静态的password,并且存在一个登录页面/JWT/refresh/login,如果用户为Jerry,密码为bm5nhSkxCXZkKRy4的话就会登录成功,然后新的token。
根据responsebody,我们构造请求包,这里知道是json格式:
通过checkout的请求包,将conten-type改为json,然后构造参数,即可获得refresh token:
现在通过Jerry的refresh token来得到tom的新的access_token。
接口也在源码中:
在/JWT/refresh/newToken接口,首先接收Authorization的值,进行jwt解码,得到user,然后再从json中得到refresh_token,如果refresh_token有效,则刷新access_token:
现在就可以拿着刷新后的access_token 结账了:
eyJhbGciOiJIUzUxMiJ9.eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IlRvbSJ9.a4yUoDOuv6L7ICs-HsE6craLHG_u6YDTkmXiGHjF7GdJZVZWCTurWBBunW9ujab8f4vNG31XAEvWYUEmAt0SGg
JWT Tokens 08
最后的挑战
下面你会看到两个账户,一个是Jerry,一个是Tome。Jerry想从Twitter上删除Tom的账户,但是他的token只能删除他自己的账户。你能帮他删除Tom的账号吗?
点击delete tom
我们是Jerry的token,没有办法删除Tom
解密后,发现header头的kid参数,kid是jwt header中的一个可选参数,全称是key ID,它用于指定加密算法的密钥,通过修改它的值,我们可能可以进行任意文件读取、SQL注入、命令执行等操作。
现在看一下源码:
webgoat/jwt/JWTFinalEndpoint.java
/JWT/final/delete
首先得到请求参数里的token,如果token不为空的话,进行解析:
从Header中获取“kid”直接插入sql查询语句中,存在SQL注入,将返回结果进行了base64解码作为KEY,然后进行解析。
然后解析后 ,username参数等于”Tom”,则删除
PS:
主要是利用注入,关注点有以下几个:
第一, select的值要是编码之后的,因为源码里取得值之后,有进行了base64解码。
第二, username改为Tom
第三, exp过期时间一定要注意,我前面很快就改好了,但是提示“Not a valid JWT token, please try again",最后发现到过过期时间了,修改之后就可以了。
WebGoat-XXE
XXE-01
Concept:
This lesson teaches how to perform a XML External Entity attack is and how it can be abused and protected against.
Goals:
The user should have basic knowledge of XML
The user will understand how XML parsers work
The user will learn to perform a XXE attack and how to protected against it.
XXE-02
什么是XML实体:
XML Entity允许定义标记,这些标记将在解析XML文档时被内容替换。 通常有三种类型的实体: 内部实体、外部实体、参数实体。
实体必须在文档类型定义(DTD)中创建,例如:
正如可以看到的,一旦XML文档被解析器处理,它将用已定义的常量“Jo Smith”替换已定义的实体js。这有很多好处,因为你可以在一个地方更改js,例如“John Smith”。
在Java应用程序中,XML可以用来从客户端获取数据到服务器,我们都熟悉JSON api,我们也可以使用XML来获取信息。 大多数情况下,框架会基于xml结构自动填充Java对象,例如:
什么是XXE注入?
XML外部实体攻击是针对解析XML输入的应用程序的一种攻击。当包含对外部实体引用的XML输入由配置较弱的XML解析器处理时,就会发生这种攻击。这种攻击可能导致泄露机密数据、拒绝服务、伪造服务器端请求、从解析器所在机器的角度扫描端口,以及其他系统影响。
攻击者可以使用file: 协议和系统标识符中的相对路径,来包含本地文件,这些文件可能包含密码或私人用户数据等敏感数据。由于攻击是相对于处理XML文档的应用程序发生的,攻击者可能使用这个受信任的应用程序来转移到其他内部系统,可能通过http请求公开其他内部内容,或者对任何不受保护的内部服务发起CSRF攻击。在某些情况下,容易出现客户端内存损坏问题的XML处理器库可能会被解除对恶意URI的引用所利用,可能允许在应用程序帐户下执行任意代码。其他攻击可以访问本地资源,这些资源可能不会
停止返回数据,如果太多的线程或进程没有被释放,可能会影响应用程序的可用性。
一般来说,我们分为以下几种XXE攻击:
经典:在这种情况下,外部实体包含在本地DTD中;
盲:响应中没有输出或错误显示;
错误:尝试在错误消息中获取资源的内容
XXE-03
XXE例子
让我们看一个XXE注入的例子,在前一节中,我们看到XML实体可以如下使用:
外部DTD声明:
定义这些实体还可以在外部文件中定义另一个DTD,例如:
email.dtd可被定义如下:
XXE
如果XML解析器被配置为允许外部DTD或实体,我们可以用下面的代码修改以下XML片段:
现在发生了什么?我们从本地文件系统定义了一个include, XML解析器将加载该文件,并在引用实体的地方添加内容。让我们假设XML消息被返回给用户,消息将是:
额外的文档类型定义(DOCTYPE)总是可以添加到xml文档中,如果启用了解析器设置以允许处理外部实体,那么就为寻找XXE注入提供了一个良好的开端。
XXE-04
在这个作业中,您将向照片添加评论,当提交表单时,尝试执行带有XXE注入的评论。尝试列出文件系统的根目录。
解题:
源码分析:
webgoat/xxe/SimpleXXE.java
createNewComment中,接收POST请求正文中的内容赋值给commentStr这个字符串对象。
调用 Comment 的parseXml(commentStr, secure)方法进行xml解析。
查看源码文件Comments.java:
如下代码,描述了parseXml如何处理commentStr。
可以通过设置XMLConstants的两个属性来禁用外部实体解析,默认的空字符串就是禁用,但是这里secure值为true,所以没有禁用(SimpleXXE.java)
最后创建一个Unmarshaller对象。返回的值是xml经过unmarshal方法处理的值。由于unmarshal在执行过程中解析了XML,导致XXE注入
XXE-05
参考解决方案
本练习的目标是列出文件系统的根目录。如果我们首先尝试一个正常的帖子,我们看到以下请求:
这个网页发出一个xhr请求来发布一个xml消息,之后评论就会显示在评论部分。现在,让我们试着像前一节中所示的那样稍微更改一下请求:
因此,我们使用file:///来引用文件系统的根,而不是包含一个特定的文件。如果我们只是将其复制并粘贴到注释文本框中,您将在响应正文中得到一个错误
这是由于JavaScript正在接收输入。
创建以下请求:
第7行包含要使用注释表单时在文本框中输入的输入。
要解决这个问题,您必须拦截完整的传出请求,并用解决方案替换完整的正文
XXE-06
通过代码审计找到XXE漏洞
现在我们知道了注入是如何工作的,让我们看看为什么会发生这种情况。在Java应用程序中,默认情况下XML库配置是不安全的,您必须更改设置。假设在代码审计期间发现以下代码片段:
问题:解析器易受攻击吗?
这段代码定义了一个新的XmlMapper (ObjectMapper),它是一个用于读写xml和json的流行框架。如果我们再深入一层,我们会发现:
① 这是我们从上面的代码(1)中调用的'构造函数'。
② 调用另一个“构造函数”并初始化XmlFactory的新实例
让我们看一下XMLFactory的源代码:
① 这是3中创建的新实例的“构造函数”定义
② 调用3中定义的另一个“构造函数”
在4这里,我们知道if (xmlIn==null)将不为真,因为如果我们查看顶部的声明,我们创建了自己的实例XMLInputFactory.newInstance(),它不为空。这意味着我们有一个XML解析器,它在默认情况下不受XXE注入的保护。5和6中有趣的部分是嵌套在if语句中的额外保护,它们将起不到作用。
如果我们看看Spring Boot框架,例如它们是如何初始化相同的解析器的:
① 调用了一个可以安全的初始化解析器的方法
正如我们看到的,通过私有方法XMLInputFactory()显式地定义了XMLInputFactory,该方法实际上为解析器设置了与最上面代码中所示相同的属性。
正如你所看到的,要确定解析器对注入是否安全并不容易,您必须深入研究代码和库,以确定解析器设置是什么。
XXE-07
Modern REST framework
在现代REST框架中,服务器可能能够接受您作为开发人员没有考虑过的数据格式。 因此,这可能会导致JSON端点容易受到XXE攻击。
同样的练习,但尝试执行与第一次赋值中相同的XML注入。
解题:
改为xml格式
源码分析:
webgoat/xxe/ContentTypeAssignment.java
代码根据contentType判断数据格式,之后xml的解析和XXE-04一样,所以同样存在XXE
XXE-08
参考解决方案
这个赋值背后的想法是,虽然应用程序看起来只接受JSON,但如果我们将消息体更改为XML,框架可能会处理它。 当你尝试输入评论时,请求体将是:
这是一个普通的json消息,让我们试着改变请求的内容类型
这将导致以下异常:
根据XML解析器的不同,您可能会得到更详细的错误消息,在这种情况下,消息有点神秘,这意味着我们没有发送有效的XML。 例如,Jackson库给出以下信息:
这个错误消息出现是因为我们请求体的内容仍然是json格式,所以如果我们拦截并更改json消息为xml消息:
返回错误消息:
解析器抱怨消息不是有效的xml消息,需要嵌入到comment标签中:
现在不再报错,如果在WebGoat中刷新页面,发布的评论就会出现。
为了攻击工作,我们需要发布:
在一些公司的网络中,如果通过HTTP发送,一些网络设备可能会完全丢弃这个payload。 在这种情况下,POST不会返回响应,并且终端永远不会接收请求。 然而,这种保护的作用是有限的,因为相同的请求将在HTTPS设置中成功通过,而负载将被加密。
XXE-09
XXE DOS attack
使用同样的XXE攻击,我们可以对服务器执行DOS服务攻击。 这种攻击的一个例子是:
十亿笑脸DOS攻击
当XML解析器加载该文档时,它看到它包含一个根元素“lolz”,其中包含文本“&lol9;”。然而,“&lol9;”是一个定义的实体,它扩展为一个包含十个“&lol8;”字符串。 每个“&lol8;”字符串都是一个被定义的实体,扩展为10个“&lol7;”字符串,以此类推。 在处理完所有实体扩展之后,这个小的(< 1 KB) XML块实际上将占用几乎3g的内存。
XXE-10
Blind XXE
在某些情况下,您将看不到输出,因为尽管您的攻击可能有效,但该字段不会反映在页面的输出中。 或者您试图读取的资源包含导致解析器失败的非法XML字符。让我们从一个例子开始,在这个例子中,我们引用一个外部DTD,我们在自己的服务器上控制它。
作为一个攻击者,你可以控制WebWolf(这可以是你控制的任何服务器),例如,你可以使用这个服务器使用http://127.0.0.1:9090/landing来ping它
我们如何使用这个终端来验证是否可以执行XXE?
我们可以再次使用WebWolf来创建一个名为attack.dtd的文件,包含以下内容:
现在提交表单,将xml改为:
现在在WebWolf浏览到“传入请求”,你会看到:
所以有了XXE,我们可以ping我们自己的服务器,这意味着XXE注入是可能的。 因此,通过使用XXE注入,我们基本上能够达到与一开始使用curl命令时相同的效果。
XXE-11
Blind XXE assignment
在前面的页面中,我们展示了如何用XXE攻击ping服务器,在这个任务中,尝试创建一个DTD,它将从WebGoat服务器上传文件secret.txt的内容到我们的WebWolf服务器。 您可以使用WebWolf来提供DTD。txt文件位于WebGoat服务器的这个位置,所以你不需要扫描所有的目录和文件:
尝试使用WebWolf登陆页面上传这个文件,例如:http://127.0.0.1:9090/landing?text=contents_file(注意:这个终端在你的完全控制之下)。一旦你获得了文件的内容,将其作为一个新的评论发布在页面上,你将解决这一任务。
源码分析:
webgoat/xxe/BlindSendFileAssignment.java
源码层面还是一样的,只不过不返回我们的信息了。
这里有个if判断不让我们直接使用file协议进行直接读取,所以要读取只能借助dtd
解题:
test.dtd
遇到的问题:
第一次写的时候是这种:
一直觉得没啥问题,但是一直不成功,报错说实体名词必须紧跟在%后面
但是看了几遍都没啥问题,卡了挺长时间。
最后,看之前的笔记,说”实体的值中不能有 %, 所以将其转成html实体编码 % “
然后就可以成功了。
XXE-12
XXE 防御
为了防止XXE攻击,您需要确保验证从不受信任的客户机接收到的输入。在Java世界中,您还可以指示您的解析器完全忽略DTD,例如:
如果您不能完全关闭DTD支持,您也可以指示XML解析器忽略外部实体,例如:
验证
为Content-type和Accept报头实现正确的验证,不要简单地依赖框架来处理传入请求。如果客户端指定了一个合适的accept报头,返回一个' 406/Not Acceptable。
各语言中推荐的禁用外部实体的方法:
永远相信 永远热爱