This commit is contained in:
Nanne Baars 2020-12-05 13:23:33 +01:00 committed by Nanne Baars
parent e78549fb72
commit 142631c7a0
12 changed files with 236 additions and 239 deletions

View File

@ -41,7 +41,7 @@ public class WebWolfMacro extends InlineMacroProcessor {
* The purpose is to make it possible to use the application behind a reverse proxy. For instance in the docker * The purpose is to make it possible to use the application behind a reverse proxy. For instance in the docker
* compose/stack version with webgoat webwolf and nginx proxy. * compose/stack version with webgoat webwolf and nginx proxy.
* You do not have to use the indicated hostname, but if you do, you should define two hosts aliases * You do not have to use the indicated hostname, but if you do, you should define two hosts aliases
* 127.0.0.1 www.webgoat.local www.webwolf.locaal * 127.0.0.1 www.webgoat.local www.webwolf.local
*/ */
private String determineHost(String host, String port) { private String determineHost(String host, String port) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();

View File

@ -0,0 +1,22 @@
package org.owasp.webgoat.jwt;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AttackResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class JWTDecodeEndpoint extends AssignmentEndpoint {
@PostMapping("/JWT/decode")
@ResponseBody
public AttackResult decode(@RequestParam("jwt-encode-user") String user) {
if ("user".equals(user)) {
return success(this).build();
} else {
return failed(this).build();
}
}
}

View File

@ -84,62 +84,6 @@ public class JWTFinalEndpoint extends AssignmentEndpoint {
private JWTFinalEndpoint(DataSource dataSource) { private JWTFinalEndpoint(DataSource dataSource) {
this.dataSource = dataSource; this.dataSource = dataSource;
} }
@PostMapping(path="/JWT/encode",produces=MediaType.TEXT_HTML_VALUE)
@ResponseBody
public String encode(@RequestParam("jsonHeader") String jsonHeader,
@RequestParam("jsonPayload") String jsonPayload,
@RequestParam("jsonSecret") String jsonSecret) throws NoSuchAlgorithmException {
//System.out.println(jsonHeader);
//System.out.println(jsonPayload);
String encodedHeader;
String encodedPayload;
String encodedSignature;
try {
encodedHeader = TextCodec.BASE64URL.encode(jsonHeader);
encodedPayload = TextCodec.BASE64URL.encode(jsonPayload);
if (jsonHeader.toLowerCase().contains("none")) {
encodedSignature="";
} else {
encodedSignature = TextCodec.BASE64URL.encode(getJWTSignature(jsonHeader, encodedHeader, encodedPayload, jsonSecret));
}
} catch (Exception e) {
encodedHeader="";
encodedPayload="signature type not supported in this tool, try jwt.io";
encodedSignature = "";
}
String result = "{\"encodedHeader\":\""+encodedHeader+"\",\"encodedPayload\":\""+encodedPayload+"\",\"encodedSignature\":\""+encodedSignature+"\"}";
//System.out.println(result);
return result;
}
private byte[] getJWTSignature(String jsonHeader, String encodedHeader, String encodedPayload, String jsonSecret) throws NoSuchAlgorithmException, InvalidKeyException {
String message = encodedHeader+"."+encodedPayload;
String algorithm = "HmacSHA256";
if (jsonHeader.equals("HS512")) {
algorithm = "HmacSHA512";
}
Mac macInstance = Mac.getInstance(algorithm);
SecretKeySpec secret_key = new SecretKeySpec(TextCodec.BASE64.decode(jsonSecret), algorithm);
macInstance.init(secret_key);
return macInstance.doFinal(message.getBytes(StandardCharsets.UTF_8));
}
@PostMapping(path="/JWT/decode",produces=MediaType.TEXT_HTML_VALUE)
@ResponseBody
public String decode(@RequestParam("jwtToken") String jwtToken) throws NoSuchAlgorithmException {
try {
String encodedHeader = jwtToken.substring(0, jwtToken.indexOf("."));
String encodedPayload = jwtToken.substring(jwtToken.indexOf(".")+1, jwtToken.lastIndexOf("."));
String jsonHeader = TextCodec.BASE64URL.decodeToString(encodedHeader);
String jsonPayload = TextCodec.BASE64URL.decodeToString(encodedPayload);
return "{\"jsonHeader\":\""+jsonHeader.replace("\"", "\\\"")+"\",\"jsonPayload\":\""+jsonPayload.replace("\"", "\\\"").replace("\t","").replace("\r", "").replace("\n", "")+"\"}";
} catch (Exception e) {
return "{\"jsonHeader\":\"\",\"jsonPayload\":\"\"}";
}
}
@PostMapping("/JWT/final/follow/{user}") @PostMapping("/JWT/final/follow/{user}")
public @ResponseBody public @ResponseBody

View File

@ -1,61 +1,39 @@
<!DOCTYPE html> <!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> <html xmlns:th="http://www.thymeleaf.org">
<header>
<script>
$(document).ready(
function(){
$("#secrettoken").load('/WebGoat/JWT/secret/gettoken');
}
);
</script>
</header>
<body> <body>
<div class="lesson-page-wrapper"> <div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:JWT_plan.adoc"></div> <div class="adoc-content" th:replace="doc:JWT_plan.adoc"></div>
</div> </div>
<div class="lesson-page-wrapper"> <div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:JWT_structure.adoc"></div> <div class="adoc-content" th:replace="doc:JWT_structure.adoc"></div>
<form id="encode" class="attack-form" method="POST" name="form" action="/WebGoat/JWT/encode" >
<table width="100%"><tbody>
<tr><td>JWT header: </td><td width="100%"><input class="form-control" id="jsonHeader" name="jsonHeader" value='{ "alg":"HS256", "typ":"JWT"}' type="TEXT"/></td></tr>
<tr><td>JWT payload: </td><td width="100%">
<textarea class="form-control" rows="7" id="jsonPayload" name="jsonPayload" >
{
"exp": 1416471934, "user_name": "user",
"scope": [ "read", "write" ],
"authorities": [ "ROLE_ADMIN", "ROLE_USER" ],
"jti": "9bc92a44-0b1a-4c5e-be70-da52075b9a84",
"client_id": "my-client-with-secret"
}
</textarea>
</td></tr>
<tr><td align="center">.</td></tr>
<tr><td>encryption key: </td><td width="100%"><input class="form-control" id="jsonSecret" name="jsonSecret" value="secret" type="TEXT"/></td></tr>
<tr><td><input name="SUBMIT" value="generate JWT token" type="SUBMIT"/></td></tr>
</tbody></table>
</form>
<form id="decode" class="attack-form" method="POST" name="form" action="/WebGoat/JWT/decode" >
<table width=100%><tbody>
<tr><td>JWT token: </td><td width="100%">
<span id="encodedHeader" style="color: orange;font-family:courier">eyAgImFsZyI6IkhTMjU2IiwgICJ0eXAiOiJKV1QifQ</span>
<span style="color: black;font-family:courier"><b>.//header</b></span><br/>
<div id="encodedPayload" style="color: green;font-family:courier;width:50%">eyAgCQ0KCSJleHAiOiAxNDE2NDcxOTM0LCAgInVzZXJfbmFtZSI6ICJ1c2Vy
IiwgIA0KCSJzY29wZSI6IFsgICAgInJlYWQiLCAgICAid3JpdGUiICBdLCAg
DQoJImF1dGhvcml0aWVzIjogWyAgICAiUk9MRV9BRE1JTiIsICAgICJST0xF
X1VTRVIiICBdLCAgDQoJImp0aSI6ICI5YmM5MmE0NC0wYjFhLTRjNWUtYmU3
MC1kYTUyMDc1YjlhODQiLCAgDQoJImNsaWVudF9pZCI6ICJteS1jbGllbnQt
d2l0aC1zZWNyZXQiDQp9DQoJCQk<span style="color: black;font-family:courier"><b>. //payload</b></span></div>
<span id="encodedSignature" style="color: blue;font-family:courier">gc32RepP65NANPLQP31Aq7QPbpWKBOiaS9UXczYVZLE</span><span style="color: black;font-family:courier"><b>//signature</b></span></td></tr>
<tr><td>JWT token: </td><td width="100%"><input class="form-control" id="jwtToken" name="jwtToken" value='' type="text"/></td></tr>
<tr><td><input name="SUBMIT" value="decode JWT token" type="SUBMIT"/></td></tr>
</tbody></table>
</form>
</div> </div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:JWT_decode.adoc"></div>
<div class="attack-container">
<img th:src="@{/images/wolf-enabled.png}" class="webwolf-enabled"/>
<form id="decode" class="attack-form" method="POST" name="form" action="/WebGoat/JWT/decode">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<br>
<div class="row">
<div class="col-lg-10">
<span>
<span>
Username:
</span>
<input type="text" name="jwt-encode-user">
<button type="SUBMIT">Submit</button>
</span>
</div>
</div>
<br>
</form>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
<div class="lesson-page-wrapper"> <div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:JWT_login_to_token.adoc"></div> <div class="adoc-content" th:replace="doc:JWT_login_to_token.adoc"></div>
</div> </div>
@ -63,7 +41,6 @@ $(document).ready(
<div class="adoc-content" th:replace="doc:JWT_signing.adoc"></div> <div class="adoc-content" th:replace="doc:JWT_signing.adoc"></div>
<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/jwt-voting.js}" language="JavaScript"></script> <script th:src="@{/lesson_js/jwt-voting.js}" language="JavaScript"></script>
<div class="attack-container"> <div class="attack-container">
<div class="attack-feedback"></div> <div class="attack-feedback"></div>
@ -125,7 +102,7 @@ $(document).ready(
<div class="lesson-page-wrapper"> <div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:JWT_weak_keys"></div> <div class="adoc-content" th:replace="doc:JWT_weak_keys"></div>
<div id="secrettoken" ></div> <div id="secrettoken"></div>
<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>
@ -225,9 +202,11 @@ $(document).ready(
<td class="col-sm-1 col-md-1" style="text-align: center"> <td class="col-sm-1 col-md-1" style="text-align: center">
<input type="text" class="form-control" id="quantity2" value="2"></input> <input type="text" class="form-control" id="quantity2" value="2"></input>
</td> </td>
<td class="col-sm-1 col-md-1 text-center"><strong>$<span id="piecePrice2">4.99</span></strong> <td class="col-sm-1 col-md-1 text-center"><strong>$<span
id="piecePrice2">4.99</span></strong>
</td> </td>
<td class="col-sm-1 col-md-1 text-center"><strong>$<span id="totalPrice2">9.98</span></strong></td> <td class="col-sm-1 col-md-1 text-center"><strong>$<span
id="totalPrice2">9.98</span></strong></td>
<td class="col-md-1"> <td class="col-md-1">
<button type="button" class="btn btn-danger"> <button type="button" class="btn btn-danger">
<span class="glyphicon glyphicon-remove"></span> Remove <span class="glyphicon glyphicon-remove"></span> Remove
@ -242,7 +221,8 @@ $(document).ready(
<td>  </td> <td>  </td>
<td><h5>Subtotal<br></br>Estimated shipping</h5> <td><h5>Subtotal<br></br>Estimated shipping</h5>
<h3>Total</h3></td> <h3>Total</h3></td>
<td class="text-right"><h5><strong>$<span id="subtotalJwt">24.59</span><br></br>$6.94</strong></h5> <td class="text-right"><h5><strong>$<span
id="subtotalJwt">24.59</span><br></br>$6.94</strong></h5>
<h3>$<span id="totalJwt">31.53</span></h3></td> <h3>$<span id="totalJwt">31.53</span></h3></td>
</tr> </tr>
<tr> <tr>
@ -332,44 +312,6 @@ $(document).ready(
<div class="attack-output"></div> <div class="attack-output"></div>
</div> </div>
</div> </div>
<script>
string_chop = function(str, size){
if (str == null) return [];
str = String(str);
size = ~~size;
var strArray = str.match(new RegExp('.{1,' + size + '}', 'g'));
var strResult = "";
var i;
for (i = 0; i < strArray.length; i++) {
strResult = strResult + strArray[i] + "\n";
}
return strResult;
}
$('#encode').submit(function () {
$.post('/WebGoat/JWT/encode', $('#encode').serialize(), function (data, textStatus) {
var obj = JSON.parse(data);
document.getElementById("jwtToken").value=obj.encodedHeader+"."+obj.encodedPayload+"."+obj.encodedSignature;
document.getElementById("encodedHeader").innerHTML=obj.encodedHeader;
document.getElementById("encodedPayload").innerHTML=string_chop(obj.encodedPayload, 80);
document.getElementById("encodedSignature").innerHTML=obj.encodedSignature;
});
return false;
});
$('#decode').submit(function () {
$.post('/WebGoat/JWT/decode', $('#decode').serialize(), function (data, textStatus) {
var obj = JSON.parse(data);
document.getElementById("jsonHeader").value=obj.jsonHeader;
document.getElementById("jsonPayload").value=obj.jsonPayload;
document.getElementById("jsonSignature").value="";
});
return false;
});
</script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,12 @@
== Decoding a JWT token
Let's try decoding a JWT token, for this you can use the webWolfLink:jwt[noLink] inside WebWolf.
Given the following token:
[source]
----
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MDcwOTk2MDgsInVzZXJfbmFtZSI6InVzZXIiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiI5YmM5MmE0NC0wYjFhLTRjNWUtYmU3MC1kYTUyMDc1YjlhODQiLCJjbGllbnRfaWQiOiJteS1jbGllbnQtd2l0aC1zZWNyZXQifQ.N9TsIXpvMoICVeGI9mEOPVlYODjMOCis--yB-34BOOw
----
Copy and paste the following token and decode the token, can you find the user inside the token?

View File

@ -1,10 +1,17 @@
== Structure of a JWT token == Structure of a JWT token
Let's take a look at the structure of a JWT token. Let's take a look at the structure of a JWT token:
The token is base64-url-encoded and consists of three parts `header.claims.signature`. [role="lesson-image"]
image::images/jwt_token.png[JWT]
The token is base64 encoded and consists of three parts:
- header
- claims
- signature
Both header and claims consist are respresented by a JSON object. The header describes the cryptographic operations applied to the JWT and optionally, additional properties of the JWT.
The claims represent a JSON object whose members are the claims conveyed by the JWT.
Based on the algorithm the signature will be added to the token. This way you can verify that someone did not modify the token (one change to the token will invalidate the signature).
You can use the below forms as a simple way to encode and decode JWT tokens. Or you can make use of the more extensive options at https://jwt.io[jwt.io,window=_blank].
You can revisit this page for the assignments to come and use it as part of your attempts to solve it.

View File

@ -1,16 +1,6 @@
package org.owasp.webgoat.jwt; package org.owasp.webgoat.jwt;
import static org.hamcrest.Matchers.is; import io.jsonwebtoken.Jwts;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.hamcrest.CoreMatchers; import org.hamcrest.CoreMatchers;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -21,9 +11,15 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import io.jsonwebtoken.Jwt; import java.util.Date;
import io.jsonwebtoken.Jwts; import java.util.HashMap;
import lombok.SneakyThrows; import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
public class JWTFinalEndpointTest extends LessonTest { public class JWTFinalEndpointTest extends LessonTest {
@ -77,22 +73,4 @@ public class JWTFinalEndpointTest extends LessonTest {
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("jwt-invalid-token")))); .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("jwt-invalid-token"))));
} }
@Test
@SneakyThrows
public void testJWTTestTools() {
//JWTFinalEndpoint jwtFinalEndpoint = new JWTFinalEndpoint(null);
String jsonHeader = "{\"alg\":\"HS256\"}";
String jsonPayload = "{\"iss\":\"OWASP\"}";
String jsonSecret = "secret";
String jwtToken = jwtFinalEndpoint.encode(jsonHeader, jsonPayload, jsonSecret).replace(":", "")
.replace("encodedHeader", "").replace("encodedPayload", "").replace("encodedSignature", "")
.replace("{", "").replace("}", "").replace("\"", "").replace(",", ".");
Jwt jwt = Jwts.parser().setSigningKey(jsonSecret).parse(jwtToken);
String revert = jwtFinalEndpoint.decode(jwtToken);
//System.out.println("revert: "+revert);
}
} }

