Integrated XXE assigment from CTF to XXE lesson

This commit is contained in:
Nanne Baars 2017-05-04 02:25:56 +02:00
parent d25f71532b
commit 4a061f61a6
10 changed files with 333 additions and 112 deletions

View File

@ -22,8 +22,6 @@ import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.List; import java.util.List;
import static org.owasp.webgoat.plugin.SimpleXXE.parseXml;
/** /**
* ************************************************************************************************ * ************************************************************************************************
* This file is part of WebGoat, an Open Web Application Security Project utility. For details, * This file is part of WebGoat, an Open Web Application Security Project utility. For details,
@ -75,7 +73,7 @@ public class BlindSendFileAssignment extends AssignmentEndpoint {
public AttackResult createNewUser(@RequestBody String userInfo) throws Exception { public AttackResult createNewUser(@RequestBody String userInfo) throws Exception {
String error = "Parsing successful contents not send to server"; String error = "Parsing successful contents not send to server";
try { try {
parseXml(userInfo); //parseXml(userInfo);
} catch (Exception e) { } catch (Exception e) {
error = ExceptionUtils.getFullStackTrace(e); error = ExceptionUtils.getFullStackTrace(e);
} }

View File

@ -0,0 +1,23 @@
package org.owasp.webgoat.plugin;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.xml.bind.annotation.XmlRootElement;
/**
* @author nbaars
* @since 4/8/17.
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@XmlRootElement
public class Comment {
private String user;
private String dateTime;
private String text;
}

View File

@ -0,0 +1,90 @@
package org.owasp.webgoat.plugin;
import com.beust.jcommander.internal.Lists;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.EvictingQueue;
import com.google.common.collect.Maps;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.owasp.webgoat.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.Collection;
import java.util.Map;
/**
* @author nbaars
* @since 5/3/17.
*/
@Component
@Scope("singleton")
public class Comments {
@Autowired
protected WebSession webSession;
protected static DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd, HH:mm:ss");
private static final Map<String, EvictingQueue<Comment>> userComments = Maps.newHashMap();
private static final EvictingQueue<Comment> comments = EvictingQueue.create(100);
static {
comments.add(new Comment("webgoat", DateTime.now().toString(fmt), "Silly cat...."));
comments.add(new Comment("guest", DateTime.now().toString(fmt), "I think I will use this picture in one of my projects."));
comments.add(new Comment("guest", DateTime.now().toString(fmt), "Lol!! :-)."));
}
protected Collection<Comment> getComments() {
Collection<Comment> allComments = Lists.newArrayList();
Collection<Comment> xmlComments = userComments.get(webSession.getUserName());
if (xmlComments != null) {
allComments.addAll(xmlComments);
}
allComments.addAll(comments);
return allComments;
}
protected Comment parseXml(String xml) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Comment.class);
XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
xif.setProperty(XMLInputFactory.IS_VALIDATING, false);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, true);
XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(xml));
Unmarshaller unmarshaller = jc.createUnmarshaller();
return (Comment) unmarshaller.unmarshal(xsr);
}
protected Comment parseJson(String comment) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue(comment, Comment.class);
} catch (IOException e) {
return new Comment();
}
}
public void addComment(Comment comment, boolean visibleForAllUsers) {
comment.setDateTime(DateTime.now().toString(fmt));
comment.setUser(webSession.getUserName());
if (visibleForAllUsers) {
comments.add(comment);
} else {
EvictingQueue<Comment> comments = userComments.getOrDefault(webSession.getUserName(), EvictingQueue.create(100));
comments.add(comment);
userComments.put(webSession.getUserName(), comments);
}
}
}

View File

