517 lines
16 KiB
Java
517 lines
16 KiB
Java
|
|
package org.owasp.webgoat.lessons;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Date;
|
|
import java.util.List;
|
|
import java.security.MessageDigest;
|
|
import javax.servlet.http.HttpServletResponse;
|
|
import org.apache.ecs.Element;
|
|
import org.apache.ecs.ElementContainer;
|
|
import org.apache.ecs.StringElement;
|
|
import org.apache.ecs.html.A;
|
|
import org.apache.ecs.html.Form;
|
|
import org.apache.ecs.html.IMG;
|
|
import org.apache.ecs.html.Input;
|
|
import org.apache.ecs.html.TD;
|
|
import org.apache.ecs.html.TR;
|
|
import org.apache.ecs.html.Table;
|
|
import org.owasp.webgoat.session.WebSession;
|
|
import sun.misc.BASE64Encoder;
|
|
|
|
|
|
/***************************************************************************************************
|
|
*
|
|
*
|
|
* 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
|
|
*/
|
|
public class HttpOnly extends LessonAdapter
|
|
{
|
|
|
|
public final static A ASPECT_LOGO = new A().setHref("http://www.aspectsecurity.com")
|
|
.addElement(
|
|
new IMG("images/logos/aspect.jpg").setAlt("Aspect Security").setBorder(0).setHspace(0)
|
|
.setVspace(0));
|
|
|
|
private final static Integer DEFAULT_RANKING = new Integer(125);
|
|
|
|
private final static String UNIQUE2U = "unique2u";
|
|
|
|
private final static String HTTPONLY = "httponly";
|
|
|
|
private final static String ACTION = "action";
|
|
|
|
private final static String READ = "Read Cookie";
|
|
|
|
private final static String WRITE = "Write Cookie";
|
|
|
|
private final static String READ_RESULT = "read_result";
|
|
|
|
private boolean httpOnly = false;
|
|
|
|
private boolean readSuccess = false;
|
|
|
|
private boolean writeSuccess = false;
|
|
|
|
private String original = "undefined";
|
|
|
|
/**
|
|
* Gets the title attribute of the EmailScreen object
|
|
*
|
|
* @return The title value
|
|
*/
|
|
public String getTitle()
|
|
{
|
|
return ("HTTPOnly Test");
|
|
}
|
|
|
|
protected Integer getDefaultRanking()
|
|
{
|
|
return DEFAULT_RANKING;
|
|
}
|
|
|
|
/**
|
|
* Description of the Method
|
|
*
|
|
* @param s
|
|
* Description of the Parameter
|
|
* @return Description of the Return Value
|
|
*/
|
|
|
|
protected Element createContent(WebSession s)
|
|
{
|
|
ElementContainer ec = new ElementContainer();
|
|
String action = null;
|
|
String http = null;
|
|
|
|
http = s.getRequest().getParameter(HTTPONLY);
|
|
action = s.getRequest().getParameter(ACTION);
|
|
|
|
if (http != null)
|
|
{
|
|
httpOnly = Boolean.parseBoolean(http);
|
|
}
|
|
|
|
if (httpOnly)
|
|
{
|
|
// System.out.println("HttpOnly: Setting HttpOnly for cookie");
|
|
setHttpOnly(s);
|
|
}
|
|
else
|
|
{
|
|
// System.out.println("HttpOnly: Removing HttpOnly for cookie");
|
|
removeHttpOnly(s);
|
|
}
|
|
|
|
if (action != null)
|
|
{
|
|
if (action.equals(READ))
|
|
{
|
|
handleReadAction(s);
|
|
}
|
|
else if (action.equals(WRITE))
|
|
{
|
|
handleWriteAction(s);
|
|
}
|
|
else
|
|
{
|
|
// s.setMessage("Invalid Request. Please try again.");
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
ec.addElement(makeContent(s));
|
|
} catch (Exception e)
|
|
{
|
|
s.setMessage("Error generating " + this.getClass().getName());
|
|
e.printStackTrace();
|
|
}
|
|
|
|
return (ec);
|
|
}
|
|
|
|
/**
|
|
* DOCUMENT ME!
|
|
*
|
|
* @return DOCUMENT ME!
|
|
*/
|
|
protected Category getDefaultCategory()
|
|
{
|
|
return Category.XSS;
|
|
}
|
|
|
|
/**
|
|
* Gets the hints attribute of the EmailScreen object
|
|
*
|
|
* @return The hints value
|
|
*/
|
|
protected List<String> getHints(WebSession s)
|
|
{
|
|
List<String> hints = new ArrayList<String>();
|
|
hints.add("Read the directions and try out the buttons.");
|
|
return hints;
|
|
}
|
|
|
|
private String createCustomCookieValue()
|
|
{
|
|
String value = null;
|
|
byte[] buffer = null;
|
|
MessageDigest md = null;
|
|
BASE64Encoder encoder = new BASE64Encoder();
|
|
|
|
try
|
|
{
|
|
md = MessageDigest.getInstance("SHA");
|
|
buffer = new Date().toString().getBytes();
|
|
|
|
md.update(buffer);
|
|
value = encoder.encode(md.digest());
|
|
original = value;
|
|
|
|
} catch (Exception e)
|
|
{
|
|
e.printStackTrace();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
private void setHttpOnly(WebSession s)
|
|
{
|
|
String value = createCustomCookieValue();
|
|
HttpServletResponse response = s.getResponse();
|
|
String cookie = s.getCookie(UNIQUE2U);
|
|
|
|
if (cookie == null || cookie.equals("HACKED"))
|
|
{
|
|
response.setHeader("Set-Cookie", UNIQUE2U + "=" + value + "; HttpOnly");
|
|
original = value;
|
|
}
|
|
else
|
|
{
|
|
response.setHeader("Set-Cookie", UNIQUE2U + "=" + cookie + "; HttpOnly");
|
|
original = cookie;
|
|
}
|
|
}
|
|
|
|
private void removeHttpOnly(WebSession s)
|
|
{
|
|
String value = createCustomCookieValue();
|
|
HttpServletResponse response = s.getResponse();
|
|
String cookie = s.getCookie(UNIQUE2U);
|
|
|
|
if (cookie == null || cookie.equals("HACKED"))
|
|
{
|
|
response.setHeader("Set-Cookie", UNIQUE2U + "=" + value + ";");
|
|
original = value;
|
|
}
|
|
else
|
|
{
|
|
response.setHeader("Set-Cookie", UNIQUE2U + "=" + cookie + ";");
|
|
original = cookie;
|
|
}
|
|
}
|
|
|
|
private ElementContainer makeContent(WebSession s)
|
|
{
|
|
ElementContainer ec = new ElementContainer();
|
|
Element r = null;
|
|
Table t = null;
|
|
TR tr = null;
|
|
Form f = null;
|
|
|
|
ec.addElement(new StringElement(getJavaScript()));
|
|
|
|
f = new Form();
|
|
|
|
t = new Table();
|
|
t.setWidth(500);
|
|
|
|
tr = new TR();
|
|
|
|
tr.addElement(new TD(new StringElement("Your browser appears to be: " + getBrowserType(s))));
|
|
t.addElement(tr);
|
|
|
|
tr = new TR();
|
|
t.addElement(tr);
|
|
|
|
tr = new TR();
|
|
|
|
tr.addElement(new TD(new StringElement("Do you wish to turn HTTPOnly on?")));
|
|
|
|
tr.addElement(new TD(new StringElement("Yes")));
|
|
|
|
if (httpOnly == true)
|
|
{
|
|
r = new Input(Input.RADIO, HTTPONLY, "True").addAttribute("Checked", "true");
|
|
}
|
|
else
|
|
{
|
|
r = new Input(Input.RADIO, HTTPONLY, "True").addAttribute("onClick", "document.form.submit()");
|
|
}
|
|
|
|
tr.addElement(new TD(r));
|
|
|
|
tr.addElement(new TD(new StringElement("No")));
|
|
|
|
if (httpOnly == false)
|
|
{
|
|
r = new Input(Input.RADIO, HTTPONLY, "False").addAttribute("Checked", "True");
|
|
}
|
|
else
|
|
{
|
|
r = new Input(Input.RADIO, HTTPONLY, "False").addAttribute("onClick", "document.form.submit()");
|
|
}
|
|
|
|
tr.addElement(new TD(r));
|
|
|
|
r = new Input(Input.HIDDEN, READ_RESULT, "");
|
|
tr.addElement(r);
|
|
|
|
t.addElement(tr);
|
|
|
|
/*
|
|
* tr.addElement(new TD(new StringElement("<strong>Status:</strong> " ))); t.addElement(tr);
|
|
* if(httpOnly == true) { tr.addElement(new TD(new StringElement("<div
|
|
* id=\"status\">On</div>"))); } else { tr.addElement(new TD(new StringElement ("<div
|
|
* id=\"status\">Off</div>"))); } t.addElement(tr); t.addElement(new TR(new TD(new
|
|
* StringElement("<br/>"))));
|
|
*/f.addElement(t);
|
|
|
|
t = new Table();
|
|
tr = new TR();
|
|
|
|
r = new Input(Input.SUBMIT, ACTION, READ).addAttribute("onclick", "myAlert();");
|
|
tr.addElement(new TD(r));
|
|
|
|
r = new Input(Input.SUBMIT, ACTION, WRITE).addAttribute("onclick", "modifyAlert();");
|
|
tr.addElement(new TD(r));
|
|
t.addElement(tr);
|
|
|
|
f.addElement(t);
|
|
ec.addElement(f);
|
|
|
|
return ec;
|
|
}
|
|
|
|
private void handleReadAction(WebSession s)
|
|
{
|
|
|
|
String displayed = s.getRequest().getParameter(READ_RESULT);
|
|
|
|
if (httpOnly == true)
|
|
{
|
|
if (displayed.indexOf(UNIQUE2U) != -1)
|
|
{
|
|
s.setMessage("FAILURE: Your browser did not enforce the HTTPOnly flag properly for the '" + UNIQUE2U
|
|
+ "' cookie. It allowed direct client side read access to this cookie.");
|
|
}
|
|
else
|
|
{
|
|
s.setMessage("SUCCESS: Your browser enforced the HTTPOnly flag properly for the '" + UNIQUE2U
|
|
+ "' cookie by preventing direct client side read access to this cookie.");
|
|
if (writeSuccess)
|
|
{
|
|
if (!this.isCompleted(s))
|
|
{
|
|
makeSuccess(s);
|
|
readSuccess = false;
|
|
writeSuccess = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!this.isCompleted(s))
|
|
{
|
|
s.setMessage("Now try to see if your browser protects write access to this cookie.");
|
|
readSuccess = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (displayed.indexOf(UNIQUE2U) != -1)
|
|
{
|
|
s.setMessage("Since HTTPOnly was not enabled, the '" + UNIQUE2U
|
|
+ "' cookie was displayed in the alert dialog.");
|
|
}
|
|
else
|
|
{
|
|
s.setMessage("Since HTTPOnly was not enabled, the '" + UNIQUE2U
|
|
+ "' cookie should have been displayed in the alert dialog, but was not for some reason. "
|
|
+ "(This shouldn't happen)");
|
|
}
|
|
}
|
|
|
|
private void handleWriteAction(WebSession s)
|
|
{
|
|
String hacked = s.getCookie(UNIQUE2U);
|
|
|
|
if (httpOnly == true)
|
|
{
|
|
if (!original.equals(hacked))
|
|
{
|
|
s
|
|
.setMessage("FAILURE: Your browser did not enforce the write protection property of the HTTPOnly flag for the '"
|
|
+ UNIQUE2U + "' cookie.");
|
|
s.setMessage("The " + UNIQUE2U + " cookie was successfully modified to " + hacked
|
|
+ " on the client side.");
|
|
}
|
|
else
|
|
{
|
|
s
|
|
.setMessage("SUCCESS: Your browser enforced the write protection property of the HTTPOnly flag for the '"
|
|
+ UNIQUE2U + "' cookie by preventing client side modification.");
|
|
if (readSuccess)
|
|
{
|
|
if (!this.isCompleted(s))
|
|
{
|
|
makeSuccess(s);
|
|
readSuccess = false;
|
|
writeSuccess = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!this.isCompleted(s))
|
|
{
|
|
s.setMessage("Now try to see if your browser protects read access to this cookie.");
|
|
writeSuccess = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!original.equals(hacked))
|
|
{
|
|
s.setMessage("Since HTTPOnly was not enabled, the browser allowed the '" + UNIQUE2U
|
|
+ "' cookie to be modified on the client side.");
|
|
}
|
|
else
|
|
{
|
|
s.setMessage("Since HTTPOnly was not enabled, the browser should have allowed the '" + UNIQUE2U
|
|
+ "' cookie to be modified on the client side, but it was not for some reason. "
|
|
+ "(This shouldn't happen)");
|
|
}
|
|
}
|
|
|
|
private String getJavaScript()
|
|
{
|
|
StringBuffer buffer = new StringBuffer();
|
|
|
|
buffer.append("<script language=\"javascript\">\n");
|
|
buffer.append("function myAlert() {\n");
|
|
buffer.append("alert(document.cookie);\n");
|
|
buffer.append("document.form.read_result.value=document.cookie;\n");
|
|
buffer.append("return true;\n");
|
|
buffer.append("}\n");
|
|
buffer.append("function modifyAlert() {\n");
|
|
buffer.append("document.cookie='" + UNIQUE2U + "=HACKED;\';\n");
|
|
buffer.append("alert(document.cookie);\n");
|
|
buffer.append("return true;\n");
|
|
buffer.append("}\n");
|
|
buffer.append("</script>\n");
|
|
|
|
return buffer.toString();
|
|
}
|
|
|
|
private String getBrowserType(WebSession s)
|
|
{
|
|
int offset = -1;
|
|
String result = "unknown";
|
|
String browser = s.getHeader("user-agent").toLowerCase();
|
|
|
|
if (browser != null)
|
|
{
|
|
if (browser.indexOf("firefox") != -1)
|
|
{
|
|
browser = browser.substring(browser.indexOf("firefox"));
|
|
|
|
offset = getOffset(browser);
|
|
|
|
result = browser.substring(0, offset);
|
|
}
|
|
else if (browser.indexOf("msie 6") != -1)
|
|
{
|
|
result = "Internet Explorer 6";
|
|
}
|
|
else if (browser.indexOf("msie 7") != -1)
|
|
{
|
|
result = "Internet Explorer 7";
|
|
}
|
|
else if (browser.indexOf("msie") != -1)
|
|
{
|
|
result = "Internet Explorer";
|
|
}
|
|
else if (browser.indexOf("opera") != -1)
|
|
{
|
|
result = "Opera";
|
|
}
|
|
else if (browser.indexOf("safari") != -1)
|
|
{
|
|
result = "Safari";
|
|
}
|
|
else if (browser.indexOf("netscape") != -1)
|
|
{
|
|
browser = browser.substring(browser.indexOf("netscape"));
|
|
|
|
offset = getOffset(browser);
|
|
|
|
result = browser.substring(0, offset);
|
|
}
|
|
else if (browser.indexOf("konqueror") != -1)
|
|
{
|
|
result = "Konqueror";
|
|
}
|
|
else if (browser.indexOf("mozilla") != -1)
|
|
{
|
|
result = "Mozilla";
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private int getOffset(String s)
|
|
{
|
|
int result = s.length();
|
|
|
|
for (int i = 0; i < s.length(); i++)
|
|
{
|
|
if (s.charAt(i) < 33 || s.charAt(i) > 126)
|
|
{
|
|
result = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public Element getCredits()
|
|
{
|
|
return super.getCustomCredits("", ASPECT_LOGO);
|
|
}
|
|
}
|