diff --git a/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/BlindSendFileAssignment.java b/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/BlindSendFileAssignment.java index 69b0e8e1c..1896cad1f 100644 --- a/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/BlindSendFileAssignment.java +++ b/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/BlindSendFileAssignment.java @@ -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); } diff --git a/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/Comment.java b/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/Comment.java new file mode 100644 index 000000000..bce74cc40 --- /dev/null +++ b/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/Comment.java @@ -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; +} diff --git a/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/Comments.java b/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/Comments.java new file mode 100644 index 000000000..22ba7cf72 --- /dev/null +++ b/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/Comments.java @@ -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); + } + } +} diff --git a/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/ContentTypeAssignment.java b/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/ContentTypeAssignment.java index 0b2fa3611..be850944e 100644 --- a/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/ContentTypeAssignment.java +++ b/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/ContentTypeAssignment.java @@ -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; } diff --git a/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/SimpleXXE.java b/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/SimpleXXE.java index eff49b9d3..d5c6b6a82 100644 --- a/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/SimpleXXE.java +++ b/webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/plugin/SimpleXXE.java @@ -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; } - - } diff --git a/webgoat-lessons/xxe/src/main/resources/css/xxe.css b/webgoat-lessons/xxe/src/main/resources/css/xxe.css new file mode 100644 index 000000000..3bc2ca4eb --- /dev/null +++ b/webgoat-lessons/xxe/src/main/resources/css/xxe.css @@ -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; +} \ No newline at end of file diff --git a/webgoat-lessons/xxe/src/main/resources/html/XXE.html b/webgoat-lessons/xxe/src/main/resources/html/XXE.html index 831d55cdc..7d79b58d3 100644 --- a/webgoat-lessons/xxe/src/main/resources/html/XXE.html +++ b/webgoat-lessons/xxe/src/main/resources/html/XXE.html @@ -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"> diff --git a/webgoat-lessons/xxe/src/main/resources/images/cat.jpg b/webgoat-lessons/xxe/src/main/resources/images/cat.jpg new file mode 100644 index 000000000..e0e1fb983 Binary files /dev/null and b/webgoat-lessons/xxe/src/main/resources/images/cat.jpg differ diff --git a/webgoat-lessons/xxe/src/main/resources/js/xxe.js b/webgoat-lessons/xxe/src/main/resources/js/xxe.js index 3cf292d83..9002de2e4 100644 --- a/webgoat-lessons/xxe/src/main/resources/js/xxe.js +++ b/webgoat-lessons/xxe/src/main/resources/js/xxe.js @@ -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); + } + + }); +} \ No newline at end of file diff --git a/webgoat-lessons/xxe/src/main/resources/lessonPlans/en/XXE_simple.adoc b/webgoat-lessons/xxe/src/main/resources/lessonPlans/en/XXE_simple.adoc index dd5506af9..ef51f2d19 100644 --- a/webgoat-lessons/xxe/src/main/resources/lessonPlans/en/XXE_simple.adoc +++ b/webgoat-lessons/xxe/src/main/resources/lessonPlans/en/XXE_simple.adoc @@ -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.