Added more content for CSRF lesson

This commit is contained in:
Nanne Baars 2017-11-22 01:34:05 +01:00
parent 5eed385d5d
commit 43b82027f5
14 changed files with 512 additions and 56 deletions

5
.gitignore vendored
View File

@ -14,7 +14,8 @@
/.settings/org.eclipse.wst.validation.prefs
/.externalToolBuilders/
.project
/target
*/target/*
mongo-data/*
.classpath
.idea/
.settings/
@ -29,6 +30,7 @@ src/main/webapp/plugin_lessons/*.jar
src/main/webapp/users/*.props
classes/*
*.iml
pom.xml.versionsBackup
/*.iml
.extract/*
@ -39,3 +41,4 @@ webgoat-lessons/**/target
**/*.jar
**/.DS_Store
webgoat-server/mongo-data/*
webgoat-lessons/vulnerable-components/dependency-reduced-pom.xml

View File

@ -15,7 +15,7 @@ import javax.validation.constraints.Size;
public class UserForm {
@NotNull
@Size(min=6, max=10)
@Size(min=6, max=20)
private String username;
@NotNull
@Size(min=6, max=10)

View File

@ -0,0 +1,88 @@
package org.owasp.webgoat.plugin;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
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.UserSessionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
/**
* @author nbaars
* @since 11/17/17.
*/
@AssignmentPath("/csrf/feedback")
@AssignmentHints({"csrf-feedback-hint1", "csrf-feedback-hint2", "csrf-feedback-hint3"})
public class CSRFFeedback extends AssignmentEndpoint {
@Autowired
private UserSessionData userSessionData;
@Autowired
private ObjectMapper objectMapper;
@PostMapping(value = "/message", produces = {"application/json"})
@ResponseBody
public AttackResult completed(HttpServletRequest request, @RequestBody String feedback) {
try {
objectMapper.readValue(feedback.getBytes(), Map.class);
} catch (IOException e) {
return failed().feedback(ExceptionUtils.getStackTrace(e)).build();
}
boolean correctCSRF = requestContainsWebGoatCookie(request.getCookies()) && request.getContentType().equals(MediaType.TEXT_PLAIN_VALUE);
correctCSRF &= hostOrRefererDifferentHost(request);
if (correctCSRF) {
String flag = UUID.randomUUID().toString();
userSessionData.setValue("csrf-feedback", flag);
return success().feedback("csrf-feedback-success").feedbackArgs(flag).build();
}
return failed().build();
}
@PostMapping(produces = "application/json")
@ResponseBody
public AttackResult flag(@RequestParam("confirmFlagVal") String flag) {
if (flag.equals(userSessionData.getValue("csrf-feedback"))) {
return trackProgress(success().build());
} else {
return trackProgress(failed().build());
}
}
private boolean hostOrRefererDifferentHost(HttpServletRequest request) {
String referer = request.getHeader("referer");
String host = request.getHeader("host");
return !StringUtils.contains(referer, host);
}
private boolean requestContainsWebGoatCookie(Cookie[] cookies) {
if (cookies != null) {
for (Cookie c : cookies) {
if (c.getName().equals("JSESSIONID")) {
return true;
}
}
}
return false;
}
/** Solution
<form name="attack" enctype="text/plain" action="http://localhost:8080/WebGoat/csrf/feedback/message" METHOD="POST">
<input type="hidden" name='{"name": "Test", "email": "test1233@dfssdf.de", "subject": "service", "message":"dsaffd"}'>
</form>
<script>document.attack.submit();</script>
*/
}

View File

