View Javadoc
1   package com.jsql.model.injection.strategy.blind;
2   
3   import com.jsql.model.InjectionModel;
4   import com.jsql.model.exception.InjectionFailureException;
5   import com.jsql.model.exception.StoppedByUserSlidingException;
6   import com.jsql.model.injection.strategy.blind.callable.CallableBlindBin;
7   import com.jsql.model.injection.strategy.blind.patch.Diff;
8   import com.jsql.model.injection.strategy.blind.patch.DiffMatchPatch;
9   import com.jsql.util.LogLevelUtil;
10  import org.apache.commons.lang3.StringUtils;
11  import org.apache.logging.log4j.LogManager;
12  import org.apache.logging.log4j.Logger;
13  
14  import java.util.ArrayList;
15  import java.util.Collection;
16  import java.util.List;
17  import java.util.concurrent.CompletionService;
18  import java.util.concurrent.ExecutionException;
19  import java.util.concurrent.ExecutorService;
20  import java.util.concurrent.Future;
21  import java.util.concurrent.atomic.AtomicInteger;
22  
23  /**
24   * A blind attack class using concurrent threads.
25   */
26  public class InjectionBlindBin extends AbstractInjectionMonobit<CallableBlindBin> {
27  
28      private static final Logger LOGGER = LogManager.getRootLogger();
29      private static final int LOW = 0;
30      private static final int HIGH = 127;
31  
32      private String sourceReferencePage;  // Source code of the TRUE web page (usually ?id=1)
33  
34      /**
35       * List of string differences found in all the FALSE queries, compared
36       * to the reference page. Each FALSE pages should contain
37       * at least one same string, which shouldn't be present in all
38       * the TRUE queries.
39       */
40      private List<Diff> falseDiffs = new ArrayList<>();
41      private List<Diff> trueDiffs = new ArrayList<>();
42  
43      /**
44       * Create blind attack initialization.
45       * If every false diffs are not in true diffs and every true diffs are in
46       * true diffs, then Blind attack is confirmed.
47       */
48      public InjectionBlindBin(InjectionModel injectionModel, BlindOperator blindOperator) {
49          super(injectionModel, blindOperator);
50  
51          List<String> falsys = this.injectionModel.getMediatorVendor().getVendor().instance().getFalsyBin();
52          if (falsys.isEmpty() || this.injectionModel.isStoppedByUser()) {
53              return;
54          }
55          
56          // Call the SQL request which must be TRUE (usually ?id=1)
57          this.sourceReferencePage = this.callUrl(StringUtils.EMPTY, "bin#ref:"+ blindOperator.toString().toLowerCase());
58  
59          // Concurrent calls to the FALSE statements,
60          // it will use inject() from the model
61          ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableGetBlindBinTagFalse");
62          Collection<CallableBlindBin> callablesFalsys = new ArrayList<>();
63          for (String falsy: falsys) {
64              callablesFalsys.add(new CallableBlindBin(
65                  falsy,
66                  injectionModel,
67                  this,
68                  blindOperator,
69                  -1, -1, -1,
70                  "bin#falsy"
71              ));
72          }
73          
74          // Delete junk from the results of FALSE statements,
75          // keep only diffs found in each and every FALSE pages.
76          // Allow the user to stop the loop
77          try {
78              List<Future<CallableBlindBin>> futuresFalsys = taskExecutor.invokeAll(callablesFalsys);
79              this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor);
80              for (Future<CallableBlindBin> futureFalsy: futuresFalsys) {
81                  if (this.injectionModel.isStoppedByUser()) {
82                      return;
83                  }
84                  if (this.falseDiffs.isEmpty()) {
85                      this.falseDiffs = futureFalsy.get().getDiffsWithReference();  // Init diffs
86                  } else {
87                      this.falseDiffs.retainAll(futureFalsy.get().getDiffsWithReference());  // Clean un-matching diffs
88                  }
89              }
90          } catch (ExecutionException e) {
91              LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
92          } catch (InterruptedException e) {
93              LOGGER.log(LogLevelUtil.IGNORE, e, e);
94              Thread.currentThread().interrupt();
95          }
96  
97          if (this.injectionModel.isStoppedByUser()) {
98              return;
99          }
100         
101         this.cleanTrueDiffs(injectionModel, blindOperator);
102     }
103 
104     private void cleanTrueDiffs(InjectionModel injectionModel, BlindOperator blindOperator) {
105         ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableGetBlindBinTagTrue");
106         Collection<CallableBlindBin> callablesTruthys = new ArrayList<>();
107         List<String> truthys = this.injectionModel.getMediatorVendor().getVendor().instance().getTruthyBin();
108         for (String truthy: truthys) {
109             callablesTruthys.add(new CallableBlindBin(
110                 truthy,
111                 injectionModel,
112                 this,
113                 blindOperator,
114                 -1, -1, -1,
115                 "bin#truthy"
116             ));
117         }
118         
119         // Remove TRUE diffs in the FALSE diffs as FALSE statement shouldn't contain any TRUE diff.
120         try {
121             List<Future<CallableBlindBin>> futuresTruthys = taskExecutor.invokeAll(callablesTruthys);
122             this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor);
123             for (Future<CallableBlindBin> futureTruthy: futuresTruthys) {
124                 if (this.injectionModel.isStoppedByUser()) {
125                     return;
126                 }
127                 if (this.trueDiffs.isEmpty()) {
128                     this.trueDiffs = futureTruthy.get().getDiffsWithReference();  // Init diffs
129                 } else {
130                     this.trueDiffs.retainAll(futureTruthy.get().getDiffsWithReference());  // Clean un-matching diffs
131                 }
132                 this.falseDiffs.removeAll(futureTruthy.get().getDiffsWithReference());
133             }
134         } catch (ExecutionException e) {
135             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
136         } catch (InterruptedException e) {
137             LOGGER.log(LogLevelUtil.IGNORE, e, e);
138             Thread.currentThread().interrupt();
139         }
140     }
141 
142     @Override
143     public CallableBlindBin getCallableBitTest(String sqlQuery, int indexChar, int bit) {
144         return null;  // unused
145     }
146 
147     @Override
148     public boolean isInjectable() throws StoppedByUserSlidingException {
149         if (this.injectionModel.isStoppedByUser()) {
150             throw new StoppedByUserSlidingException();
151         }
152         var blindTest = new CallableBlindBin(
153             this.injectionModel.getMediatorVendor().getVendor().instance().sqlBlindConfirm(),
154             this.injectionModel,
155             this,
156             this.blindOperator,
157             -1, -1, -1,
158             "bin#confirm"
159         );
160         try {
161             blindTest.call();
162         } catch (Exception e) {
163             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
164         }
165         return blindTest.isTrue()
166             // when insertionChar = true then pages ref == truthy == falsy == confirm => falsy cleaned empty, truthy with opcode EQUAL not reliable
167             && this.trueDiffs.stream().anyMatch(diff -> !DiffMatchPatch.Operation.EQUAL.equals(diff.getOperation()))
168             || this.falseDiffs.stream().anyMatch(diff -> !DiffMatchPatch.Operation.EQUAL.equals(diff.getOperation()));
169     }
170 
171     @Override
172     public void initNextChar(
173         String sqlQuery,
174         List<char[]> bytes,
175         AtomicInteger indexChar,
176         CompletionService<CallableBlindBin> taskCompletionService,
177         AtomicInteger countTasksSubmitted,
178         AtomicInteger countBadAsciiCode,
179         CallableBlindBin currentCallable
180     ) {
181         int low;
182         int mid;
183         int high;
184 
185         if (currentCallable != null) {
186             low = currentCallable.getLow();
187             mid = currentCallable.getMid();
188             high = currentCallable.getHigh();
189 
190             if (low >= high) {  // char found
191                 if (this.isCorruptOrElseNextChar(bytes, indexChar, countBadAsciiCode, currentCallable, low)) {
192                     return;  // too many errors
193                 }
194                 low = InjectionBlindBin.LOW;
195                 high = InjectionBlindBin.HIGH;
196             } else if (currentCallable.isTrue()) {  // current >= mid
197                 low = mid + 1;
198             } else {  // current < mid
199                 high = mid - 1;
200             }
201         } else {
202             low = InjectionBlindBin.LOW;
203             high = InjectionBlindBin.HIGH;
204             bytes.add(AbstractInjectionBit.getBitsUnset());
205             indexChar.incrementAndGet();
206         }
207 
208         mid = low + (high - low) / 2;
209         taskCompletionService.submit(
210             new CallableBlindBin(
211                 sqlQuery,
212                 indexChar.get(),
213                 this.injectionModel,
214                 this,
215                 this.blindOperator,
216                 low, mid, high,
217                 String.format("bin#%s~%s<%s<%s", indexChar, low, mid, high)
218             )
219         );
220         countTasksSubmitted.addAndGet(1);
221     }
222 
223     private boolean isCorruptOrElseNextChar(List<char[]> bytes, AtomicInteger indexChar, AtomicInteger countBadAsciiCode, CallableBlindBin currentCallable, int low) {
224         int currentLow = low;
225         if (currentLow == 0 || currentLow == 127) {
226             countBadAsciiCode.incrementAndGet();
227         } else {
228             currentLow = currentCallable.isTrue() ? currentLow : currentLow - 1;
229         }
230         char[] asciiCodeMask = bytes.get(currentCallable.getCurrentIndex() - 1);  // bits for current url
231         this.setAsciiCodeMask(asciiCodeMask, currentLow);
232 
233         try {
234             this.isCharCompleteWithCorruptCheck(bytes, countBadAsciiCode, currentCallable);
235         } catch (InjectionFailureException e) {
236             return true;
237         }
238 
239         bytes.add(AbstractInjectionBit.getBitsUnset());
240         indexChar.incrementAndGet();
241         return false;
242     }
243 
244     private void setAsciiCodeMask(char[] asciiCodeMask, int value) {
245         String binary = StringUtils.leftPad(Integer.toBinaryString((char) value), 8, "0");
246         for (int i = 0; i <= 7; i++) {
247             asciiCodeMask[i] = binary.charAt(i);
248         }
249     }
250 
251     @Override
252     public char[] initMaskAsciiChar(List<char[]> bytes, CallableBlindBin currentCallable) {
253         return bytes.get(currentCallable.getCurrentIndex() - 1);
254     }
255 
256     @Override
257     public String getInfoMessage() {
258         return "- Strategy Blind bin: query True when Diffs are matching " + this.falseDiffs + "\n\n";
259     }
260     
261     
262     // Getter and setter
263 
264     public String getSourceReferencePage() {
265         return this.sourceReferencePage;
266     }
267     
268     public List<Diff> getFalseDiffs() {
269         return this.falseDiffs;
270     }
271 
272     public List<Diff> getTrueDiffs() {
273         return this.trueDiffs;
274     }
275 }