@ -6,17 +6,10 @@ 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.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.io.IOException; import java.io.IOException;
import static org.owasp.webgoat.plugin.SimpleXXE.checkSolution;
import static org.owasp.webgoat.plugin.SimpleXXE.parseXml;
/** /**
* ************************************************************************************************ * ************************************************************************************************
* This file is part of WebGoat, an Open Web Application Security Project utility. For details, * This file is part of WebGoat, an Open Web Application Security Project utility. For details,
@ -60,13 +53,13 @@ public class ContentTypeAssignment extends AssignmentEndpoint {
attackResult = failed().feedback("xxe.content.type.feedback.json").build(); attackResult = failed().feedback("xxe.content.type.feedback.json").build();
} }
if (MediaType.APPLICATION_XML_VALUE.equals(contentType)) { if (MediaType.APPLICATION_XML_VALUE.equals(contentType)) {
user = parseXml(userInfo); // user = parseXml(userInfo);
attackResult = failed().feedback("xxe.content.type.feedback.xml").build(); attackResult = failed().feedback("xxe.content.type.feedback.xml").build();
} }
if (checkSolution(user)) { // if (checkSolution(user)) {
attackResult = success().output("xxe.content.output").outputArgs(user.getUsername()).build(); // attackResult = success().output("xxe.content.output").outputArgs(user.getUsername()).build();
} // }
return attackResult; return attackResult;
} }

View File

@ -5,17 +5,21 @@ import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints; 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.owasp.webgoat.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import javax.xml.bind.JAXBContext; import java.util.Collection;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory; import static org.springframework.http.MediaType.ALL_VALUE;
import javax.xml.stream.XMLStreamReader; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import java.io.StringReader; import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
/** /**
* ************************************************************************************************ * ************************************************************************************************
@ -46,47 +50,58 @@ import java.io.StringReader;
* @version $Id: $Id * @version $Id: $Id
* @since November 17, 2016 * @since November 17, 2016
*/ */
@AssignmentPath("XXE/simple")
/**
* @author nbaars
* @since 4/8/17.
*/
@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"})
public class SimpleXXE extends AssignmentEndpoint { public class SimpleXXE extends AssignmentEndpoint {
private final static String[] DEFAULT_LINUX_DIRECTORIES = {"usr", "opt", "var"}; private final static String[] DEFAULT_LINUX_DIRECTORIES = {"usr", "opt", "var"};
private final static String[] DEFAULT_WINDOWS_DIRECTORIES = {"Windows", "Program Files (x86)", "Program Files"}; private final static String[] DEFAULT_WINDOWS_DIRECTORIES = {"Windows", "Program Files (x86)", "Program Files"};
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.ALL_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @Value("${webgoat.server.directory}")
private String webGoatHomeDirectory;
@Autowired
private WebSession webSession;
@Autowired
private Comments comments;
@RequestMapping(method = GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody @ResponseBody
public AttackResult createNewUser(@RequestBody String userInfo) throws Exception { public Collection<Comment> retrieveComments() {
User user = parseXml(userInfo); return comments.getComments();
if (checkSolution(user)) { }
@RequestMapping(method = POST, consumes = ALL_VALUE, produces = APPLICATION_JSON_VALUE)
@ResponseBody
public AttackResult createNewComment(@RequestBody String commentStr, @RequestHeader("Content-Type") String contentType) throws Exception {
Comment comment = null;
if (APPLICATION_JSON_VALUE.equals(contentType)) {
comment = comments.parseJson(commentStr);
comments.addComment(comment, true);
}
if (MediaType.APPLICATION_XML_VALUE.equals(contentType)) {
//Do not show these comments to all users
comment = comments.parseXml(commentStr);
comments.addComment(comment, false);
}
if (checkSolution(comment)) {
return trackProgress(success() return trackProgress(success()
.output("xxe.simple.output") .output("xxe.simple.output")
.outputArgs(user.getUsername()).build()); .outputArgs(webSession.getUserName()).build());
} }
return trackProgress(failed().build()); return trackProgress(failed().build());
} }
public static User parseXml(String xml) throws Exception { private boolean checkSolution(Comment comment) {
JAXBContext jc = JAXBContext.newInstance(User.class);
XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
xif.setProperty(XMLInputFactory.IS_VALIDATING, false);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, true);
XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(xml));
Unmarshaller unmarshaller = jc.createUnmarshaller();
return (User) unmarshaller.unmarshal(xsr);
}
public static boolean checkSolution(User userInfo) {
String[] directoriesToCheck = OS.isFamilyUnix() ? DEFAULT_LINUX_DIRECTORIES : DEFAULT_WINDOWS_DIRECTORIES; String[] directoriesToCheck = OS.isFamilyUnix() ? DEFAULT_LINUX_DIRECTORIES : DEFAULT_WINDOWS_DIRECTORIES;
boolean success = true; boolean success = true;
for (String directory : directoriesToCheck) { for (String directory : directoriesToCheck) {
success &= userInfo.getUsername().contains(directory); success &= comment.getText().contains(directory);
} }
return success; return success;
} }
} }

View File