@ -0,0 +1,40 @@
package org.owasp.webgoat.plugin;
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.users.UserTracker;
import org.owasp.webgoat.users.UserTrackerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author nbaars
* @since 11/17/17.
*/
@AssignmentPath("/csrf/login")
@AssignmentHints({"csrf-login-hint1", "csrf-login-hint2", "csrf-login-hint3"})
public class CSRFLogin extends AssignmentEndpoint {
@Autowired
private UserTrackerRepository userTrackerRepository;
@PostMapping(produces = {"application/json"})
@ResponseBody
public AttackResult completed() {
String userName = getWebSession().getUserName();
if (userName.startsWith("csrf")) {
markAssignmentSolvedWithRealUser(userName.substring("csrf-".length()));
return trackProgress(success().feedback("csrf-login-success").build());
}
return trackProgress(failed().feedback("csrf-login-failed").feedbackArgs(userName).build());
}
private void markAssignmentSolvedWithRealUser(String username) {
UserTracker userTracker = userTrackerRepository.findOne(username);
userTracker.assignmentSolved(getWebSession().getCurrentLesson(), this.getClass().getSimpleName());
userTrackerRepository.save(userTracker);
}
}

View File

@ -2,62 +2,63 @@
<html xmlns:th="http://www.thymeleaf.org">
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:CSRF_intro.adoc"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:CSRF_intro.adoc"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:CSRF_GET.adoc"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:CSRF_GET.adoc"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:CSRF_Get_Flag.adoc"></div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:CSRF_Get_Flag.adoc"></div>
<form accept-charset="UNKNOWN" id="basic-csrf-get"
method="GET" name="form1"
<form accept-charset="UNKNOWN" id="basic-csrf-get"
method="GET" name="form1"
successCallback=""
action="/WebGoat/csrf/basic-get-flag"
enctype="application/json;charset=UTF-8">
<input name="csrf" type="hidden" value="false"/>
<input type="submit" name="ubmit="/>
</form>
<div class="adoc-content" th:replace="doc:CSRF_Basic_Get-1.adoc"></div>
<div class="attack-container">
<div class="assignment-success">
<i class="fa fa-2 fa-check hidden" aria-hidden="true">
</i>
</div>
<form class="attack-form" accept-charset="UNKNOWN" id="confirm-flag-1"
method="POST" name="form2"
successCallback=""
action="/WebGoat/csrf/basic-get-flag"
action="/WebGoat/csrf/confirm-flag-1"
enctype="application/json;charset=UTF-8">
<input name="csrf" type="hidden" value="false" />
<input type="submit" name="ubmit=" />
Confirm Flag Value:
<input type="text" length="6" name="confirmFlagVal" value=""/>
<input name="submit" value="Submit" type="submit"/>
</form>
<div class="adoc-content" th:replace="doc:CSRF_Basic_Get-1.adoc"></div>
<div class="attack-container">
<div class="assignment-success">
<i class="fa fa-2 fa-check hidden" aria-hidden="true">
</i>
</div>
<form class="attack-form" accept-charset="UNKNOWN" id="confirm-flag-1"
method="POST" name="form2"
successCallback=""
action="/WebGoat/csrf/confirm-flag-1"
enctype="application/json;charset=UTF-8">
Confirm Flag Value:
<input type="text" length="6" name="confirmFlagVal" value="" />
<input name="submit" value="Submit" type="submit"/>
</form>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
<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:CSRF_Reviews.adoc"></div>
<div class="adoc-content" th:replace="doc:CSRF_Reviews.adoc"></div>
<!-- comment area -->
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/reviews.css}"/>
<script th:src="@{/lesson_js/csrf-review.js}" language="JavaScript"></script>
<!-- comment area -->
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/reviews.css}"/>
<script th:src="@{/lesson_js/csrf-review.js}" language="JavaScript"></script>
<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>
<div class="attack-container">
<div class="container-fluid">
<div class="panel post">
<div class="post-heading">
@ -89,16 +90,17 @@
method="POST" name="review-form"
successCallback=""
action="/WebGoat/csrf/review">
<input class="form-control" id="reviewText" name="reviewText" placeholder="Add a Review" type="text"/>
<input class="form-control" id="reviewStars" name="stars" type="text" />
<input type="hidden" name="validateReq" value="2aa14227b9a13d0bede0388a7fba9aa9" />
<input class="form-control" id="reviewText" name="reviewText" placeholder="Add a Review"
type="text"/>
<input class="form-control" id="reviewStars" name="stars" type="text"/>
<input type="hidden" name="validateReq" value="2aa14227b9a13d0bede0388a7fba9aa9"/>
<input type="submit" name="submit" value="Submit review"/>
</form>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
<!--<span class="input-group-addon">-->
<!--<i id="postReview" class="fa fa-edit" style="font-size: 20px"></i>-->
<!--</span>-->
<!--<i id="postReview" class="fa fa-edit" style="font-size: 20px"></i>-->
<!--</span>-->
</div>
<ul class="comments-list">
<div id="list">
@ -108,14 +110,144 @@
</div>
</div>
</div>
<!-- end comments -->
</div>
<!-- end comments -->
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:CSRF_Frameworks.adoc"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:CSRF_JSON.adoc"></div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:CSRF_ContentType.adoc"></div>
<script th:src="@{/lesson_js/feedback.js}" language="JavaScript"></script>
<div style="container-fluid; background-color: #f1f1f1; border: 2px solid #a66;
border-radius: 12px;
padding: 7px;
margin-top:7px;
padding:5px;">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<div class="container-fluid">
<div class="row">
<div class="col-md-8">
<div class="well well-sm">
<form class="attack-form" accept-charset="UNKNOWN" id="csrf-feedback"
method="POST"
prepareData="feedback"
action="/WebGoat/csrf/feedback/message"
contentType="application/json">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="name">
Name</label>
<input type="text" class="form-control" name="name" id="name"
placeholder="Enter name"
required="required"/>
</div>
<div class="form-group">
<label for="email">
Email Address</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-envelope"></span>
</span>
<input type="email" name="email" class="form-control" id="email"
placeholder="Enter email"
required="required"/></div>
</div>
<div class="form-group">
<label for="subject">
Subject</label>
<select id="subject" name="subject" class="form-control" required="required">
<option value="na" selected="">Choose One:</option>
<option value="service">General Customer Service</option>
<option value="suggestions">Suggestions</option>
<option value="product">Product Support</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="name">
Message</label>
<textarea name="message" id="message" class="form-control" rows="9" cols="25"
required="required"
placeholder="Message"></textarea>
</div>
</div>
<div class="col-md-12">
<button class="btn btn-primary pull-right" id="btnContactUs">
Send Message
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:CSRF_Impact_Defense.adoc"></div>
<div class="attack-container">
<div class="assignment-success">
<i class="fa fa-2 fa-check hidden" aria-hidden="true">
</i>
</div>
<form class="attack-form" accept-charset="UNKNOWN" id="confirm-flag-feedback"
method="POST" name="form2"
action="/WebGoat/csrf/feedback"
enctype="application/json;charset=UTF-8">
Confirm Flag Value:
<input type="text" length="6" name="confirmFlagVal" value=""/>
<input name="submit" value="Submit" type="submit"/>
</form>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
<!--</div>-->
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:CSRF_Login.adoc"></div>
<div class="attack-container">
<div class="assignment-success">
<i class="fa fa-2 fa-check hidden" aria-hidden="true">
</i>
</div>
<form class="attack-form" accept-charset="UNKNOWN" id="confirm-flag-login"
method="POST" name="form2"
action="/WebGoat/csrf/login"
enctype="application/json;charset=UTF-8">
Press the button below when your are logged in as the other user<br/>
<input name="submit" value="Solved!" type="submit"/>
</form>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:CSRF_Impact_Defense.adoc"></div>
</div>
<!--</div>-->
</html>

