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