Last assignment for JWT tokens finished
This commit is contained in:
parent
e06d4642eb
commit
dda6f674a3
@ -90,6 +90,8 @@ define(['jquery',
|
|||||||
var prepareDataFunctionName = $(curForm).attr('prepareData');
|
var prepareDataFunctionName = $(curForm).attr('prepareData');
|
||||||
var callbackFunctionName = $(curForm).attr('callback');
|
var callbackFunctionName = $(curForm).attr('callback');
|
||||||
var submitData = (typeof webgoat.customjs[prepareDataFunctionName] === 'function') ? webgoat.customjs[prepareDataFunctionName]() : $(curForm).serialize();
|
var submitData = (typeof webgoat.customjs[prepareDataFunctionName] === 'function') ? webgoat.customjs[prepareDataFunctionName]() : $(curForm).serialize();
|
||||||
|
var additionalHeadersFunctionName = $(curForm).attr('additionalHeaders');
|
||||||
|
var additionalHeaders = (typeof webgoat.customjs[additionalHeadersFunctionName] === 'function') ? webgoat.customjs[additionalHeadersFunctionName]() : function() {};
|
||||||
var successCallBackFunctionName = $(curForm).attr('successCallback');
|
var successCallBackFunctionName = $(curForm).attr('successCallback');
|
||||||
var failureCallbackFunctionName = $(curForm).attr('failureCallback');
|
var failureCallbackFunctionName = $(curForm).attr('failureCallback');
|
||||||
var callbackFunction = (typeof webgoat.customjs[callbackFunctionName] === 'function') ? webgoat.customjs[callbackFunctionName] : function() {};
|
var callbackFunction = (typeof webgoat.customjs[callbackFunctionName] === 'function') ? webgoat.customjs[callbackFunctionName] : function() {};
|
||||||
@ -104,6 +106,7 @@ define(['jquery',
|
|||||||
$.ajax({
|
$.ajax({
|
||||||
//data:submitData,
|
//data:submitData,
|
||||||
url:formUrl,
|
url:formUrl,
|
||||||
|
headers: additionalHeaders,
|
||||||
method:formMethod,
|
method:formMethod,
|
||||||
contentType:contentType,
|
contentType:contentType,
|
||||||
data: submitData,
|
data: submitData,
|
||||||
|
@ -1 +1,15 @@
|
|||||||
<configuration />
|
<configuration />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Enable below if you want to debug a unit test and see why the controller fails the configuration above is there
|
||||||
|
to keep the Travis build going otherwise it fails with too much logging.
|
||||||
|
//TODO we should use a different Spring profile for Travis
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<configuration>
|
||||||
|
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||||
|
<logger name="org.springframework.web" level="DEBUG"/>
|
||||||
|
</configuration>
|
||||||
|
|
||||||
|
-->
|
@ -0,0 +1,109 @@
|
|||||||
|
package org.owasp.webgoat.plugin;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import io.jsonwebtoken.*;
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||||
|
import org.owasp.webgoat.assignments.AssignmentHints;
|
||||||
|
import org.owasp.webgoat.assignments.AssignmentPath;
|
||||||
|
import org.owasp.webgoat.assignments.AttackResult;
|
||||||
|
import org.owasp.webgoat.session.WebSession;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author nbaars
|
||||||
|
* @since 4/23/17.
|
||||||
|
*/
|
||||||
|
@AssignmentPath("/JWT/refresh/")
|
||||||
|
@AssignmentHints({"jwt-refresh-hint1", "jwt-refresh-hint2", "jwt-refresh-hint3", "jwt-refresh-hint4"})
|
||||||
|
public class JWTRefreshEndpoint extends AssignmentEndpoint {
|
||||||
|
|
||||||
|
public static final String PASSWORD = "bm5nhSkxCXZkKRy4";
|
||||||
|
private static final String JWT_PASSWORD = "bm5n3SkxCX4kKRy4";
|
||||||
|
private static final List<String> validRefreshTokens = Lists.newArrayList();
|
||||||
|
|
||||||
|
@PostMapping(value = "login", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public @ResponseBody
|
||||||
|
ResponseEntity follow(@RequestBody Map<String, Object> json) {
|
||||||
|
String user = (String) json.get("user");
|
||||||
|
String password = (String) json.get("password");
|
||||||
|
|
||||||
|
if ("Jerry".equals(user) && PASSWORD.equals(password)) {
|
||||||
|
return ResponseEntity.ok(createNewTokens(user));
|
||||||
|
}
|
||||||
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> createNewTokens(String user) {
|
||||||
|
Map<String, Object> claims = Maps.newHashMap();
|
||||||
|
claims.put("admin", "false");
|
||||||
|
claims.put("user", user);
|
||||||
|
String token = Jwts.builder()
|
||||||
|
.setIssuedAt(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toDays(10)))
|
||||||
|
.setClaims(claims)
|
||||||
|
.signWith(io.jsonwebtoken.SignatureAlgorithm.HS512, JWT_PASSWORD)
|
||||||
|
.compact();
|
||||||
|
Map<String, Object> tokenJson = Maps.newHashMap();
|
||||||
|
String refreshToken = RandomStringUtils.randomAlphabetic(20);
|
||||||
|
validRefreshTokens.add(refreshToken);
|
||||||
|
tokenJson.put("access_token", token);
|
||||||
|
tokenJson.put("refresh_token", refreshToken);
|
||||||
|
return tokenJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("checkout")
|
||||||
|
public @ResponseBody
|
||||||
|
AttackResult checkout(@RequestHeader("Authorization") String token) {
|
||||||
|
try {
|
||||||
|
Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(token.replace("Bearer ", ""));
|
||||||
|
Claims claims = (Claims) jwt.getBody();
|
||||||
|
String user = (String) claims.get("user");
|
||||||
|
if ("Tom".equals(user)) {
|
||||||
|
return trackProgress(success().build());
|
||||||
|
}
|
||||||
|
return trackProgress(failed().feedback("jwt-refresh-not-tom").feedbackArgs(user).build());
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
return trackProgress(failed().output(e.getMessage()).build());
|
||||||
|
} catch (JwtException e) {
|
||||||
|
return trackProgress(failed().feedback("jwt-invalid-token").build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("newToken")
|
||||||
|
public @ResponseBody
|
||||||
|
ResponseEntity newToken(@RequestHeader("Authorization") String token, @RequestBody Map<String, Object> json) {
|
||||||
|
String user;
|
||||||
|
String refreshToken;
|
||||||
|
try {
|
||||||
|
Jwt<Header, Claims> jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(token.replace("Bearer ", ""));
|
||||||
|
user = (String) jwt.getBody().get("user");
|
||||||
|
refreshToken = (String) json.get("refresh_token");
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
user = (String) e.getClaims().get("user");
|
||||||
|
refreshToken = (String) json.get("refresh_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user == null || refreshToken == null) {
|
||||||
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
|
||||||
|
} else if (validRefreshTokens.contains(refreshToken)) {
|
||||||
|
validRefreshTokens.remove(refreshToken);
|
||||||
|
return ResponseEntity.ok(createNewTokens(user));
|
||||||
|
} else {
|
||||||
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -110,49 +110,101 @@
|
|||||||
|
|
||||||
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/jwt.css}"/>
|
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/jwt.css}"/>
|
||||||
<script th:src="@{/lesson_js/bootstrap.min.js}" language="JavaScript"></script>
|
<script th:src="@{/lesson_js/bootstrap.min.js}" language="JavaScript"></script>
|
||||||
<script th:src="@{/lesson_js/jwt-final.js}" language="JavaScript"></script>
|
<script th:src="@{/lesson_js/jwt-refresh.js}" language="JavaScript"></script>
|
||||||
<div class="attack-container">
|
<div class="attack-container">
|
||||||
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
|
||||||
<form class="attack-form" accept-charset="UNKNOWN"
|
<form class="attack-form" accept-charset="UNKNOWN"
|
||||||
method="POST"
|
method="POST"
|
||||||
successCallback="jwtSigningCallback"
|
additionalHeaders="addBearerToken"
|
||||||
action="/WebGoat/JWT/refresh/reset"
|
action="/WebGoat/JWT/refresh/checkout"
|
||||||
enctype="application/json;charset=UTF-8">
|
enctype="application/json;charset=UTF-8">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="col-sm-6 col-md-4 col-lg-3 mt-4">
|
<div class="row">
|
||||||
<div class="card card-inverse card-info">
|
<div class="col-sm-12 col-md-10 col-md-offset-1">
|
||||||
<img th:src="@{/images/jerry.png}" class="card-img-top"></img>
|
<table class="table table-hover">
|
||||||
<div class="card-block">
|
<thead>
|
||||||
<figure class="profile profile-inline">
|
<tr>
|
||||||
<img th:src="@{/images/jerry.png}" class="profile-avatar" alt=""></img>
|
<th>Product</th>
|
||||||
</figure>
|
<th>Quantity</th>
|
||||||
<h4 class="card-title">Jerry</h4>
|
<th class="text-center">Price</th>
|
||||||
<div class="card-text">
|
<th class="text-center">Total</th>
|
||||||
Jerry is a small, brown, house mouse.
|
<th> </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="col-sm-8 col-md-6">
|
||||||
|
<div class="media">
|
||||||
|
<img class="media-object" src="http://icons.iconarchive.com/icons/custom-icon-design/flatastic-2/72/product-icon.png" style="width: 72px; height: 72px;"></img>
|
||||||
|
<div class="media-body">
|
||||||
|
<h4 class="media-heading"><a href="#">Learn defending your application with WebGoat</a></h4>
|
||||||
|
<h5 class="media-heading"> by <a href="#">WebGoat Publishing</a></h5>
|
||||||
|
<span>Status: </span><span
|
||||||
|
class="text-success"><strong>In Stock</strong></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
</td>
|
||||||
<small>Last updated 12 minutes ago</small>
|
<td class="col-sm-1 col-md-1" style="text-align: center">
|
||||||
<button class="btn btn-info float-right btn-sm">Follow</button>
|
<input type="text" class="form-control" id="quantity1" value="3"></input>
|
||||||
|
</td>
|
||||||
|
<td class="col-sm-1 col-md-1 text-center"><strong>$4.87</strong></td>
|
||||||
|
<td class="col-sm-1 col-md-1 text-center"><strong>$14.61</strong></td>
|
||||||
|
<td class="col-sm-1 col-md-1">
|
||||||
|
<button type="button" class="btn btn-danger">
|
||||||
|
<span class="glyphicon glyphicon-remove"></span> Remove
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="col-md-6">
|
||||||
|
<div class="media">
|
||||||
|
<img class="media-object" src="http://icons.iconarchive.com/icons/custom-icon-design/flatastic-2/72/product-icon.png" style="width: 72px; height: 72px;"></img>
|
||||||
|
<div class="media-body">
|
||||||
|
<h4 class="media-heading"><a href="#">Pentesting for professionals</a></h4>
|
||||||
|
<h5 class="media-heading"> by <a href="#">WebWolf Publishing</a></h5>
|
||||||
|
<span>Status: </span><span class="text-warning"><strong>Leaves warehouse in 2 - 3 weeks</strong></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</td>
|
||||||
<div class="col-sm-6 col-md-4 col-lg-3 mt-4">
|
<td class="col-md-1" style="text-align: center">
|
||||||
<div class="card card-inverse card-info">
|
<input type="text" class="form-control" id="quantity2" value="2"></input>
|
||||||
<img th:src="@{/images/tom.png}" class="card-img-top"></img>
|
</td>
|
||||||
<div class="card-block">
|
<td class="col-md-1 text-center"><strong>$4.99</strong></td>
|
||||||
<figure class="profile profile-inline">
|
<td class="col-md-1 text-center"><strong>$9.98</strong></td>
|
||||||
<img th:src="@{/images/tom.png}" class="profile-avatar" alt=""></img>
|
<td class="col-md-1">
|
||||||
</figure>
|
<button type="button" class="btn btn-danger">
|
||||||
<h4 class="card-title">Tom</h4>
|
<span class="glyphicon glyphicon-remove"></span> Remove
|
||||||
<div class="card-text">
|
</button>
|
||||||
Tom is a grey and white domestic short hair cat.
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
</div>
|
</tbody>
|
||||||
<div class="card-footer">
|
<tfoot>
|
||||||
<small>Last updated 12 days ago</small>
|
<tr>
|
||||||
<button class="btn btn-info float-right btn-sm">Follow</button>
|
<td> </td>
|
||||||
</div>
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td><h5>Subtotal<br></br>Estimated shipping</h5>
|
||||||
|
<h3>Total</h3></td>
|
||||||
|
<td class="text-right"><h5><strong>$24.59<br></br>$6.94</strong></h5>
|
||||||
|
<h3>$31.53</h3></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td> </td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-default">
|
||||||
|
<span class="glyphicon glyphicon-shopping-cart"></span> Continue Shopping
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
Checkout <span class="glyphicon glyphicon-play"></span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -209,7 +261,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<small>Last updated 12 days ago</small>
|
<small>Last updated 12 days ago</small>
|
||||||
<button type="button" class="btn btn-info float-right btn-sm" onclick="javascript:follow('Tom')">Follow</button>
|
<button type="button" class="btn btn-info float-right btn-sm"
|
||||||
|
onclick="javascript:follow('Tom')">Follow
|
||||||
|
</button>
|
||||||
<button class="btn btn-info float-right btn-sm">Delete</button>
|
<button class="btn btn-info float-right btn-sm">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,6 +14,12 @@ jwt-secret-hint1=Save the token and try to verify the token locally
|
|||||||
jwt-secret-hint2=Download a word list dictionary (https://github.com/first20hours/google-10000-english)
|
jwt-secret-hint2=Download a word list dictionary (https://github.com/first20hours/google-10000-english)
|
||||||
jwt-secret-hint3=Write a small program or use HashCat for brute forcing the token according the word list
|
jwt-secret-hint3=Write a small program or use HashCat for brute forcing the token according the word list
|
||||||
|
|
||||||
|
jwt-refresh-hint1=Look at the access log you will find a token there
|
||||||
|
jwt-refresh-hint2=The token from the access log is no longer valid, can you find a way to refresh it?
|
||||||
|
jwt-refresh-hint3=The endpoint for refreshing a token is 'jwt/refresh/newToken'
|
||||||
|
jwt-refresh-hint4=Use the found access token in the Authorization: Bearer header and use your refresh token
|
||||||
|
jwt-refresh-not-tom=User is not Tom but {0}, please try again
|
||||||
|
|
||||||
jwt-final-jerry-account=Yikes, you are removing Jerry's account, try to delete the account of Tom
|
jwt-final-jerry-account=Yikes, you are removing Jerry's account, try to delete the account of Tom
|
||||||
jwt-final-not-tom=Username is not Tom try to pass a token for Tom
|
jwt-final-not-tom=Username is not Tom try to pass a token for Tom
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
205.167.170.15 - - [28/Jan/2016:21:28:01 +0100] "GET /statuses/605086114483826688/favorite?authenticity_token=264c8bf11e0212227c001b77543c3519 HTTP/1.1" 404 242 "-" "Go-http-client/1.1" "-"
|
194.201.170.15 - - [28/Jan/2016:21:28:01 +0100] "GET /JWT/refresh/checkout?token=eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q HTTP/1.1" 401 242 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-"
|
||||||
205.167.170.15 - - [28/Jan/2016:21:28:01 +0100] "GET /images/phocagallery/almhuette/thumbs/phoca_thumb_l_zimmer.jpg HTTP/1.1" 200 12783 "-" "Go-http-client/1.1" "-"
|
194.201.170.15 - - [28/Jan/2016:21:28:01 +0100] "POST /JWT/refresh/moveToCheckout HTTP/1.1" 200 12783 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-"
|
||||||
205.167.170.15 - - [28/Jan/2016:21:28:01 +0100] "GET /signup HTTP/1.1" 404 212 "-" "Go-http-client/1.1" "-"
|
194.201.170.15 - - [28/Jan/2016:21:28:01 +0100] "POST /JWT/refresh/login HTTP/1.1" 200 212 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-"
|
||||||
205.167.170.15 - - [28/Jan/2016:21:28:01 +0100] "GET /vsmartseo/status/605086114483826688/actions HTTP/1.1" 404 249 "-" "Go-http-client/1.1" "-"
|
194.201.170.15 - - [28/Jan/2016:21:28:01 +0100] "GET /JWT/refresh/addItems HTTP/1.1" 404 249 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "-"
|
||||||
205.167.170.15 - - [28/Jan/2016:21:28:01 +0100] "GET /vsmartseo?p=s HTTP/1.1" 404 215 "-" "Go-http-client/1.1" "-"
|
195.206.170.15 - - [28/Jan/2016:21:28:01 +0100] "POST /JWT/refresh/moveToCheckout HTTP/1.1" 404 215 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36" "-"
|
||||||
|
42
webgoat-lessons/jwt/src/main/resources/js/jwt-refresh.js
Normal file
42
webgoat-lessons/jwt/src/main/resources/js/jwt-refresh.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
$(document).ready(function () {
|
||||||
|
login('Jerry');
|
||||||
|
})
|
||||||
|
|
||||||
|
function login(user) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: 'JWT/refresh/login',
|
||||||
|
contentType: "application/json",
|
||||||
|
data: JSON.stringify({user: user, password: "bm5nhSkxCXZkKRy4"})
|
||||||
|
}).success(
|
||||||
|
function (response) {
|
||||||
|
localStorage.setItem('access_token', response['access_token']);
|
||||||
|
localStorage.setItem('refresh_token', response['refresh_token']);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Dev comment: Pass token as header as we had an issue with tokens ending up in the access_log
|
||||||
|
webgoat.customjs.addBearerToken = function () {
|
||||||
|
var headers_to_set = {};
|
||||||
|
headers_to_set['Authorization'] = 'Bearer ' + localStorage.getItem('access_token');
|
||||||
|
return headers_to_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Dev comment: Temporarily disabled from page we need to work out the refresh token flow but for now we can go live with the checkout page
|
||||||
|
function newToken() {
|
||||||
|
localStorage.getItem('refreshToken');
|
||||||
|
$.ajax({
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + localStorage.getItem('access_token')
|
||||||
|
},
|
||||||
|
type: 'POST',
|
||||||
|
url: 'JWT/refresh/newToken',
|
||||||
|
data: JSON.stringify({refreshToken: localStorage.getItem('refresh_token')})
|
||||||
|
}).success(
|
||||||
|
function () {
|
||||||
|
localStorage.setItem('access_token', apiToken);
|
||||||
|
localStorage.setItem('refresh_token', refreshToken);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
=== Assignment
|
=== Assignment
|
||||||
|
|
||||||
Jerry really wants to follow Tom, can you help him to follow Jerry?
|
|
||||||
From a breach of last year the following logfile is available link:images/logs.txt[here]
|
From a breach of last year the following logfile is available link:images/logs.txt[here]
|
||||||
|
Can you find a way to order the books but let *Tom* pay for them?
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
package org.owasp.webgoat.plugin;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import org.hamcrest.CoreMatchers;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.owasp.webgoat.plugins.LessonTest;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.owasp.webgoat.plugin.JWTRefreshEndpoint.PASSWORD;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
public class JWTRefreshEndpointTest extends LessonTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
JWT jwt = new JWT();
|
||||||
|
when(webSession.getCurrentLesson()).thenReturn(jwt);
|
||||||
|
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
|
||||||
|
when(webSession.getUserName()).thenReturn("unit-test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void solveAssignment() throws Exception {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
//First login to obtain tokens for Jerry
|
||||||
|
Map<String, Object> loginJson = Maps.newHashMap();
|
||||||
|
loginJson.put("user", "Jerry");
|
||||||
|
loginJson.put("password", PASSWORD);
|
||||||
|
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/JWT/refresh/login")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(loginJson)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andReturn();
|
||||||
|
Map<String, String> tokens = objectMapper.readValue(result.getResponse().getContentAsString(), Map.class);
|
||||||
|
String accessToken = tokens.get("access_token");
|
||||||
|
String refreshToken = tokens.get("refresh_token");
|
||||||
|
|
||||||
|
//Now create a new refresh token for Tom based on Toms old access token and send the refresh token of Jerry
|
||||||
|
String accessTokenTom = "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q";
|
||||||
|
Map<String, Object> refreshJson = Maps.newHashMap();
|
||||||
|
refreshJson.put("refresh_token", refreshToken);
|
||||||
|
result = mockMvc.perform(MockMvcRequestBuilders.post("/JWT/refresh/newToken")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.header("Authorization", "Bearer " + accessTokenTom)
|
||||||
|
.content(objectMapper.writeValueAsString(refreshJson)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andReturn();
|
||||||
|
tokens = objectMapper.readValue(result.getResponse().getContentAsString(), Map.class);
|
||||||
|
accessTokenTom = tokens.get("access_token");
|
||||||
|
|
||||||
|
//Now checkout with the new token from Tom
|
||||||
|
mockMvc.perform(MockMvcRequestBuilders.post("/JWT/refresh/checkout")
|
||||||
|
.header("Authorization", "Bearer " + accessTokenTom))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.lessonCompleted", is(true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkoutWithTomsTokenFromAccessLogShouldFail() throws Exception {
|
||||||
|
String accessTokenTom = "eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q";
|
||||||
|
mockMvc.perform(MockMvcRequestBuilders.post("/JWT/refresh/checkout")
|
||||||
|
.header("Authorization", "Bearer " + accessTokenTom))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.output", CoreMatchers.containsString("JWT expired at")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void flowForJerryAlwaysWorks() throws Exception {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
Map<String, Object> loginJson = Maps.newHashMap();
|
||||||
|
loginJson.put("user", "Jerry");
|
||||||
|
loginJson.put("password", PASSWORD);
|
||||||
|
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/JWT/refresh/login")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content(objectMapper.writeValueAsString(loginJson)))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andReturn();
|
||||||
|
Map<String, String> tokens = objectMapper.readValue(result.getResponse().getContentAsString(), Map.class);
|
||||||
|
String accessToken = tokens.get("access_token");
|
||||||
|
|
||||||
|
mockMvc.perform(MockMvcRequestBuilders.post("/JWT/refresh/checkout")
|
||||||
|
.header("Authorization", "Bearer " + accessToken))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.feedback", is("User is not Tom but Jerry, please try again")));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,10 @@ import io.jsonwebtoken.*;
|
|||||||
import io.jsonwebtoken.impl.TextCodec;
|
import io.jsonwebtoken.impl.TextCodec;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.Period;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -34,4 +38,19 @@ public class TokenTest {
|
|||||||
}).parse(token);
|
}).parse(token);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRefresh() {
|
||||||
|
Instant now = Instant.now(); //current date
|
||||||
|
Claims claims = Jwts.claims().setIssuedAt(Date.from(now.minus(Duration.ofDays(10))));
|
||||||
|
claims.setExpiration(Date.from(now.minus(Duration.ofDays(9))));
|
||||||
|
claims.put("admin", "false");
|
||||||
|
claims.put("user", "Tom");
|
||||||
|
String token = Jwts.builder().setClaims(claims)
|
||||||
|
.signWith(io.jsonwebtoken.SignatureAlgorithm.HS512, "bm5n3SkxCX4kKRy4")
|
||||||
|
.compact();
|
||||||
|
//Jws<Claims> jws = Jwts.parser().setSigningKey("bm5n3SkxCX4kKRy4").parseClaimsJws(token);
|
||||||
|
//Jwts.parser().setSigningKey().parsePlaintextJws(token);
|
||||||
|
System.out.println(token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user