webgoat[A4-A10]
(A5) Security Misconfiguration
XXE
lesson4
提交,发现抓到了以POST的方式提交的,请求地址为/WebGoat/xxe/simple
的包,内容是一个标准的XML。并且输入内容可控
伪造一下,成功读取/etc/passwd文件
在java中file可以直接列目录,payload如下:
1 | <?xml version="1.0"?> |
代码审计
createNewComment()方法
- 将post请求的内容赋值给了commentStr变量
var comment = comments.parseXml(commentStr);
使用comments.parseXml
处理完后赋值给comment实例comments.addComment(comment, false);
添加评论
跳转到Comments.parseXml()
JAXBContext
是 Java 中用于管理 Java 对象和 XML 数据之间映射的类。它是 Java Architecture for XML Binding (JAXB) 的一部分,提供了从 Java 对象生成 XML 或从 XML 数据创建 Java 对象的功能。- 对象到 XML 的绑定(Marshaller): 将 Java 对象转换为 XML 表示。
- XML 到对象的绑定(Unmarshaller): 将 XML 数据解析并绑定到 Java 对象。
- 元数据管理: 管理绑定操作所需的上下文,包括类信息和配置信息。
- 从 Java 11 开始,JAXB 被移除出 JDK,需要手动添加依赖
当把 XML 格式的字符串传递给 Unmarshaller 接口转变成 Java 对象时,会解析一遍 XML,如果传入的值可控就会导致 XXE 注入攻击。
lesson 7 Modern REST framework
In modern REST frameworks the server might be able to accept data formats that you as a developer did not think about. So this might result in JSON endpoints being vulnerable to XXE attacks.
在现代 REST 框架中,服务器可能会接受您作为开发人员没有考虑到的数据格式。因此,这可能导致 JSON 端点容易受到 XXE 攻击。
Again same exercise but try to perform the same XML injection as we did in the first assignment.
再次进行相同的练习,但尝试执行与第一次作业中相同的 XML 注入。
先来了解一下REST
REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。REST指的是一组架构约束条件和原则。”如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。
REST资源的表述:
例如文本资源可以采用html、xml、json等格式,图片可以使用PNG或JPG展现出来。
资源的表述包括数据和描述数据的元数据,例如,HTTP头”Content-Type” 就是这样一个元数据属性。
RESTful 架构详解 | 菜鸟教程
一文彻底弄懂REST API - 知乎
回到题目本身,抓包发现
此处资源的表述:Content-Type: application/json
,对应解析的内容为json格式
修改一下,使得其能解析xml
lesson8中有详细的解决方案
lesson9 XXE DOS attack
With the same XXE attack we can perform a DOS service attack towards the server. An example of such an attack is:
1 | <?xml version="1.0"?> |
当 XML 解析器加载此文档时,会发现它包含一个根元素 “lolz”,其中包含文本“&lol9;”。然而,“&lol9; ”是一个已定义的实体,可扩展为一个包含十个“&lol8; ”字符串的字符串。每个“&lol8; ”字符串都是一个定义实体,可扩展为十个“&lol7; ”字符串,依此类推。处理完所有实体扩展后,这一小块(< 1 KB)XML 实际上将占用近 3 gigabytes 的内存。
lesson 11 Blind XXE assignment
在webwolf中上传一个attack.dtd
文件
1 | <?xml version='1.0' ?> |
抓包,构造payload
1 | <?xml version="1.0"?> |
返回 WebGoat 并发表评论
代码层面上:
和lesson7是一样的
(A7) Identity & Auth Failure
Authentication Bypasses
2FA Password Reset
根据抓到的路由找源代码
代码审计
直接看AttackResult
看verifyAccount()
几个检查机制:
- 检查提交问题数量是否匹配
- secQuestion0的值和取到的值相同
equals(secQuestionStore.get(verifyUserId).get("secQuestion0")))
,secQuestion1也是一样
看看那个值是怎么定义的
即检查机制为: - 检查提交问题数量是否匹配
- “secQuestion0”的值为”Dr. Watson”
- “secQuestion1”的值为”Baker Street”
相等的情况下就会被didUserLikelylCheat()
拦住didUserLikelylCheat()
但如果我们从submittedQuestions.containsKey("secQuestion0"
条件入手,使其为false,则会跳过一整个if语句
那么检查机制就变为:
- 检查提交问题数量是否匹配
具体payload如下:
将secQuestion0
写为secQuestion011
,secQuestion1
写为secQuestion111
感觉这题纯考逻辑…
Insecure Login
log in 后拦截到明文传输的账密,然后改包,传上去即可
JWT tokens
结构:
- 头
- 声明
- 签名
lesson 3 Decoding a JWT token
jwt.io或者是cyberchef都可以一把梭
lesson 5 JWT伪造
代码审计JWTVotesEndpoint.java
文件
- 只判断claims中admin的值
lesson6是答案
注意!在没有密钥的情况下直接对cliams进行修改,签名会失效
We can change the admin
claim to false
but then signature will become invalid. How do we end up with a valid signature? Looking at the RFC specification alg: none
is a valid choice and gives an unsecured JWT. Let’s change our token:我们可以将管理员声明改为 false,但这样签名就会失效。我们如何才能获得有效的签名呢?根据 RFC 规范,“无”(alg: none)是一个有效的选择,它提供的是一个不安全的 JWT。让我们改变我们的标记:直接去掉签名
lesson 7 Code review
比较一下
parseClaimsJws和parse的区别
parseClaimsJws:会自动校验签名的合法性
parse:提供一个通用的接口,可以解析 JWT 的任何部分,包括签名无效的情况
答案很明显
1 , 2
JWT cracking JWT 爆破
With the HMAC with SHA-2 Functions you use a secret key to sign and verify the token. Once we figure out this key we can create a new token and sign it. So it is very important the key is strong enough so a brute force or dictionary attack is not feasible. Once you have a token you can start an offline brute force or dictionary attack.使用带有 SHA-2 函数的 HMAC,您可以使用密钥来签署和验证令牌。一旦我们知道了这个密钥,就可以创建一个新的令牌并对其进行签名。因此,密钥必须足够强大,这样暴力或字典攻击就不可行了。一旦有了令牌,就可以开始离线暴力或字典攻击。
密钥为victory
打印当前时间
1 | python -c "import time;print(time.time())" |
修改时间戳,把时间间隙改大一点
再结合密钥重新生成jwt
代码审计
密钥从这里随机挑选
- parseClaimsJws(token) 要验证签名
Refreshing a token刷新令牌
token类型分为两种:access token 和 refreshing token
refreshing token 的存在是为了避免多次验证access token
题目要求:Can you find a way to order the books but let Tom pay for them?
先看日志
找到失效的token,然后对时间戳进行修改,无果
发现日志中存在”POST /JWT/refresh/login HTTP/1.1” 200”, 尝试访问,回显400
checkout中也没有access_token等信息。
那我们先去审一下源码叭………………………
代码审计
对于/JWT/refresh/login
路由而言
账号密码以json对象的形式传入。没有以json对象传入,则直接返回
ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
。账密为
jerry:bm5nhSkxCXZkKRy4
则createNewTokens(user)
createNewTokens()
逻辑如下创建access_token和refresh
再看/JWT/refresh/checkout
路由接收一个
Authorization
头中的 JWT,验证其有效性并解析。检查用户是否为
"Tom"
。检查 JWT 是否使用了不安全的
alg
算法(如"none"
)/JWT/refresh/newToken
路由当 user 与 refreshToken 都存在,JWT 成功被 Refresh
注意!此处只验证了是否存在, 没有验证对应的对象,所以可以存在下方使用a用户的refresh_token刷新b用户的access_token的漏洞
回到题目本身
再次看这道题的时候,发现再HTTP history中,我们曾经捉到了这样的包
是jerry的记录,response中存在jerry的access_token和refresh_token
在log中发现tom的access_token
根据题意猜测,那应该是使用tom的access_token和jerry的refresh_token来更新tom的access_token
在/WebGoat/JWT/refresh/newToken
路由刷新access_token,注意refresh_token
只能使用一次。
改Authorization头为Tom的access_token,使用jerry的refresh_token作为refresh_token
然后改checkout
的包,使用tom
的access_token
注意:这个包中本身就存在Authorization
头
over
Final challenge
题意大概是:用jerry的账户,delete tom的账户
点击delete抓包,接口为/WebGoat/JWT/final/delete?token=
从这里可以截获到jerry的jwt,解码发现:
多了个kid
即Key ID
详情见:What’s the meaning of the “kid” claim in a JWT token? - Stack Overflow ~ JWT 标记中的 “kid”(孩子)声称是什么意思?- Stack Overflow
hit: Using a SQL injection you might be able to manipulate the key to something you know and create a new token.
改一下jwt
其中:
bXlfa2V5
是my_key
的base64加密SALARIES
是已知的随机表- iat和exp改时间戳,用python打印一个即可
- 改 “username”: “Tom”
- key用my_key
发包
代码审计
两个路由,/JWT/final/follow/{user}
,点关注的,没什么用。我们来看另一个路由/JWT/final/delete
1 |
|
发现这里存在一个sql查询,由于其直接拼接,存在sql注入。然后将查询到的值返回给rs
然后返回一个base64解码的rs.getString(1)
所以上方payload需要base64加密,拿出来详细讲讲
上方payload拼接进sql语句中如下:
1 | SELECT key FROM jwt_keys WHERE id = '111' UNION SELECT 'bXlfa2V5' FROM SALARIES --' |
查询结果返回了 bXlfa2V5
,被视为有效的密钥
UNION SELECT
是 SQL 中的一个操作符
- 合并的结果集会同时包含前一个查询和后一个查询的结果。
- 如果第一个查询没有结果,而第二个查询有结果,那么返回的就只有第二个查询的结果。
然后使用了parseClaimsJws(token)
,无法构造 {"alg":"none"}
来绕过
对jwt中其他内容的验证只剩下:
Best practices最佳实践
Some best practices when working with JWT:使用 JWT 时的一些最佳实践:
Fix the algorithm, do not allow a client to switch the algorithm.固定算法,不允许客户端切换算法。
Make sure you use an appropriate key length when using a symmetric key for signing the token.使用对称密钥签署令牌时,确保使用适当的密钥长度。
Make sure the claims added to the token do not contain personal information. If you need to add more information opt for encrypting the token as well.确保添加到令牌的声明不包含个人信息。如果需要添加更多信息,请选择对令牌进行加密。
Add sufficient test cases to your project to verify invalid tokens actually do not work. Integration with a third party to check your token does not mean you do not have test your application at all.在项目中添加足够的测试用例,以验证无效令牌实际上不起作用。与第三方集成以检查您的令牌并不意味着您完全不需要测试您的应用程序。
Take a look at the best practices mentioned in https://tools.ietf.org/html/rfc8725#section-2请参阅 https://tools.ietf.org/html/rfc8725#section-2 中提到的最佳实践。
Password reset
大多都是逻辑洞
lesson 2
测试webWolf 是否能正常使用
lesson 4 Security questions安全问题 爆破密保
这些安全问题(s)应被视为与相同级别的安全,这是应用于储存密码在数据库
题目:
抓包,发现颜色是以post传参的securityQuestion
gpt生成颜色字典,爆破
存在网络错误,所以需要多爆破几次
代码审计COLORS
映射表直接硬编码在代码
1 | String securityQuestion = (String) json.getOrDefault("securityQuestion", ""); |
其中,getOrDefault
返回 key 相映射的的 value,如果没有返回defaultValue
lesson 5
If you have to pick a security question, we recommend not answering them truthfully.如果你必须选择一个安全问题,我们建议不回答它们如实。
lesson 6 the password reset link 密码重置链接
题目
Try to reset the password of Tom (tom@webgoat-cloud.org) to your own choice and login as Tom with that password. Note: it is not possible to use OWASP ZAP for this lesson, also browsers might not work, command line tools like
curl
and the like will be more successful for this attack.尝试将汤姆 (tom@webgoat-cloud.org) 的密码重设为您自己选择的密码,并用该密码以汤姆的身份登录。注意:本课不可能使用 OWASP ZAP,浏览器也可能不起作用,curl 等命令行工具在本攻击中会更成功。
Tom always resets his password immediately after receiving the email with the link.汤姆总是在收到带有链接的电子邮件后立即重设密码。
在Forgot your password?处,抓包
该host让tom@webgoat-cloud.org
的重置链接发到webwolf中,也是就9090端口
Host
头:指定目标服务器的域名和端口号,用于告诉服务器当前请求是发往哪个具体主机的。
在webWolf中的incoming requests中找到传入请求。
在 WebWolf 的传入请求中,我们将获得Tom点击的链接
此时,我们将获取的链接的套接字改为webgoat的套接字,即
1 | http://127.0.0.1:8080/WebGoat/PasswordReset/reset/reset-password/8c180843-8fb6-4a2d-a366-de4db9f8b0b9 |
然后更改密码,再以tom的身份登录即可
代码审计
- host 的值是Request 包中的 host 值
- if语句判断 host 当中是否存在 WebWlof 服务对应的端口与 Host
(A8) Software & Data Integrity
lesson 3 The Simplest Exploit
简单的反序列化漏洞示例
1 | InputStream is = request.getInputStream(); |
它期待一个 AcmeObject 对象,但会在转换之前执行 readObject()。如果攻击者找到了在 readObject() 中执行危险操作的适当类,他就可以序列化该对象并强制存在漏洞的应用程序执行这些操作。
Class included in ClassPath类路径中包含的类
攻击者需要在类路径中找到一个支持序列化并在 readObject() 中执行危险操作的类。
1 | package org.dummy.insecure.framework; |
可利用是因为重写了readObject方法
exp
1 | VulnerableTaskHolder go = new VulnerableTaskHolder("delete all", "rm -rf somefile"); |
执行位置在:taskAction,执行rm -rf somefile 的操作
lesson5
题目要求:尝试更改此序列化对象,以便将页面响应延迟整整 5 秒钟
代码审计
1 | public class InsecureDeserializationTask extends AssignmentEndpoint { |
token经过了一个特殊字符替代,然后base64解码,得到的值进行readObject反序列化。
instanceof()
是一个二元操作符,用于测试一个对象是否是某个类的实例,或者是否实现了某个接口
判断是否为VulnerableTaskHolder
的实例
在VulnerableTaskHolder
类中找到重写的readObject()
该方法直接调用Runtime.getRuntime().exec(),参数为taskAction
该taskAction参数在构造参数中直接传入
所以该参数可控
exp,在VulnerableTaskHolder的同级目录下
1 | package org.dummy.insecure.framework; |
一开始用sleep来打,但是出现sleep找不到对应文件的问题,于是使用ping
可以使用sleep或ping
(A9) Security Logging Failures
Logging Security 日志安全
lesson2
本挑战的目标是让用户名 “admin ”看起来成功登录。
payload
1 | normal_user\n[INFO] admin accessed the system |
日志伪造攻击
但实测下来,payload为
1 | # 在username位置上 |
就能通过
代码审计
- 把
\n
替换为<br/>
- 比较字符串中
"<br/>"
和"admin"
出现的索引位置indexOf("<br/>")
返回username
字符串中首次出现"<br/>"
的位置(从 0 开始计数,数到出现位置)- 如果
"<br/>"
不存在,indexOf
会返回-1
- admin同理
这也解释了为什么payload为admin就能直接通过
因为"<br/>"
的值为-1,admin出现了,值为0,-1 < 0
再来解释一下为什么日志伪造攻击payload生效
看这个demo
1 | package org.dummy.insecure.framework; |
运行结果为
1 | normal_user<br/>[INFO] admin accessed the system |
所以其实后端的检查机制只需要admin在<br/>
之后即可成功
和日志伪造没什么关系hhhh
lesson 3
当用户试图欺骗日志时,日志欺骗就会成为一个问题。除表单发布外,还有其他多种方法可以做到这一点。比如 URL 参数或伪造的 JSON 有效负载。
1、应用适当的输入过滤
2、确保您可以建立源真实性并实施完整性控制以检测日志篡改。
3、确保用户无法从任何通道注入日志
4、确保日志存储受到保护
lesson 4 log-bleeding
本挑战的目标是在 WebGoat 服务器的应用程序日志中找到秘密,以便以管理员用户身份登录。
请注意,我们试图 “保护 ”它。您能解码吗?
在启动程序中
base64 decode
1 | OTdjYzhhNjEtMGNhMC00NjI3LWJjNDItZDliNTllYTA0M2Q1 |
有个比较坑的点
username是Admin而非admin
代码审计
很直接了
最好将应用程序(调试)日志与 SEM 日志和审计日志区分开来,以便于应用程序输出、在企业内部存储和处理日志
(A10) Server-side Request Forgery 服务器端请求伪造
Cross-Site Request Forgeries跨站请求伪造
- csrf 构造 payload 然后诱导受害者点击,从而利用受害者的身份去做一些事情。
- ssrf 请求是服务端发起的,通常有的功能会存在从第三方的链接等获取资源,但是如果没有对资源来源进行一个限定那么就可以导致我们可以利用服务端来请求他本地或者他其中的内网信息
lesson 2Basic Get CSRF Exercise基本获取 CSRF 练习
使用burpsuite中的CSRF POC 功能,生成一个payload
1 | <html> |
保存,本地打开,点击链接,得到flag
代码审计
- 检查机制是令 referer != host
lesson 4 Post a review on someone else’s behalf
该页面通常模拟用户对帖子的评论和评分。我们的任务是从另一台主机上提交该评论和评分
和上文类似,payload为
1 | <html> |
lesson4
现在,大多数框架都默认支持防止 CSRF。例如,在 Angular 中,拦截器默认从 Cookie 中读取令牌 XSRF-TOKEN,并将其设置为 HTTP 标头 X-XSRF-TOKEN。由于只有在您的域上运行的代码才能读取 cookie,因此后端可以确定 HTTP 请求来自您的客户端应用程序,而不是攻击者。
为了实现这一功能,后端服务器会在 Cookie 中设置标记。由于 Cookie 的值应由 Angular(JavaScript)读取,因此不应将此 Cookie 标记为 http-only 标志。在每次向服务器发出请求时,Angular 都会将令牌作为 HTTP 标头放入 X-XSRF-TOKEN 中。服务器可以验证这两个令牌是否匹配,这将确保服务器的请求是在同一域上运行的。
lesson 7 CSRF and content-type
您需要从另一个源进行调用(WebWolf 可以提供帮助),实现将以下 JSON 消息 POST 到我们的端点
CORS 策略是一种机制,它告诉浏览器,如果请求不是来自同一源,何时允许访问特定资源。基本上,当请求从服务器域以外的不同域托管的网站发出时,服务器需要在响应中包含一个额外的标头(Access-Control-Allow-Origin),说明可以访问该资源。浏览器会执行这一策略。
预检: 在实际请求之前,浏览器会询问网络服务器该请求是否安全。简单请求不会触发 CORS 预检
不会触发的请求类型:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
已知存在cors,我们使用不会触发的请求类型
payload
1 | <html> |
运行
填进去即可
lesson8 Login CSRF attack登录 CSRF 攻击
新建一个账户即可
ssrf 服务器端请求伪造
在服务器端请求伪造(SSRF)攻击中,攻击者可以滥用服务器上的功能来读取或更新内部资源。攻击者可以提供或修改 URL,让服务器上运行的代码读取或提交数据。此外,通过仔细选择 URL,攻击者还可以读取 AWS 元数据等服务器配置,连接到启用 HTTP 的数据库等内部服务,或对无意暴露的内部服务执行发布请求。
lesson 2 Find and modify the request to display Jerry
检查浏览器向服务器发送的内容,并调整请求以从服务器获取其他内容。
lesson 3
感觉这两个题都不算是ssrf