View File

@ -37,9 +37,9 @@
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>org.bitbucket.b_c</groupId>
<artifactId>jjwt</artifactId> <artifactId>jose4j</artifactId>
<version>0.7.0</version> <version>0.7.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@ -1,20 +1,28 @@
package org.owasp.webwolf.jwt; package org.owasp.webwolf.jwt;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.*; import lombok.*;
import lombok.Getter; import org.jose4j.jws.AlgorithmIdentifiers;
import lombok.NoArgsConstructor; import org.jose4j.jws.JsonWebSignature;
import lombok.Setter; import org.jose4j.jwt.consumer.InvalidJwtException;
import org.springframework.util.Base64Utils; import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.jwx.CompactSerializer;
import org.jose4j.keys.HmacKey;
import org.jose4j.lang.JoseException;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.springframework.util.Base64Utils.decodeFromUrlSafeString;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor
@Getter @Getter
@Setter @Setter
@Builder
public class JWTToken { public class JWTToken {
private static final Pattern jwtPattern = Pattern.compile("(.*)\\.(.*)\\.(.*)"); private static final Pattern jwtPattern = Pattern.compile("(.*)\\.(.*)\\.(.*)");
@ -24,63 +32,70 @@ public class JWTToken {
private String header; private String header;
private String payload; private String payload;
private boolean signatureValid = true; private boolean signatureValid = true;
private boolean validToken = true;
public void decode() { public void decode() {
validToken = parseToken(encoded); parseToken(encoded.trim().replace(System.getProperty("line.separator"), ""));
signatureValid = validateSignature(secretKey, encoded); signatureValid = validateSignature(secretKey, encoded);
} }
public void encode() { public void encode() {
var mapper = new ObjectMapper(); JsonWebSignature jws = new JsonWebSignature();
try { jws.setPayload(payload);
if (StringUtils.hasText(secretKey)) { jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
encoded = Jwts.builder() jws.setDoKeyValidation(false);
.signWith(SignatureAlgorithm.HS256, Base64Utils.encodeToUrlSafeString(secretKey.getBytes())) if (StringUtils.hasText(secretKey)) {
.setClaims(mapper.readValue(payload, Map.class)) jws.setKey(new HmacKey(secretKey.getBytes(UTF_8)));
.setHeader(mapper.readValue(header, Map.class)) try {
.compact(); encoded = jws.getCompactSerialization();
} else { signatureValid = true;
encoded = Jwts.builder() } catch (JoseException e) {
.setClaims(mapper.readValue(payload, Map.class)) header = "";
.setHeader(mapper.readValue(header, Map.class)) payload = "";
.compact();
} }
validToken = true; } else {
} catch (JsonProcessingException e) { var encodedHeader = jws.getHeaders().getEncodedHeader();
validToken = false; var encodedPayload = jws.getEncodedPayload();
signatureValid = false; encoded = CompactSerializer.serialize(new String[]{encodedHeader, encodedPayload});
} }
} }
private boolean parseToken(String jwt) { private boolean parseToken(String jwt) {
var matcher = jwtPattern.matcher(jwt); var matcher = jwtPattern.matcher(jwt);
var mapper = new ObjectMapper().writerWithDefaultPrettyPrinter(); var mapper = new ObjectMapper();
if (matcher.matches()) { if (matcher.matches()) {
try { try {
Jwt<Header, Claims> headerClaimsJwt = Jwts.parser().parseClaimsJwt(matcher.group(1) + "." + matcher.group(2) + "."); var prettyPrint = mapper.writerWithDefaultPrettyPrinter();
this.header = mapper.writeValueAsString(headerClaimsJwt.getHeader()); this.header = prettyPrint.writeValueAsString(mapper.readValue(decodeFromUrlSafeString(matcher.group(1)), Map.class));
this.payload = mapper.writeValueAsString(headerClaimsJwt.getBody()); this.payload = prettyPrint.writeValueAsString(mapper.readValue(decodeFromUrlSafeString(matcher.group(2)), Map.class));
return true;
} catch (Exception e) { } catch (Exception e) {
try { this.header = new String(decodeFromUrlSafeString(matcher.group(1)));
this.header = mapper.writeValueAsString(new String(Base64Utils.decodeFromUrlSafeString(matcher.group(1)))); this.payload = new String(decodeFromUrlSafeString(matcher.group(2)));
this.payload = mapper.writeValueAsString(new String(Base64Utils.decodeFromUrlSafeString(matcher.group(2)))); return false;
} catch (Exception ex) {
return false;
}
} }
return true; } else {
this.header = "error";
this.payload = "error";
} }
return false; return false;
} }
private boolean validateSignature(String secretKey, String jwt) { private boolean validateSignature(String secretKey, String jwt) {
try { if (StringUtils.hasText(secretKey)) {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt); JwtConsumer jwtConsumer = new JwtConsumerBuilder()
return true; .setSkipAllValidators()
} catch (Exception e) { .setVerificationKey(new HmacKey(secretKey.getBytes(UTF_8)))
return false; .setRelaxVerificationKeyValidation()
.build();
try {
jwtConsumer.processToClaims(jwt);
return true;
} catch (InvalidJwtException e) {
return false;
}
} }
return false;
} }
} }

