1 package com.jsql.util;
2
3 import com.jsql.model.InjectionModel;
4 import com.jsql.view.subscriber.Seal;
5 import com.jsql.model.exception.InjectionFailureException;
6 import com.jsql.model.injection.method.AbstractMethodInjection;
7 import org.apache.commons.lang3.StringUtils;
8 import org.apache.logging.log4j.LogManager;
9 import org.apache.logging.log4j.Logger;
10
11 import java.net.IDN;
12 import java.net.MalformedURLException;
13 import java.net.URI;
14 import java.net.URISyntaxException;
15 import java.util.AbstractMap.SimpleEntry;
16 import java.util.Arrays;
17 import java.util.List;
18 import java.util.Objects;
19 import java.util.concurrent.CopyOnWriteArrayList;
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
22 import java.util.stream.Collectors;
23
24 public class ParameterUtil {
25
26 private static final Logger LOGGER = LogManager.getRootLogger();
27
28
29
30
31
32 private List<SimpleEntry<String, String>> listQueryString = new CopyOnWriteArrayList<>();
33
34
35
36
37 private List<SimpleEntry<String, String>> listRequest = new CopyOnWriteArrayList<>();
38
39
40
41
42 private List<SimpleEntry<String, String>> listHeader = new CopyOnWriteArrayList<>();
43
44 private String rawRequest = StringUtils.EMPTY;
45 private String rawHeader = StringUtils.EMPTY;
46 private boolean isMultipartRequest = false;
47
48 public static final String PREFIX_COMMAND_QUERY = "Query#";
49 public static final String PREFIX_COMMAND_REQUEST = "Request#";
50 public static final String PREFIX_COMMAND_HEADER = "Header#";
51 public static final String PREFIX_COMMAND_COOKIE = "Cookie#";
52 private static final String FORMAT_KEY_VALUE = "%s=%s";
53
54
55 private static final boolean[] TCHAR = new boolean[256];
56
57 static {
58 char[] allowedTokenChars = (
59 "!#$%&'*+-.^_`|~0123456789" +
60 "abcdefghijklmnopqrstuvwxyz" +
61 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
62 ).toCharArray();
63 for (char c : allowedTokenChars) {
64 ParameterUtil.TCHAR[c] = true;
65 }
66 }
67
68 private final InjectionModel injectionModel;
69
70 public ParameterUtil(InjectionModel injectionModel) {
71 this.injectionModel = injectionModel;
72 }
73
74
75
76
77
78
79 public void controlInput(
80 String selectionCommand,
81 String urlQuery,
82 String rawRequest,
83 String rawHeader,
84 AbstractMethodInjection methodInjection,
85 String typeRequest,
86 boolean isScanning
87 ) {
88 try {
89 String urlQueryFixed = urlQuery;
90
91 if (urlQueryFixed.isEmpty()) {
92 throw new MalformedURLException("empty URL");
93 } else if (!urlQueryFixed.matches("(?i)^https?://.*")) {
94 if (!urlQueryFixed.matches("(?i)^\\w+://.*")) {
95 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Undefined URL protocol, forcing to [http://]");
96 urlQueryFixed = "http://"+ urlQueryFixed;
97 } else {
98 throw new MalformedURLException("unknown URL protocol");
99 }
100 }
101
102 int port = URI.create(urlQueryFixed).getPort();
103 if (port > 65535) {
104 throw new MalformedURLException("port must be 65535 or lower");
105 }
106 String authority = URI.create(urlQueryFixed).getAuthority();
107 if (authority == null) {
108 throw new MalformedURLException("undefined domain authority");
109 }
110 String authorityPunycode = IDN.toASCII(authority);
111 if (!authority.equals(authorityPunycode)) {
112 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Punycode domain detected, using [{}] instead of [{}]", authorityPunycode, authority);
113 urlQueryFixed = urlQueryFixed.replace(authority, authorityPunycode);
114 }
115
116 this.initQueryString(urlQueryFixed, selectionCommand);
117 this.initRequest(rawRequest, selectionCommand);
118 this.initHeader(rawHeader, selectionCommand);
119
120 this.injectionModel.getMediatorUtils().connectionUtil().withMethodInjection(methodInjection);
121 this.injectionModel.getMediatorUtils().connectionUtil().withTypeRequest(typeRequest);
122
123 if (isScanning) {
124 this.injectionModel.beginInjection();
125 } else {
126 new Thread(this.injectionModel::beginInjection, "ThreadBeginInjection").start();
127 }
128 } catch (IllegalArgumentException | MalformedURLException | URISyntaxException e) {
129 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Incorrect Url: {}", e.getMessage());
130
131
132 this.injectionModel.sendToViews(new Seal.EndPreparation());
133 }
134 }
135
136
137
138
139
140 public void checkParametersFormat() throws InjectionFailureException {
141 this.checkOneOrLessStar();
142 this.checkStarMatchMethod();
143 this.checkMethodNotEmpty();
144 this.checkMultipart();
145 if (ParameterUtil.isInvalidName(this.injectionModel.getMediatorUtils().connectionUtil().getTypeRequest())) {
146 throw new InjectionFailureException(String.format(
147 "Illegal method: %s",
148 this.injectionModel.getMediatorUtils().connectionUtil().getTypeRequest()
149 ));
150 }
151 }
152
153
154
155
156 public static boolean isInvalidName(String token) {
157 for (int i = 0 ; i < token.length() ; i++) {
158 char c = token.charAt(i);
159 if (c > 255 || !ParameterUtil.TCHAR[c]) {
160 return true;
161 }
162 }
163 return token.isEmpty();
164 }
165
166 private void checkMultipart() throws InjectionFailureException {
167 this.isMultipartRequest = false;
168
169 if (
170 this.getListHeader()
171 .stream()
172 .filter(entry -> "Content-Type".equals(entry.getKey()))
173 .anyMatch(entry ->
174 entry.getValue() != null
175 && entry.getValue().contains("multipart/form-data")
176 && entry.getValue().contains("boundary=")
177 )
178 ) {
179 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Multipart boundary found in header");
180 Matcher matcherBoundary = Pattern.compile("boundary=([^;]*)").matcher(this.getHeaderFromEntries());
181 if (matcherBoundary.find()) {
182 String boundary = matcherBoundary.group(1);
183 if (!this.rawRequest.contains(boundary)) {
184 throw new InjectionFailureException(
185 String.format("Incorrect multipart data, boundary not found in body: %s", boundary)
186 );
187 } else {
188 this.isMultipartRequest = true;
189 }
190 }
191 }
192 }
193
194 private void checkOneOrLessStar() throws InjectionFailureException {
195 var nbStarAcrossParameters = 0;
196
197 if (this.getQueryStringFromEntries().contains(InjectionModel.STAR)) {
198 nbStarAcrossParameters++;
199 }
200 if (this.getRequestFromEntries().contains(InjectionModel.STAR)) {
201 nbStarAcrossParameters++;
202 }
203 if (
204 this.getHeaderFromEntries().contains(InjectionModel.STAR)
205 && this.getCountHeadersWithStar() > 1
206 ) {
207 nbStarAcrossParameters++;
208 }
209
210 if (
211 nbStarAcrossParameters >= 2
212 || StringUtils.countMatches(this.getQueryStringFromEntries(), "*") >= 2
213 || StringUtils.countMatches(this.getRequestFromEntries(), "*") >= 2
214 || this.getCountHeadersWithStar() > 1
215 ) {
216 throw new InjectionFailureException("param selected or [*] can be only used once in URL, Request or Header");
217 }
218 }
219
220 private long getCountHeadersWithStar() {
221 return this.getListHeader().stream()
222 .filter(s -> s.getValue().contains(InjectionModel.STAR))
223 .filter(s ->
224 !List.of(
225 "accept", "accept-encoding", "accept-language", "access-control-request-headers", "if-match", "if-none-match", "allow"
226 ).contains(s.getKey().toLowerCase())
227 )
228 .count();
229 }
230
231 public void checkStarMatchMethod() throws InjectionFailureException {
232 AbstractMethodInjection methodInjection = this.injectionModel.getMediatorUtils().connectionUtil().getMethodInjection();
233 boolean isCheckingAllParam = this.injectionModel.getMediatorUtils().preferencesUtil().isCheckingAllParam();
234
235 if (
236 this.getQueryStringFromEntries().contains(InjectionModel.STAR)
237 && methodInjection != this.injectionModel.getMediatorMethod().getQuery()
238 && !isCheckingAllParam
239 ) {
240 throw new InjectionFailureException("param in URL selected but method Request or Header selected");
241 } else if (
242 this.getRequestFromEntries().contains(InjectionModel.STAR)
243 && methodInjection != this.injectionModel.getMediatorMethod().getRequest()
244 && !isCheckingAllParam
245 ) {
246 throw new InjectionFailureException("param in Request selected but method URL or Header selected");
247 } else if (
248 this.getHeaderFromEntries().contains(InjectionModel.STAR)
249 && methodInjection != this.injectionModel.getMediatorMethod().getHeader()
250 && !isCheckingAllParam
251 && this.getCountHeadersWithStar() > 0
252 ) {
253 throw new InjectionFailureException("param in Header selected but method URL or Request selected");
254 }
255 }
256
257 public void checkMethodNotEmpty() throws InjectionFailureException {
258 AbstractMethodInjection methodInjection = this.injectionModel.getMediatorUtils().connectionUtil().getMethodInjection();
259
260 if (
261 methodInjection == this.injectionModel.getMediatorMethod().getQuery()
262 && this.getListQueryString().isEmpty()
263 && !this.injectionModel.getMediatorUtils().connectionUtil().getUrlBase().contains(InjectionModel.STAR)
264 ) {
265 throw new InjectionFailureException("empty URL param");
266 } else if (
267 methodInjection == this.injectionModel.getMediatorMethod().getRequest()
268 && this.getListRequest().isEmpty()
269 ) {
270 throw new InjectionFailureException("empty Request param");
271 } else if (
272 methodInjection == this.injectionModel.getMediatorMethod().getHeader()
273 && this.getListHeader().isEmpty()
274 ) {
275 throw new InjectionFailureException("empty Header param");
276 }
277 }
278
279 public String initStar(SimpleEntry<String, String> parameterToInject) {
280 String characterInsertionByUser;
281 if (parameterToInject.getValue().contains(InjectionModel.STAR)) {
282 characterInsertionByUser = parameterToInject.getValue().replace(
283 InjectionModel.STAR,
284 InjectionModel.STAR
285 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
286 );
287 } else {
288 characterInsertionByUser = parameterToInject.getValue()
289 + (parameterToInject.getValue().matches("\\d$") ? "+" : StringUtils.EMPTY)
290 + InjectionModel.STAR
291 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment();
292 }
293
294 parameterToInject.setValue(
295 InjectionModel.STAR
296 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
297 );
298 return characterInsertionByUser;
299 }
300
301 public void initQueryString(String urlQuery) throws MalformedURLException, URISyntaxException {
302 this.initQueryString(urlQuery, StringUtils.EMPTY);
303 }
304
305 public void initQueryString(String urlQuery, String selectionCommand) throws MalformedURLException, URISyntaxException {
306
307 var url = new URI(urlQuery).toURL();
308
309 if (
310 StringUtils.isEmpty(urlQuery)
311 || StringUtils.isEmpty(url.getHost())
312 ) {
313 throw new MalformedURLException("empty URL");
314 }
315
316 this.injectionModel.getMediatorUtils().connectionUtil().setUrlByUser(urlQuery);
317 this.injectionModel.getMediatorUtils().connectionUtil().setUrlBase(urlQuery);
318 this.listQueryString.clear();
319
320
321 var regexQueryString = Pattern.compile("(.*\\?)(.*)").matcher(urlQuery);
322 if (!regexQueryString.find()) {
323 return;
324 }
325
326 this.injectionModel.getMediatorUtils().connectionUtil().setUrlBase(regexQueryString.group(1));
327
328 if (StringUtils.isNotEmpty(url.getQuery())) {
329 this.listQueryString = Pattern.compile("&")
330 .splitAsStream(url.getQuery())
331 .map(keyValue -> Arrays.copyOf(keyValue.split("="), 2))
332 .map(keyValue -> {
333 var paramToAddStar = selectionCommand.replaceAll("^"+ ParameterUtil.PREFIX_COMMAND_QUERY, StringUtils.EMPTY);
334 return new SimpleEntry<>(
335 keyValue[0],
336 (keyValue[1] == null ? StringUtils.EMPTY : keyValue[1])
337 + (paramToAddStar.equals(keyValue[0]) ? InjectionModel.STAR : StringUtils.EMPTY)
338 );
339 }).collect(Collectors.toCollection(CopyOnWriteArrayList::new));
340 }
341 }
342
343 public void initRequest(String rawRequest) {
344 this.initRequest(rawRequest, StringUtils.EMPTY);
345 }
346
347 public void initRequest(String rawRequest, String selectionCommand) {
348 this.rawRequest = rawRequest;
349 this.listRequest.clear();
350 if (StringUtils.isNotEmpty(rawRequest)) {
351 if (this.isMultipartRequest || this.isRequestSoap()) {
352
353 this.listRequest = new CopyOnWriteArrayList<>(List.of(new SimpleEntry<>(
354 rawRequest,
355 StringUtils.EMPTY
356 )));
357 } else {
358 this.listRequest = Pattern.compile("&")
359 .splitAsStream(rawRequest)
360 .map(keyValue -> Arrays.copyOf(keyValue.split("="), 2))
361 .map(keyValue -> {
362 var paramToAddStar = selectionCommand.replaceAll("^"+ ParameterUtil.PREFIX_COMMAND_REQUEST, StringUtils.EMPTY);
363 return new SimpleEntry<>(
364 keyValue[0],
365 (keyValue[1] == null ? StringUtils.EMPTY : keyValue[1].replace("\\n", "\n"))
366 + (paramToAddStar.equals(keyValue[0]) ? InjectionModel.STAR : StringUtils.EMPTY)
367 );
368 }).collect(Collectors.toCollection(CopyOnWriteArrayList::new));
369 }
370 }
371 }
372
373 public void initHeader(String rawHeader) {
374 this.initHeader(rawHeader, StringUtils.EMPTY);
375 }
376
377 public void initHeader(String rawHeader, String selectionCommand) {
378 this.rawHeader = rawHeader;
379 this.listHeader.clear();
380 if (StringUtils.isNotEmpty(rawHeader)) {
381 this.listHeader = Pattern.compile("\\\\r\\\\n")
382 .splitAsStream(rawHeader)
383 .map(keyValue -> Arrays.copyOf(keyValue.split(":"), 2))
384 .map(keyValue -> {
385 var paramToAddStar = selectionCommand.replaceAll("^"+ ParameterUtil.PREFIX_COMMAND_HEADER, StringUtils.EMPTY);
386 return new SimpleEntry<>(
387 keyValue[0],
388 (keyValue[1] == null ? StringUtils.EMPTY : keyValue[1])
389 + (paramToAddStar.equals(keyValue[0]) ? InjectionModel.STAR : StringUtils.EMPTY)
390 );
391 }).collect(Collectors.toCollection(CopyOnWriteArrayList::new));
392 }
393 }
394
395 public String getQueryStringFromEntries() {
396 return this.listQueryString.stream()
397 .filter(Objects::nonNull)
398 .map(entry -> String.format(
399 ParameterUtil.FORMAT_KEY_VALUE,
400 entry.getKey(),
401 entry.getValue())
402 )
403 .collect(Collectors.joining("&"));
404 }
405
406 public String getRequestFromEntries() {
407 return this.listRequest.stream()
408 .filter(Objects::nonNull)
409 .map(entry -> String.format(
410 ParameterUtil.FORMAT_KEY_VALUE,
411 entry.getKey(),
412 StringUtils.isEmpty(entry.getValue()) ? StringUtils.EMPTY : entry.getValue()
413 ))
414 .collect(Collectors.joining("&"));
415 }
416
417 public String getHeaderFromEntries() {
418 return this.listHeader.stream()
419 .filter(Objects::nonNull)
420 .map(entry -> String.format("%s:%s", entry.getKey(), entry.getValue()))
421 .collect(Collectors.joining("\\r\\n"));
422 }
423
424 public boolean isRequestSoap() {
425 return this.rawRequest.trim().matches("(?s)^\\s*(<soapenv:|<\\?xml).*");
426 }
427
428
429
430
431 public String getRawRequest() {
432 return this.rawRequest;
433 }
434
435 public String getRawHeader() {
436 return this.rawHeader;
437 }
438
439 public List<SimpleEntry<String, String>> getListRequest() {
440 return this.listRequest;
441 }
442
443 public List<SimpleEntry<String, String>> getListHeader() {
444 return this.listHeader;
445 }
446
447 public List<SimpleEntry<String, String>> getListQueryString() {
448 return this.listQueryString;
449 }
450
451 public boolean isMultipartRequest() {
452 return this.isMultipartRequest;
453 }
454 }