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