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.model.bean.util.Interaction;
6   import com.jsql.model.bean.util.Request;
7   import com.jsql.model.exception.InjectionFailureException;
8   import com.jsql.model.exception.StoppedByUserSlidingException;
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 AbstractInjectionBinary<T extends AbstractCallableBinary<T>> {
23      
24      /**
25       * Log4j logger sent to view.
26       */
27      private static final Logger LOGGER = LogManager.getRootLogger();
28      
29      // Every FALSE SQL statements will be checked,
30      // more statements means a more robust application
31      protected final List<String> falsy;
32      
33      // Every TRUE SQL statements will be checked,
34      // more statements means a more robust application
35      protected final List<String> truthy;
36      
37      public enum BinaryMode {
38          AND, OR, STACK, NO_MODE
39      }
40      
41      protected final InjectionModel injectionModel;
42      
43      protected final BinaryMode binaryMode;
44      
45      protected AbstractInjectionBinary(InjectionModel injectionModel, BinaryMode binaryMode) {
46          this.injectionModel = injectionModel;
47          this.binaryMode = binaryMode;
48          this.falsy = this.injectionModel.getMediatorVendor().getVendor().instance().getFalsy();
49          this.truthy = this.injectionModel.getMediatorVendor().getVendor().instance().getTruthy();
50      }
51  
52      /**
53       * Start one test to verify if boolean works.
54       * @return true if boolean method is confirmed
55       */
56      public abstract boolean isInjectable() throws StoppedByUserSlidingException;
57  
58      public abstract void initNextChars(
59          String sqlQuery,
60          List<char[]> bytes,
61          AtomicInteger indexCharacter,
62          CompletionService<T> taskCompletionService,
63          AtomicInteger countTasksSubmitted
64      );
65  
66      public abstract char[] initBinaryMask(List<char[]> bytes, T currentCallable);
67  
68      /**
69       * Display a message to explain how is blind/time working.
70       */
71      public abstract String getInfoMessage();
72  
73      /**
74       * Process the whole boolean injection, character by character, bit by bit.
75       * @param sqlQuery SQL query
76       * @param suspendable Action a user can stop
77       * @return Final string: SQLiABCDEF...
78       */
79      public String inject(String sqlQuery, AbstractSuspendable suspendable) throws StoppedByUserSlidingException {
80          // List of the characters, each one represented by an array of 8 bits
81          // e.g. SQLi: bytes[0] => 01010011:S, bytes[1] => 01010001:Q ...
82          List<char[]> bytes = new ArrayList<>();
83          
84          // Cursor for current character position
85          var indexCharacter = new AtomicInteger(0);
86  
87          // Concurrent URL requests
88          ExecutorService taskExecutor = this.injectionModel.getMediatorUtils().getThreadUtil().getExecutor("CallableAbstractBoolean");
89          
90          CompletionService<T> taskCompletionService = new ExecutorCompletionService<>(taskExecutor);
91  
92          var countTasksSubmitted = new AtomicInteger(0);
93          var countBadAsciiCode = new AtomicInteger(0);
94  
95          this.initNextChars(sqlQuery, bytes, indexCharacter, taskCompletionService, countTasksSubmitted);
96  
97          // Process the job until there is no more active task,
98          // in other word until all HTTP requests are done
99          while (countTasksSubmitted.get() > 0) {
100             if (suspendable.isSuspended()) {
101                 String result = this.stop(bytes, taskExecutor);
102                 throw new StoppedByUserSlidingException(result);
103             }
104             
105             try {
106                 // The URL call is done, bring back the finished task
107                 var currentCallable = taskCompletionService.take().get();
108                 
109                 // One task has just ended, decrease active tasks by 1
110                 countTasksSubmitted.decrementAndGet();
111                 
112                 // If SQL result is not empty, then add a new unknown character,
113                 // and define a new array of 8 undefined bit.
114                 // Then add 8 bits requests for that new character.
115                 this.injectCharacter(bytes, countTasksSubmitted, countBadAsciiCode, currentCallable);
116                 this.initNextChars(sqlQuery, bytes, indexCharacter, taskCompletionService, countTasksSubmitted);
117 
118                 String result = AbstractInjectionBinary.convert(bytes);
119                 if (result.matches("(?s).*"+ DataAccess.TRAIL_RGX +".*")) {
120                     countTasksSubmitted.set(0);
121                     break;
122                 }
123             } catch (InterruptedException e) {
124                 LOGGER.log(LogLevelUtil.IGNORE, e, e);
125                 Thread.currentThread().interrupt();
126             } catch (ExecutionException e) {
127                 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
128             } catch (InjectionFailureException e) {
129                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, e.getMessage());
130                 break;
131             }
132         }
133         return this.stop(bytes, taskExecutor);
134     }
135 
136     private static String convert(List<char[]> bytes) {
137         var result = new StringBuilder();
138 
139         for (char[] c: bytes) {
140             try {
141                 var charCode = Integer.parseInt(new String(c), 2);
142                 var str = Character.toString((char) charCode);
143                 result.append(str);
144 
145             } catch (NumberFormatException err) {
146                 // Byte string not fully constructed : 0x1x010x
147                 // Ignore
148             }
149         }
150         return result.toString();
151     }
152 
153     private void injectCharacter(List<char[]> bytes, AtomicInteger countTasksSubmitted, AtomicInteger countBadAsciiCode, T currentCallable) throws InjectionFailureException {
154         // Process url that has just checked one bit, convert bits to chars,
155         // and change current bit from undefined to 0 or 1
156         
157         char[] asciiCodeMask = this.initBinaryMask(bytes, currentCallable);
158         var asciiCodeBinary = new String(asciiCodeMask);
159 
160         // Inform the View if bits array is complete, else nothing #Need fix
161         if (asciiCodeBinary.matches("^[01]{8}$")) {
162             var asciiCode = Integer.parseInt(asciiCodeBinary, 2);
163             if (asciiCode == 127 || asciiCode == 0) {  // Stop if many 11111111, 01111111 or 00000000
164                 if (countTasksSubmitted.get() != 0 && countBadAsciiCode.get() > 15) {
165                     throw new InjectionFailureException("Boolean false positive, stopping...");
166                 }
167                 countBadAsciiCode.incrementAndGet();
168             }
169 
170             currentCallable.charText = Character.toString((char) asciiCode);
171             
172             var interaction = new Request();
173             interaction.setMessage(Interaction.MESSAGE_BINARY);
174             interaction.setParameters(
175                 asciiCodeBinary
176                 + "="
177                 + currentCallable.charText
178                 .replace("\\n", "\\\\\\n")
179                 .replace("\\r", "\\\\\\r")
180                 .replace("\\t", "\\\\\\t")
181             );
182             this.injectionModel.sendToViews(interaction);
183         }
184     }
185 
186     private String stop(List<char[]> bytes, ExecutorService taskExecutor) {
187         this.injectionModel.getMediatorUtils().getThreadUtil().shutdown(taskExecutor);
188 
189         // Get current progress and display
190         var result = new StringBuilder();
191         
192         for (char[] c: bytes) {
193             try {
194                 var charCode = Integer.parseInt(new String(c), 2);
195                 var str = Character.toString((char) charCode);
196                 result.append(str);
197                 
198             } catch (NumberFormatException err) {
199                 // Byte string not fully constructed : 0x1x010x
200                 // Ignore
201             }
202         }
203         return result.toString();
204     }
205 
206     /**
207      * Run a HTTP call via the model.
208      * @param urlString URL to inject
209      * @return Source code
210      */
211     public String callUrl(String urlString, String metadataInjectionProcess) {
212         return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess);
213     }
214 
215     public String callUrl(String urlString, String metadataInjectionProcess, AbstractCallableBinary<?> callableBoolean) {
216         return this.injectionModel.injectWithoutIndex(urlString, metadataInjectionProcess, callableBoolean);
217     }
218 
219     public BinaryMode getBooleanMode() {
220         return this.binaryMode;
221     }
222 }