@ -0,0 +1,75 @@
/* Component: Posts */
.post .post-heading {
height: 95px;
padding: 20px 15px;
}
.post .post-heading .avatar {
width: 60px;
height: 60px;
display: block;
margin-right: 15px;
}
.post .post-heading .meta .title {
margin-bottom: 0;
}
.post .post-heading .meta .title a {
color: black;
}
.post .post-heading .meta .title a:hover {
color: #aaaaaa;
}
.post .post-heading .meta .time {
margin-top: 8px;
color: #999;
}
.post .post-image .image {
width:20%;
height: 40%;
}
.post .post-description {
padding: 5px;
}
.post .post-footer {
border-top: 1px solid #ddd;
padding: 15px;
}
.post .post-footer .input-group-addon a {
color: #454545;
}
.post .post-footer .comments-list {
padding: 0;
margin-top: 20px;
list-style-type: none;
}
.post .post-footer .comments-list .comment {
display: block;
width: 100%;
margin: 20px 0;
}
.post .post-footer .comments-list .comment .avatar {
width: 35px;
height: 35px;
}
.post .post-footer .comments-list .comment .comment-heading {
display: block;
width: 100%;
}
.post .post-footer .comments-list .comment .comment-heading .user {
font-size: 14px;
font-weight: bold;
display: inline;
margin-top: 0;
margin-right: 10px;
}
.post .post-footer .comments-list .comment .comment-heading .time {
font-size: 12px;
color: #aaa;
margin-top: 0;
display: inline;
}
.post .post-footer .comments-list .comment .comment-body {
margin-left: 50px;
}
.post .post-footer .comments-list .comment > .comments-list {
margin-left: 50px;
}

View File

