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