View Javadoc
1   package com.jsql.util;
2   
3   import com.jsql.model.InjectionModel;
4   import com.jsql.model.bean.util.Interaction;
5   import com.jsql.model.bean.util.Request;
6   import com.jsql.model.exception.InjectionFailureException;
7   import com.jsql.model.injection.method.AbstractMethodInjection;
8   import org.apache.commons.lang3.StringUtils;
9   import org.apache.logging.log4j.LogManager;
10  import org.apache.logging.log4j.Logger;
11  
12  import java.net.IDN;
13  import java.net.MalformedURLException;
14  import java.net.URI;
15  import java.net.URISyntaxException;
16  import java.util.AbstractMap.SimpleEntry;
17  import java.util.ArrayList;
18  import java.util.Arrays;
19  import java.util.List;
20  import java.util.Objects;
21  import java.util.concurrent.CopyOnWriteArrayList;
22  import java.util.regex.Matcher;
23  import java.util.regex.Pattern;
24  import java.util.stream.Collectors;
25  
26  public class ParameterUtil {
27  
28      private static final Logger LOGGER = LogManager.getRootLogger();
29  
30      /**
31       * Query string built from the URL submitted by user.
32       */
33      private List<SimpleEntry<String, String>> listQueryString = new CopyOnWriteArrayList<>();
34  
35      /**
36       * Request submitted by user.
37       */
38      private List<SimpleEntry<String, String>> listRequest = new CopyOnWriteArrayList<>();
39  
40      /**
41       * Header submitted by user.
42       */
43      private List<SimpleEntry<String, String>> listHeader = new CopyOnWriteArrayList<>();
44  
45      private String rawRequest = StringUtils.EMPTY;
46      private String rawHeader = StringUtils.EMPTY;
47      private boolean isMultipartRequest = false;
48      private static final String FORMAT_KEY_VALUE = "%s=%s";
49  
50      private final InjectionModel injectionModel;
51      
52      public ParameterUtil(InjectionModel injectionModel) {
53          this.injectionModel = injectionModel;
54      }
55      
56      /**
57       * Send each parameter from the GUI to the model in order to
58       * start the preparation of injection, the injection process is
59       * started in a new thread via model function inputValidation().
60       */
61      public void controlInput(
62          String urlQuery,
63          String rawRequest,
64          String rawHeader,
65          AbstractMethodInjection methodInjection,
66          String typeRequest,
67          boolean isScanning
68      ) {
69          try {
70              String urlQueryFixed = urlQuery;
71              // Keep single check
72              if (urlQueryFixed.isEmpty()) {
73                  throw new MalformedURLException("empty URL");
74              } else if (!urlQueryFixed.matches("(?i)^https?://.*")) {
75                  if (!urlQueryFixed.matches("(?i)^\\w+://.*")) {
76                      LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Undefined URL protocol, forcing to [http://]");
77                      urlQueryFixed = "http://"+ urlQueryFixed;
78                  } else {
79                      throw new MalformedURLException("unknown URL protocol");
80                  }
81              }
82  
83              String authority = URI.create(urlQueryFixed).getAuthority();
84              if (authority == null) {
85                  throw new MalformedURLException("incorrect domain authority");
86              }
87              String authorityPunycode = IDN.toASCII(authority);
88              if (!authority.equals(authorityPunycode)) {
89                  LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Punycode domain detected, using [{}] instead of [{}]", authorityPunycode, authority);
90                  urlQueryFixed = urlQueryFixed.replace(authority, authorityPunycode);
91              }
92  
93              this.initQueryString(urlQueryFixed);
94              this.initHeader(rawHeader);
95              this.initRequest(rawRequest);
96  
97              this.injectionModel.getMediatorUtils().getConnectionUtil().setMethodInjection(methodInjection);
98              this.injectionModel.getMediatorUtils().getConnectionUtil().setTypeRequest(typeRequest);
99              
100             if (isScanning) {
101                 this.injectionModel.beginInjection();
102             } else {
103                 new Thread(this.injectionModel::beginInjection, "ThreadBeginInjection").start();  // in thread
104             }
105         } catch (IllegalArgumentException | MalformedURLException | URISyntaxException e) {
106             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Incorrect Url: {}", e.getMessage());
107             
108             // Incorrect URL, reset the start button
109             var request = new Request();
110             request.setMessage(Interaction.END_PREPARATION);
111             this.injectionModel.sendToViews(request);
112         }
113     }
114 
115     /**
116      * Check integrity of parameters defined by user.
117      * @throws InjectionFailureException when params integrity is failure
118      */
119     public void checkParametersFormat() throws InjectionFailureException {
120         this.checkOneOrLessStar();
121         this.checkStarMatchMethod();
122         this.checkMethodNotEmpty();
123         this.checkMultipart();
124         if (ParameterUtil.isInvalidName(this.injectionModel.getMediatorUtils().getConnectionUtil().getTypeRequest())) {
125             throw new InjectionFailureException(String.format(
126                 "Illegal method: \"%s\"",
127                 this.injectionModel.getMediatorUtils().getConnectionUtil().getTypeRequest()
128             ));
129         }
130     }
131 
132     // ABNF primitives defined in RFC 7230
133     private static final boolean[] tchar = new boolean[256];
134 
135     static {
136         char[] allowedTokenChars = (
137             "!#$%&'*+-.^_`|~0123456789" +
138             "abcdefghijklmnopqrstuvwxyz" +
139             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
140         ).toCharArray();
141         for (char c : allowedTokenChars) {
142             ParameterUtil.tchar[c] = true;
143         }
144     }
145 
146     /*
147      * Validates a RFC 7230 field-name.
148      */
149     public static boolean isInvalidName(String token) {
150         for (int i = 0 ; i < token.length() ; i++) {
151             char c = token.charAt(i);
152             if (c > 255 || !ParameterUtil.tchar[c]) {
153                 return true;
154             }
155         }
156         return token.isEmpty();
157     }
158 
159     private void checkMultipart() throws InjectionFailureException {
160         this.isMultipartRequest = false;
161 
162         if (
163             this.getListHeader()
164             .stream()
165             .filter(entry -> "Content-Type".equals(entry.getKey()))
166             .anyMatch(entry ->
167                 entry.getValue() != null
168                 && entry.getValue().contains("multipart/form-data")
169                 && entry.getValue().contains("boundary=")
170             )
171         ) {
172             LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Multipart boundary found in header");
173             Matcher matcherBoundary = Pattern.compile("boundary=([^;]*)").matcher(this.getHeaderFromEntries());
174             if (matcherBoundary.find()) {
175                 String boundary = matcherBoundary.group(1);
176                 if (!this.rawRequest.contains(boundary)) {
177                     throw new InjectionFailureException(
178                         String.format("Incorrect multipart data, boundary not found in body: %s", boundary)
179                     );
180                 } else {
181                     this.isMultipartRequest = true;
182                 }
183             }
184         }
185     }
186 
187     private void checkOneOrLessStar() throws InjectionFailureException {
188         var nbStarInParameter = 0;
189         
190         if (this.getQueryStringFromEntries().contains(InjectionModel.STAR)) {
191             nbStarInParameter++;
192         }
193         if (this.getRequestFromEntries().contains(InjectionModel.STAR)) {
194             nbStarInParameter++;
195         }
196         if (this.getHeaderFromEntries().contains(InjectionModel.STAR)) {
197             nbStarInParameter++;
198         }
199         
200         // Injection Point
201         if (
202             nbStarInParameter > 1
203             || StringUtils.countMatches(this.getQueryStringFromEntries(), "*") > 1
204             || StringUtils.countMatches(this.getRequestFromEntries(), "*") > 1
205             || StringUtils.countMatches(this.getHeaderFromEntries(), "*") > 1
206         ) {
207             throw new InjectionFailureException("Character insertion [*] must be used once in Query String, Request or Header parameters");
208         }
209     }
210     
211     public void checkStarMatchMethod() throws InjectionFailureException {
212         AbstractMethodInjection methodInjection = this.injectionModel.getMediatorUtils().getConnectionUtil().getMethodInjection();
213         boolean isCheckingAllParam = this.injectionModel.getMediatorUtils().getPreferencesUtil().isCheckingAllParam();
214 
215         if (
216             this.getQueryStringFromEntries().contains(InjectionModel.STAR)
217             && methodInjection != this.injectionModel.getMediatorMethod().getQuery()
218             && !isCheckingAllParam
219         ) {
220             throw new InjectionFailureException("Select method GET to use character [*] or remove [*] from GET parameters");
221         } else if (
222             this.getRequestFromEntries().contains(InjectionModel.STAR)
223             && methodInjection != this.injectionModel.getMediatorMethod().getRequest()
224             && !isCheckingAllParam
225         ) {
226             throw new InjectionFailureException("Select a Request method (like POST) to use [*], or remove [*] from Request parameters");
227         } else if (
228             this.getHeaderFromEntries().contains(InjectionModel.STAR)
229             && methodInjection != this.injectionModel.getMediatorMethod().getHeader()
230             && !isCheckingAllParam
231         ) {
232             throw new InjectionFailureException("Select method Header to use character [*] or remove [*] from Header parameters");
233         }
234     }
235     
236     public void checkMethodNotEmpty() throws InjectionFailureException {
237         AbstractMethodInjection methodInjection = this.injectionModel.getMediatorUtils().getConnectionUtil().getMethodInjection();
238         boolean isCheckingAllParam = this.injectionModel.getMediatorUtils().getPreferencesUtil().isCheckingAllParam();
239         
240         if (
241             methodInjection == this.injectionModel.getMediatorMethod().getQuery()
242             && !isCheckingAllParam
243             && this.getListQueryString().isEmpty()
244             && !this.injectionModel.getMediatorUtils().getConnectionUtil().getUrlBase().contains(InjectionModel.STAR)
245         ) {
246             throw new InjectionFailureException("No query string");
247         } else if (
248             methodInjection == this.injectionModel.getMediatorMethod().getRequest()
249             && this.getListRequest().isEmpty()
250         ) {
251             throw new InjectionFailureException("Incorrect Request format");
252         } else if (
253             methodInjection == this.injectionModel.getMediatorMethod().getHeader()
254             && this.getListHeader().isEmpty()
255         ) {
256             throw new InjectionFailureException("Incorrect Header format");
257         }
258     }
259     
260     public String initStar(SimpleEntry<String, String> parameterToInject) {
261         String characterInsertionByUser;
262         if (parameterToInject == null) {
263             characterInsertionByUser = InjectionModel.STAR;
264         } else {
265             characterInsertionByUser = parameterToInject.getValue();
266             parameterToInject.setValue(InjectionModel.STAR);
267         }
268         return characterInsertionByUser;
269     }
270 
271     public void initQueryString(String urlQuery) throws MalformedURLException, URISyntaxException {
272         // Format and get rid of anchor fragment using native URL
273         var url = new URI(urlQuery).toURL();
274         
275         if (
276             StringUtils.isEmpty(urlQuery)
277             || StringUtils.isEmpty(url.getHost())
278         ) {
279             throw new MalformedURLException("empty URL");
280         }
281         
282         this.injectionModel.getMediatorUtils().getConnectionUtil().setUrlByUser(urlQuery);
283         this.injectionModel.getMediatorUtils().getConnectionUtil().setUrlBase(urlQuery);
284         this.listQueryString.clear();
285         
286         // Parse url and GET query string
287         var regexQueryString = Pattern.compile("(.*\\?)(.*)").matcher(urlQuery);
288         if (!regexQueryString.find()) {
289             return;
290         }
291         
292         this.injectionModel.getMediatorUtils().getConnectionUtil().setUrlBase(regexQueryString.group(1));
293         
294         if (StringUtils.isNotEmpty(url.getQuery())) {
295             this.listQueryString = Pattern.compile("&")
296                 .splitAsStream(url.getQuery())
297                 .map(keyValue -> Arrays.copyOf(keyValue.split("="), 2))
298                 .map(keyValue -> new SimpleEntry<>(
299                     keyValue[0],
300                     keyValue[1] == null ? StringUtils.EMPTY : keyValue[1]
301                 ))
302                 .collect(Collectors.toList());
303         }
304     }
305 
306     public void initRequest(String rawRequest) {
307         this.rawRequest = rawRequest;
308         this.listRequest.clear();
309         if (StringUtils.isNotEmpty(rawRequest)) {
310             if (this.isMultipartRequest) {
311                 // Pass request containing star * param without any parsing
312                 this.listRequest = new ArrayList<>(List.of(new SimpleEntry<>(
313                     rawRequest,
314                     StringUtils.EMPTY
315                 )));
316             } else {
317                 this.listRequest = Pattern.compile("&")
318                     .splitAsStream(rawRequest)
319                     .map(keyValue -> Arrays.copyOf(keyValue.split("="), 2))
320                     .map(keyValue -> new SimpleEntry<>(
321                         keyValue[0],
322                         keyValue[1] == null ? StringUtils.EMPTY : keyValue[1]
323                     ))
324                     .collect(Collectors.toList());
325             }
326         }
327     }
328 
329     public void initHeader(String rawHeader) {
330         this.rawHeader = rawHeader;
331         this.listHeader.clear();
332         if (StringUtils.isNotEmpty(rawHeader)) {
333             this.listHeader = Pattern.compile("\\\\r\\\\n")
334                 .splitAsStream(rawHeader)
335                 .map(keyValue -> Arrays.copyOf(keyValue.split(":"), 2))
336                 .map(keyValue -> new SimpleEntry<>(
337                     keyValue[0],
338                     keyValue[1] == null ? StringUtils.EMPTY : keyValue[1]
339                 ))
340                 .collect(Collectors.toList());
341         }
342     }
343     
344     public String getQueryStringFromEntries() {
345         return this.listQueryString.stream()
346             .filter(Objects::nonNull)
347             .map(entry -> {
348                 if (
349                     this.injectionModel.getMediatorStrategy().getStrategy() == this.injectionModel.getMediatorStrategy().getMultibit()
350                     && entry.getValue() != null
351                     && entry.getValue().contains(InjectionModel.STAR)
352                 ) {
353                     return String.format(ParameterUtil.FORMAT_KEY_VALUE, entry.getKey(), InjectionModel.STAR);
354                 } else {
355                     return String.format(ParameterUtil.FORMAT_KEY_VALUE, entry.getKey(), entry.getValue());
356                 }
357             })
358             .collect(Collectors.joining("&"));
359     }
360 
361     public String getRequestFromEntries() {
362         return this.listRequest.stream()
363             .filter(Objects::nonNull)
364             .map(entry -> String.format(
365                 ParameterUtil.FORMAT_KEY_VALUE,
366                 entry.getKey(),
367                 StringUtils.isEmpty(entry.getValue()) ? StringUtils.EMPTY : entry.getValue()
368             ))
369             .collect(Collectors.joining("&"));
370     }
371     
372     public String getHeaderFromEntries() {
373         return this.listHeader.stream()
374             .filter(Objects::nonNull)
375             .map(entry -> String.format("%s:%s", entry.getKey(), entry.getValue()))
376             .collect(Collectors.joining("\\r\\n"));
377     }
378 
379     public boolean isRequestSoap() {
380         return this.rawRequest
381             .trim()
382             .matches("^(<soapenv:|<\\?xml).*");
383     }
384 
385     
386     // Getters / setters
387     
388     public String getRawRequest() {
389         return this.rawRequest;
390     }
391 
392     public String getRawHeader() {
393         return this.rawHeader;
394     }
395 
396     public List<SimpleEntry<String, String>> getListRequest() {
397         return this.listRequest;
398     }
399 
400     public List<SimpleEntry<String, String>> getListHeader() {
401         return this.listHeader;
402     }
403 
404     public List<SimpleEntry<String, String>> getListQueryString() {
405         return this.listQueryString;
406     }
407     
408     public boolean isMultipartRequest() {
409         return this.isMultipartRequest;
410     }
411 }