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