View Javadoc
1   /*******************************************************************************
2    * Copyhacked (H) 2012-2025.
3    * This program and the accompanying materials
4    * are made available under no term at all, use it like
5    * you want, but share and discuss it
6    * every time possible with every body.
7    * 
8    * Contributors:
9    *      ron190 at ymail dot com - initial implementation
10   ******************************************************************************/
11  package com.jsql.model;
12  
13  import com.jsql.model.accessible.DataAccess;
14  import com.jsql.model.accessible.ResourceAccess;
15  import com.jsql.model.bean.util.Header;
16  import com.jsql.model.bean.util.Interaction;
17  import com.jsql.model.bean.util.Request;
18  import com.jsql.model.exception.JSqlException;
19  import com.jsql.model.exception.JSqlRuntimeException;
20  import com.jsql.model.injection.method.AbstractMethodInjection;
21  import com.jsql.model.injection.method.MediatorMethod;
22  import com.jsql.model.injection.strategy.MediatorStrategy;
23  import com.jsql.model.injection.strategy.blind.AbstractCallableBinary;
24  import com.jsql.model.injection.vendor.MediatorVendor;
25  import com.jsql.model.injection.vendor.model.VendorYaml;
26  import com.jsql.util.*;
27  import com.jsql.util.GitUtil.ShowOnConsole;
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.logging.log4j.LogManager;
30  import org.apache.logging.log4j.Logger;
31  
32  import javax.swing.*;
33  import java.awt.*;
34  import java.io.IOException;
35  import java.io.Serializable;
36  import java.net.*;
37  import java.net.http.HttpRequest;
38  import java.net.http.HttpRequest.BodyPublishers;
39  import java.net.http.HttpRequest.Builder;
40  import java.net.http.HttpResponse;
41  import java.net.http.HttpResponse.BodyHandlers;
42  import java.nio.charset.StandardCharsets;
43  import java.text.DecimalFormat;
44  import java.time.Duration;
45  import java.util.AbstractMap.SimpleEntry;
46  import java.util.EnumMap;
47  import java.util.Map;
48  import java.util.regex.Matcher;
49  import java.util.stream.Collectors;
50  import java.util.stream.Stream;
51  
52  /**
53   * Model class of MVC pattern for processing SQL injection automatically.<br>
54   * Different views can be attached to this observable, like Swing or command line, in order to separate
55   * the functional job from the graphical processing.<br>
56   * The Model has a specific database vendor and strategy which run an automatic injection to get name of
57   * databases, tables, columns and values, and it can also retrieve resources like files and shell.<br>
58   * Tasks are run in multi-threads in general to speed the process.
59   */
60  public class InjectionModel extends AbstractModelObservable implements Serializable {
61      
62      /**
63       * Log4j logger sent to view.
64       */
65      private static final Logger LOGGER = LogManager.getRootLogger();
66      
67      private final transient MediatorVendor mediatorVendor = new MediatorVendor(this);
68      private final transient MediatorMethod mediatorMethod = new MediatorMethod(this);
69      private final transient MediatorUtils mediatorUtils;
70      private final transient MediatorStrategy mediatorStrategy;
71      private final transient PropertiesUtil propertiesUtil = new PropertiesUtil();
72      private final transient DataAccess dataAccess = new DataAccess(this);
73      private final transient ResourceAccess resourceAccess = new ResourceAccess(this);
74      
75      public static final String STAR = "*";
76      public static final String BR = "<br>&#10;";
77  
78      /**
79       * initialUrl transformed to a correct injection url.
80       */
81      private String indexesInUrl = StringUtils.EMPTY;
82      private String analysisReport = StringUtils.EMPTY;
83  
84      /**
85       * Allow to directly start an injection after a failed one
86       * without asking the user 'Start a new injection?'.
87       */
88      private boolean shouldErasePreviousInjection = false;
89      private boolean isScanning = false;
90  
91      public InjectionModel() {
92          this.mediatorStrategy = new MediatorStrategy(this);
93          this.mediatorUtils = new MediatorUtils();
94          this.mediatorUtils.setCertificateUtil(new CertificateUtil());
95          this.mediatorUtils.setPropertiesUtil(this.propertiesUtil);
96          this.mediatorUtils.setConnectionUtil(new ConnectionUtil(this));
97          this.mediatorUtils.setAuthenticationUtil(new AuthenticationUtil());
98          this.mediatorUtils.setGitUtil(new GitUtil(this));
99          this.mediatorUtils.setHeaderUtil(new HeaderUtil(this));
100         this.mediatorUtils.setParameterUtil(new ParameterUtil(this));
101         this.mediatorUtils.setExceptionUtil(new ExceptionUtil(this));
102         this.mediatorUtils.setSoapUtil(new SoapUtil(this));
103         this.mediatorUtils.setMultipartUtil(new MultipartUtil(this));
104         this.mediatorUtils.setCookiesUtil(new CookiesUtil(this));
105         this.mediatorUtils.setJsonUtil(new JsonUtil(this));
106         this.mediatorUtils.setPreferencesUtil(new PreferencesUtil());
107         this.mediatorUtils.setProxyUtil(new ProxyUtil());
108         this.mediatorUtils.setThreadUtil(new ThreadUtil(this));
109         this.mediatorUtils.setTamperingUtil(new TamperingUtil());
110         this.mediatorUtils.setUserAgentUtil(new UserAgentUtil());
111         this.mediatorUtils.setCsrfUtil(new CsrfUtil(this));
112         this.mediatorUtils.setFormUtil(new FormUtil(this));
113         this.mediatorUtils.setDigestUtil(new DigestUtil(this));
114     }
115 
116     /**
117      * Reset each injection attributes: Database metadata, General Thread status, Strategy.
118      */
119     public void resetModel() {
120         this.mediatorStrategy.getSpecificUnion().setVisibleIndex(null);
121         
122         this.mediatorStrategy.getUnion().setApplicable(false);
123         this.mediatorStrategy.getError().setApplicable(false);
124         this.mediatorStrategy.getBlind().setApplicable(false);
125         this.mediatorStrategy.getMultibit().setApplicable(false);
126         this.mediatorStrategy.getTime().setApplicable(false);
127         this.mediatorStrategy.getStack().setApplicable(false);
128         this.mediatorStrategy.setStrategy(null);
129 
130         this.indexesInUrl = StringUtils.EMPTY;
131         this.analysisReport = StringUtils.EMPTY;
132         this.isStoppedByUser = false;
133         this.shouldErasePreviousInjection = false;
134 
135         this.mediatorUtils.getCsrfUtil().setTokenCsrf(null);
136         this.mediatorUtils.getDigestUtil().setTokenDigest(null);
137         this.mediatorUtils.getThreadUtil().reset();
138     }
139 
140     /**
141      * Prepare the injection process, can be interrupted by the user (via shouldStopAll).
142      * Erase all attributes eventually defined in a previous injection.
143      * Run by Scan, Standard and TU.
144      */
145     public void beginInjection() {
146         this.resetModel();
147         try {
148             if (this.mediatorUtils.getProxyUtil().isNotLive(ShowOnConsole.YES)) {
149                 return;
150             }
151             LOGGER.log(
152                 LogLevelUtil.CONSOLE_INFORM,
153                 "{}: {}",
154                 () -> I18nUtil.valueByKey("LOG_START_INJECTION"),
155                 () -> this.mediatorUtils.getConnectionUtil().getUrlByUser()
156             );
157             
158             // Check general integrity if user's parameters
159             this.mediatorUtils.getParameterUtil().checkParametersFormat();
160             this.mediatorUtils.getConnectionUtil().testConnection();
161 
162             // TODO Check all path params
163             boolean hasFoundInjection = this.mediatorMethod.getQuery().testParameters(false);
164             hasFoundInjection = this.mediatorUtils.getMultipartUtil().testParameters(hasFoundInjection);
165             hasFoundInjection = this.mediatorUtils.getSoapUtil().testParameters(hasFoundInjection);
166             hasFoundInjection = this.mediatorMethod.getRequest().testParameters(hasFoundInjection);
167             hasFoundInjection = this.mediatorMethod.getHeader().testParameters(hasFoundInjection);
168             hasFoundInjection = this.mediatorUtils.getCookiesUtil().testParameters(hasFoundInjection);
169 
170             if (hasFoundInjection && !this.isScanning) {
171                 if (!this.getMediatorUtils().getPreferencesUtil().isNotShowingVulnReport()) {
172                     var requestSetVendor = new Request();
173                     requestSetVendor.setMessage(Interaction.CREATE_ANALYSIS_REPORT);
174                     requestSetVendor.setParameters(this.analysisReport);
175                     this.sendToViews(requestSetVendor);
176                 }
177                 if (this.getMediatorUtils().getPreferencesUtil().isZipStrategy()) {
178                     LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Using Zip mode for reduced query size");
179                 } else if (this.getMediatorUtils().getPreferencesUtil().isDiosStrategy()) {
180                     LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Using Dump In One Shot strategy for single query dump");
181                 }
182                 if (!this.mediatorUtils.getPreferencesUtil().isNotInjectingMetadata()) {
183                     this.dataAccess.getDatabaseInfos();
184                 }
185                 this.dataAccess.listDatabases();
186             }
187             
188             LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, () -> I18nUtil.valueByKey("LOG_DONE"));
189             
190             this.shouldErasePreviousInjection = true;
191         } catch (InterruptedException e) {
192             LOGGER.log(LogLevelUtil.IGNORE, e, e);
193             Thread.currentThread().interrupt();
194         } catch (JSqlRuntimeException | JSqlException | IOException e) {  // Catch expected exceptions only
195             if (e.getMessage() == null) {
196                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Interruption: {}", InjectionModel.getImplicitReason(e));
197             } else {
198                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Interruption: {}", e.getMessage());
199             }
200         } finally {
201             var request = new Request();
202             request.setMessage(Interaction.END_PREPARATION);
203             this.sendToViews(request);
204         }
205     }
206     
207     public static String getImplicitReason(Throwable e) {
208         String message = e.getClass().getSimpleName();
209         if (e.getMessage() != null) {
210             message += ": "+ e.getMessage();
211         }
212         if (e.getCause() != null && !e.equals(e.getCause())) {
213             message += " > "+ InjectionModel.getImplicitReason(e.getCause());
214         }
215         return message;
216     }
217     
218     /**
219      * Run an HTTP connection to the web server.
220      * @param dataInjection SQL query
221      * @return source code of current page
222      */
223     @Override
224     public String inject(
225         String dataInjection,
226         boolean isUsingIndex,
227         String metadataInjectionProcess,
228         AbstractCallableBinary<?> callableBoolean,
229         boolean isReport
230     ) {
231         // Temporary url, we go from "select 1,2,3,4..." to "select 1,([complex query]),2...", but keep initial url
232         String urlInjection = this.mediatorUtils.getConnectionUtil().getUrlBase();
233         urlInjection = this.mediatorStrategy.buildPath(urlInjection, isUsingIndex, dataInjection);
234         urlInjection = StringUtil.cleanSql(urlInjection.trim());
235 
236         URL urlObject;
237         try {  // TODO Keep only a single check
238             urlObject = new URI(urlInjection).toURL();
239         } catch (MalformedURLException | URISyntaxException e) {
240             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, String.format("Incorrect Query Url: %s", e.getMessage()));
241             return StringUtils.EMPTY;
242         }
243 
244         Map<Header, Object> msgHeader = new EnumMap<>(Header.class);
245         urlObject = this.initQueryString(  // TODO identique urlInjection == urlObject
246             isUsingIndex,
247             urlInjection,
248             dataInjection,
249             urlObject,
250             msgHeader
251         );
252         
253         String pageSource = StringUtils.EMPTY;
254         
255         // Define the connection
256         try {
257             var httpRequestBuilder = HttpRequest.newBuilder()
258                 .uri(URI.create(urlObject.toString()))
259                 .setHeader(HeaderUtil.CONTENT_TYPE_REQUEST, "text/plain")
260                 .timeout(Duration.ofSeconds(15));
261             
262             this.mediatorUtils.getCsrfUtil().addHeaderToken(httpRequestBuilder);
263             this.mediatorUtils.getDigestUtil().addHeaderToken(httpRequestBuilder);
264             this.mediatorUtils.getConnectionUtil().setCustomUserAgent(httpRequestBuilder);
265 
266             String body = this.initRequest(isUsingIndex, dataInjection, httpRequestBuilder, msgHeader);
267             this.initHeader(isUsingIndex, dataInjection, httpRequestBuilder);
268             
269             var httpRequest = httpRequestBuilder.build();
270 
271             if (isReport) {
272                 Color colorReport = UIManager.getColor("TextArea.inactiveForeground");
273                 String report = InjectionModel.BR + StringUtil.formatReport(colorReport, "Method: ") + httpRequest.method();
274                 report += InjectionModel.BR + StringUtil.formatReport(colorReport, "Path: ") + httpRequest.uri().getPath();
275                 if (httpRequest.uri().getQuery() != null) {
276                     report += InjectionModel.BR + StringUtil.formatReport(colorReport, "Query: ") + httpRequest.uri().getQuery();
277                 }
278                 if (
279                     !(this.mediatorUtils.getParameterUtil().getListRequest().isEmpty()
280                     && this.mediatorUtils.getCsrfUtil().getTokenCsrf() == null)
281                 ) {
282                     report += InjectionModel.BR + StringUtil.formatReport(colorReport, "Body: ") + body;
283                 }
284                 report += InjectionModel.BR + StringUtil.formatReport(colorReport, "Header: ") + httpRequest.headers().map().entrySet().stream()
285                     .map(entry -> String.format("%s: %s", entry.getKey(), String.join(StringUtils.EMPTY, entry.getValue())))
286                     .collect(Collectors.joining(InjectionModel.BR));
287                 return report;
288             }
289             
290             HttpResponse<String> response = this.getMediatorUtils().getConnectionUtil().getHttpClient().build().send(
291                 httpRequestBuilder.build(),
292                 BodyHandlers.ofString()
293             );
294             if (this.mediatorUtils.getParameterUtil().isRequestSoap()) {
295                 // Invalid XML control chars like \x04 requires urlencoding from server
296                 pageSource = URLDecoder.decode(response.body(), StandardCharsets.UTF_8);
297             } else {
298                 pageSource = response.body();
299             }
300 
301             Map<String, String> headersResponse = ConnectionUtil.getHeadersMap(response);
302             msgHeader.put(Header.RESPONSE, headersResponse);
303             msgHeader.put(Header.HEADER, ConnectionUtil.getHeadersMap(httpRequest.headers()));
304             
305             int sizeHeaders = headersResponse.keySet()
306                 .stream()
307                 .map(key -> headersResponse.get(key).length() + key.length())
308                 .mapToInt(Integer::intValue)
309                 .sum();
310             float size = (float) (pageSource.length() + sizeHeaders) / 1024;
311             var decimalFormat = new DecimalFormat("0.000");
312             msgHeader.put(Header.PAGE_SIZE, decimalFormat.format(size));
313             
314             if (this.mediatorUtils.getParameterUtil().isRequestSoap()) {
315                 pageSource = StringUtil.fromHtml(pageSource);
316             }
317             
318             msgHeader.put(
319                 Header.SOURCE,
320                 pageSource
321                 .replaceAll("("+ VendorYaml.CALIBRATOR_SQL +"){60,}", "$1...")  // Remove ranges of # created by calibration
322                 .replaceAll("(jIyM){60,}", "$1...")  // Remove batch of chars created by Dios
323             );
324             msgHeader.put(Header.METADATA_PROCESS, metadataInjectionProcess);
325             msgHeader.put(Header.METADATA_STRATEGY, this.mediatorStrategy.getMeta());
326             msgHeader.put(Header.METADATA_BOOLEAN, callableBoolean);
327             
328             // Send data to Views
329             var request = new Request();
330             request.setMessage(Interaction.MESSAGE_HEADER);
331             request.setParameters(msgHeader);
332             this.sendToViews(request);
333         } catch (IOException e) {
334             LOGGER.log(
335                 LogLevelUtil.CONSOLE_ERROR,
336                 String.format("Error during connection: %s", e.getMessage())
337             );
338         } catch (InterruptedException e) {
339             LOGGER.log(LogLevelUtil.IGNORE, e, e);
340             Thread.currentThread().interrupt();
341         }
342 
343         // return the source code of the page
344         return pageSource;
345     }
346 
347     private URL initQueryString(
348         boolean isUsingIndex,
349         String urlInjection,
350         String dataInjection,
351         URL urlObject,
352         Map<Header, Object> msgHeader
353     ) {
354         String urlInjectionFixed = urlInjection;
355         var urlObjectFixed = urlObject;
356         if (
357             this.mediatorUtils.getParameterUtil().getListQueryString().isEmpty()
358             && !this.mediatorUtils.getPreferencesUtil().isProcessingCsrf()
359         ) {
360             msgHeader.put(Header.URL, urlInjectionFixed);
361             return urlObjectFixed;
362         }
363             
364         // URL without query string like Request and Header can receive
365         // new params from <form> parsing, in that case add the '?' to URL
366         if (!urlInjectionFixed.contains("?")) {
367             urlInjectionFixed += "?";
368         }
369         urlInjectionFixed += this.buildQuery(
370             this.mediatorMethod.getQuery(),
371             this.mediatorUtils.getParameterUtil().getQueryStringFromEntries(),
372             isUsingIndex,
373             dataInjection
374         );
375         urlInjectionFixed = this.mediatorUtils.getCsrfUtil().addQueryStringToken(urlInjectionFixed);
376         
377         // TODO Keep single check
378         try {
379             urlObjectFixed = new URI(urlInjectionFixed).toURL();
380         } catch (MalformedURLException | URISyntaxException e) {
381             LOGGER.log(
382                 LogLevelUtil.CONSOLE_ERROR,
383                 String.format("Incorrect Url: %s", e.getMessage())
384             );
385         }
386 
387         msgHeader.put(Header.URL, urlInjectionFixed);
388         return urlObjectFixed;
389     }
390 
391     private void initHeader(
392         boolean isUsingIndex,
393         String dataInjection,
394         Builder httpRequest
395     ) {
396         if (!this.mediatorUtils.getParameterUtil().getListHeader().isEmpty()) {
397             Stream.of(
398                 this.buildQuery(
399                     this.mediatorMethod.getHeader(),
400                     this.mediatorUtils.getParameterUtil().getHeaderFromEntries(),
401                     isUsingIndex,
402                     dataInjection
403                 )
404                 .split("\\\\r\\\\n")
405             )
406             .forEach(header -> {
407                 if (header.split(":").length == 2) {
408                     try {  // TODO Should not catch, rethrow or use runtime exception
409                         HeaderUtil.sanitizeHeaders(
410                             httpRequest,
411                             new SimpleEntry<>(
412                                 header.split(":")[0],
413                                 header.split(":")[1]
414                             )
415                         );
416                     } catch (JSqlException e) {
417                         LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Headers sanitizing issue caught already during connection, ignoring", e);
418                     }
419                 }
420             });
421         }
422     }
423 
424     private String initRequest(
425         boolean isUsingIndex,
426         String dataInjection,
427         Builder httpRequest,
428         Map<Header, Object> msgHeader
429     ) {
430         if (
431             this.mediatorUtils.getParameterUtil().getListRequest().isEmpty()
432             && this.mediatorUtils.getCsrfUtil().getTokenCsrf() == null
433         ) {
434             return dataInjection;
435         }
436             
437         // Set connection method
438         // Active for query string injection too, in that case inject query string still with altered method
439         
440         if (this.mediatorUtils.getParameterUtil().isRequestSoap()) {
441             httpRequest.setHeader(HeaderUtil.CONTENT_TYPE_REQUEST, "text/xml");
442         } else {
443             httpRequest.setHeader(HeaderUtil.CONTENT_TYPE_REQUEST, "application/x-www-form-urlencoded");
444         }
445 
446         var body = new StringBuilder();
447         this.mediatorUtils.getCsrfUtil().addRequestToken(body);
448             
449         if (this.mediatorUtils.getConnectionUtil().getTypeRequest().matches("PUT|POST")) {
450             if (this.mediatorUtils.getParameterUtil().isRequestSoap()) {
451                 body.append(
452                     this.buildQuery(
453                         this.mediatorMethod.getRequest(),
454                         this.mediatorUtils.getParameterUtil().getRawRequest(),
455                         isUsingIndex,
456                         dataInjection
457                     )
458                     // Invalid XML characters in recent Spring version
459                     // Server needs to urldecode, or stop using out of range chars
460                     .replace("\u0001", "&#01;")
461                     .replace("\u0003", "&#03;")
462                     .replace("\u0004", "&#04;")
463                     .replace("\u0005", "&#05;")
464                     .replace("\u0006", "&#06;")
465                     .replace("\u0007", "&#07;")
466                     .replace("+", "%2B")  // Prevent replace '+' into 'space' on server side urldecode
467                 );
468             } else {
469                 body.append(
470                     this.buildQuery(
471                         this.mediatorMethod.getRequest(),
472                         this.mediatorUtils.getParameterUtil().getRequestFromEntries(),
473                         isUsingIndex,
474                         dataInjection
475                     )
476                 );
477             }
478         }
479         
480         var bodyPublisher = BodyPublishers.ofString(body.toString());
481         httpRequest.method(
482             this.mediatorUtils.getConnectionUtil().getTypeRequest(),
483             bodyPublisher
484         );
485         
486         msgHeader.put(Header.POST, body.toString());
487         return body.toString();
488     }
489     
490     private String buildQuery(AbstractMethodInjection methodInjection, String paramLead, boolean isUsingIndex, String sqlTrail) {
491         String query;
492         String paramLeadFixed = paramLead.replace(
493             InjectionModel.STAR,
494             TamperingUtil.TAG_OPENED + InjectionModel.STAR + TamperingUtil.TAG_CLOSED
495         );
496         if (
497             // No parameter transformation if method is not selected by user
498             this.mediatorUtils.getConnectionUtil().getMethodInjection() != methodInjection
499             // No parameter transformation if injection point in URL
500             || this.mediatorUtils.getConnectionUtil().getUrlBase().contains(InjectionModel.STAR)
501         ) {
502             // Just pass parameters without any transformation
503             query = paramLeadFixed;
504         } else if (
505             // If method is selected by user and URL does not contain injection point
506             // but parameters contain an injection point
507             // then replace injection point by SQL expression in this parameter
508             paramLeadFixed.contains(InjectionModel.STAR)
509         ) {
510             query = this.initStarInjection(paramLeadFixed, isUsingIndex, sqlTrail);
511         } else {
512             query = this.initRawInjection(paramLeadFixed, isUsingIndex, sqlTrail);
513         }
514         // Remove comments except empty /**/
515         query = this.cleanQuery(methodInjection, query);
516         // Add empty comments with space=>/**/
517         if (this.mediatorUtils.getConnectionUtil().getMethodInjection() == methodInjection) {
518             query = this.mediatorUtils.getTamperingUtil().tamper(query);
519         }
520         return this.applyEncoding(methodInjection, query);
521     }
522 
523     private String initRawInjection(String paramLead, boolean isUsingIndex, String sqlTrail) {
524         String query;
525         // Method is selected by user and there's no injection point
526         if (!isUsingIndex) {
527             // Several SQL expressions does not use indexes in SELECT,
528             // like Boolean, Error, Shell and search for character insertion,
529             // in that case concat SQL expression to the end of param.
530             query = paramLead + sqlTrail;
531         } else {
532             // Concat indexes found for Union strategy to params
533             // and use visible Index for injection
534             query = paramLead + this.indexesInUrl.replaceAll(
535                 String.format(VendorYaml.FORMAT_INDEX, this.mediatorStrategy.getSpecificUnion().getVisibleIndex()),
536                 // Oracle column often contains $, which is reserved for regex.
537                 // => need to be escape with quoteReplacement()
538                 Matcher.quoteReplacement(sqlTrail)
539             );
540         }
541         // Add ending line comment by vendor
542         return query + this.mediatorVendor.getVendor().instance().endingComment();
543     }
544 
545     private String initStarInjection(String paramLead, boolean isUsingIndex, String sqlTrail) {
546         String query;
547         // Several SQL expressions does not use indexes in SELECT,
548         // like Boolean, Error, Shell and search for character insertion,
549         // in that case replace injection point by SQL expression.
550         // Injection point is always at the end?
551         if (!isUsingIndex) {
552             query = paramLead.replace(
553                 InjectionModel.STAR,
554                 sqlTrail + this.mediatorVendor.getVendor().instance().endingComment()
555             );
556         } else {
557             // Replace injection point by indexes found for Union strategy
558             // and use visible Index for injection
559             query = paramLead.replace(
560                 InjectionModel.STAR,
561                 this.indexesInUrl.replace(
562                     String.format(VendorYaml.FORMAT_INDEX, this.mediatorStrategy.getSpecificUnion().getVisibleIndex()),
563                     sqlTrail
564                 )
565                 + this.mediatorVendor.getVendor().instance().endingComment()
566             );
567         }
568         return query;
569     }
570 
571     /**
572      * Dependency:
573      * - Tamper space=>comment
574      */
575     private String cleanQuery(AbstractMethodInjection methodInjection, String query) {
576         String queryFixed = query;
577         if (
578             methodInjection == this.mediatorMethod.getRequest()
579             && (
580                 this.mediatorUtils.getParameterUtil().isRequestSoap()
581                 || this.mediatorUtils.getParameterUtil().isMultipartRequest()
582             )
583         ) {
584             queryFixed = StringUtil.removeSqlComment(queryFixed)
585                 .replace("+", " ")
586                 .replace("%2b", "+")  // Failsafe
587                 .replace("%23", "#");  // End comment
588             if (this.mediatorUtils.getParameterUtil().isMultipartRequest()) {
589                 // restore linefeed from textfield
590                 queryFixed = queryFixed.replaceAll("(?s)\\\\n", "\r\n");
591             }
592         } else {
593             queryFixed = StringUtil.cleanSql(queryFixed);
594         }
595         return queryFixed;
596     }
597 
598     private String applyEncoding(AbstractMethodInjection methodInjection, String query) {
599         String queryFixed = query;
600         if (!this.mediatorUtils.getParameterUtil().isRequestSoap()) {
601             if (methodInjection == this.mediatorMethod.getQuery()) {
602                 // URL encode each character because no query parameter context
603                 if (!this.mediatorUtils.getPreferencesUtil().isUrlEncodingDisabled()) {
604                     queryFixed = queryFixed.replace("'", "%27");
605                     queryFixed = queryFixed.replace("(", "%28");
606                     queryFixed = queryFixed.replace(")", "%29");
607                     queryFixed = queryFixed.replace("{", "%7b");
608                     queryFixed = queryFixed.replace("[", "%5b");
609                     queryFixed = queryFixed.replace("]", "%5d");
610                     queryFixed = queryFixed.replace("}", "%7d");
611                     queryFixed = queryFixed.replace(">", "%3e");
612                     queryFixed = queryFixed.replace("<", "%3c");
613                     queryFixed = queryFixed.replace("?", "%3f");
614                     queryFixed = queryFixed.replace("_", "%5f");
615                     queryFixed = queryFixed.replace(",", "%2c");
616                 }
617                 // HTTP forbidden characters
618                 queryFixed = queryFixed.replace(StringUtils.SPACE, "+");
619                 queryFixed = queryFixed.replace("`", "%60");  // from `${database}`.`${table}`
620                 queryFixed = queryFixed.replace("\"", "%22");
621                 queryFixed = queryFixed.replace("|", "%7c");
622                 queryFixed = queryFixed.replace("\\", "%5c");
623             } else if (methodInjection != this.mediatorMethod.getRequest()) {
624                 // For cookies in Spring (confirmed, covered by integration tests)
625                 queryFixed = queryFixed.replace("+", "%20");
626                 queryFixed = queryFixed.replace(",", "%2c");
627                 try {  // fix #95709: IllegalArgumentException on decode()
628                     queryFixed = URLDecoder.decode(queryFixed, StandardCharsets.UTF_8);
629                 } catch (IllegalArgumentException e) {
630                     LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Incorrect values in [{}], please check the parameters", methodInjection.name());
631                     throw new JSqlRuntimeException(e);
632                 }
633             }
634         }
635         return queryFixed;
636     }
637     
638     /**
639      * Display source code in console.
640      * @param message Error message
641      * @param source Text to display in console
642      */
643     public void sendResponseFromSite(String message, String source) {
644         LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "{}, response from site:", message);
645         LOGGER.log(LogLevelUtil.CONSOLE_ERROR, ">>>{}", source);
646     }
647     
648     
649     // Getters and setters
650 
651     public String getIndexesInUrl() {
652         return this.indexesInUrl;
653     }
654 
655     public void setIndexesInUrl(String indexesInUrl) {
656         this.indexesInUrl = indexesInUrl;
657     }
658 
659     public boolean shouldErasePreviousInjection() {
660         return this.shouldErasePreviousInjection;
661     }
662 
663     public void setIsScanning(boolean isScanning) {
664         this.isScanning = isScanning;
665     }
666 
667     public PropertiesUtil getPropertiesUtil() {
668         return this.propertiesUtil;
669     }
670 
671     public MediatorUtils getMediatorUtils() {
672         return this.mediatorUtils;
673     }
674 
675     public MediatorVendor getMediatorVendor() {
676         return this.mediatorVendor;
677     }
678 
679     public MediatorMethod getMediatorMethod() {
680         return this.mediatorMethod;
681     }
682 
683     public DataAccess getDataAccess() {
684         return this.dataAccess;
685     }
686 
687     public ResourceAccess getResourceAccess() {
688         return this.resourceAccess;
689     }
690 
691     public MediatorStrategy getMediatorStrategy() {
692         return this.mediatorStrategy;
693     }
694 
695     public void appendAnalysisReport(String analysisReport) {
696         this.appendAnalysisReport(analysisReport, false);
697     }
698 
699     public void appendAnalysisReport(String analysisReport, boolean isInit) {
700         this.analysisReport += (isInit ? StringUtils.EMPTY : "<br>&#10;<br>&#10;") + analysisReport;
701     }
702 }