/*
* SoapUI, Copyright (C) 2004-2016 SmartBear Software
*
* Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the Licence for the specific language governing permissions and limitations
* under the Licence.
*/
package com.eviware.soapui.security.scan;
import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.MalformedXmlAttributeConfig;
import com.eviware.soapui.config.MalformedXmlConfig;
import com.eviware.soapui.config.SecurityScanConfig;
import com.eviware.soapui.config.StrategyTypeConfig;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.iface.MessageExchange;
import com.eviware.soapui.model.security.SecurityCheckedParameter;
import com.eviware.soapui.model.testsuite.TestCaseRunner;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.security.SecurityTestRunContext;
import com.eviware.soapui.security.SecurityTestRunner;
import com.eviware.soapui.security.ui.MalformedXmlAdvancedSettingsPanel;
import com.eviware.soapui.support.types.StringToStringMap;
import com.eviware.soapui.support.xml.XmlObjectTreeModel;
import com.eviware.soapui.support.xml.XmlObjectTreeModel.AttributeXmlTreeNode;
import com.eviware.soapui.support.xml.XmlObjectTreeModel.XmlTreeNode;
import com.eviware.soapui.support.xml.XmlUtils;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlOptions;
import javax.swing.JComponent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class MalformedXmlSecurityScan extends AbstractSecurityScanWithProperties {
public static final String TYPE = "MalformedXmlSecurityScan";
public static final String NAME = "Malformed XML";
private Map<SecurityCheckedParameter, ArrayList<String>> parameterMutations = new HashMap<SecurityCheckedParameter, ArrayList<String>>();
private boolean mutation;
private MalformedXmlConfig malformedXmlConfig;
private MalformedXmlAttributeConfig malformedAttributeConfig;
private MalformedXmlAdvancedSettingsPanel advancedSettingsPanel;
public MalformedXmlSecurityScan(TestStep testStep, SecurityScanConfig config, ModelItem parent, String icon) {
super(testStep, config, parent, icon);
if (config.getConfig() == null || !(config.getConfig() instanceof MalformedXmlConfig)) {
initMalformedXmlConfig();
} else {
malformedXmlConfig = ((MalformedXmlConfig) config.getConfig());
malformedAttributeConfig = malformedXmlConfig.getAttributeMutation();
}
}
/**
* Default malformed xml configuration
*/
protected void initMalformedXmlConfig() {
getConfig().setConfig(MalformedXmlConfig.Factory.newInstance());
malformedXmlConfig = (MalformedXmlConfig) getConfig().getConfig();
malformedXmlConfig.addNewAttributeMutation();
// init default configuration
malformedXmlConfig.setInsertNewElement(true);
malformedXmlConfig.setNewElementValue("<xml>xml <joke> </xml> </joke>");
malformedXmlConfig.setChangeTagName(true);
malformedXmlConfig.setLeaveTagOpen(true);
malformedXmlConfig.setInsertInvalidCharacter(true);
malformedAttributeConfig = malformedXmlConfig.getAttributeMutation();
malformedAttributeConfig.setMutateAttributes(true);
malformedAttributeConfig.setInsertInvalidChars(true);
malformedAttributeConfig.setLeaveAttributeOpen(true);
malformedAttributeConfig.setAddNewAttribute(true);
malformedAttributeConfig.setNewAttributeName("newAttribute");
malformedAttributeConfig.setNewAttributeValue("XXX");
}
@Override
protected void execute(SecurityTestRunner runner, TestStep testStep, SecurityTestRunContext context) {
try {
StringToStringMap paramsUpdated = update(testStep, context);
MessageExchange message = (MessageExchange) testStep.run((TestCaseRunner) runner, context);
createMessageExchange(paramsUpdated, message, context);
} catch (XmlException e) {
SoapUI.logError(e, "[MalformedXmlSecurityScan]XPath seems to be invalid!");
reportSecurityScanException("Property value is not XML or XPath is wrong!");
} catch (Exception e) {
SoapUI.logError(e, "[MalformedXmlSecurityScan]Property value is not valid xml!");
reportSecurityScanException("Property value is not XML or XPath is wrong!");
}
}
protected StringToStringMap update(TestStep testStep, SecurityTestRunContext context) throws XmlException,
Exception {
StringToStringMap params = new StringToStringMap();
if (parameterMutations.size() == 0) {
mutateParameters(testStep, context);
}
if (getExecutionStrategy().getStrategy() == StrategyTypeConfig.ONE_BY_ONE) {
/*
* Idea is to drain for each parameter mutations.
*/
for (SecurityCheckedParameter param : getParameterHolder().getParameterList()) {
if (parameterMutations.containsKey(param)) {
if (parameterMutations.get(param).size() > 0) {
TestProperty property = testStep.getProperties().get(param.getName());
String value = context.expand(property.getValue());
if (param.getXpath() == null || param.getXpath().trim().length() == 0) {
// no xpath ignore
} else {
// no value, do nothing.
if (value == null || value.trim().equals("")) {
continue;
}
// XmlObjectTreeModel model = new XmlObjectTreeModel(
// property.getSchemaType().getTypeSystem(),
// XmlObject.Factory.parse( value ) );
XmlObjectTreeModel model = new XmlObjectTreeModel(property.getSchemaType().getTypeSystem(),
XmlUtils.createXmlObject(value));
XmlTreeNode[] nodes = model.selectTreeNodes(context.expand(param.getXpath()));
StringBuffer buffer = new StringBuffer(value);
for (int cnt = 0; cnt < nodes.length; cnt++) {
// find right node
// this finds where node that needs updateing begins
int start = value.indexOf("<" + nodes[cnt].getNodeName()); // keeps
// node
// start
int cnt2 = 0;
// if have more than one node that matches xpath, find
// next one.
while (cnt2 < cnt) {
start = value.indexOf("<" + nodes[cnt].getNodeName(), start + 1);
cnt2++;
}
// get node xml
String nodeXml = getXmlForNode(nodes[cnt]);
// find end of target xml node
int end = value.indexOf("<" + nodes[cnt].getNodeName(), start + 1);
if (end <= 0) {
if (nodeXml.endsWith("</" + nodes[cnt].getDomNode().getNodeName() + ">")) {
end = value.indexOf("</" + nodes[cnt].getDomNode().getNodeName() + ">")
+ ("</" + nodes[cnt].getDomNode().getNodeName() + ">").length();
} else {
end = value.indexOf(">", value.indexOf("/", start));
}
}
if (end <= 0 || end <= start) {
break;
}
// replace node with right value
buffer.replace(start, end + 1, parameterMutations.get(param).get(0));
}
params.put(param.getLabel(), parameterMutations.get(param).get(0));
parameterMutations.get(param).remove(0);
testStep.getProperties().get(param.getName()).setValue(buffer.toString());
}
break;
}
}
}
} else {
for (TestProperty property : testStep.getPropertyList()) {
String value = context.expand(property.getValue());
if (XmlUtils.seemsToBeXml(value)) {
StringBuffer buffer = new StringBuffer(value);
XmlObjectTreeModel model = null;
// model = new XmlObjectTreeModel(
// property.getSchemaType().getTypeSystem(),
// XmlObject.Factory.parse( value ) );
model = new XmlObjectTreeModel(property.getSchemaType().getTypeSystem(),
XmlUtils.createXmlObject(value));
for (SecurityCheckedParameter param : getParameterHolder().getParameterList()) {
if (param.getXpath() == null || param.getXpath().trim().length() == 0) {
if (parameterMutations.containsKey(param)) {
testStep.getProperties().get(param.getName())
.setValue(parameterMutations.get(param).get(0));
params.put(param.getLabel(), parameterMutations.get(param).get(0));
parameterMutations.get(param).remove(0);
}
} else {
// no value, do nothing.
if (value == null || value.trim().equals("")) {
continue;
}
if (param.getName().equals(property.getName())) {
XmlTreeNode[] nodes = model.selectTreeNodes(context.expand(param.getXpath()));
if (parameterMutations.containsKey(param)) {
if (parameterMutations.get(param).size() > 0) {
for (int cnt = 0; cnt < nodes.length; cnt++) {
// find right node
// keeps node start
int start = value.indexOf("<" + nodes[cnt].getNodeName());
int cnt2 = 0;
while (cnt2 < cnt) {
start = value.indexOf("<" + nodes[cnt].getNodeName(), start + 1);
cnt2++;
}
String nodeXml = getXmlForNode(nodes[cnt]);
int end = value.indexOf("<" + nodes[cnt].getNodeName(), start + 1);
if (end <= 0) {
if (nodeXml.endsWith("</" + nodes[cnt].getDomNode().getNodeName() + ">")) {
end = value.indexOf("</" + nodes[cnt].getDomNode().getNodeName() + ">");
} else {
end = value.indexOf(">", value.indexOf("/", start));
}
}
if (end <= 0 || end <= start) {
break;
}
buffer.replace(start, end + 1, parameterMutations.get(param).get(0));
}
params.put(param.getLabel(), parameterMutations.get(param).get(0));
parameterMutations.get(param).remove(0);
}
}
}
}
}
if (model != null) {
property.setValue(buffer.toString());
}
}
}
}
return params;
}
protected void mutateParameters(TestStep testStep, SecurityTestRunContext context) throws XmlException,
IOException {
mutation = true;
// for each parameter
for (SecurityCheckedParameter parameter : getParameterHolder().getParameterList()) {
if (parameter.isChecked()) {
TestProperty property = getTestStep().getProperties().get(parameter.getName());
// check parameter does not have any xpath
if (parameter.getXpath() == null || parameter.getXpath().trim().length() == 0) {
/*
* parameter xpath is not set ignore than ignore this parameter
*/
} else {
// we have xpath but do we have xml which need to mutate
// ignore if there is no value, since than we'll get exception
if (!(property.getValue() == null && property.getDefaultValue() == null)) {
// get value of that property
String value = context.expand(property.getValue());
// we have something that looks like xpath, or hope so.
// XmlObjectTreeModel model = new XmlObjectTreeModel(
// property.getSchemaType().getTypeSystem(),
// XmlObject.Factory.parse( value ) );
XmlObjectTreeModel model = new XmlObjectTreeModel(property.getSchemaType().getTypeSystem(),
XmlUtils.createXmlObject(value));
XmlTreeNode[] nodes = model.selectTreeNodes(context.expand(parameter.getXpath()));
if (nodes.length > 0 && !(nodes[0] instanceof AttributeXmlTreeNode)) {
if (!parameterMutations.containsKey(parameter)) {
parameterMutations.put(parameter, new ArrayList<String>());
}
parameterMutations.get(parameter).addAll(mutateNode(nodes[0], value));
}
}
}
}
}
}
protected Collection<? extends String> mutateNode(XmlTreeNode node, String xml) throws IOException {
ArrayList<String> result = new ArrayList<String>();
String nodeXml = getXmlForNode(node);
// insert new element
if (malformedXmlConfig.getInsertNewElement()) {
StringBuffer buffer = new StringBuffer(nodeXml);
if (nodeXml.endsWith("</" + node.getDomNode().getNodeName() + ">")) {
buffer.insert(nodeXml.indexOf(">") + 1, malformedXmlConfig.getNewElementValue());
} else {
buffer.delete(nodeXml.lastIndexOf("/"), nodeXml.length());
buffer.append(">" + malformedXmlConfig.getNewElementValue() + "</" + node.getDomNode().getNodeName() + ">");
}
result.add(buffer.toString());
}
// change name
if (malformedXmlConfig.getChangeTagName()) {
String original = node.getNodeName();
if (original.toUpperCase().equals(original)) {
result.add(nodeXml.replaceAll(original, original.toLowerCase()));
} else if (original.toLowerCase().equals(original)) {
result.add(nodeXml.replaceAll(original, original.toUpperCase()));
} else {
StringBuffer buffer = new StringBuffer();
// kewl
for (char ch : original.toCharArray()) {
if (Character.isUpperCase(ch)) {
buffer.append(Character.toLowerCase(ch));
} else {
buffer.append(Character.toUpperCase(ch));
}
}
result.add(nodeXml.replaceAll(original, buffer.toString()));
// add '_' before upper case and make uppercase lowercase
// just start tag change
buffer = new StringBuffer();
for (char ch : original.toCharArray()) {
if (Character.isUpperCase(ch)) {
buffer.append("_").append(Character.toLowerCase(ch));
} else {
buffer.append(ch);
}
}
result.add(nodeXml.replaceAll(original, buffer.toString()));
}
}
// leave tag open
if (malformedXmlConfig.getLeaveTagOpen()) {
if (nodeXml.endsWith("</" + node.getDomNode().getNodeName() + ">")) {
// cut end tag
StringBuffer buffer = new StringBuffer(nodeXml);
buffer.delete(buffer.indexOf("</" + node.getDomNode().getNodeName() + ">"), buffer.length());
result.add(buffer.toString());
// cut start tag
buffer = new StringBuffer(nodeXml);
buffer.delete(0, buffer.indexOf(">") + 1);
result.add(buffer.toString());
// cut start tag and remove '/' from end tag
buffer = new StringBuffer(nodeXml);
buffer.delete(0, buffer.indexOf(">") + 1);
buffer.delete(buffer.indexOf("</" + node.getDomNode().getNodeName() + ">") + 1,
buffer.indexOf("</" + node.getDomNode().getNodeName() + ">") + 2);
result.add(buffer.toString());
} else {
// remove '/>' from end of tag
StringBuffer buffer = new StringBuffer(nodeXml);
buffer.delete(nodeXml.lastIndexOf("/"), nodeXml.length());
result.add(buffer.toString());
}
}
if (malformedXmlConfig.getInsertInvalidCharacter()) {
for (char ch : new char[]{'<', '>', '&'}) {
StringBuffer buffer = new StringBuffer(nodeXml);
if (nodeXml.endsWith("</" + node.getDomNode().getNodeName() + ">")) {
buffer.insert(buffer.indexOf("</" + node.getDomNode().getNodeName() + ">"), ch);
} else {
buffer.delete(nodeXml.lastIndexOf("/"), nodeXml.length());
buffer.append('>').append(ch).append("</").append(node.getDomNode().getNodeName()).append(">");
}
result.add(buffer.toString());
}
}
// mutate attributes
if (malformedAttributeConfig.getMutateAttributes()) {
if (malformedAttributeConfig.getAddNewAttribute()) {
if (malformedAttributeConfig.getNewAttributeName().trim().length() > 0) {
// insert new attribute just after node tag
StringBuffer buffer = new StringBuffer(nodeXml);
buffer.insert(node.getNodeName().length() + 1, " " + malformedAttributeConfig.getNewAttributeName()
+ "=" + "\"" + malformedAttributeConfig.getNewAttributeValue() + "\" ");
result.add(buffer.toString());
}
}
if (malformedAttributeConfig.getInsertInvalidChars()) {
if (node.getDomNode().hasAttributes()) {
for (char ch : new char[]{'"', '\'', '<', '>', '&'}) {
// add it at beggining of attribute value
StringBuffer buffer = new StringBuffer(nodeXml);
buffer.insert(buffer.indexOf("=") + 3, ch);
result.add(buffer.toString());
}
}
}
if (malformedAttributeConfig.getLeaveAttributeOpen()) {
if (node.getDomNode().hasAttributes()) {
StringBuffer buffer = new StringBuffer(nodeXml);
buffer.delete(buffer.indexOf("=") + 1, buffer.indexOf("=") + 2);
result.add(buffer.toString());
}
}
}
return result;
}
private String getXmlForNode(XmlTreeNode nodes) {
XmlOptions options = new XmlOptions();
options.setSaveOuter();
options.setSavePrettyPrint();
String xml = nodes.getXmlObject().xmlText(options);
return XmlUtils.removeUnneccessaryNamespaces(xml);
}
@Override
public String getConfigDescription() {
return "Configures Malformed XML Security Scan";
}
@Override
public String getConfigName() {
return "Malformed XML Security Scan";
}
@Override
public String getHelpURL() {
return "http://soapui.org/Security/malformed-xml.html";
}
@Override
public String getType() {
return TYPE;
}
@Override
protected boolean hasNext(TestStep testStep, SecurityTestRunContext context) {
boolean hasNext = false;
if ((parameterMutations == null || parameterMutations.size() == 0) && !mutation) {
if (getParameterHolder().getParameterList().size() > 0) {
hasNext = true;
} else {
hasNext = false;
}
} else {
for (SecurityCheckedParameter param : parameterMutations.keySet()) {
if (parameterMutations.get(param).size() > 0) {
hasNext = true;
break;
}
}
}
if (!hasNext) {
parameterMutations.clear();
mutation = false;
}
return hasNext;
}
@Override
protected void clear() {
parameterMutations.clear();
mutation = false;
}
@Override
public JComponent getAdvancedSettingsPanel() {
if (advancedSettingsPanel == null) {
advancedSettingsPanel = new MalformedXmlAdvancedSettingsPanel(malformedXmlConfig);
}
return advancedSettingsPanel.getPanel();
}
@Override
public void release() {
if (advancedSettingsPanel != null) {
advancedSettingsPanel.release();
}
super.release();
}
}