View Javadoc
1   package com.jsql.model.injection.strategy;
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.JSqlException;
8   import com.jsql.model.injection.vendor.model.VendorYaml;
9   import com.jsql.model.suspendable.AbstractSuspendable;
10  import com.jsql.model.suspendable.SuspendableGetIndexes;
11  import com.jsql.util.I18nUtil;
12  import com.jsql.util.LogLevelUtil;
13  import com.jsql.util.StringUtil;
14  import org.apache.commons.lang3.StringUtils;
15  import org.apache.logging.log4j.LogManager;
16  import org.apache.logging.log4j.Logger;
17  
18  import java.util.ArrayList;
19  import java.util.Arrays;
20  import java.util.Comparator;
21  import java.util.List;
22  import java.util.regex.Pattern;
23  
24  public class StrategyInjectionUnion extends AbstractStrategy {
25      
26      /**
27       * Log4j logger sent to view.
28       */
29      private static final Logger LOGGER = LogManager.getRootLogger();
30  
31      /**
32       * i.e, 2 in "..union select 1,2,..", if 2 is found in HTML body.
33       */
34      protected String visibleIndex;
35  
36      /**
37       * HTML body of page successfully responding to
38       * multiple fields selection (select 1,2,3,..).
39       */
40      protected String sourceIndexesFound = StringUtils.EMPTY;
41      private int nbIndexesFound = 0;
42  
43      private String performanceLength = "0";
44      
45      public StrategyInjectionUnion(InjectionModel injectionModel) {
46          super(injectionModel);
47      }
48  
49      @Override
50      public void checkApplicability() throws JSqlException {
51          if (this.injectionModel.getMediatorUtils().getPreferencesUtil().isStrategyUnionDisabled()) {
52              LOGGER.log(LogLevelUtil.CONSOLE_INFORM, AbstractStrategy.FORMAT_SKIP_STRATEGY_DISABLED, this.getName());
53              return;
54          }
55  
56          this.logChecking();
57  
58          this.injectionModel.setIndexesInUrl(new SuspendableGetIndexes(this.injectionModel).run());
59  
60          // Define visibleIndex, i.e, 2 in "..union select 1,2,..", if 2 is found in HTML body
61          if (StringUtils.isNotEmpty(this.injectionModel.getIndexesInUrl())) {
62              this.visibleIndex = this.getVisibleIndex(this.sourceIndexesFound);
63          }
64          
65          this.isApplicable = StringUtils.isNotEmpty(this.injectionModel.getIndexesInUrl())
66              && Integer.parseInt(this.injectionModel.getMediatorStrategy().getUnion().getPerformanceLength()) > 0
67              && StringUtils.isNotBlank(this.visibleIndex);
68          
69          if (this.isApplicable) {
70              LOGGER.log(
71                  LogLevelUtil.CONSOLE_SUCCESS,
72                  "{} [{}] at index [{}] showing [{}] characters",
73                  () -> I18nUtil.valueByKey("LOG_VULNERABLE"),
74                  this::getName,
75                  () -> this.visibleIndex,
76                  () -> this.performanceLength
77              );
78              this.allow();
79          } else {
80              this.unallow();
81          }
82      }
83  
84      @Override
85      public void allow(int... i) {
86          this.injectionModel.appendAnalysisReport(
87              StringUtil.formatReport(LogLevelUtil.COLOR_BLU, "### Strategy: " + this.getName())
88              + this.injectionModel.getReportWithIndexes(
89                  this.injectionModel.getMediatorVendor().getVendor().instance().sqlUnion(StringUtil.formatReport(LogLevelUtil.COLOR_GREEN, "<query>"), "0", true),
90                  "metadataInjectionProcess"
91              )
92          );
93          this.markVulnerability(Interaction.MARK_UNION_VULNERABLE);
94      }
95  
96      @Override
97      public void unallow(int... i) {
98          this.markVulnerability(Interaction.MARK_UNION_INVULNERABLE);
99      }
100 
101     @Override
102     public String inject(String sqlQuery, String startPosition, AbstractSuspendable stoppable, String metadataInjectionProcess) {
103         return this.injectionModel.injectWithIndexes(
104             this.injectionModel.getMediatorVendor().getVendor().instance().sqlUnion(sqlQuery, startPosition, false),
105             metadataInjectionProcess
106         );
107     }
108 
109     @Override
110     public void activateWhenApplicable() {
111         if (this.injectionModel.getMediatorStrategy().getStrategy() == null && this.isApplicable()) {
112             LOGGER.log(
113                 LogLevelUtil.CONSOLE_INFORM,
114                 "{} [{}]",
115                 () -> I18nUtil.valueByKey("LOG_USING_STRATEGY"),
116                 this::getName
117             );
118             this.injectionModel.getMediatorStrategy().setStrategy(this.injectionModel.getMediatorStrategy().getUnion());
119 
120             var request = new Request();
121             request.setMessage(Interaction.MARK_UNION_STRATEGY);
122             this.injectionModel.sendToViews(request);
123         }
124     }
125     
126     /**
127      * Runnable class, search the most efficient index.<br>
128      * Some indexes will display a lots of characters, others won't,
129      * so sort them by order of efficiency:<br>
130      * find the one that displays the most number of characters.
131      * @return Integer index with most efficiency and visible in source code
132      */
133     public String getVisibleIndex(String firstSuccessPageSource) {
134         // Parse all indexes found
135         // Fix #4007 (initialize firstSuccessPageSource to empty String instead of null)
136         String regexAllIndexes = String.format(VendorYaml.FORMAT_INDEX, "(\\d+?)");
137         var regexSearch = Pattern.compile("(?s)"+ regexAllIndexes).matcher(firstSuccessPageSource);
138         
139         List<String> foundIndexes = new ArrayList<>();
140         while (regexSearch.find()) {
141             foundIndexes.add(regexSearch.group(1));
142         }
143 
144         String[] indexes = foundIndexes.toArray(new String[0]);
145 
146         // Make url shorter, replace useless indexes from 1337[index]7331 to 1
147         String regexAllExceptIndexesFound = String.format(
148             VendorYaml.FORMAT_INDEX,
149             "(?!"+ String.join("|", indexes) +"7331)\\d*"
150         );
151         String indexesInUrl = this.injectionModel.getIndexesInUrl().replaceAll(regexAllExceptIndexesFound, "1");
152 
153         // Replace correct indexes from 1337(index)7331 to
154         // ==> ${lead}(index)######...######
155         // Search for index that displays the most #
156         String performanceQuery = this.injectionModel.getMediatorVendor().getVendor().instance().sqlCapacity(indexes);
157         String performanceSourcePage = this.injectionModel.injectWithoutIndex(performanceQuery, "union#size");
158 
159         // Build a 2D array of string with:
160         //     column 1: index
161         //     column 2: # found, so #######...#######
162         regexSearch = Pattern.compile("(?s)"+ DataAccess.LEAD +"(\\d+)("+ VendorYaml.CALIBRATOR_SQL +"+)").matcher(performanceSourcePage);
163         List<String[]> performanceResults = new ArrayList<>();
164         while (regexSearch.find()) {
165             performanceResults.add(new String[]{regexSearch.group(1), regexSearch.group(2)});
166         }
167 
168         if (performanceResults.isEmpty()) {
169             this.performanceLength = "0";
170             return null;
171         }
172         
173         // Switch from previous array to 2D integer array
174         //     column 1: length of #######...#######
175         //     column 2: index
176         var lengthFields = new Integer[performanceResults.size()][2];
177         
178         for (var i = 0; i < performanceResults.size(); i++) {
179             lengthFields[i] = new Integer[] {
180                 performanceResults.get(i)[1].length() + performanceResults.get(i)[0].length(),
181                 Integer.parseInt(performanceResults.get(i)[0])
182             };
183         }
184 
185         // Sort by length of #######...#######
186         Arrays.sort(lengthFields, Comparator.comparing((Integer[] s) -> s[0]));
187         Integer[] bestLengthFields = lengthFields[lengthFields.length - 1];
188         this.performanceLength = bestLengthFields[0].toString();
189 
190         // Reduce all others indexes
191         String regexAllIndexesExceptBest = String.format(
192             VendorYaml.FORMAT_INDEX,
193             "(?!"+ bestLengthFields[1] +"7331)\\d*"
194         );
195         indexesInUrl = indexesInUrl.replaceAll(regexAllIndexesExceptBest, "1");
196         
197         this.injectionModel.setIndexesInUrl(indexesInUrl);
198         
199         return Integer.toString(bestLengthFields[1]);
200     }
201     
202     
203     // Getters and setters
204     
205     @Override
206     public String getPerformanceLength() {
207         return this.performanceLength;
208     }
209     
210     @Override
211     public String getName() {
212         return "Union";
213     }
214 
215     public String getVisibleIndex() {
216         return this.visibleIndex;
217     }
218 
219     public void setVisibleIndex(String visibleIndex) {
220         this.visibleIndex = visibleIndex;
221     }
222 
223     public void setSourceIndexesFound(String sourceIndexesFound) {
224         this.sourceIndexesFound = sourceIndexesFound;
225     }
226 
227     public int getNbIndexesFound() {
228         return this.nbIndexesFound;
229     }
230 
231     public void setNbIndexesFound(int nbIndexesFound) {
232         this.nbIndexesFound = nbIndexesFound;
233     }
234 }