@ -1,5 +1,8 @@
<html xmlns:th="http://www.thymeleaf.org"> <html xmlns:th="http://www.thymeleaf.org">
<script th:src="@{/lesson_js/xxe.js}" language="JavaScript"></script>
<div class="lesson-page-wrapper"> <div class="lesson-page-wrapper">
<!-- reuse this lesson-page-wrapper block for each 'page' of content in your lesson --> <!-- reuse this lesson-page-wrapper block for each 'page' of content in your lesson -->
<!-- include content here, or can be placed in another location. Content will be presented via asciidocs files, <!-- include content here, or can be placed in another location. Content will be presented via asciidocs files,
@ -15,54 +18,53 @@
</div> </div>
<div class="lesson-page-wrapper"> <div class="lesson-page-wrapper">
<!-- reuse this block for each 'page' of content -->
<!-- sample ascii doc content for second page -->
<div class="adoc-content" th:replace="doc:XXE_simple.adoc"></div> <div class="adoc-content" th:replace="doc:XXE_simple.adoc"></div>
<!-- if including attack, reuse this section, leave classes in place --> <link rel="stylesheet" type="text/css" th:href="@{/lesson_css/xxe.css}"/>
<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>
<!-- using attack-form class on your form, will allow your request to be ajaxified and stay within the display framework for webgoat -->
<!-- using attack-form class on your form will allow your request to be ajaxified and stay within the display framework for webgoat --> <div class="container-fluid">
<!-- you can write your own custom forms, but standard form submission will take you to your endpoint and outside of the WebGoat framework --> <div class="panel post">
<!-- of course, you can write your own ajax submission /handling in your own javascript if you like --> <div class="post-heading">
<form class="attack-form" accept-charset="UNKNOWN" prepareData="register" method="POST" name="form" <div class="pull-left image">
action="/WebGoat/XXE/simple" contentType="application/xml"> <img th:src="@{/images/avatar1.png}"
<script th:src="@{/lesson_js/xxe.js}" class="img-circle avatar" alt="user profile image"/>
language="JavaScript"></script> </div>
<div id="lessonContent"> <div class="pull-left meta">
<strong>Registration form</strong> <div class="title h5">
<form prepareData="register" accept-charset="UNKNOWN" method="POST" name="form" action="#attack/307/100"> <a href="#"><b>John Doe</b></a>
<table> uploaded a photo.
<tr> </div>
<td>Username</td> <h6 class="text-muted time">24 days ago</h6>
<td><input name="username" value="" type="TEXT"/></td> </div>
</tr> </div>
<tr>
<td>E-mail</td> <div class="post-image">
<td><input name="email" value="" type="TEXT"/></td> <img th:src="@{images/cat.jpg}" class="image" alt="image post"/>
</tr> </div>
<tr>
<td>Password</td> <div class="post-description">
<td><input name="email" value="" type="TEXT"/></td>
</tr> </div>
<tr> <div class="post-footer">
<td></td> <div class="input-group">
<td align="right"><input type="submit" id="registerButton" value="Sign up"/></td> <input class="form-control" id="commentInput" placeholder="Add a comment" type="text"/>
</tr> <span class="input-group-addon">
</table> <i id="postComment" class="fa fa-edit" style="font-size: 20px"></i>
</span>
</div>
<ul class="comments-list">
<div id="comments_list">
</div>
</ul>
</div>
</div>
</div>
<br/> <br/>
<br/>
</form>
<div class="attack-feedback"></div> <div class="attack-feedback"></div>
<div class="attack-output"></div> <div class="attack-output"></div>
</div> </div>
</form>
<div id='registration_success'></div>
</div>
<!-- do not remove the two following div's, this is where your feedback/output will land -->
<div class="attack-feedback"></div>
<div class="attack-output"></div>
<!-- ... of course, you can move them if you want to, but that will not look consistent to other lessons -->
</div> </div>
<div class="lesson-page-wrapper"> <div class="lesson-page-wrapper">
@ -77,8 +79,6 @@
<!-- of course, you can write your own ajax submission /handling in your own javascript if you like --> <!-- of course, you can write your own ajax submission /handling in your own javascript if you like -->
<form class="attack-form" accept-charset="UNKNOWN" prepareData="registerJson" method="POST" name="form" <form class="attack-form" accept-charset="UNKNOWN" prepareData="registerJson" method="POST" name="form"
action="/WebGoat/XXE/content-type" contentType="application/json"> action="/WebGoat/XXE/content-type" contentType="application/json">
<script th:src="@{/lesson_js/xxe.js}"
language="JavaScript"></script>
<div id="lessonContent"> <div id="lessonContent">
<strong>Registration form</strong> <strong>Registration form</strong>
<form prepareData="registerJson" accept-charset="UNKNOWN" method="POST" name="form" action="#attack/307/100"> <form prepareData="registerJson" accept-charset="UNKNOWN" method="POST" name="form" action="#attack/307/100">
@ -137,8 +137,6 @@
<!-- of course, you can write your own ajax submission /handling in your own javascript if you like --> <!-- of course, you can write your own ajax submission /handling in your own javascript if you like -->
<form class="attack-form" accept-charset="UNKNOWN" prepareData="register" method="POST" name="form" <form class="attack-form" accept-charset="UNKNOWN" prepareData="register" method="POST" name="form"
action="/WebGoat/XXE/blind" contentType="application/xml"> action="/WebGoat/XXE/blind" contentType="application/xml">
<script th:src="@{/lesson_js/xxe.js}"
language="JavaScript"></script>
<div id="lessonContent"> <div id="lessonContent">
<strong>Registration form</strong> <strong>Registration form</strong>
<form prepareData="register" accept-charset="UNKNOWN" method="POST" name="form" action="#attack/307/100"> <form prepareData="register" accept-charset="UNKNOWN" method="POST" name="form" action="#attack/307/100">

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -1,16 +1,45 @@
webgoat.customjs.register = function () { $(document).ready(function () {
var xml = '<?xml version="1.0"?>' + $("#postComment").unbind();
'<user>' + $("#postComment").on("click", function () {
' <username>' + 'test' + '</username>' + var commentInput = $("#commentInput").val();
' <password>' + 'test' + '</password>' + $.ajax({
'</user>'; type: 'POST',
return xml; url: 'xxe/simple',
data: JSON.stringify({text: commentInput}),
contentType: "application/json",
dataType: 'json'
}).then(
function () {
getComments();
$("#commentInput").val('');
} }
webgoat.customjs.registerJson = function () { )
var json; })
json = '{' + getComments();
' "user":' + '"test"' + })
' "password":' + '"test"' +
'}'; var html = '<li class="comment">' +
return json; '<div class="pull-left">' +
'<img class="avatar" src="images/avatar1.png" alt="avatar"/>' +
'</div>' +
'<div class="comment-body">' +
'<div class="comment-heading">' +
'<h4 class="user">USER</h4>' +
'<h5 class="time">DATETIME</h5>' +
'</div>' +
'<p>COMMENT</p>' +
'</div>' +
'</li>';
function getComments() {
$.get("xxe/simple", function (result, status) {
$("#comments_list").empty();
for (var i = 0; i < result.length; i++) {
var comment = html.replace('USER', result[i].user);
comment = comment.replace('DATETIME', result[i].dateTime);
comment = comment.replace('COMMENT', result[i].text);
$("#comments_list").append(comment);
}
});
} }

View File

@ -1,4 +1,4 @@
== Let's try == Let's try
In this assignment you will need to sign up with a registration form. When submitting the form try to execute an XXE injection with the In this assignment you will add a comment to the photo, when submitting the form try to execute an XXE
username field. Try listing the root directory of the filesystem. injection with the comments field. Try listing the root directory of the filesystem.