From feead6b740bdacb7ed632bfe8a320ae256e72160 Mon Sep 17 00:00:00 2001 From: Jason White Date: Thu, 18 May 2017 14:41:14 -0400 Subject: [PATCH] initial cut on XSS, need to add some tests still --- .../static/js/goatApp/view/GoatRouter.js | 13 +- .../org/owasp/webgoat/plugin/Comment.java | 24 + .../plugin/CrossSiteScriptingLesson5a.java | 73 +- .../webgoat/plugin/DOMCrossSiteScripting.java | 25 +- ...ava => DOMCrossSiteScriptingVerifier.java} | 17 +- .../StoredCrossSiteScriptingVerifier.java | 33 + .../webgoat/plugin/StoredXssComments.java | 94 +++ .../src/main/resources/css/stored-xss.css | 75 +++ .../resources/html/CrossSiteScripting.html | 629 ++++++++---------- .../resources/i18n/WebGoatLabels.properties | 6 +- .../src/main/resources/images/avatar1.png | Bin 0 -> 28394 bytes .../src/main/resources/js/stored-xss.js | 45 ++ ... => CrossSiteScripting_content10-off.adoc} | 0 ... => CrossSiteScripting_content11-off.adoc} | 0 ... => CrossSiteScripting_content12-off.adoc} | 0 ... => CrossSiteScripting_content13-off.adoc} | 0 ...=> CrossSiteScripting_content13a-off.adoc} | 0 ... => CrossSiteScripting_content14-off.adoc} | 0 ... => CrossSiteScripting_content15-off.adoc} | 0 ...=> CrossSiteScripting_content15a-off.adoc} | 0 ... => CrossSiteScripting_content16-off.adoc} | 0 .../en/CrossSiteScripting_content5b.adoc | 9 +- .../en/CrossSiteScripting_content6.adoc | 8 +- .../en/CrossSiteScripting_content6a.adoc | 9 +- .../en/CrossSiteScripting_content6b.adoc | 8 +- .../en/CrossSiteScripting_content7-off.adoc | 18 + .../en/CrossSiteScripting_content7.adoc | 27 +- .../en/CrossSiteScripting_content7b.adoc | 6 + .../en/CrossSiteScripting_content8-off.adoc | 20 + .../en/CrossSiteScripting_content8.adoc | 43 +- .../en/CrossSiteScripting_content9-off.adoc | 8 + .../en/CrossSiteScripting_content9.adoc | 52 +- ... => CrossSiteScripting_content9a-off.adoc} | 0 33 files changed, 779 insertions(+), 463 deletions(-) create mode 100644 webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/Comment.java rename webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/{DOMCrossSiteScriptingFollowUp.java => DOMCrossSiteScriptingVerifier.java} (79%) create mode 100644 webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/StoredCrossSiteScriptingVerifier.java create mode 100644 webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/StoredXssComments.java create mode 100644 webgoat-lessons/cross-site-scripting/src/main/resources/css/stored-xss.css create mode 100644 webgoat-lessons/cross-site-scripting/src/main/resources/images/avatar1.png create mode 100644 webgoat-lessons/cross-site-scripting/src/main/resources/js/stored-xss.js rename webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/{CrossSiteScripting_content10.adoc => CrossSiteScripting_content10-off.adoc} (100%) rename webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/{CrossSiteScripting_content11.adoc => CrossSiteScripting_content11-off.adoc} (100%) rename webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/{CrossSiteScripting_content12.adoc => CrossSiteScripting_content12-off.adoc} (100%) rename webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/{CrossSiteScripting_content13.adoc => CrossSiteScripting_content13-off.adoc} (100%) rename webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/{CrossSiteScripting_content13a.adoc => CrossSiteScripting_content13a-off.adoc} (100%) rename webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/{CrossSiteScripting_content14.adoc => CrossSiteScripting_content14-off.adoc} (100%) rename webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/{CrossSiteScripting_content15.adoc => CrossSiteScripting_content15-off.adoc} (100%) rename webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/{CrossSiteScripting_content15a.adoc => CrossSiteScripting_content15a-off.adoc} (100%) rename webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/{CrossSiteScripting_content16.adoc => CrossSiteScripting_content16-off.adoc} (100%) create mode 100644 webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content7-off.adoc create mode 100644 webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content7b.adoc create mode 100644 webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content8-off.adoc create mode 100644 webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content9-off.adoc rename webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/{CrossSiteScripting_content9a.adoc => CrossSiteScripting_content9a-off.adoc} (100%) diff --git a/webgoat-container/src/main/resources/static/js/goatApp/view/GoatRouter.js b/webgoat-container/src/main/resources/static/js/goatApp/view/GoatRouter.js index ec1667826..54aa52828 100644 --- a/webgoat-container/src/main/resources/static/js/goatApp/view/GoatRouter.js +++ b/webgoat-container/src/main/resources/static/js/goatApp/view/GoatRouter.js @@ -54,21 +54,24 @@ define(['jquery', webgoat.customjs.jquery = $; //passing jquery into custom js scope ... still klunky, but works for now webgoat.customjs.jqueryVuln = $vuln; - // temporary shim to support dom-xss assignment + // shim to support xss lesson webgoat.customjs.phoneHome = function (e) { console.log('phoneHome invoked'); - console.log(arguments.callee); - // webgoat.customjs.jquery.ajax({ method: "POST", - url: "/WebGoat/CrossSiteScripting/dom-xss", + url: "/WebGoat/CrossSiteScripting/phone-home-xss", data: {param1: 42, param2: 24}, headers: { "webgoat-requested-by": "dom-xss-vuln" }, - contentType: 'application/x-www-form-urlencoded; charset=UTF-8' + contentType: 'application/x-www-form-urlencoded; charset=UTF-8', + success: function (data) { + //devs leave stuff like this in all the time + console.log('phone home said ' + data); + } }); } + }, initialize: function () { diff --git a/webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/Comment.java b/webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/Comment.java new file mode 100644 index 000000000..9ebb4ecc3 --- /dev/null +++ b/webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/Comment.java @@ -0,0 +1,24 @@ +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/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/CrossSiteScriptingLesson5a.java b/webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/CrossSiteScriptingLesson5a.java index 6b87d7475..27bc2b4d5 100644 --- a/webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/CrossSiteScriptingLesson5a.java +++ b/webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/CrossSiteScriptingLesson5a.java @@ -17,40 +17,40 @@ import java.io.IOException; /*************************************************************************************************** - * - * + * + * * This file is part of WebGoat, an Open Web Application Security Project utility. For details, * please see http://www.owasp.org/ - * + * * Copyright (c) 2002 - 20014 Bruce Mayhew - * + * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * + * * You should have received a copy of the GNU General Public License along with this program; if * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. - * + * * Getting Source ============== - * + * * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software * projects. - * + * * For details, please see http://webgoat.github.io - * + * * @author Bruce Mayhew WebGoat * @created October 28, 2003 */ @AssignmentPath("/CrossSiteScripting/attack5a") public class CrossSiteScriptingLesson5a extends AssignmentEndpoint { - @Autowired - UserSessionData userSessionData; + @Autowired + UserSessionData userSessionData; @RequestMapping(method = RequestMethod.GET) public @ResponseBody AttackResult completed(@RequestParam Integer QTY1, @@ -58,35 +58,34 @@ public class CrossSiteScriptingLesson5a extends AssignmentEndpoint { @RequestParam Integer QTY4, @RequestParam String field1, @RequestParam Integer field2, HttpServletRequest request) throws IOException { - // System.out.println("foo"); - // Should add some QTY validation here. Someone could have fun and enter a negative quantity and get merchanidise and a refund :) - double totalSale = QTY1.intValue() * 69.99 + QTY2.intValue() * 27.99 + QTY3.intValue() * 1599.99 + QTY4.intValue() * 299.99; - userSessionData.setValue("xss-reflected1-complete",(Object)"false"); - StringBuffer cart = new StringBuffer(); - cart.append("Thank you for shopping at WebGoat.
You're support is appreciated
"); - cart.append("

We have chaged credit card:" + field1 + "
"); - cart.append( " -------------------
"); - cart.append( " $" + totalSale); + double totalSale = QTY1.intValue() * 69.99 + QTY2.intValue() * 27.99 + QTY3.intValue() * 1599.99 + QTY4.intValue() * 299.99; - //init state - if (userSessionData.getValue("xss-reflected1-complete") == null) { - userSessionData.setValue("xss-reflected1-complete",(Object)"false"); - } + userSessionData.setValue("xss-reflected1-complete",(Object)"false"); + StringBuffer cart = new StringBuffer(); + cart.append("Thank you for shopping at WebGoat.
You're support is appreciated


"); + cart.append("

We have chaged credit card:" + field1 + "
"); + cart.append( " -------------------
"); + cart.append( " $" + totalSale); - if (field1.toLowerCase().contains("")) { + //init state + if (userSessionData.getValue("xss-reflected1-complete") == null) { + userSessionData.setValue("xss-reflected1-complete",(Object)"false"); + } + + if (field1.toLowerCase().contains("")) { //return trackProgress() - userSessionData.setValue("xss-reflected-5a-complete","true"); - return trackProgress(success() - .feedback("xss-reflected-5a-success") - .output(cart.toString()) - .build()); + userSessionData.setValue("xss-reflected-5a-complete","true"); + return trackProgress(success() + .feedback("xss-reflected-5a-success") + .output(cart.toString()) + .build()); } else { - userSessionData.setValue("xss-reflected1-complete","false"); - return trackProgress(success() - .feedback("xss-reflected-5a-failure") - .output(cart.toString()) - .build()); - } + userSessionData.setValue("xss-reflected1-complete","false"); + return trackProgress(success() + .feedback("xss-reflected-5a-failure") + .output(cart.toString()) + .build()); + } } } \ No newline at end of file diff --git a/webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/DOMCrossSiteScripting.java b/webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/DOMCrossSiteScripting.java index 7bd3456ff..4ccc2f742 100644 --- a/webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/DOMCrossSiteScripting.java +++ b/webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/plugin/DOMCrossSiteScripting.java @@ -3,6 +3,7 @@ package org.owasp.webgoat.plugin; import org.owasp.webgoat.assignments.AssignmentEndpoint; import org.owasp.webgoat.assignments.AssignmentPath; import org.owasp.webgoat.assignments.AttackResult; +import org.owasp.webgoat.session.UserSessionData; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -10,27 +11,27 @@ import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import java.io.IOException; +import java.security.SecureRandom; -/** - * Created by jason on 11/23/16. - */ -@AssignmentPath("/CrossSiteScripting/dom-xss") + +@AssignmentPath("/CrossSiteScripting/phone-home-xss") public class DOMCrossSiteScripting extends AssignmentEndpoint { @RequestMapping(method = RequestMethod.POST) public @ResponseBody AttackResult completed(@RequestParam Integer param1, @RequestParam Integer param2, HttpServletRequest request) throws IOException { + + UserSessionData userSessionData = getUserSessionData(); + SecureRandom number = new SecureRandom(); + userSessionData.setValue("randValue",number.nextInt()); + if (param1 == 42 && param2 == 24 && request.getHeader("webgoat-requested-by").equals("dom-xss-vuln")) { - System.out.println("DOM-XSS successful, param1 is 42"); - return trackProgress(success().build()); + System.out.println(userSessionData.getValue("randValue") + " << randValue"); + return trackProgress(success().output("phoneHome Response is " + userSessionData.getValue("randValue").toString()).build()); } else { return trackProgress(failed().build()); } } } -// something like ... http://localhost:8080/WebGoat/start.mvc#test/testParam=foobar&_someVar=234902384lotslsfjdOf9889080GarbageHere%3Cscript%3Ewebgoat.customjs.phoneHome();%3C%2Fscript%3E -// or http://localhost:8080/WebGoat/start.mvc#test/testParam=foobar&_someVar=234902384lotslsfjdOf9889080GarbageHere"; + + static { + comments.add(new Comment("webgoat", DateTime.now().toString(fmt), "This comment is safe")); + comments.add(new Comment("guest", DateTime.now().toString(fmt), "This one is safe too.")); + comments.add(new Comment("guest", DateTime.now().toString(fmt), "Can you post a comment, calling webgoat.customjs.phoneHome() ?")); + } + + @RequestMapping(method = GET, produces = MediaType.APPLICATION_JSON_VALUE,consumes = ALL_VALUE) + @ResponseBody + public Collection retrieveComments() { + Collection allComments = Lists.newArrayList(); + Collection xmlComments = userComments.get(webSession.getUserName()); + if (xmlComments != null) { + allComments.addAll(xmlComments); + } + allComments.addAll(comments); + return allComments; + } + + @RequestMapping(method = RequestMethod.POST) + @ResponseBody + public AttackResult createNewComment (@RequestBody String commentStr) throws IOException { + + Comment comment = parseJson(commentStr); + + EvictingQueue comments = userComments.getOrDefault(webSession.getUserName(), EvictingQueue.create(100)); + comments.add(comment); + comment.setDateTime(DateTime.now().toString(fmt)); + comment.setUser(webSession.getUserName()); + + userComments.put(webSession.getUserName(), comments); + + if (comment.getText().contains(phoneHomeString)) { + return (success().feedback("xss-stored-comment-success").build()); + } else { + return (failed().feedback("xss-stored-comment-failure").build()); + } + } + + private Comment parseJson(String comment) { + ObjectMapper mapper = new ObjectMapper(); + try { + return mapper.readValue(comment, Comment.class); + } catch (IOException e) { + return new Comment(); + } + } +} + + + + + diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/css/stored-xss.css b/webgoat-lessons/cross-site-scripting/src/main/resources/css/stored-xss.css new file mode 100644 index 000000000..3bc2ca4eb --- /dev/null +++ b/webgoat-lessons/cross-site-scripting/src/main/resources/css/stored-xss.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/cross-site-scripting/src/main/resources/html/CrossSiteScripting.html b/webgoat-lessons/cross-site-scripting/src/main/resources/html/CrossSiteScripting.html index 4371a5def..c1c614b52 100644 --- a/webgoat-lessons/cross-site-scripting/src/main/resources/html/CrossSiteScripting.html +++ b/webgoat-lessons/cross-site-scripting/src/main/resources/html/CrossSiteScripting.html @@ -2,368 +2,307 @@ -

- - -
-
+
+ + +
+
-
- - -
-
- -
- - - -
- - - - - - - -
Were the cookies the same on each tab?
-
-
- -
-
- +
+ + +
+
+ +
+ + + +
+ + + + + + + +
Were the cookies the same on each tab?
+
-
-
- - -
+ +
+
+
-
- - -
-
-
- - -
-
-
- - -
- -
-
- - -
-
- -
- - - -
-
-
-

Shopping Cart

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Shopping Cart Items -- To Buy NowPriceQuantityTotal
Studio RTA - Laptop/Reading Cart with Tilting Surface - - Cherry69.99$0.00
Dynex - Traditional Notebook Case27.99$0.00
Hewlett-Packard - Pavilion Notebook with Intel Centrino1599.99$0.00
3 - Year Performance Service Plan $1000 and Over299.99$0.00
-
- - - - - - - - - - - - - - - - - - - - - - -
The total charged to your credit card:$0.00
 
Enter your credit card number:
Enter your three digit access code:
-
-
-
-
- -
-
- -
- -
- -
-
-
-
- - -
- -
-
- -
-
- -
-
-
- -
- - + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+ +
+
+ + -
-
-
- - - - -
- - -
- -
-
- +
+
+ +
+ + + +
+
+
+

Shopping Cart

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Shopping Cart Items -- To Buy NowPriceQuantityTotal
Studio RTA - Laptop/Reading Cart with Tilting Surface - + Cherry69.99$0.00
Dynex - Traditional Notebook Case27.99$0.00
Hewlett-Packard - Pavilion Notebook with Intel Centrino1599.99$0.00
3 - Year Performance Service Plan $1000 and Over299.99$0.00
+
+ + + + + + + + + + + + + + + + + + + + + + +
The total charged to your credit card:$0.00
 
Enter your credit card number:
Enter your three digit access code:
+
+
+
+ +
+
+
-
- - -
-
-
- - - - -
- - -
- -
-
- -
-
+
+
+
- - - - - - - - - - - - - - - - - - - +
+
+
+
- - - - - - - - - +
+ + +
+
+
+ + + + +
+ + +
+ +
+
+ +
+
- - - - - - +
+ + +
+
+
+ + + + +
+ + +
+ +
+
+ +
+
- - - - - - - - - - - - - - - - - - - - - - - - - - +
+
+
- - - - - - +
+
+ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ +
+
+
+
+ user profile image +
+
+
+ John Doe + uploaded a photo. +
+
24 days ago
+
+
+ +
+ image post +
+ +
+ +
+ +
+
+ + +
+ +
+ +
+ + +
+ +
+
+ +
+
+ +
+ +
+
+ +
+ +
+
- - - - - - \ No newline at end of file diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/i18n/WebGoatLabels.properties b/webgoat-lessons/cross-site-scripting/src/main/resources/i18n/WebGoatLabels.properties index 68d7f6267..9d3490287 100644 --- a/webgoat-lessons/cross-site-scripting/src/main/resources/i18n/WebGoatLabels.properties +++ b/webgoat-lessons/cross-site-scripting/src/main/resources/i18n/WebGoatLabels.properties @@ -8,5 +8,7 @@ xss-reflected-6a-failure=No, look at the example. Check the GoatRouter.js file. xss.lesson1.failure=Are you sure? Try using a tab from a different site. xss-dom-message-success=Correct, I hope you didn't cheat, using the console! xss-dom-message-failure=Incorrect, keep trying. It should be obvious in the log when you are successful. - -#xss-reflected-5b-do5a-first=Do the reflected xss attack prior to this, then come back and answer this. \ No newline at end of file +xss-stored-comment-success=It appears your payload should invoke the function. To tell for sure, you need to capture the value and put it in the form below. Then we will really know. +xss-stored-comment-failure=We can't see the payload in your submission, but XSS can be tricky. Look for the call back fired after the comments reload. If you see that and can put the correct value there and put it in, maybe you did succeed. +xss-stored-callback-success=Yes, that is the correct value (note, it will be a different value each time the phoneHome endpoint is called). +xss-stored-callback-failure=No, that is not the correct value (note, it will be a different value each time the phoneHome endpoint is called). diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/images/avatar1.png b/webgoat-lessons/cross-site-scripting/src/main/resources/images/avatar1.png new file mode 100644 index 0000000000000000000000000000000000000000..4ea864f90009013d7d30ea3fb8c70b1f49c7e4b8 GIT binary patch literal 28394 zcmeI(f5ENDT zB&p@Sc=YAQ>&<*i$#bO7-o&Gu?VKUsO?QUKLA9X!NhjL%0D`5Zr6Vlefig3uH=W86 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' + + 'avatar' + + '
' + + '
' + + '
' + + '

USER

' + + '
DATETIME
' + + '
' + + '

COMMENT

' + + '
' + + ''; + + getChallenges(); + + function getChallenges() { + $("#list").empty(); + $.get('CrossSiteScripting/stored-xss', function (result, status) { + 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); + $("#list").append(comment); + } + + }); + } +}) \ No newline at end of file diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content10.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content10-off.adoc similarity index 100% rename from webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content10.adoc rename to webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content10-off.adoc diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content11.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content11-off.adoc similarity index 100% rename from webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content11.adoc rename to webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content11-off.adoc diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content12.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content12-off.adoc similarity index 100% rename from webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content12.adoc rename to webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content12-off.adoc diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content13.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content13-off.adoc similarity index 100% rename from webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content13.adoc rename to webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content13-off.adoc diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content13a.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content13a-off.adoc similarity index 100% rename from webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content13a.adoc rename to webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content13a-off.adoc diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content14.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content14-off.adoc similarity index 100% rename from webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content14.adoc rename to webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content14-off.adoc diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content15.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content15-off.adoc similarity index 100% rename from webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content15.adoc rename to webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content15-off.adoc diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content15a.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content15a-off.adoc similarity index 100% rename from webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content15a.adoc rename to webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content15a-off.adoc diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content16.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content16-off.adoc similarity index 100% rename from webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content16.adoc rename to webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content16-off.adoc diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content5b.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content5b.adoc index 477752127..16de62421 100644 --- a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content5b.adoc +++ b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content5b.adoc @@ -1,5 +1,10 @@ -== Was it Really Reflected XSS? +== Self XSS or Reflected XSS? -You should have been able to execute script with the last example. Was it truly reflected XSS? +You should have been able to execute script with the last example. At this point, it would be considered 'self XSS' though. +Why is that? +That is because there is no link that would tigger that XSS. +You can try it yourself to see what happens ... go to (substitute localhost with your server's name or IP if you need to): + +link: http://localhost:8080/WebGoat/CrossSiteScripting/attack5a?QTY1=1&QTY2=1&QTY3=1&QTY4=1&field1=4128+3214+0002+1999&field2=111 diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content6.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content6.adoc index 1445c3baf..0a8ba10db 100644 --- a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content6.adoc +++ b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content6.adoc @@ -1,8 +1,10 @@ -== DOM-Based XSS Scenario +== Reflected and DOM-Based XSS -DOM-based XSS is similar to reflected. The difference is that the payload will never go to the server, but will only ever be processed by the client. +DOM-based XSS is another form of reflected XSS. Both are triggered by sending a link with inputs that are reflected to the browser. +The difference between DOM and 'traditional' reflected XSS is that, with DOM, the payload will never go to the server. It will only ever be processed by the client. -* Attacker sends a malicious URL to victim + +* Attacker sends a malicious URL to victim * Victim clicks on the link * That link may load a malicious web page or a web page they use (are logged into?) that has a vulnerable route/handler * If it's a malicious web page, it may use it's own JavaScript to attack another page/url with a vulnerable route/handler diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content6a.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content6a.adoc index 08176abe4..656d9f4de 100644 --- a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content6a.adoc +++ b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content6a.adoc @@ -1,12 +1,15 @@ == Ientify Potential for DOM-Based XSS -For this, you'll want to look for some 'test' code in the route handlers (javascript/backbone). Sometimes, test code gets left in. -(Often times test code is very simple and lacks security or any quality controls!). +DOM-Based XSS can usually be found by looking for the route configurations in the client-side code. +Look for a route that takes inputs that you can ID being 'reflected' to the page. + +For this example, you'll want to look for some 'test' code in the route handlers (WebGoat uses backbone as its primary javascript library). +Sometimes, test code gets left in production (and often times test code is very simple and lacks security or any quality controls!). Your objective is to find the route and exploit it. First though ... what is the base route? As an example, look at the URL for this lesson ... it should look something like /WebGoat/start.mvc#lesson/CrossSiteScripting.lesson/5 (although maybe slightly different). The 'base route' in this case is: *start.mvc#lesson/* -The *CrossSiteScripting.lesson/5* after that are parameters that are processed by javascript route handler. +The *CrossSiteScripting.lesson/#* after that are parameters that are processed by javascript route handler. So, what is test route for this test code? diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content6b.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content6b.adoc index d66d36423..3fd67b860 100644 --- a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content6b.adoc +++ b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content6b.adoc @@ -1,9 +1,11 @@ == Try It! DOM-Based XSS -Some attacks are 'blind'. Fortunately, you have the server running here so you will be able to tell if you are successful. Use the route you just found and see if -you can use the fact that it reflects a parameter from the route without encoding to execute an internal function in WebGoat. The function you want to execute is ... +Some attacks are 'blind'. Fortunately, you have the server running here so you will be able to tell if you are successful. +Use the route you just found and see if you can use the fact that it reflects a parameter from the route without encoding to execute an internal function in WebGoat. +The function you want to execute is ... *webgoat.customjs.phoneHome()* -Sure, you could just use console/debug to trigger it, but you need to trigger it via a URL in a new tab. Once you complete it, paste the output message from the log below ... +Sure, you could just use console/debug to trigger it, but you need to trigger it via a URL in a new tab. +Once you do trigger it, a subsequent response will come to the browser with a random number. Put that random number in below. diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content7-off.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content7-off.adoc new file mode 100644 index 000000000..95ec99a41 --- /dev/null +++ b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content7-off.adoc @@ -0,0 +1,18 @@ +== DOM-Based XSS Example + +---- + +---- diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content7.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content7.adoc index 95ec99a41..ebfc3c4f0 100644 --- a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content7.adoc +++ b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content7.adoc @@ -1,18 +1,11 @@ -== DOM-Based XSS Example +== Stored XSS +Stored cross-site scripting is different in that the payload is persisted (stored) as opposed to passed/injected via a link. ----- - ----- +== Stored XSS Scenario +* Attacker posts malicious script to a message board +* Message is stored in a server database +* Victim reads the message +* The malicious script embedded in the message board post executes in the victim’s browser +** The script steals sensitive information, like the session id, and releases it to the attacker + +*Victim does not realize attack occurred* diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content7b.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content7b.adoc new file mode 100644 index 000000000..f7141ffa6 --- /dev/null +++ b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content7b.adoc @@ -0,0 +1,6 @@ +See the comments below. + +Add a comment with a javascript payload. Again ... you want to call the _webgoat.customjs.phoneHome_ function. + +As an attacker (offensive security), keep in mind that most apps are not going to have such a straight-forwardly named compromise. +Also, you may have to find a way to load your own javascript dynamically to fully achieve goals of exfiltrating data. \ No newline at end of file diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content8-off.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content8-off.adoc new file mode 100644 index 000000000..6eda0dd78 --- /dev/null +++ b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content8-off.adoc @@ -0,0 +1,20 @@ +== DOM-based XSS Defense + +* Attacker creates url: ++ +---- +http://mylogin.com/login?error= +---- + +* JavaScript must enforce input validation ++ +---- + if ( errorMsg\[1\].match(/^[ a-zA-Z0-9:-]$/)) + { + document.write(‘some error’); + } + else + { + document.write(''+errorMsg\[1\]+''); + } +---- diff --git a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content8.adoc b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content8.adoc index 6eda0dd78..80a3c7a25 100644 --- a/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content8.adoc +++ b/webgoat-lessons/cross-site-scripting/src/main/resources/lessonPlans/en/CrossSiteScripting_content8.adoc @@ -1,20 +1,27 @@ -== DOM-based XSS Defense +== XSS Defense -* Attacker creates url: -+ ----- -http://mylogin.com/login?error= ----- -* JavaScript must enforce input validation -+ ----- - if ( errorMsg\[1\].match(/^[ a-zA-Z0-9:-]$/)) - { - document.write(‘some error’); - } - else - { - document.write(''+errorMsg\[1\]+''); - } ----- +=== Why? +Hopefully we've covered that by now. Bottom line, you don't want someone else's code running in the context of your users and their logged-in seession + +=== What to encode? +The basic premise of defending against XSS is *output endoding* any untrusted input that goes to the screen. +That may be changing with more sophisticated attacks, but is still the best defense we currently have. *AND* ... *context matters* + +Another word on 'untrusted input'. If in doubt, treat everything (even data you populated in your DB as untrusted). +Sometimes data is shared across multiple systems and what you think is your data, may not have been created by you/your team. + +=== When/Where? +Encode *as the data is sent to the browser* (not in your persisted data). In the case of *Single Page Apps (SPA's), you will need to encode +in the client*. Consult your framework/library for me details, but some resources will be provided on the next page. + +=== How? + + * Encode as HTML Entities in HTML Body + * Encode as HTML Entities in HTML Attribute + * Encode for Javascript if outputting user input to javascript (but think about that ... you're outputting user input into javascript on your page!!) + +*DO NOT* try to blacklist/negative filter on strings like '