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