/*********************************************************************** * * $CVSHeader$ * * This file is part of WebScarab, an Open Web Application Security * Project utility. For details, please see http://www.owasp.org/ * * Copyright (c) 2002 - 2004 Rogan Dawes * * 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 Sourceforge.net, a * repository for free software projects. * * For details, please see http://www.sourceforge.net/projects/owasp * */ /* * Fragments.java * * Created on August 25, 2004, 10:45 PM */ package org.owasp.webscarab.plugin.fragments; import java.io.File; import java.io.UnsupportedEncodingException; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.htmlparser.nodes.RemarkNode; import org.htmlparser.tags.FormTag; import org.htmlparser.tags.InputTag; import org.htmlparser.tags.ScriptTag; import org.htmlparser.util.NodeIterator; import org.htmlparser.util.NodeList; import org.htmlparser.util.ParserException; import org.owasp.webscarab.model.ConversationID; import org.owasp.webscarab.model.HttpUrl; import org.owasp.webscarab.model.Request; import org.owasp.webscarab.model.Response; import org.owasp.webscarab.model.StoreException; import org.owasp.webscarab.parser.Parser; import org.owasp.webscarab.plugin.Framework; import org.owasp.webscarab.plugin.Hook; import org.owasp.webscarab.plugin.Plugin; /** * This plugin looks for comments and scripts in the source of HTML pages. * @author knoppix */ public class Fragments implements Plugin { private Logger _logger = Logger.getLogger(getClass().getName()); private FragmentsModel _model = null; /** * Pattern that searches for window.location in right-hand side assignments. * Can trap potential DOM-based xss These ones search for * window.location, * window.top.location document.URL document.location document.URLUnencoded */ Pattern[] jsDomXssPatterns = { //This one searches for // fobobar = window.location // baz = window.top.location Pattern.compile("[\\S&&[^=]]+\\s*=\\s*window\\.(?:top\\.)?location"), //This one searches for // foo= document.URL // bar = document.URLUnencoded // gazonk = document.location Pattern .compile("[\\S&&[^=]]+\\s*=\\s*document\\.(?:URL|URLUnencoded|location)"), //This one searches for string concatenation // such as a = "<img src='"+document.URL+"/foobar' />"; Pattern.compile("\\+\\s*window\\.(?:top\\.)?location"), Pattern.compile("\\+\\s*document\\.(?:URL|URLUnencoded|location)"), }; Pattern[] jsDomXssFalsePositivesPattern = { //This one removes false positives on the form // if(blaha != window.location) // if(blaha == document.URL) Pattern.compile(".+[!=]+=.*(?:document|window)"), //This one removes // + escape(document.location) //which normally is not a problem Pattern.compile("escape\\((?:document.|window.).+\\)"), }; /** * Creates a new instance of Fragments * @param props contains the user's configuration properties */ public Fragments(Framework framework) { _model = new FragmentsModel(framework.getModel()); } public FragmentsModel getModel() { return _model; } /** * Sets the store that this plugin uses * @param session the new session */ public void setSession(String type, Object store, String session) throws StoreException { if (type.equals("FileSystem") && (store instanceof File)) { _model.setStore(new FileSystemStore((File) store, session)); } else { throw new StoreException("Store type '" + type + "' is not supported in " + getClass().getName()); } } /** * returns the name of the plugin * @return the name of the plugin */ public String getPluginName() { return "Fragments"; } /** * calls the main loop of the plugin */ public void run() { _model.setRunning(true); } /** * stops the plugin running * @return true if the plugin could be stopped within a (unspecified) timeout period, false otherwise */ public boolean stop() { _model.setRunning(false); return ! _model.isRunning(); } public void analyse(ConversationID id, Request request, Response response, String origin) { HttpUrl url = request.getURL(); Object parsed = Parser.parse(url, response); if (parsed != null && parsed instanceof NodeList) { NodeList nodes = (NodeList) parsed; try { NodeList comments = nodes.searchFor(RemarkNode.class); NodeList scripts = nodes.searchFor(ScriptTag.class); NodeList forms = nodes.searchFor(FormTag.class); NodeList inputs = nodes.searchFor(InputTag.class); for (NodeIterator ni = comments.elements(); ni.hasMoreNodes(); ) { String fragment = ni.nextNode().toHtml(); _model.addFragment(url, id, FragmentsModel.KEY_COMMENTS, fragment); } for (NodeIterator ni = scripts.elements(); ni.hasMoreNodes(); ) { String fragment = ni.nextNode().toHtml(); _model.addFragment(url, id, FragmentsModel.KEY_SCRIPTS, fragment); } for (NodeIterator ni = forms.elements(); ni.hasMoreNodes(); ) { FormTag form = (FormTag) ni.nextNode(); String fragment = "action:"+form.getAttribute("action")+" method:"+form.getAttribute("method"); _model.addFragment(url, id, FragmentsModel.KEY_FORMS,fragment ); } for (NodeIterator ni = inputs.elements(); ni.hasMoreNodes(); ) { InputTag tag = (InputTag) ni.nextNode(); String type = tag.getAttribute("type"); if( "hidden".equals(type)) { String fragment = tag.toHtml(); _model.addFragment(url, id, FragmentsModel.KEY_HIDDENFIELD, fragment); } if("file".equals(type)) { String fragment = tag.toHtml(); _model.addFragment(url, id, FragmentsModel.KEY_FILEUPLOAD, fragment); } } } catch (ParserException pe) { _logger.warning("Looking for fragments, got '" + pe + "'"); } } //Now, look for "dangerous" javascript try { String content = new String(response.getContent(),"UTF-8"); for (int i = 0; i < jsDomXssPatterns.length; i++) { Matcher m = jsDomXssPatterns[i].matcher(content); while(m.find()) { String fragment = m.group(); boolean falsePositive = false; //Test false positives for (int j = 0; j < jsDomXssFalsePositivesPattern.length; j++) { Matcher fp = jsDomXssFalsePositivesPattern[j] .matcher(fragment); if (fp.find()) { falsePositive = true; _logger .info("Ignoring XSS-DOM fragment '" + fragment + "' - false positive according to pattern :" + jsDomXssFalsePositivesPattern[j] .pattern()); break; } } if (!falsePositive) { _model.addFragment(url, id, FragmentsModel.KEY_DOMXSS, fragment); } } } } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void flush() throws StoreException { _model.flush(); } public boolean isBusy() { return _model.isBusy(); } public String getStatus() { return _model.getStatus(); } public boolean isModified() { return _model.isModified(); } public boolean isRunning() { return _model.isRunning(); } public Object getScriptableObject() { return null; } public Hook[] getScriptingHooks() { return new Hook[0]; } }