1 package com.jsql.util;
2
3 import com.jsql.model.InjectionModel;
4 import com.jsql.model.bean.util.Header;
5 import com.jsql.model.bean.util.Interaction;
6 import com.jsql.model.bean.util.Request;
7 import com.jsql.model.exception.JSqlException;
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.io.IOException;
13 import java.net.HttpCookie;
14 import java.net.URLEncoder;
15 import java.net.http.HttpRequest.Builder;
16 import java.net.http.HttpResponse;
17 import java.net.http.HttpResponse.BodyHandlers;
18 import java.nio.charset.StandardCharsets;
19 import java.text.DecimalFormat;
20 import java.util.AbstractMap.SimpleEntry;
21 import java.util.EnumMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.regex.Pattern;
25 import java.util.stream.Collectors;
26 import java.util.stream.Stream;
27
28 public class HeaderUtil {
29
30 private static final Logger LOGGER = LogManager.getRootLogger();
31
32 public static final String CONTENT_TYPE_REQUEST = "Content-Type";
33 public static final String WWW_AUTHENTICATE_RESPONSE = "www-authenticate";
34 private static final String REGEX_HTTP_STATUS = "4\\d\\d";
35 private static final String FOUND_STATUS_HTTP = "Found status HTTP";
36
37 private final InjectionModel injectionModel;
38
39 public HeaderUtil(InjectionModel injectionModel) {
40 this.injectionModel = injectionModel;
41 }
42
43
44
45
46
47
48
49 public static void sanitizeHeaders(Builder httpRequest, SimpleEntry<String, String> header) throws JSqlException {
50 String keyHeader = header.getKey().trim();
51 String valueHeader = header.getValue().trim();
52
53 if ("cookie".equalsIgnoreCase(keyHeader) && Pattern.compile(".+=.*").matcher(valueHeader).find()) {
54
55 List<String> cookies = Stream.of(valueHeader.split(";"))
56 .filter(value -> value != null && value.contains("="))
57 .map(cookie -> cookie.split("=", 2))
58 .map(arrayEntry -> arrayEntry[0].trim() + "=" + (
59 arrayEntry[1] == null
60 ? StringUtils.EMPTY
61
62 : URLEncoder.encode(arrayEntry[1].trim().replace("+", "%2B"), StandardCharsets.UTF_8)
63 ))
64 .collect(Collectors.toList());
65 valueHeader = String.join("; ", cookies);
66 }
67
68 try {
69 httpRequest.setHeader(
70 keyHeader,
71 valueHeader.replaceAll("[^\\p{ASCII}]", StringUtils.EMPTY)
72 );
73 } catch (IllegalArgumentException e) {
74 throw new JSqlException(e);
75 }
76 }
77
78
79
80
81
82
83
84
85 public HttpResponse<String> checkResponseHeader(Builder httpRequestBuilder, String body) throws IOException, InterruptedException {
86 var httpRequest = httpRequestBuilder.build();
87 HttpResponse<String> httpResponse = this.injectionModel.getMediatorUtils().getConnectionUtil().getHttpClient().build().send(
88 httpRequest,
89 BodyHandlers.ofString()
90 );
91 String pageSource = httpResponse.body();
92
93 List<HttpCookie> cookies = this.injectionModel.getMediatorUtils().getConnectionUtil().getCookieManager().getCookieStore().getCookies();
94 if (!cookies.isEmpty()) {
95 LOGGER.info("Cookies set by host: {}", cookies);
96 }
97
98 var responseCode = Integer.toString(httpResponse.statusCode());
99 Map<String, String> mapResponseHeaders = ConnectionUtil.getHeadersMap(httpResponse);
100 this.checkResponse(responseCode, mapResponseHeaders);
101 this.checkStatus(httpResponse);
102
103 this.injectionModel.getMediatorUtils().getFormUtil().parseForms(httpResponse.statusCode(), pageSource);
104 this.injectionModel.getMediatorUtils().getCsrfUtil().parseForCsrfToken(pageSource, mapResponseHeaders);
105 this.injectionModel.getMediatorUtils().getDigestUtil().parseWwwAuthenticate(mapResponseHeaders);
106
107 Map<Header, Object> msgHeader = new EnumMap<>(Header.class);
108
109 int sizeHeaders = mapResponseHeaders.keySet()
110 .stream()
111 .map(key -> mapResponseHeaders.get(key).length() + key.length())
112 .mapToInt(Integer::intValue)
113 .sum();
114
115 float size = (float) (pageSource.length() + sizeHeaders) / 1024;
116 var decimalFormat = new DecimalFormat("0.000");
117 msgHeader.put(Header.PAGE_SIZE, decimalFormat.format(size));
118
119 msgHeader.put(Header.URL, httpRequest.uri().toURL().toString());
120 msgHeader.put(Header.POST, body);
121 msgHeader.put(Header.HEADER, ConnectionUtil.getHeadersMap(httpRequest.headers()));
122 msgHeader.put(Header.RESPONSE, mapResponseHeaders);
123 msgHeader.put(Header.SOURCE, pageSource);
124 msgHeader.put(Header.METADATA_STRATEGY, "#none");
125 msgHeader.put(Header.METADATA_PROCESS, "test#conn");
126
127
128 var request = new Request();
129 request.setMessage(Interaction.MESSAGE_HEADER);
130 request.setParameters(msgHeader);
131 this.injectionModel.sendToViews(request);
132
133 return httpResponse;
134 }
135
136 private void checkStatus(HttpResponse<String> response) {
137 if (response.statusCode() >= 400) {
138 if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isNotTestingConnection()) {
139 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "Connection test disabled, skipping error {}...", response.statusCode());
140 } else {
141 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Try with option 'Disable connection test' to skip HTTP error {}", response.statusCode());
142 }
143 }
144 }
145
146 private void checkResponse(String responseCode, Map<String, String> mapResponse) {
147 if (this.isBasicAuth(responseCode, mapResponse)) {
148 LOGGER.log(
149 LogLevelUtil.CONSOLE_ERROR,
150 "Basic Authentication detected: "
151 + "set authentication in preferences, "
152 + "or add header 'Authorization: Basic b3N..3Jk', with b3N..3Jk as "
153 + "'osUserName:osPassword' encoded in Base64 (use the Coder in jSQL to encode the string)."
154 );
155 } else if (this.isNtlm(responseCode, mapResponse)) {
156 LOGGER.log(
157 LogLevelUtil.CONSOLE_ERROR,
158 "NTLM Authentication detected: "
159 + "set authentication in preferences, "
160 + "or add username, password and domain information to the URL, e.g. http://domain\\user:password@127.0.0.1/[..]"
161 );
162 } else if (this.isDigest(responseCode, mapResponse)) {
163 LOGGER.log(
164 LogLevelUtil.CONSOLE_ERROR,
165 "Digest Authentication detected: set authentication in preferences."
166 );
167 } else if (this.isNegotiate(responseCode, mapResponse)) {
168 LOGGER.log(
169 LogLevelUtil.CONSOLE_ERROR,
170 "Negotiate Authentication detected: "
171 + "add username, password and domain information to the URL, e.g. http://domain\\user:password@127.0.0.1/[..]"
172 );
173 } else if (Pattern.matches("1\\d\\d", responseCode)) {
174 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "{} {} Informational", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
175 } else if (Pattern.matches("2\\d\\d", responseCode)) {
176 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "{} {} Success", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
177 } else if (Pattern.matches("3\\d\\d", responseCode)) {
178
179 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "{} {} Redirection", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
180
181 if (!this.injectionModel.getMediatorUtils().getPreferencesUtil().isFollowingRedirection()) {
182 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "If injection fails retry with option 'Follow HTTP redirection' activated");
183 } else {
184 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Redirecting to the next page...");
185 }
186 } else if (Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)) {
187 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "{} {} Client Error", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
188 } else if (Pattern.matches("5\\d\\d", responseCode)) {
189 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "{} {} Server Error", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
190 } else {
191 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "{} {} Unknown", HeaderUtil.FOUND_STATUS_HTTP, responseCode);
192 }
193 }
194
195 private boolean isNegotiate(String responseCode, Map<String, String> mapResponse) {
196 return Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)
197 && mapResponse.containsKey(HeaderUtil.WWW_AUTHENTICATE_RESPONSE)
198 && "Negotiate".equals(mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE));
199 }
200
201 private boolean isDigest(String responseCode, Map<String, String> mapResponse) {
202 return Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)
203 && mapResponse.containsKey(HeaderUtil.WWW_AUTHENTICATE_RESPONSE)
204 && mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE) != null
205 && mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE).startsWith("Digest ");
206 }
207
208 private boolean isNtlm(String responseCode, Map<String, String> mapResponse) {
209 return Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)
210 && mapResponse.containsKey(HeaderUtil.WWW_AUTHENTICATE_RESPONSE)
211 && "NTLM".equals(mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE));
212 }
213
214 private boolean isBasicAuth(String responseCode, Map<String, String> mapResponse) {
215 return Pattern.matches(HeaderUtil.REGEX_HTTP_STATUS, responseCode)
216 && mapResponse.containsKey(HeaderUtil.WWW_AUTHENTICATE_RESPONSE)
217 && mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE) != null
218 && mapResponse.get(HeaderUtil.WWW_AUTHENTICATE_RESPONSE).startsWith("Basic ");
219 }
220 }