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