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) {  // ending either when no more task or force break
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);  // force break
104                 }
105             } catch (InterruptedException e) {
106                 LOGGER.log(LogLevelUtil.IGNORE, e, e);
107                 Thread.currentThread().interrupt();
108             } catch (ExecutionException e) {
109                 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
110             } catch (InjectionFailureException e) {
111                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, e.getMessage());
112                 countTasksSubmitted.set(0);  // force break
113             }
114         }
115         return this.stop(bytes, taskExecutor);
116     }
117 
118     private static String convert(List<char[]> bytes) {
119         var result = new StringBuilder();
120         for (char[] c: bytes) {
121             try {
122                 var charCode = Integer.parseInt(new String(c), 2);
123                 var str = Character.toString((char) charCode);
124                 result.append(str);
125             } catch (NumberFormatException err) {
126                 // Ignore, byte string not fully constructed (0x1x010x)
127             }
128         }
129         return result.toString();
130     }
131 
132     protected boolean isCharCompleteWithCorruptCheck(
133         List<char[]> bytes,
134         AtomicInteger countBadAsciiCode,
135         T currentCallable
136     ) throws InjectionFailureException {
137         // Process url that has just checked one bit, convert bits to chars,
138         // and change current bit from undefined to 0 or 1
139         char[] maskAsciiChar = this.initMaskAsciiChar(bytes, currentCallable);
140         var asciiCodeBit = new String(maskAsciiChar);
141         var isComplete = false;
142 
143         // Inform the View if bits array is complete, else nothing #Need fix
144         if (asciiCodeBit.matches("^[01]{8}$")) {
145             var asciiCode = Integer.parseInt(asciiCodeBit, 2);
146             if (asciiCode == 255 || asciiCode == 127 || asciiCode == 0) {  // Stop if many 11111111, 01111111 or 00000000
147                 if (countBadAsciiCode.get() > 15) {
148                     throw new InjectionFailureException("Boolean false positive, stopping...");
149                 }
150                 countBadAsciiCode.incrementAndGet();
151             }
152 
153             currentCallable.setCharText(Character.toString((char) asciiCode));
154             
155             this.injectionModel.sendToViews(new Seal.MessageBinary(
156                 asciiCodeBit
157                 + "="
158                 + currentCallable.getCharText()
159                 .replace("\n", "\\n")
160                 .replace("\r", "\\r")
161                 .replace("\t", "\\t")
162             ));
163             isComplete = true;
164         }
165         return isComplete;
166     }
167 
168     private String stop(List<char[]> bytes, ExecutorService taskExecutor) {
169         this.injectionModel.getMediatorUtils().threadUtil().shutdown(taskExecutor);
170 
171         // Get current progress and display
172         var result = new StringBuilder();
173         
174         for (char[] c: bytes) {
175             try {
176                 var charCode = Integer.parseInt(new String(c), 2);
177                 var str = Character.toString((char) charCode);
178                 result.append(str);
179             } catch (NumberFormatException err) {
180                 // Byte string not fully constructed : 0x1x010x
181                 // Ignore
182             }
183         }
184         return result.toString();
185     }
186 
187     /**
188      * Run an HTTP call via the model.
189      * @param urlString URL to inject
190      * @return Source code
191      */
192     public String callUrl(String urlString, String metadataInjectionProcess) {
193         return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess);
194     }
195 
196     public String callUrl(String urlString, String metadataInjectionProcess, AbstractCallableBit<?> callableBoolean) {
197         return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess, callableBoolean);
198     }
199 
200     public BlindOperator getBlindOperator() {
201         return this.blindOperator;
202     }
203 
204     protected static char[] getBitsUnset() {
205         return new char[]{ '0', 'x', 'x', 'x', 'x', 'x', 'x', 'x' };
206     }
207 }