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