ParameterUtil.java
package com.jsql.util;
import com.jsql.model.InjectionModel;
import com.jsql.view.subscriber.Seal;
import com.jsql.model.exception.InjectionFailureException;
import com.jsql.model.injection.method.AbstractMethodInjection;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.net.IDN;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class ParameterUtil {
private static final Logger LOGGER = LogManager.getRootLogger();
/**
* Query string built from the URL submitted by user.
*/
// Fix #95787: ConcurrentModificationException
private List<SimpleEntry<String, String>> listQueryString = new CopyOnWriteArrayList<>();
/**
* Request submitted by user.
*/
private List<SimpleEntry<String, String>> listRequest = new CopyOnWriteArrayList<>();
/**
* Header submitted by user.
*/
private List<SimpleEntry<String, String>> listHeader = new CopyOnWriteArrayList<>();
private String rawRequest = StringUtils.EMPTY;
private String rawHeader = StringUtils.EMPTY;
private boolean isMultipartRequest = false;
public static final String PREFIX_COMMAND_QUERY = "Query#";
public static final String PREFIX_COMMAND_REQUEST = "Request#";
public static final String PREFIX_COMMAND_HEADER = "Header#";
public static final String PREFIX_COMMAND_COOKIE = "Cookie#";
private static final String FORMAT_KEY_VALUE = "%s=%s";
// ABNF primitives defined in RFC 7230
private static final boolean[] TCHAR = new boolean[256];
static {
char[] allowedTokenChars = (
"!#$%&'*+-.^_`|~0123456789" +
"abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
).toCharArray();
for (char c : allowedTokenChars) {
ParameterUtil.TCHAR[c] = true;
}
}
private final InjectionModel injectionModel;
public ParameterUtil(InjectionModel injectionModel) {
this.injectionModel = injectionModel;
}
/**
* Send each parameter from the GUI to the model in order to
* start the preparation of injection, the injection process is
* started in a new thread via model function inputValidation().
*/
public void controlInput(
String selectionCommand,
String urlQuery,
String rawRequest,
String rawHeader,
AbstractMethodInjection methodInjection,
String typeRequest,
boolean isScanning
) {
try {
String urlQueryFixed = urlQuery;
// Keep single check
if (urlQueryFixed.isEmpty()) {
throw new MalformedURLException("empty URL");
} else if (!urlQueryFixed.matches("(?i)^https?://.*")) {
if (!urlQueryFixed.matches("(?i)^\\w+://.*")) {
LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Undefined URL protocol, forcing to [http://]");
urlQueryFixed = "http://"+ urlQueryFixed;
} else {
throw new MalformedURLException("unknown URL protocol");
}
}
int port = URI.create(urlQueryFixed).getPort();
if (port > 65535) { // Fix #96227: IllegalArgumentException on checkConnectionResponse()
throw new MalformedURLException("port must be 65535 or lower");
}
String authority = URI.create(urlQueryFixed).getAuthority();
if (authority == null) {
throw new MalformedURLException("undefined domain authority");
}
String authorityPunycode = IDN.toASCII(authority);
if (!authority.equals(authorityPunycode)) {
LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Punycode domain detected, using [{}] instead of [{}]", authorityPunycode, authority);
urlQueryFixed = urlQueryFixed.replace(authority, authorityPunycode);
}
this.initQueryString(urlQueryFixed, selectionCommand);
this.initRequest(rawRequest, selectionCommand);
this.initHeader(rawHeader, selectionCommand);
this.injectionModel.getMediatorUtils().connectionUtil().withMethodInjection(methodInjection);
this.injectionModel.getMediatorUtils().connectionUtil().withTypeRequest(typeRequest);
if (isScanning) {
this.injectionModel.beginInjection();
} else {
new Thread(this.injectionModel::beginInjection, "ThreadBeginInjection").start(); // in thread
}
} catch (IllegalArgumentException | MalformedURLException | URISyntaxException e) {
LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Incorrect Url: {}", e.getMessage());
// Incorrect URL, reset the start button
this.injectionModel.sendToViews(new Seal.EndPreparation());
}
}
/**
* Check integrity of parameters defined by user.
* @throws InjectionFailureException when params integrity is failure
*/
public void checkParametersFormat() throws InjectionFailureException {
this.checkOneOrLessStar();
this.checkStarMatchMethod();
this.checkMethodNotEmpty();
this.checkMultipart();
if (ParameterUtil.isInvalidName(this.injectionModel.getMediatorUtils().connectionUtil().getTypeRequest())) {
throw new InjectionFailureException(String.format(
"Illegal method: %s",
this.injectionModel.getMediatorUtils().connectionUtil().getTypeRequest()
));
}
}
/*
* Validates an RFC 7230 field-name.
*/
public static boolean isInvalidName(String token) {
for (int i = 0 ; i < token.length() ; i++) {
char c = token.charAt(i);
if (c > 255 || !ParameterUtil.TCHAR[c]) {
return true;
}
}
return token.isEmpty();
}
private void checkMultipart() throws InjectionFailureException {
this.isMultipartRequest = false;
if (
this.getListHeader()
.stream()
.filter(entry -> "Content-Type".equals(entry.getKey()))
.anyMatch(entry ->
entry.getValue() != null
&& entry.getValue().contains("multipart/form-data")
&& entry.getValue().contains("boundary=")
)
) {
LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Multipart boundary found in header");
Matcher matcherBoundary = Pattern.compile("boundary=([^;]*)").matcher(this.getHeaderFromEntries());
if (matcherBoundary.find()) {
String boundary = matcherBoundary.group(1);
if (!this.rawRequest.contains(boundary)) {
throw new InjectionFailureException(
String.format("Incorrect multipart data, boundary not found in body: %s", boundary)
);
} else {
this.isMultipartRequest = true;
}
}
}
}
private void checkOneOrLessStar() throws InjectionFailureException {
var nbStarAcrossParameters = 0;
if (this.getQueryStringFromEntries().contains(InjectionModel.STAR)) {
nbStarAcrossParameters++;
}
if (this.getRequestFromEntries().contains(InjectionModel.STAR)) {
nbStarAcrossParameters++;
}
if (
this.getHeaderFromEntries().contains(InjectionModel.STAR)
&& this.getCountHeadersWithStar() > 1
) {
nbStarAcrossParameters++;
}
if (
nbStarAcrossParameters >= 2
|| StringUtils.countMatches(this.getQueryStringFromEntries(), "*") >= 2
|| StringUtils.countMatches(this.getRequestFromEntries(), "*") >= 2
|| this.getCountHeadersWithStar() > 1
) {
throw new InjectionFailureException("param selected or [*] can be only used once in URL, Request or Header");
}
}
private long getCountHeadersWithStar() {
return this.getListHeader().stream()
.filter(s -> s.getValue().contains(InjectionModel.STAR))
.filter(s ->
!List.of(
"accept", "accept-encoding", "accept-language", "access-control-request-headers", "if-match", "if-none-match", "allow"
).contains(s.getKey().toLowerCase())
)
.count();
}
public void checkStarMatchMethod() throws InjectionFailureException {
AbstractMethodInjection methodInjection = this.injectionModel.getMediatorUtils().connectionUtil().getMethodInjection();
boolean isCheckingAllParam = this.injectionModel.getMediatorUtils().preferencesUtil().isCheckingAllParam();
if (
this.getQueryStringFromEntries().contains(InjectionModel.STAR)
&& methodInjection != this.injectionModel.getMediatorMethod().getQuery()
&& !isCheckingAllParam
) {
throw new InjectionFailureException("param in URL selected but method Request or Header selected");
} else if (
this.getRequestFromEntries().contains(InjectionModel.STAR)
&& methodInjection != this.injectionModel.getMediatorMethod().getRequest()
&& !isCheckingAllParam
) {
throw new InjectionFailureException("param in Request selected but method URL or Header selected");
} else if (
this.getHeaderFromEntries().contains(InjectionModel.STAR)
&& methodInjection != this.injectionModel.getMediatorMethod().getHeader()
&& !isCheckingAllParam
&& this.getCountHeadersWithStar() > 0
) {
throw new InjectionFailureException("param in Header selected but method URL or Request selected");
}
}
public void checkMethodNotEmpty() throws InjectionFailureException {
AbstractMethodInjection methodInjection = this.injectionModel.getMediatorUtils().connectionUtil().getMethodInjection();
if (
methodInjection == this.injectionModel.getMediatorMethod().getQuery()
&& this.getListQueryString().isEmpty()
&& !this.injectionModel.getMediatorUtils().connectionUtil().getUrlBase().contains(InjectionModel.STAR)
) {
throw new InjectionFailureException("empty URL param");
} else if (
methodInjection == this.injectionModel.getMediatorMethod().getRequest()
&& this.getListRequest().isEmpty()
) {
throw new InjectionFailureException("empty Request param");
} else if (
methodInjection == this.injectionModel.getMediatorMethod().getHeader()
&& this.getListHeader().isEmpty()
) {
throw new InjectionFailureException("empty Header param");
}
}
public String initStar(SimpleEntry<String, String> parameterToInject) {
String characterInsertionByUser;
if (parameterToInject.getValue().contains(InjectionModel.STAR)) { // star already set into value when checking all cookies
characterInsertionByUser = parameterToInject.getValue().replace(
InjectionModel.STAR,
InjectionModel.STAR
+ this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
);
} else {
characterInsertionByUser = parameterToInject.getValue()
+ (parameterToInject.getValue().matches("\\d$") ? "+" : StringUtils.EMPTY) // required when char insertion numeric by user and not found, space not working as zipped
+ InjectionModel.STAR
+ this.injectionModel.getMediatorEngine().getEngine().instance().endingComment();
}
// char insertion contains remainder payload when found
parameterToInject.setValue(
InjectionModel.STAR
+ this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
);
return characterInsertionByUser;
}
public void initQueryString(String urlQuery) throws MalformedURLException, URISyntaxException {
this.initQueryString(urlQuery, StringUtils.EMPTY);
}
public void initQueryString(String urlQuery, String selectionCommand) throws MalformedURLException, URISyntaxException {
// Format and get rid of anchor fragment using native URL
var url = new URI(urlQuery).toURL();
if (
StringUtils.isEmpty(urlQuery)
|| StringUtils.isEmpty(url.getHost())
) {
throw new MalformedURLException("empty URL");
}
this.injectionModel.getMediatorUtils().connectionUtil().setUrlByUser(urlQuery);
this.injectionModel.getMediatorUtils().connectionUtil().setUrlBase(urlQuery);
this.listQueryString.clear();
// Parse url and GET query string
var regexQueryString = Pattern.compile("(.*\\?)(.*)").matcher(urlQuery);
if (!regexQueryString.find()) {
return;
}
this.injectionModel.getMediatorUtils().connectionUtil().setUrlBase(regexQueryString.group(1));
if (StringUtils.isNotEmpty(url.getQuery())) {
this.listQueryString = Pattern.compile("&")
.splitAsStream(url.getQuery())
.map(keyValue -> Arrays.copyOf(keyValue.split("="), 2))
.map(keyValue -> {
var paramToAddStar = selectionCommand.replaceAll("^"+ ParameterUtil.PREFIX_COMMAND_QUERY, StringUtils.EMPTY);
return new SimpleEntry<>(
keyValue[0],
(keyValue[1] == null ? StringUtils.EMPTY : keyValue[1])
+ (paramToAddStar.equals(keyValue[0]) ? InjectionModel.STAR : StringUtils.EMPTY)
);
}).collect(Collectors.toCollection(CopyOnWriteArrayList::new)); // Fix #96224: ConcurrentModificationException
}
}
public void initRequest(String rawRequest) {
this.initRequest(rawRequest, StringUtils.EMPTY);
}
public void initRequest(String rawRequest, String selectionCommand) {
this.rawRequest = rawRequest;
this.listRequest.clear();
if (StringUtils.isNotEmpty(rawRequest)) {
if (this.isMultipartRequest || this.isRequestSoap()) {
// Pass request containing star * param without any parsing
this.listRequest = new CopyOnWriteArrayList<>(List.of(new SimpleEntry<>(
rawRequest,
StringUtils.EMPTY
)));
} else {
this.listRequest = Pattern.compile("&")
.splitAsStream(rawRequest)
.map(keyValue -> Arrays.copyOf(keyValue.split("="), 2))
.map(keyValue -> {
var paramToAddStar = selectionCommand.replaceAll("^"+ ParameterUtil.PREFIX_COMMAND_REQUEST, StringUtils.EMPTY);
return new SimpleEntry<>(
keyValue[0],
(keyValue[1] == null ? StringUtils.EMPTY : keyValue[1].replace("\\n", "\n"))
+ (paramToAddStar.equals(keyValue[0]) ? InjectionModel.STAR : StringUtils.EMPTY)
);
}).collect(Collectors.toCollection(CopyOnWriteArrayList::new));
}
}
}
public void initHeader(String rawHeader) {
this.initHeader(rawHeader, StringUtils.EMPTY);
}
public void initHeader(String rawHeader, String selectionCommand) {
this.rawHeader = rawHeader;
this.listHeader.clear();
if (StringUtils.isNotEmpty(rawHeader)) {
this.listHeader = Pattern.compile("\\\\r\\\\n")
.splitAsStream(rawHeader)
.map(keyValue -> Arrays.copyOf(keyValue.split(":"), 2))
.map(keyValue -> {
var paramToAddStar = selectionCommand.replaceAll("^"+ ParameterUtil.PREFIX_COMMAND_HEADER, StringUtils.EMPTY);
return new SimpleEntry<>(
keyValue[0],
(keyValue[1] == null ? StringUtils.EMPTY : keyValue[1])
+ (paramToAddStar.equals(keyValue[0]) ? InjectionModel.STAR : StringUtils.EMPTY)
);
}).collect(Collectors.toCollection(CopyOnWriteArrayList::new));
}
}
public String getQueryStringFromEntries() {
return this.listQueryString.stream()
.filter(Objects::nonNull)
.map(entry -> String.format(
ParameterUtil.FORMAT_KEY_VALUE,
entry.getKey(),
entry.getValue())
)
.collect(Collectors.joining("&"));
}
public String getRequestFromEntries() {
return this.listRequest.stream()
.filter(Objects::nonNull)
.map(entry -> String.format(
ParameterUtil.FORMAT_KEY_VALUE,
entry.getKey(),
StringUtils.isEmpty(entry.getValue()) ? StringUtils.EMPTY : entry.getValue()
))
.collect(Collectors.joining("&"));
}
public String getHeaderFromEntries() {
return this.listHeader.stream()
.filter(Objects::nonNull)
.map(entry -> String.format("%s:%s", entry.getKey(), entry.getValue()))
.collect(Collectors.joining("\\r\\n"));
}
public boolean isRequestSoap() {
return this.rawRequest.trim().matches("(?s)^\\s*(<soapenv:|<\\?xml).*");
}
// Getters / setters
public String getRawRequest() {
return this.rawRequest;
}
public String getRawHeader() {
return this.rawHeader;
}
public List<SimpleEntry<String, String>> getListRequest() {
return this.listRequest;
}
public List<SimpleEntry<String, String>> getListHeader() {
return this.listHeader;
}
public List<SimpleEntry<String, String>> getListQueryString() {
return this.listQueryString;
}
public boolean isMultipartRequest() {
return this.isMultipartRequest;
}
}