WebGoat本身有专门一个课程用来介绍如何添加一个新的课程,但是估计是这个课程长期没有人维护的关系,我再使用WebGoat 5.4试图添加一个课程的时候发现有些细节的地方与WebGoat里面介绍的步骤有些不一致或者是WebGoat里面没有提到的,所以把我的步骤记录下来。
我添加的课程是HTTP参数污染(HTTP Parameter Pollution, HPP)这个漏洞相关的课程。关于这个漏洞的详细信息可以参阅我的另外一篇文章:http://blog.csdn.net/eatmilkboy/article/details/6761407
准备环境
要为WebGoat添加课程需要使用WebGoat的源代码,以及相关的编译环境。
WebGoat的源代码可以使用SVN进行下载,在这里:http://code.google.com/p/webgoat/source/checkout可以看到如何下载代码的相关说明。
WebGoat需要使用MAVEN进行编译,这里我使用JDK1.6.31和MAVEN 2.2.1。WebGoat源代码下载下来后可以直接进入WebGoat的源代码所在目录使用如下命令进行编译来确保编译环境配置正确:
mvn compile
创建新课程步骤
1. 为新课程添加源代码
新课程的源代码路径为:<WebGoat Path>\src\main\java\org\owasp\webgoat\lessons\。
我们为HTTP Parameter Pollution这个漏洞增加一个新的java文件,命名为HTTPParameterPollution.java。
这个新课程的类需要继承LessonAdapter,主要实现下面几个接口:
- public String getTitle()
这个接口设置课程的标题,设置好的标题在课程页面的相应地方会有显示。
- protected List<String> getHints(WebSession s)
这个接口设置课程的提示,在用户点击WebGoat的“提示”按钮后会显示课程的相应提示。
- protected Category getDefaultCategory()
这个接口用来设置课程需要放置在哪个菜单项下面。比如我们为这个接口返回Category.GENERAL常量,那么我们的课程就会被放置到“General”菜单项下面。
- protected Integer getDefaultRanking()
这个接口返回一个整数用来表示课程在相应菜单项下面的排序的位置。可以根据前后的课程的数值返回一个合适的值。
- protected Element createContent(WebSession s)
这个接口是最重要的一个接口,里面就包含了课程的完整实现,包括课程需要显示的界面,处理用户提交的参数,判断课程是否完成等等。
- public void handleRequest(WebSession s)
这个接口通常不需要我们自己实现,直接使用父类的实现就好。只是我们这个课程比较特殊,父类的实现无法满足我们的需求,所以才自己实现了下。
最后完整的代码如下所示。
package org.owasp.webgoat.lessons; import java.util.*; import org.apache.ecs.*; import org.apache.ecs.html.*; import org.owasp.webgoat.session.WebSession; import org.owasp.webgoat.session.ECSFactory; import javax.servlet.http.HttpServletResponse; /*************************************************************************************************** * * * This file is part of WebGoat, an Open Web Application Security Project utility. For details, * please see http://www.owasp.org/ * * Copyright (c) 2002 - 2007 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 code.google.com, a repository for free software * projects. * * For details, please see http://code.google.com/p/webgoat/ * * @author EatMilkBoy * @created August 06, 2012 */ public class HTTPParameterPollution extends LessonAdapter { // define a constant for the field name private final static String COURSE_ID = "course_id"; private final static String COURSE_NAME = "course_name"; private final static String ACTION = "action"; private final static String SURVEY_RESULT = "survey_result"; private List<String> CourseList; private int Step; private boolean IsNumber(String s) { try { Integer.parseInt(s); return true; } catch (Exception e) { return false; } } public void handleRequest(WebSession s) { // Setting a special action to be able to submit to customized URI int action = s.getParser().getIntParameter(ACTION, 0); String course_id = s.getParser().getRawParameter(COURSE_ID, ""); String course_name = s.getParser().getStringParameter(COURSE_NAME, ""); Form form; CourseList = new ArrayList<String>(); CourseList.add("HTTP Basic"); CourseList.add("HTTP Splitting"); CourseList.add("HTTP Parameter Pollution"); if (action == 1 && CourseList.contains(course_name)) { form = new Form("attack?" + "Screen=" + String.valueOf(getScreenId()) + "&menu=" + getDefaultCategory().getRanking().toString() + "&" + COURSE_ID + "=" + course_id, Form.POST).setName("form").setEncType(""); Step = 2; } else { form = new Form("attack?" + "Screen=" + String.valueOf(getScreenId()) + "&menu=" + getDefaultCategory().getRanking().toString(), Form.POST).setName("form").setEncType(""); Step = 1; } form.addElement(createContent(s)); setContent(form); } protected Element createContent(WebSession s) { ElementContainer ec = new ElementContainer(); try { // get some input from the user -- see ParameterParser for details String course_id = s.getParser().getRawParameter(COURSE_ID, ""); String course_name = s.getParser().getStringParameter(COURSE_NAME, ""); int action = s.getParser().getIntParameter(ACTION, 0); String survey_result = s.getParser().getStringParameter(SURVEY_RESULT, ""); Input input; input = new Input(Input.TEXT, ACTION, action); input.setID(ACTION); input.setStyle("display:none;"); ec.addElement(input); if (action == 2 && IsNumber(course_id)) { int course_id_int = Integer.parseInt(course_id); if (course_id_int <= CourseList.size()) { StringBuffer msg = new StringBuffer(); msg.append("Your survey result for \"" + CourseList.get(course_id_int - 1) + "\" is: " + survey_result); msg.append("\r\n\r\n"); s.setMessage(msg.toString()); } } if (Step == 1) { ec.addElement(new P().addElement(new StringElement("Please select a course to survey: "))); Select select = new Select(COURSE_NAME); Option Default = new Option("Please make a selection"); Default.addElement("Please make a selection"); if (!CourseList.contains(course_name)) { Default.setSelected(true); } select.addElement(Default); select.setID(COURSE_NAME); int real_course_id = 0; for (int i=0; i<CourseList.size(); i++) { String name = CourseList.get(i); Option CourseItem = new Option(name); CourseItem.addElement(name); if (course_name.equals(name)) { CourseItem.setSelected(true); real_course_id = i + 1; } select.addElement(CourseItem); } select.setOnChange("document.getElementById('" + COURSE_ID + "').value = document.getElementById('" + COURSE_NAME + "').selectedIndex"); ec.addElement(select); input = new Input(Input.TEXT, COURSE_ID, real_course_id); input.setID(COURSE_ID); input.setStyle("display:none;"); ec.addElement(input); input = (Input)ECSFactory.makeButton("Survey"); input.setOnClick("document.getElementById('" + ACTION + "').value = 1"); ec.addElement(input); } else { if (action == 1) { String[] arrTokens = course_id.split("&"); if (IsNumber(arrTokens[0])) { for (int i = 1; i < arrTokens.length; i++) { System.out.println("Token: " + arrTokens[i]); if (arrTokens[i].matches("^survey_result=.*")) { s.setMessage("Good job!<br>This lesson has detected your successful attack, make a select other than you specified survey result and click \"Submit\" to view the result.<br>"); // Tell the lesson tracker the lesson has completed. // This should occur when the user has 'hacked' the lesson. makeSuccess(s); break; } } } } ec.addElement(new StringElement("Please provide your opinion for course: <b>" + course_name + "</b>")); ec.addElement(new BR()); ec.addElement(new BR()); input = new Input(Input.RADIO, SURVEY_RESULT, "Good"); input.addElement("Good"); if (action != 1 || (action == 1 && survey_result.equals("Good"))) input.setChecked(true); ec.addElement(input); ec.addElement(new BR()); ec.addElement(new BR()); input = new Input(Input.RADIO, SURVEY_RESULT, "So-so"); input.addElement("So-so"); if (action == 1 && survey_result.equals("So-so")) input.setChecked(true); ec.addElement(input); ec.addElement(new BR()); ec.addElement(new BR()); input = new Input(Input.RADIO, SURVEY_RESULT, "Bad"); input.addElement("Bad"); if (action == 1 && survey_result.equals("Bad")) input.setChecked(true); ec.addElement(input); ec.addElement(new BR()); ec.addElement(new BR()); input = (Input)ECSFactory.makeButton("Submit"); input.setOnClick("document.getElementById('" + ACTION + "').value = 2"); ec.addElement(input); } } catch (Exception e) { s.setMessage("Error generating " + this.getClass().getName()); e.printStackTrace(); } return (ec); } protected Category getDefaultCategory() { return Category.GENERAL; } private final static Integer DEFAULT_RANKING = new Integer(30); protected Integer getDefaultRanking() { return DEFAULT_RANKING; } protected List<String> getHints(WebSession s) { List<String> hints = new ArrayList<String>(); hints.add("Try inject code into course_id parameter."); return hints; } public String getTitle() { return ("HTTP Parameter Pollution"); } }
一旦代码完成,就可以再次在WebGoat的目录下面执行MAVEN的编译命令进行编译。编译好的针对我们课程的class文件可以在<WebGoat Path>\target\classes\org\owasp\webgoat\lessons目录下面看到。
2. 创建课程计划
课程计划是当用户在WebGoat页面上点击“Lesson Plan”之后出现的文本,实际上是以HTML格式文件的一个片段存在,可以仿照其它课程的样式书写我们的样式。这边需要注意的是文件名需要和课程的class文件名保持一致。比如我们的课程的class文件名为HTTPParameterPollution.class,那么我们的课程计划的文件名就应该是HTTPParameterPollution.html。注意大小写也要保持一致。
<div align="Center"> <p><b>Lesson Plan Title:</b> How to Perform HTTP Parameter Pollution </p> </div> <p><b>Concept / Topic To Teach:</b> </p> This lesson teaches how to perform HTTP Parameter Pollution attacks. <br /> <div align="Left"> <p> <b>How the attack works:</b> </p> <p>HTTP Parameter Pollution attack consists of injecting encoded query string delimiters into existing HTTP parameters.</p> <p>If application does not sanitize its inputs, HPP can be used to launch client-side or server-side attacks.</p> <p>Attacker may be able to override existing parameter values, inject a new parameter or exploit variables out of a direct reach</p> </div> <p><b>General Goal(s):</b> </p> <!-- Start Instructions --> <p>This lesson focus on HPP client side attack.</p> <p>Select a class name from the list and click the "Survey" button, the server will show a form for you to submit your opinion for this course. And also when you submit your opinion server will also display the result to you.</p> <p>You should able to use HPP to launch client side attack, the goal is to make a survey result for a class is always a fixed value no matter user choose.</p> <!-- Stop Instructions -->
3. 创建课程解决方案
课程的解决方案以完整HTML文件的形式存在,同样需要注意在文件命名的时候与class文件保持一致。实际上其文件名应该与课程计划的文件名一样,只是到时候部署的时候会放到不同的文件夹里面。在解决方案里面也可以包含一些插图,在HTML文件里面设置好这些图片的相对路径之后可以一起部署到WebGoat里面去。
关于这里增加的这个课程的解决方案参看本文结尾。
部署新课程
到现在我们准备新增的课程的所有材料都准备好了,可以部署到WebGoat的运行环境里面去了。在WebGoat标准版中,当WebGoat第一次运行之后(即部署过原始的WebGoat.war文件之后),把我们准备的各文件放置到如下目录:
课程计划放置到<WebGoat标准版路径>\tomcat\webapps\webgoat\lesson_plans\English
解决方案放置到<WebGoat标准版路径>\tomcat\webapps\webgoat\lesson_solutions
课程源代码放置到<WebGoat标准版路径>\tomcat\webapps\webgoat\WEB-INF\classes\java\org\owasp\webgoat\lessons
课程class文件放置到<WebGoat标准版路径>\ tomcat\webapps\webgoat\WEB-INF\classes\org\owasp\webgoat\lessons
然后重新启动WebGoat标准版之后就可以看到我们新增加的课程了。
添加这个课程所需要的材料可以从这里下载到:
http://download.csdn.net/detail/eatmilkboy/4741693
课程解决方案
同时也附上我们这个课程的解决方案。
这个课程是给出了一个页面用来让用户对WebGoat的一些课程进行评价是好还是不好。目标是利用HPP的漏洞让用户不管选择什么样的评价,最后的结果都是好。
我们可以先进行一下操作然后使用TamperData来观察下浏览器与服务器之间的数据交互。选择一个课程并提交后,TamperData抓取到的输入如下图所示:
然后课程返回用于一个评价的界面:
提交评价之后可以从TamperData看到提交的请求如下图所示:
在这里我们可以看到其提交请求的URL里面,course_id这个参数正好是我们第一步提交的course_id的参数。所以我们可以考虑对这个参数进行注入。
利用TampderData拦截在第一步选择课程时提交的参数,把course_id的值改为1%26survey_result%3DGood,提交后就可以看到课程已经识别到了我们已经成功注入了这个参数,这时候,不管选择的评价是好还是坏,最后的结果都会是好。
我们可以再随便选择一个评价来看看提交给服务器的请求,可以看到这次提交的请求里面URL中已经多了一个survey_result=Good的参数,这正是我们注入的结果。