View File

@ -16,4 +16,17 @@ csrf-review.success=It appears you have submitted correctly from another site. G
csrf-review-hint1=Again, you will need to submit from an external domain/host to trigger this action. While CSRF can often be triggered from the same host (e.g. via persisted payload), this doesn't work that way.
csrf-review-hint2=Remember, you need to mimic the existing workflow/form.
csrf-review-hint3=This one has a weak anti-CSRF protection, but you do need to overcome (mimic) it
csrf-review-hint3=This one has a weak anti-CSRF protection, but you do need to overcome (mimic) it
csrf-feedback-hint1=Look at the content-type.
csrf-feedback-hint2=Try to post the same message with content-type text/plain
csrf-feedback-hint3=The json can be put into a hidden field inside
csrf-feedback-invalid-json=Invalid JSON received.
csrf-feedback-success=Congratulations you have found the correct solution, the flag is: {0}
csrf-login-hint1=First create a new account with csrf-username
csrf-login-hint2=Create a form which will log you in as this user (hint 1) and upload it to WebWolf
csrf-login-hint3=Visit this assignment again
csrf-login-success=Congratulations, now log out and login with your normal user account within WebGoat, remember the attacker knows you solved this assignment
csrf-login-failed=The solution is not correct, you are clicking the button while logged in as {0}

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

