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 if (
116 this.injectionModel.getMediatorUtils().preferencesUtil().isProcessingCsrf()
117 && this.injectionModel.getMediatorUtils().preferencesUtil().isCsrfUserTag()
118 && (
119 StringUtils.isBlank(this.injectionModel.getMediatorUtils().preferencesUtil().csrfUserTag())
120 || StringUtils.isBlank(this.injectionModel.getMediatorUtils().preferencesUtil().csrfUserTagOutput())
121 )
122 ) {
123 throw new IllegalArgumentException("undefined input or output custom CSRF token");
124 }
125
126 this.initQueryString(urlQueryFixed, selectionCommand);
127 this.initRequest(rawRequest, selectionCommand);
128 this.initHeader(rawHeader, selectionCommand);
129
130 this.injectionModel.getMediatorUtils().connectionUtil().withMethodInjection(methodInjection);
131 this.injectionModel.getMediatorUtils().connectionUtil().withTypeRequest(typeRequest);
132
133 this.injectionModel.setIsInjectingWithoutScan(!isScanning);
134 if (isScanning) {
135 this.injectionModel.beginInjection();
136 } else {
137 new Thread(this.injectionModel::beginInjection, "ThreadBeginInjection").start();
138 }
139 } catch (IllegalArgumentException | MalformedURLException | URISyntaxException e) {
140 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Incorrect URL or setting: {}", e.getMessage());
141
142
143 this.injectionModel.sendToViews(new Seal.EndPreparation());
144 }
145 }
146
147
148
149
150
151 public void checkParametersFormat() throws InjectionFailureException {
152 this.checkOneOrLessStar();
153 this.checkStarMatchMethod();
154 this.checkMethodNotEmpty();
155 this.checkMultipart();
156 if (ParameterUtil.isInvalidName(this.injectionModel.getMediatorUtils().connectionUtil().getTypeRequest())) {
157 throw new InjectionFailureException(String.format(
158 "Illegal method: %s",
159 this.injectionModel.getMediatorUtils().connectionUtil().getTypeRequest()
160 ));
161 }
162 }
163
164
165
166
167 public static boolean isInvalidName(String token) {
168 for (int i = 0 ; i < token.length() ; i++) {
169 char c = token.charAt(i);
170 if (c > 255 || !ParameterUtil.TCHAR[c]) {
171 return true;
172 }
173 }
174 return token.isEmpty();
175 }
176
177 private void checkMultipart() throws InjectionFailureException {
178 this.isMultipartRequest = false;
179
180 if (
181 this.getListHeader()
182 .stream()
183 .filter(entry -> "Content-Type".equals(entry.getKey()))
184 .anyMatch(entry ->
185 entry.getValue() != null
186 && entry.getValue().contains("multipart/form-data")
187 && entry.getValue().contains("boundary=")
188 )
189 ) {
190 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Multipart boundary found in header");
191 Matcher matcherBoundary = Pattern.compile("boundary=([^;]*)").matcher(this.getHeaderFromEntries());
192 if (matcherBoundary.find()) {
193 String boundary = matcherBoundary.group(1);
194 if (!this.rawRequest.contains(boundary)) {
195 throw new InjectionFailureException(
196 String.format("Incorrect multipart data, boundary not found in body: %s", boundary)
197 );
198 } else {
199 this.isMultipartRequest = true;
200 }
201 }
202 }
203 }
204
205 private void checkOneOrLessStar() throws InjectionFailureException {
206 var nbStarAcrossParameters = 0;
207
208 if (this.getQueryStringFromEntries().contains(InjectionModel.STAR)) {
209 nbStarAcrossParameters++;
210 }
211 if (this.getRequestFromEntries().contains(InjectionModel.STAR)) {
212 nbStarAcrossParameters++;
213 }
214 if (
215 this.getHeaderFromEntries().contains(InjectionModel.STAR)
216 && this.getCountHeadersWithStar() > 1
217 ) {
218 nbStarAcrossParameters++;
219 }
220
221 if (
222 nbStarAcrossParameters >= 2
223 || StringUtils.countMatches(this.getQueryStringFromEntries(), "*") >= 2
224 || StringUtils.countMatches(this.getRequestFromEntries(), "*") >= 2
225 || this.getCountHeadersWithStar() > 1
226 ) {
227 throw new InjectionFailureException("param selected or [*] can be only used once in URL, Request or Header");
228 }
229 }
230
231 private long getCountHeadersWithStar() {
232 return this.getListHeader().stream()
233 .filter(s -> s.getValue().contains(InjectionModel.STAR))
234 .filter(s ->
235 !List.of(
236 "accept", "accept-encoding", "accept-language", "access-control-request-headers", "if-match", "if-none-match", "allow"
237 ).contains(s.getKey().toLowerCase())
238 )
239 .count();
240 }
241
242 public void checkStarMatchMethod() throws InjectionFailureException {
243 AbstractMethodInjection methodInjection = this.injectionModel.getMediatorUtils().connectionUtil().getMethodInjection();
244 boolean isCheckingAllParam = this.injectionModel.getMediatorUtils().preferencesUtil().isCheckingAllParam();
245
246 if (
247 this.getQueryStringFromEntries().contains(InjectionModel.STAR)
248 && methodInjection != this.injectionModel.getMediatorMethod().getQuery()
249 && !isCheckingAllParam
250 ) {
251 throw new InjectionFailureException("param in URL selected but method Request or Header selected");
252 } else if (
253 this.getRequestFromEntries().contains(InjectionModel.STAR)
254 && methodInjection != this.injectionModel.getMediatorMethod().getRequest()
255 && !isCheckingAllParam
256 ) {
257 throw new InjectionFailureException("param in Request selected but method URL or Header selected");
258 } else if (
259 this.getHeaderFromEntries().contains(InjectionModel.STAR)
260 && methodInjection != this.injectionModel.getMediatorMethod().getHeader()
261 && !isCheckingAllParam
262 && this.getCountHeadersWithStar() > 0
263 ) {
264 throw new InjectionFailureException("param in Header selected but method URL or Request selected");
265 }
266 }
267
268 public void checkMethodNotEmpty() throws InjectionFailureException {
269 AbstractMethodInjection methodInjection = this.injectionModel.getMediatorUtils().connectionUtil().getMethodInjection();
270
271 if (
272 methodInjection == this.injectionModel.getMediatorMethod().getQuery()
273 && this.getListQueryString().isEmpty()
274 && !this.injectionModel.getMediatorUtils().connectionUtil().getUrlBase().contains(InjectionModel.STAR)
275 ) {
276 throw new InjectionFailureException("empty URL param");
277 } else if (
278 methodInjection == this.injectionModel.getMediatorMethod().getRequest()
279 && this.getListRequest().isEmpty()
280 ) {
281 throw new InjectionFailureException("empty Request param");
282 } else if (
283 methodInjection == this.injectionModel.getMediatorMethod().getHeader()
284 && this.getListHeader().isEmpty()
285 ) {
286 throw new InjectionFailureException("empty Header param");
287 }
288 }
289
290 public String initStar(SimpleEntry<String, String> parameterToInject) {
291 String characterInsertionByUser;
292 if (parameterToInject.getValue().contains(InjectionModel.STAR)) {
293 characterInsertionByUser = parameterToInject.getValue().replace(
294 InjectionModel.STAR,
295 InjectionModel.STAR
296 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
297 );
298 } else {
299 characterInsertionByUser = parameterToInject.getValue()
300 + (parameterToInject.getValue().matches("\\d$") ? "+" : StringUtils.EMPTY)
301 + InjectionModel.STAR
302 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment();
303 }
304
305 parameterToInject.setValue(
306 InjectionModel.STAR
307 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
308 );
309 return characterInsertionByUser;
310 }
311
312 public void initQueryString(String urlQuery) throws MalformedURLException, URISyntaxException {
313 this.initQueryString(urlQuery, StringUtils.EMPTY);
314 }
315
316 public void initQueryString(String urlQuery, String selectionCommand) throws MalformedURLException, URISyntaxException {
317
318 var url = new URI(urlQuery).toURL();
319
320 if (
321 StringUtils.isEmpty(urlQuery)
322 || StringUtils.isEmpty(url.getHost())
323 ) {
324 throw new MalformedURLException("empty URL");
325 }
326
327 this.injectionModel.getMediatorUtils().connectionUtil().setUrlByUser(urlQuery);
328 this.injectionModel.getMediatorUtils().connectionUtil().setUrlBase(urlQuery);
329 this.listQueryString.clear();
330
331
332 var regexQueryString = Pattern.compile("(.*\\?)(.*)").matcher(urlQuery);
333 if (!regexQueryString.find()) {
334 return;
335 }
336
337 this.injectionModel.getMediatorUtils().connectionUtil().setUrlBase(regexQueryString.group(1));
338
339 if (StringUtils.isNotEmpty(url.getQuery())) {
340 this.listQueryString = Pattern.compile("&")
341 .splitAsStream(url.getQuery())
342 .map(keyValue -> Arrays.copyOf(keyValue.split("="), 2))
343 .map(keyValue -> {
344 var paramToAddStar = selectionCommand.replaceAll("^"+ ParameterUtil.PREFIX_COMMAND_QUERY, StringUtils.EMPTY);
345 return new SimpleEntry<>(
346 keyValue[0],
347 (keyValue[1] == null ? StringUtils.EMPTY : keyValue[1])
348 + (paramToAddStar.equals(keyValue[0]) ? InjectionModel.STAR : StringUtils.EMPTY)
349 );
350 }).collect(Collectors.toCollection(CopyOnWriteArrayList::new));
351 }
352 }
353
354 public void initRequest(String rawRequest) {
355 this.initRequest(rawRequest, StringUtils.EMPTY);
356 }
357
358 public void initRequest(String rawRequest, String selectionCommand) {
359 this.rawRequest = rawRequest;
360 this.listRequest.clear();
361 if (StringUtils.isNotEmpty(rawRequest)) {
362 if (this.isMultipartRequest || this.isRequestSoap()) {
363
364 this.listRequest = new CopyOnWriteArrayList<>(List.of(new SimpleEntry<>(
365 rawRequest,
366 StringUtils.EMPTY
367 )));
368 } else {
369 this.listRequest = Pattern.compile("&")
370 .splitAsStream(rawRequest)
371 .map(keyValue -> Arrays.copyOf(keyValue.split("="), 2))
372 .map(keyValue -> {
373 var paramToAddStar = selectionCommand.replaceAll("^"+ ParameterUtil.PREFIX_COMMAND_REQUEST, StringUtils.EMPTY);
374 return new SimpleEntry<>(
375 keyValue[0],
376 (keyValue[1] == null ? StringUtils.EMPTY : keyValue[1].replace("\\n", "\n"))
377 + (paramToAddStar.equals(keyValue[0]) ? InjectionModel.STAR : StringUtils.EMPTY)
378 );
379 }).collect(Collectors.toCollection(CopyOnWriteArrayList::new));
380 }
381 }
382 }
383
384 public void initHeader(String rawHeader) {
385 this.initHeader(rawHeader, StringUtils.EMPTY);
386 }
387
388 public void initHeader(String rawHeader, String selectionCommand) {
389 this.rawHeader = rawHeader;
390 this.listHeader.clear();
391 if (StringUtils.isNotEmpty(rawHeader)) {
392 this.listHeader = Pattern.compile("\\\\r\\\\n")
393 .splitAsStream(rawHeader)
394 .map(keyValue -> Arrays.copyOf(keyValue.split(":"), 2))
395 .map(keyValue -> {
396 var paramToAddStar = selectionCommand.replaceAll("^"+ ParameterUtil.PREFIX_COMMAND_HEADER, StringUtils.EMPTY);
397 return new SimpleEntry<>(
398 keyValue[0],
399 (keyValue[1] == null ? StringUtils.EMPTY : keyValue[1])
400 + (paramToAddStar.equals(keyValue[0]) ? InjectionModel.STAR : StringUtils.EMPTY)
401 );
402 }).collect(Collectors.toCollection(CopyOnWriteArrayList::new));
403 }
404 }
405
406 public String getQueryStringFromEntries() {
407 return this.listQueryString.stream()
408 .filter(Objects::nonNull)
409 .map(entry -> String.format(
410 ParameterUtil.FORMAT_KEY_VALUE,
411 entry.getKey(),
412 entry.getValue())
413 )
414 .collect(Collectors.joining("&"));
415 }
416
417 public String getRequestFromEntries() {
418 return this.listRequest.stream()
419 .filter(Objects::nonNull)
420 .map(entry -> String.format(
421 ParameterUtil.FORMAT_KEY_VALUE,
422 entry.getKey(),
423 StringUtils.isEmpty(entry.getValue()) ? StringUtils.EMPTY : entry.getValue()
424 ))
425 .collect(Collectors.joining("&"));
426 }
427
428 public String getHeaderFromEntries() {
429 return this.listHeader.stream()
430 .filter(Objects::nonNull)
431 .map(entry -> String.format("%s:%s", entry.getKey(), entry.getValue()))
432 .collect(Collectors.joining("\\r\\n"));
433 }
434
435 public boolean isRequestSoap() {
436 return this.rawRequest.trim().matches("(?s)^\\s*(<soapenv:|<\\?xml).*");
437 }
438
439
440
441
442 public String getRawRequest() {
443 return this.rawRequest;
444 }
445
446 public String getRawHeader() {
447 return this.rawHeader;
448 }
449
450 public List<SimpleEntry<String, String>> getListRequest() {
451 return this.listRequest;
452 }
453
454 public List<SimpleEntry<String, String>> getListHeader() {
455 return this.listHeader;
456 }
457
458 public List<SimpleEntry<String, String>> getListQueryString() {
459 return this.listQueryString;
460 }
461
462 public boolean isMultipartRequest() {
463 return this.isMultipartRequest;
464 }
465 }