tested solution with unit test and verfied with lesson 5 on ie
This commit is contained in:
parent
71d9c4b61a
commit
3b050a856a
@ -22,24 +22,34 @@
|
|||||||
|
|
||||||
package org.owasp.webgoat.jwt;
|
package org.owasp.webgoat.jwt;
|
||||||
|
|
||||||
import io.jsonwebtoken.*;
|
import java.nio.charset.StandardCharsets;
|
||||||
import io.jsonwebtoken.impl.TextCodec;
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
import org.owasp.webgoat.assignments.AssignmentEndpoint;
|
||||||
import org.owasp.webgoat.assignments.AssignmentHints;
|
import org.owasp.webgoat.assignments.AssignmentHints;
|
||||||
import org.owasp.webgoat.assignments.AttackResult;
|
import org.owasp.webgoat.assignments.AttackResult;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
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;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import io.jsonwebtoken.Claims;
|
||||||
import javax.sql.DataSource;
|
import io.jsonwebtoken.JwsHeader;
|
||||||
|
import io.jsonwebtoken.Jwt;
|
||||||
import java.nio.charset.Charset;
|
import io.jsonwebtoken.JwtException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import io.jsonwebtoken.Jwts;
|
||||||
import java.sql.ResultSet;
|
import io.jsonwebtoken.SigningKeyResolverAdapter;
|
||||||
import java.sql.SQLException;
|
import io.jsonwebtoken.impl.TextCodec;
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <pre>
|
* <pre>
|
||||||
@ -81,17 +91,54 @@ public class JWTFinalEndpoint extends AssignmentEndpoint {
|
|||||||
@RequestParam("jsonPayload") String jsonPayload,
|
@RequestParam("jsonPayload") String jsonPayload,
|
||||||
@RequestParam("jsonSecret") String jsonSecret) throws NoSuchAlgorithmException {
|
@RequestParam("jsonSecret") String jsonSecret) throws NoSuchAlgorithmException {
|
||||||
|
|
||||||
String header = Base64.getUrlEncoder().encodeToString(jsonHeader.getBytes(Charset.defaultCharset()));
|
//System.out.println(jsonHeader);
|
||||||
String body = Base64.getUrlEncoder().encodeToString(jsonPayload.getBytes(Charset.defaultCharset()));
|
//System.out.println(jsonPayload);
|
||||||
String signature = "";
|
String encodedHeader;
|
||||||
return "{\"header\":\""+header+"\",\"payload\":\""+body+"\",\"secret\":\""+signature+"\"}";
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(path="/JWT/decode",produces=MediaType.APPLICATION_JSON_VALUE)
|
private byte[] getJWTSignature(String jsonHeader, String encodedHeader, String encodedPayload, String jsonSecret) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||||
@ResponseBody
|
String message = encodedHeader+"."+encodedPayload;
|
||||||
public String decode(@RequestParam("token") String token) throws NoSuchAlgorithmException {
|
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 new String(Base64.getUrlDecoder().decode(token.getBytes(Charset.defaultCharset())));
|
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}")
|
||||||
|
@ -20,22 +20,41 @@ $(document).ready(
|
|||||||
<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" >
|
<form id="encode" class="attack-form" method="POST" name="form" action="/WebGoat/JWT/encode" >
|
||||||
<table>
|
<table width="100%"><tbody>
|
||||||
<tr><td width="100%"><input class="form-control" name="jsonHeader" value='{ "alg":"HS256", "typ":"JWT"}"' type="TEXT"/></td></tr>
|
<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 width="100%"><input class="form-control" name="jsonPayload" value='{ "alg":"HS256", "typ":"JWT"}' type="TEXT"/></td></tr>
|
|
||||||
<tr><td width="100%"><input class="form-control" name="jsonSecret" value="secret" type="TEXT"/></td></tr>
|
<tr><td>JWT payload: </td><td width="100%">
|
||||||
<tr><td><input name="SUBMIT" value="encode" type="SUBMIT"/></td></tr>
|
<textarea class="form-control" rows="7" id="jsonPayload" name="jsonPayload" >
|
||||||
</table>
|
{
|
||||||
|
"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>
|
||||||
<form id="decode" class="attack-form" method="POST" name="form" action="/WebGoat/JWT/decode" >
|
<form id="decode" class="attack-form" method="POST" name="form" action="/WebGoat/JWT/decode" >
|
||||||
<table>
|
<table width=100%><tbody>
|
||||||
<tr><td width="100%"><input class="form-control" id="headervalue" name="headervalue" value='' type="TEXT"/></td></tr>
|
<tr><td>JWT token: </td><td width="100%">
|
||||||
<tr><td width="100%"><input class="form-control" name="payloadvalue" value='' type="TEXT"/></td></tr>
|
<span id="encodedHeader" style="color: orange;font-family:courier">eyAgImFsZyI6IkhTMjU2IiwgICJ0eXAiOiJKV1QifQ</span>
|
||||||
<tr><td width="100%"><input class="form-control" name="signature" value="" type="TEXT"/></td></tr>
|
<span style="color: black;font-family:courier"><b>.//header</b></span><br/>
|
||||||
<tr><td><input name="SUBMIT" value="encode" type="SUBMIT"/></td></tr>
|
<div id="encodedPayload" style="color: green;font-family:courier;width:50%">eyAgCQ0KCSJleHAiOiAxNDE2NDcxOTM0LCAgInVzZXJfbmFtZSI6ICJ1c2Vy
|
||||||
</table>
|
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>
|
</form>
|
||||||
|
|
||||||
</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>
|
||||||
@ -318,10 +337,37 @@ $(document).ready(
|
|||||||
</div>
|
</div>
|
||||||
<script>
|
<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 () {
|
$('#encode').submit(function () {
|
||||||
$.post('/WebGoat/JWT/encode', $('#encode').serialize(), function (data, textStatus) {
|
$.post('/WebGoat/JWT/encode', $('#encode').serialize(), function (data, textStatus) {
|
||||||
var obj = JSON.parse(data);
|
var obj = JSON.parse(data);
|
||||||
document.getElementById("headervalue").value=obj.header;
|
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;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -1,39 +1,10 @@
|
|||||||
== 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.
|
||||||
|
|
||||||
image::images/jwt_token.png[style="lesson-image"]
|
The token is base64-url-encoded and consists of three parts `header.claims.signature`.
|
||||||
|
|
||||||
{nbsp} +
|
|
||||||
|
|
||||||
The token is base64 encoded and consists of three parts `header.claims.signature`. The decoded version of this token is:
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"alg":"HS256",
|
|
||||||
"typ":"JWT"
|
|
||||||
}
|
|
||||||
.
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
.
|
|
||||||
qxNjYSPIKSURZEMqLQQPw1Zdk6Le2FdGHRYZG7SQnNk
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
package org.owasp.webgoat.jwt;
|
package org.owasp.webgoat.jwt;
|
||||||
|
|
||||||
import io.jsonwebtoken.Jwts;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
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;
|
||||||
@ -11,15 +21,9 @@ 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 java.util.Date;
|
import io.jsonwebtoken.Jwt;
|
||||||
import java.util.HashMap;
|
import io.jsonwebtoken.Jwts;
|
||||||
import java.util.Map;
|
import lombok.SneakyThrows;
|
||||||
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 {
|
||||||
@ -29,6 +33,9 @@ public class JWTFinalEndpointTest extends LessonTest {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private JWT jwt;
|
private JWT jwt;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JWTFinalEndpoint jwtFinalEndpoint;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
when(webSession.getCurrentLesson()).thenReturn(jwt);
|
when(webSession.getCurrentLesson()).thenReturn(jwt);
|
||||||
@ -70,4 +77,22 @@ 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);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user