View File

@ -0,0 +1,7 @@
webgoat.customjs.feedback = function() {
var data = {};
$('#csrf-feedback').find('input, textarea, select').each(function(i, field) {
data[field.name] = field.value;
});
return JSON.stringify(data);
}

View File

@ -0,0 +1,23 @@
== CSRF and content-type
In the previous section we saw how relying on the content-type is not a protection against
CSRF. In this section we will look into another way we can perform a CSRF attack against
a APIs which are not protected against CSRF.
In this assignment you need to achieve to POST the following JSON message to our endpoints:
[source]
----
POST /csrf/feedback HTTP/1.1
{
"name" : "WebGoat",
"email" : "webgoat@webgoat.org"
"content" : "WebGoat is the best!!"
}
----
More information can be found http://pentestmonkey.net/blog/csrf-xml-post-request[here]
Remember you need to make the call from another origin (WebWolf can help here) and you need to be logged in into
WebGoat.

View File

@ -0,0 +1,22 @@
=== Automatic support from frameworks
Most frameworks now have default support for preventing CSRF. For example with Angular an interceptor reads a token
from a cookie by default XSRF-TOKEN and sets it as an HTTP header, X-XSRF-TOKEN. Since only code that runs on your domain
could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker.
In order for this to work the backend server sets the token in a cookie. As the value of the cookie should be read
by Angular (JavaScript) this cookie should not be marked with the http-only flag. On every request towards the server
Angular will put the token in the X-XSRF-TOKEN as a HTTP header. The server can validate whether those two tokens
match and this will ensure the server the request is running on the same domain.
*Important: DEFINE A SEPARATE COOKIE, DO NOT REUSE THE SESSION COOKIE*
Remember the session cookie should always be defined with http-only flag.
Another effective defense can be to add a custom request header to each call. This will work if all the interactions
with the server are performed with JavaScript. On the server side you only need to check the presence of this header
if this header is not present deny the request.

View File

@ -0,0 +1,46 @@
**But I only have JSON APIs and no CORS enabled, how can those be susceptible to CSRF?**
A lot of web applications implement no protection against CSRF they are somehow protected by the fact that
they only work with `application/json` as content type. The only way to make a request with this content-type from the
browser is with a XHR request. Before the browser can make such a request a preflight request will be made towards
the server (remember the CSRF request will be cross origin). If the preflight response does not allow the cross origin
request the browser will not make the call.
To make a long answer short: this is *not* a valid protection against CSRF.
One example why this protection is not enough can be found https://bugs.chromium.org/p/chromium/issues/detail?id=490015[here].
Turns out `Navigator.sendBeacon()` was allowed to send POST request with an arbitrary content-type.
[qoute, developer.mozilla.org]
____
The navigator.sendBeacon() method can be used to asynchronously transfer a small amount of
data over HTTP to a web server. This method addresses the needs of analytics and diagnostics
code that typically attempts to send data to a web server prior to the unloading of the
document. Sending the data any sooner may result in a missed opportunity to gather data..."
____
{nbsp} +
For example:
[source]
----
function postBeacon() {
var data= new Blob([JSON.stringify({"author" :"WebGoat"})], {type : 'application/json'});
navigator.sendBeacon("http://localhost:8083", data)
}
----
[quote, Eduardo Vela]
____
I think Content-Type restrictions are useful for websites that are accidentally safe against CSRF. They are not meant to be, but they are because they happen to only accept XML or JSON payloads.
That said, it's somewhat obvious the websites depending on this behavior should be fixed, and any reputable pentesters will point that out. The issue is whether it's the browser responsibility to act as a nanny to weak websites, or we should leave weak websites as sacrifice for great justice. Survival of the fittest.
IMHO, the answer is somewhere in between, and a good first step would be to document all these Same Origin Policy gotchas that websites might depend upon for security.
But wrt to this bug in specific, if it never got fixed, I don't think it would be the end of the world. But then again, on this day and age, maybe there's a way to launch nuclear missiles with a XML RPC interface, so maybe it would be the end of the world.
____
{nbsp} +
Both Firefox and Chrome fixed this issue, but it shows why you should implement a CSRF protection instead
of relying on the content-type of your APIs.

