View Javadoc
1   package com.jsql.model.injection.strategy.blind;
2   
3   import com.jsql.model.InjectionModel;
4   import com.jsql.model.accessible.DataAccess;
5   import com.jsql.view.subscriber.Seal;
6   import com.jsql.model.exception.InjectionFailureException;
7   import com.jsql.model.exception.StoppedByUserSlidingException;
8   import com.jsql.model.injection.strategy.blind.callable.AbstractCallableBit;
9   import com.jsql.model.suspendable.AbstractSuspendable;
10  import com.jsql.util.LogLevelUtil;
11  import org.apache.logging.log4j.LogManager;
12  import org.apache.logging.log4j.Logger;
13  
14  import java.util.ArrayList;
15  import java.util.List;
16  import java.util.concurrent.CompletionService;
17  import java.util.concurrent.ExecutionException;
18  import java.util.concurrent.ExecutorCompletionService;
19  import java.util.concurrent.ExecutorService;
20  import java.util.concurrent.atomic.AtomicInteger;
21  
22  public abstract class AbstractInjectionBit<T extends AbstractCallableBit<T>> {
23      
24      private static final Logger LOGGER = LogManager.getRootLogger();
25  
26      public enum BlindOperator {
27          AND, OR, STACK, NO_MODE
28      }
29  
30      protected final InjectionModel injectionModel;
31      protected final BlindOperator blindOperator;
32      
33      protected AbstractInjectionBit(InjectionModel injectionModel, BlindOperator blindOperator) {
34          this.injectionModel = injectionModel;
35          this.blindOperator = blindOperator;
36      }
37  
38      /**
39       * Start one test to verify if boolean works.
40       * @return true if boolean method is confirmed
41       */
42      public abstract boolean isInjectable() throws StoppedByUserSlidingException;
43  
44      public abstract void initNextChar(
45          String sqlQuery,
46          List<char[]> bytes,
47          AtomicInteger indexChar,
48          CompletionService<T> taskCompletionService,
49          AtomicInteger countTasksSubmitted,
50          AtomicInteger countBadAsciiCode,
51          T currentCallable  // required by sequential calls like binary search
52      );
53  
54      public abstract char[] initMaskAsciiChar(List<char[]> bytes, T currentCallable);
55  
56      /**
57       * Display a message to explain how is blind/time working.
58       */
59      public abstract String getInfoMessage();
60  
61      /**
62       * Process the whole boolean injection, character by character, bit by bit.
63       * @param sqlQuery SQL query
64       * @param suspendable Action a user can stop
65       * @return Final string: SQLiABCDEF...
66       */
67      public String inject(String sqlQuery, AbstractSuspendable suspendable) throws StoppedByUserSlidingException {
68          // List of the characters, each one represented by an array of 8 bits
69          // e.g. SQLi: bytes[0] => 01010011:S, bytes[1] => 01010001:Q ...
70          List<char[]> bytes = new ArrayList<>();
71          var indexChar = new AtomicInteger(0);  // current char position
72  
73          // Concurrent URL requests
74          ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().threadUtil().getExecutor("CallableAbstractBoolean");
75          CompletionService<T> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
76  
77          var countTasksSubmitted = new AtomicInteger(0);
78          var countBadAsciiCode = new AtomicInteger(0);
79  
80          this.initNextChar(sqlQuery, bytes, indexChar, taskCompletionService, countTasksSubmitted, countBadAsciiCode, null);
81  
82          // Process the job until there is no more active task,
83          // in other word until all HTTP requests are done
84          while (countTasksSubmitted.get() > 0) {
85              if (suspendable.isSuspended()) {
86                  String result = this.stop(bytes, taskExecutor);
87                  throw new StoppedByUserSlidingException(result);
88              }
89              
90              try {
91                  var currentCallable = taskCompletionService.take().get();  // URL call done
92                  countTasksSubmitted.decrementAndGet();  // one task just ended
93                  
94                  // If SQL result is not empty, then add a new unknown character and define a new array of 7 undefined bit.
95                  // Then add 7 bits requests for that new character.
96                  var isComplete = this.isCharCompleteWithCorruptCheck(bytes, countBadAsciiCode, currentCallable);
97                  if (isComplete || currentCallable.isBinary()) {  // prevents bitwise overload new char init on each bit
98                      this.initNextChar(sqlQuery, bytes, indexChar, taskCompletionService, countTasksSubmitted, countBadAsciiCode, currentCallable);
99                  }
100 
101                 String result = AbstractInjectionBit.convert(bytes);
102                 if (result.matches("(?s).*"+ DataAccess.TRAIL_RGX +".*")) {
103                     countTasksSubmitted.set(0);
104                     break;
105                 }
106             } catch (InterruptedException e) {
107                 LOGGER.log(LogLevelUtil.IGNORE, e, e);
108                 Thread.currentThread().interrupt();
109             } catch (ExecutionException e) {
110                 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
111             } catch (InjectionFailureException e) {
112                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, e.getMessage());
113                 break;
114             }
115         }
116         return this.stop(bytes, taskExecutor);
117     }
118 
119     private static String convert(List<char[]> bytes) {
120         var result = new StringBuilder();
121         for (char[] c: bytes) {
122             try {
123                 var charCode = Integer.parseInt(new String(c), 2);
124                 var str = Character.toString((char) charCode);
125                 result.append(str);
126             } catch (NumberFormatException err) {
127                 // Ignore, byte string not fully constructed (0x1x010x)
128             }
129         }
130         return result.toString();
131     }
132 
133     protected boolean isCharCompleteWithCorruptCheck(
134         List<char[]> bytes,
135         AtomicInteger countBadAsciiCode,
136         T currentCallable
137     ) throws InjectionFailureException {
138         // Process url that has just checked one bit, convert bits to chars,
139         // and change current bit from undefined to 0 or 1
140         char[] maskAsciiChar = this.initMaskAsciiChar(bytes, currentCallable);
141         var asciiCodeBit = new String(maskAsciiChar);
142         var isComplete = false;
143 
144         // Inform the View if bits array is complete, else nothing #Need fix
145         if (asciiCodeBit.matches("^[01]{8}$")) {
146             var asciiCode = Integer.parseInt(asciiCodeBit, 2);
147             if (asciiCode == 127 || asciiCode == 0) {  // Stop if many 11111111, 01111111 or 00000000
148                 if (countBadAsciiCode.get() > 15) {
149                     throw new InjectionFailureException("Boolean false positive, stopping...");
150                 }
151                 countBadAsciiCode.incrementAndGet();
152             }
153 
154             currentCallable.setCharText(Character.toString((char) asciiCode));
155             
156             this.injectionModel.sendToViews(new Seal.MessageBinary(
157                 asciiCodeBit
158                 + "="
159                 + currentCallable.getCharText()
160                 .replace("\n", "\\n")
161                 .replace("\r", "\\r")
162                 .replace("\t", "\\t")
163             ));
164             isComplete = true;
165         }
166         return isComplete;
167     }
168 
169     private String stop(List<char[]> bytes, ExecutorService taskExecutor) {
170         this.injectionModel.getMediatorUtils().threadUtil().shutdown(taskExecutor);
171 
172         // Get current progress and display
173         var result = new StringBuilder();
174         
175         for (char[] c: bytes) {
176             try {
177                 var charCode = Integer.parseInt(new String(c), 2);
178                 var str = Character.toString((char) charCode);
179                 result.append(str);
180             } catch (NumberFormatException err) {
181                 // Byte string not fully constructed : 0x1x010x
182                 // Ignore
183             }
184         }
185         return result.toString();
186     }
187 
188     /**
189      * Run an HTTP call via the model.
190      * @param urlString URL to inject
191      * @return Source code
192      */
193     public String callUrl(String urlString, String metadataInjectionProcess) {
194         return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess);
195     }
196 
197     public String callUrl(String urlString, String metadataInjectionProcess, AbstractCallableBit<?> callableBoolean) {
198         return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess, callableBoolean);
199     }
200 
201     public BlindOperator getBlindOperator() {
202         return this.blindOperator;
203     }
204 
205     protected static char[] getBitsUnset() {
206         return new char[]{ '0', 'x', 'x', 'x', 'x', 'x', 'x', 'x' };
207     }
208 }