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  
23  /**
24   * A blind attack class using concurrent threads.
25   */
26  public class InjectionBlindBin extends AbstractInjectionMonobit<CallableBlindBin> {
27  
28      /**
29       * Log4j logger sent to view.
30       */
31      private static final Logger LOGGER = LogManager.getRootLogger();
32      private static final int LOW = 0;
33      private static final int HIGH = 127;
34  
35      // Source code of the TRUE web page (usually ?id=1)
36      private String sourceReferencePage;
37  
38      /**
39       * List of string differences found in all the FALSE queries, compared
40       * to the reference page. Each FALSE pages should contain
41       * at least one same string, which shouldn't be present in all
42       * the TRUE queries.
43       */
44      private List<Diff> falseDiffs = new ArrayList<>();
45  
46      /**
47       * Create blind attack initialization.
48       * If every false diffs are not in true diffs and every true diffs are in
49       * true diffs, then Blind attack is confirmed.
50       */
51      public InjectionBlindBin(InjectionModel injectionModel, BlindOperator blindMode) {
52          super(injectionModel, blindMode);
53          
54          // No blind
55          if (this.falsyBin.isEmpty() || this.injectionModel.isStoppedByUser()) {
56              return;
57          }
58          
59          // Call the SQL request which must be TRUE (usually ?id=1)
60          this.sourceReferencePage = this.callUrl(StringUtils.EMPTY, "bin#ref");
61  
62          // Concurrent calls to the FALSE statements,
63          // it will use inject() from the model
64          ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableGetBlindBinTagFalse");
65          Collection<CallableBlindBin> callablesFalseTest = new ArrayList<>();
66          for (String falseTest: this.falsyBin) {
67              callablesFalseTest.add(new CallableBlindBin(
68                  falseTest,
69                  injectionModel,
70                  this,
71                  blindMode,
72                  -1, -1, -1,
73                  "bin#falsy"
74              ));
75          }
76          
77          // Delete junk from the results of FALSE statements,
78          // keep only diffs found in each and every FALSE pages.
79          // Allow the user to stop the loop
80          try {
81              List<Future<CallableBlindBin>> futuresFalseTest = taskExecutor.invokeAll(callablesFalseTest);
82              this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor);
83              for (Future<CallableBlindBin> futureFalseTest: futuresFalseTest) {
84                  if (this.injectionModel.isStoppedByUser()) {
85                      return;
86                  }
87                  if (this.falseDiffs.isEmpty()) {
88                      this.falseDiffs = futureFalseTest.get().getDiffsWithReference();  // Init diffs
89                  } else {
90                      this.falseDiffs.retainAll(futureFalseTest.get().getDiffsWithReference());  // Clean un-matching diffs
91                  }
92              }
93          } catch (ExecutionException e) {
94              LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
95          } catch (InterruptedException e) {
96              LOGGER.log(LogLevelUtil.IGNORE, e, e);
97              Thread.currentThread().interrupt();
98          }
99  
100         if (this.injectionModel.isStoppedByUser()) {
101             return;
102         }
103         
104         this.cleanTrueDiffs(injectionModel, blindMode);
105     }
106 
107     private void cleanTrueDiffs(InjectionModel injectionModel, BlindOperator blindMode) {
108         // Concurrent calls to the TRUE statements,
109         // it will use inject() from the model.
110         ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableGetBlindBinTagTrue");
111         Collection<CallableBlindBin> callablesTrueTest = new ArrayList<>();
112         for (String trueTest: this.truthyBin) {
113             callablesTrueTest.add(new CallableBlindBin(
114                 trueTest,
115                 injectionModel,
116                 this,
117                 blindMode,
118                 -1, -1, -1,
119                 "bin#truthy"
120             ));
121         }
122         
123         // Remove TRUE diffs in the FALSE diffs, because
124         // a significant FALSE statement shouldn't contain any TRUE diff.
125         // Allow the user to stop the loop.
126         try {
127             List<Future<CallableBlindBin>> futuresTrueTest = taskExecutor.invokeAll(callablesTrueTest);
128             this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor);
129             for (Future<CallableBlindBin> futureTrueTest: futuresTrueTest) {
130                 if (this.injectionModel.isStoppedByUser()) {
131                     return;
132                 }
133                 this.falseDiffs.removeAll(futureTrueTest.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() && !this.falseDiffs.isEmpty();
167     }
168 
169     @Override
170     public void initNextChar(
171         String sqlQuery,
172         List<char[]> bytes,
173         AtomicInteger indexChar,
174         CompletionService<CallableBlindBin> taskCompletionService,
175         AtomicInteger countTasksSubmitted,
176         CallableBlindBin currentCallable
177     ) {
178         int low;
179         int mid;
180         int high;
181 
182         if (currentCallable != null) {
183             low = currentCallable.getLow();
184             mid = currentCallable.getMid();
185             high = currentCallable.getHigh();
186 
187             if (low >= high) {
188                 if (low == high) {  // n-1 >= n
189                     low = currentCallable.isTrue() ? low : low - 1;
190                 } else {
191                     low = currentCallable.isTrue() ? low : high;
192                 }
193                 char[] asciiCodeMask = bytes.get(currentCallable.getCurrentIndex() - 1);  // bits for current url
194                 this.setAsciiCodeMask(asciiCodeMask, low);
195 
196                 try {
197                     AtomicInteger countBadAsciiCode = new AtomicInteger();  // todo unused
198                     this.injectCharacter(bytes, countTasksSubmitted, countBadAsciiCode, currentCallable);
199                 } catch (InjectionFailureException e) {
200                     return;
201                 }
202 
203                 bytes.add(AbstractInjectionBit.getBitsUnset());
204                 indexChar.incrementAndGet();
205                 low = InjectionBlindBin.LOW;
206                 high = InjectionBlindBin.HIGH;
207             } else if (currentCallable.isTrue()) {  // key < mid
208                 low = mid + 1;
209             } else {  // key > mid
210                 high = mid - 1;
211             }
212             mid = low + (high - low) / 2;
213         } else {
214             low = InjectionBlindBin.LOW;
215             mid = InjectionBlindBin.LOW + (InjectionBlindBin.HIGH - InjectionBlindBin.LOW) / 2;
216             high = InjectionBlindBin.HIGH;
217             bytes.add(AbstractInjectionBit.getBitsUnset());
218             indexChar.incrementAndGet();
219         }
220 
221         taskCompletionService.submit(
222             new CallableBlindBin(
223                 sqlQuery,
224                 indexChar.get(),
225                 this.injectionModel,
226                 this,
227                 this.blindOperator,
228                 low, mid, high,
229                 "bin#" + indexChar + "~" + mid
230             )
231         );
232         countTasksSubmitted.addAndGet(1);
233     }
234 
235     private void setAsciiCodeMask(char[] asciiCodeMask, int value) {
236         String binary = StringUtils.leftPad(Integer.toBinaryString((char) value), 8, "0");
237         for (int i = 0; i <= 7; i++) {
238             asciiCodeMask[i] = binary.charAt(i);
239         }
240     }
241 
242     @Override
243     public char[] initMaskAsciiChar(List<char[]> bytes, CallableBlindBin currentCallable) {
244         return bytes.get(currentCallable.getCurrentIndex() - 1);
245     }
246 
247     @Override
248     public String getInfoMessage() {
249         return "- Strategy Blind bin: query True when Diffs are matching " + this.falseDiffs + "\n\n";
250     }
251     
252     
253     // Getter and setter
254 
255     public String getSourceReferencePage() {
256         return this.sourceReferencePage;
257     }
258     
259     public List<Diff> getFalseDiffs() {
260         return this.falseDiffs;
261     }
262 }