View File

@ -0,0 +1,24 @@
:blank: pass:[ +]
== Login CSRF attack
In a login CSRF attack, the attacker forges a login request to an honest site using the attackers username
and password at that site. If the forgery succeeds, the honest server responds with a `Set-Cookie` header
that instructs the browser to mutate its state by storing a session cookie, logging the user into
the honest site as the attacker. This session cookie is used to bind subsequent requests to the users session and hence
to the attackers authentication credentials. Login CSRF attacks can have serious consequences, for example
see the picture below where an attacker created an account at google.com the victim visits the malicious
website and the user is logged in as the attacker. The attacker could then later on gather information about
the activities of the user.
{blank}
image::images/login-csrf.png[caption="Figure: ", title="Login CSRF from Robust Defenses for Cross-Site Request Forgery", width="800", height="500", style="lesson-image" link="http://seclab.stanford.edu/websec/csrf/csrf.pdf"]
{blank}
For more information read the following http://seclab.stanford.edu/websec/csrf/csrf.pdf[paper]
In this assignment try to see if WebGoat is also vulnerable for a login CSRF attack. First create a user
based on your own username prefixed with csrf. So if your username is `tom` you must create
a new user called `csrf-tom`

View File

@ -1,9 +1,9 @@
== Post a review on someone else's behalf
The page below simulates a comment/review page. The difference here is that you have to inititate the submission elsewhere as you might
The page below simulates a comment/review page. The difference here is that you have to initiate the submission elsewhere as you might
with a CSRF attack and like the previous exercise. It's easier than you think. In most cases, the trickier part is
finding somewhere that you want to execute the CSRF attack. The classic example is account/wire transfers in someone's bank account.
But we're keepoing it simple here. In this case, you just need to trigger a review submission on behalf of the currently
But we're keeping it simple here. In this case, you just need to trigger a review submission on behalf of the currently
logged in user.

View File

@ -0,0 +1,58 @@
package org.owasp.webgoat.plugin;
import org.hamcrest.core.StringContains;
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.setup.MockMvcBuilders;
import javax.servlet.http.Cookie;
import static org.hamcrest.core.Is.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author nbaars
* @since 11/17/17.
*/
@RunWith(SpringJUnit4ClassRunner.class)
public class CSRFFeedbackTest extends LessonTest {
@Before
public void setup() throws Exception {
CSRF csrf = new CSRF();
when(webSession.getCurrentLesson()).thenReturn(csrf);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
when(webSession.getUserName()).thenReturn("unit-test");
}
@Test
public void postingJsonMessageThroughWebGoatShouldWork() throws Exception {
mockMvc.perform(post("/csrf/feedback/message")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\": \"Test\", \"email\": \"test1233@dfssdf.de\", \"subject\": \"service\", \"message\":\"dsaffd\"}"))
.andExpect(status().isOk());
}
@Test
public void csrfAttack() throws Exception {
mockMvc.perform(post("/csrf/feedback/message")
.contentType(MediaType.TEXT_PLAIN)
.cookie(new Cookie("JSESSIONID", "test"))
.header("host", "localhost:8080")
.header("referer", "webgoat.org")
.content("{\"name\": \"Test\", \"email\": \"test1233@dfssdf.de\", \"subject\": \"service\", \"message\":\"dsaffd\"}"))
.andExpect(jsonPath("lessonCompleted", is(true)))
.andExpect(jsonPath("feedback", StringContains.containsString("the flag is: ")));
}
}