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.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,
@ -75,7 +73,7 @@ public class BlindSendFileAssignment extends AssignmentEndpoint {
public AttackResult createNewUser(@RequestBody String userInfo) throws Exception {
String error = "Parsing successful contents not send to server";
try {
parseXml(userInfo);
//parseXml(userInfo);
} catch (Exception 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.AttackResult;
import org.springframework.http.MediaType;
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.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.*;
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,
@ -60,13 +53,13 @@ public class ContentTypeAssignment extends AssignmentEndpoint {
attackResult = failed().feedback("xxe.content.type.feedback.json").build();
}
if (MediaType.APPLICATION_XML_VALUE.equals(contentType)) {
user = parseXml(userInfo);
// user = parseXml(userInfo);
attackResult = failed().feedback("xxe.content.type.feedback.xml").build();
}
if (checkSolution(user)) {
attackResult = success().output("xxe.content.output").outputArgs(user.getUsername()).build();
}
// if (checkSolution(user)) {
// attackResult = success().output("xxe.content.output").outputArgs(user.getUsername()).build();
// }
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.AssignmentPath;
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.web.bind.annotation.RequestBody;
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 javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.StringReader;
import java.util.Collection;
import static org.springframework.http.MediaType.ALL_VALUE;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
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
* @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"})
public class SimpleXXE extends AssignmentEndpoint {
private final static String[] DEFAULT_LINUX_DIRECTORIES = {"usr", "opt", "var"};
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
public AttackResult createNewUser(@RequestBody String userInfo) throws Exception {
User user = parseXml(userInfo);
if (checkSolution(user)) {
return trackProgress(success()
.output("xxe.simple.output")
.outputArgs(user.getUsername()).build());
public Collection<Comment> retrieveComments() {
return comments.getComments();
}
@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()
.output("xxe.simple.output")
.outputArgs(webSession.getUserName()).build());
}
return trackProgress(failed().build());
}
public static User parseXml(String xml) throws Exception {
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) {
private boolean checkSolution(Comment comment) {
String[] directoriesToCheck = OS.isFamilyUnix() ? DEFAULT_LINUX_DIRECTORIES : DEFAULT_WINDOWS_DIRECTORIES;
boolean success = true;
for (String directory : directoriesToCheck) {
success &= userInfo.getUsername().contains(directory);
success &= comment.getText().contains(directory);
}
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">
<script th:src="@{/lesson_js/xxe.js}" language="JavaScript"></script>
<div class="lesson-page-wrapper">
<!-- 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,
@ -15,54 +18,53 @@
</div>
<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>
<!-- 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="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 -->
<!-- you can write your own custom forms, but standard form submission will take you to your endpoint and outside of the WebGoat framework -->
<!-- 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"
action="/WebGoat/XXE/simple" contentType="application/xml">
<script th:src="@{/lesson_js/xxe.js}"
language="JavaScript"></script>
<div id="lessonContent">
<strong>Registration form</strong>
<form prepareData="register" accept-charset="UNKNOWN" method="POST" name="form" action="#attack/307/100">
<table>
<tr>
<td>Username</td>
<td><input name="username" value="" type="TEXT"/></td>
</tr>
<tr>
<td>E-mail</td>
<td><input name="email" value="" type="TEXT"/></td>
</tr>
<tr>
<td>Password</td>
<td><input name="email" value="" type="TEXT"/></td>
</tr>
<tr>
<td></td>
<td align="right"><input type="submit" id="registerButton" value="Sign up"/></td>
</tr>
</table>
<br/>
<br/>
</form>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
<div class="container-fluid">
<div class="panel post">
<div class="post-heading">
<div class="pull-left image">
<img th:src="@{/images/avatar1.png}"
class="img-circle avatar" alt="user profile image"/>
</div>
<div class="pull-left meta">
<div class="title h5">
<a href="#"><b>John Doe</b></a>
uploaded a photo.
</div>
<h6 class="text-muted time">24 days ago</h6>
</div>
</div>
<div class="post-image">
<img th:src="@{images/cat.jpg}" class="image" alt="image post"/>
</div>
<div class="post-description">
</div>
<div class="post-footer">
<div class="input-group">
<input class="form-control" id="commentInput" placeholder="Add a comment" type="text"/>
<span class="input-group-addon">
<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>
</form>
<div id='registration_success'></div>
</div>
<br/>
<div class="attack-feedback"></div>
<div class="attack-output"></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 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 -->
<form class="attack-form" accept-charset="UNKNOWN" prepareData="registerJson" method="POST" name="form"
action="/WebGoat/XXE/content-type" contentType="application/json">
<script th:src="@{/lesson_js/xxe.js}"
language="JavaScript"></script>
<div id="lessonContent">
<strong>Registration form</strong>
<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 -->
<form class="attack-form" accept-charset="UNKNOWN" prepareData="register" method="POST" name="form"
action="/WebGoat/XXE/blind" contentType="application/xml">
<script th:src="@{/lesson_js/xxe.js}"
language="JavaScript"></script>
<div id="lessonContent">
<strong>Registration form</strong>
<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 () {
var xml = '<?xml version="1.0"?>' +
'<user>' +
' <username>' + 'test' + '</username>' +
' <password>' + 'test' + '</password>' +
'</user>';
return xml;
}
webgoat.customjs.registerJson = function () {
var json;
json = '{' +
' "user":' + '"test"' +
' "password":' + '"test"' +
'}';
return json;
}
$(document).ready(function () {
$("#postComment").unbind();
$("#postComment").on("click", function () {
var commentInput = $("#commentInput").val();
$.ajax({
type: 'POST',
url: 'xxe/simple',
data: JSON.stringify({text: commentInput}),
contentType: "application/json",
dataType: 'json'
}).then(
function () {
getComments();
$("#commentInput").val('');
}
)
})
getComments();
})
var html = '<li class="comment">' +
'<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
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
username field. Try listing the root directory of the filesystem.
In this assignment you will add a comment to the photo, when submitting the form try to execute an XXE
injection with the comments field. Try listing the root directory of the filesystem.