View File

@ -8,10 +8,8 @@ $(document).ready(() => {
url: '/WebWolf/jwt/decode', url: '/WebWolf/jwt/decode',
data: JSON.stringify({encoded: token, secretKey: secretKey}), data: JSON.stringify({encoded: token, secretKey: secretKey}),
success: function (data) { success: function (data) {
if (data.validToken) { $('#tokenHeader').val(data.header);
$('#tokenHeader').val(data.header); $('#tokenPayload').val(data.payload);
$('#tokenPayload').val(data.payload);
}
updateSignature(data); updateSignature(data);
}, },
contentType: "application/json", contentType: "application/json",
@ -69,11 +67,10 @@ function parseJson(text) {
return true; return true;
} }
function updateSignature(data) { function updateSignature(data) {
if (data.signatureValid) { if (data.signatureValid) {
$('#signatureValid').val("Signature valid"); $('#signatureValid').html("Signature valid");
} else { } else {
$('#signatureValid').val("Signature invalid"); $('#signatureValid').html("Signature invalid");
} }
} }

View File

@ -16,7 +16,6 @@
<form th:action="@{/login}" method="post"> <form th:action="@{/login}" method="post">
<fieldset> <fieldset>
<h2>Sign in</h2> <h2>Sign in</h2>
Use your WebGoat account.
<br/> <br/>
<div th:if="${param.error}"> <div th:if="${param.error}">
<div class="alert alert-danger"> <div class="alert alert-danger">
@ -31,11 +30,11 @@
<div class="form-group"> <div class="form-group">
<input type="text" name="username" id="username" class="form-control input-lg" <input type="text" name="username" id="username" class="form-control input-lg"
placeholder="UserName" required="true" autofocus="true"/> placeholder="Username WebGoat" required="true" autofocus="true"/>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="password" name="password" id="password" class="form-control input-lg" <input type="password" name="password" id="password" class="form-control input-lg"
placeholder="Password" required="true"/> placeholder="Password WebGoat" required="true"/>
</div> </div>
<div class="row"> <div class="row">

View File

@ -0,0 +1,81 @@
package org.owasp.webwolf.jwt;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class JWTTokenTest {
@Test
void encodeCorrectTokenWithoutSignature() {
var headers = Map.of("alg", "HS256", "typ", "JWT");
var payload = Map.of("test", "test");
var token = JWTToken.builder().header(toString(headers)).payload(toString(payload)).build();
token.encode();
assertThat(token.getEncoded()).isEqualTo("eyJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoidGVzdCJ9");
}
@Test
void encodeCorrectTokenWithSignature() {
var headers = Map.of("alg", "HS256", "typ", "JWT");
var payload = Map.of("test", "test");
var token = JWTToken.builder()
.header(toString(headers))
.payload(toString(payload))
.secretKey("test")
.build();
token.encode();
assertThat(token.getEncoded()).isEqualTo("eyJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoidGVzdCJ9.KOobRHDYyaesV_doOk11XXGKSONwzllraAaqqM4VFE4");
}
@Test
void encodeTokenWithNonJsonInput() {
var token = JWTToken.builder()
.header("aaa")
.payload("bbb")
.secretKey("test")
.build();
token.encode();
assertThat(token.getEncoded()).isEqualTo("eyJhbGciOiJIUzI1NiJ9.YmJi.VAcRegquayARuahZZ1ednXpbAyv7KEFnyjNJlxLNX0I");
}
@Test
void decodeValidSignedToken() {
var token = JWTToken.builder()
.encoded("eyJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoidGVzdCJ9.KOobRHDYyaesV_doOk11XXGKSONwzllraAaqqM4VFE4")
.secretKey("test")
.build();
token.decode();
assertThat(token.getHeader()).contains("\"alg\" : \"HS256\"");
assertThat(token.isSignatureValid()).isTrue();
}
@Test
void decodeInvalidSignedToken() {
var token = JWTToken.builder().encoded("eyJhbGciOiJIUzI1NiJ9.eyJ0ZXsdfdfsaasfddfasN0IjoidGVzdCJ9.KOobRHDYyaesV_doOk11XXGKSONwzllraAaqqM4VFE4").build();
token.decode();
assertThat(token.getHeader()).contains("\"alg\":\"HS256\"");
assertThat(token.getPayload()).contains("{\"te");
}
@SneakyThrows
private String toString(Map<String, String> map) {
var mapper = new ObjectMapper();
return mapper.writeValueAsString(map);
}
}