PageRenderTime 52ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/java/org/owasp/esapi/reference/DefaultExecutor.java

http://owasp-esapi-java.googlecode.com/
Java | 234 lines | 147 code | 27 blank | 60 comment | 29 complexity | 0587bb02e4fdcfc61b3dab3cf201c41f MD5 | raw file
Possible License(s): BSD-3-Clause, CC-BY-SA-3.0
  1. /**
  2. * OWASP Enterprise Security API (ESAPI)
  3. *
  4. * This file is part of the Open Web Application Security Project (OWASP)
  5. * Enterprise Security API (ESAPI) project. For details, please see
  6. * <a href="http://www.owasp.org/index.php/ESAPI">http://www.owasp.org/index.php/ESAPI</a>.
  7. *
  8. * Copyright (c) 2007 - The OWASP Foundation
  9. *
  10. * The ESAPI is published by OWASP under the BSD license. You should read and accept the
  11. * LICENSE before you use, modify, and/or redistribute this software.
  12. *
  13. * @author Jeff Williams <a href="http://www.aspectsecurity.com">Aspect Security</a>
  14. * @created 2007
  15. */
  16. package org.owasp.esapi.reference;
  17. import java.io.BufferedReader;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.util.List;
  23. import java.util.Map;
  24. import org.owasp.esapi.ESAPI;
  25. import org.owasp.esapi.ExecuteResult;
  26. import org.owasp.esapi.Executor;
  27. import org.owasp.esapi.Logger;
  28. import org.owasp.esapi.codecs.Codec;
  29. import org.owasp.esapi.codecs.UnixCodec;
  30. import org.owasp.esapi.codecs.WindowsCodec;
  31. import org.owasp.esapi.errors.ExecutorException;
  32. /**
  33. * Reference implementation of the Executor interface. This implementation is very restrictive. Commands must exactly
  34. * equal the canonical path to an executable on the system.
  35. *
  36. * <p>Valid characters for parameters are codec dependent, but will usually only include alphanumeric, forward-slash, and dash.</p>
  37. *
  38. * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) <a href="http://www.aspectsecurity.com">Aspect Security</a>
  39. * @since June 1, 2007
  40. * @see org.owasp.esapi.Executor
  41. */
  42. public class DefaultExecutor implements org.owasp.esapi.Executor {
  43. private static volatile Executor singletonInstance;
  44. public static Executor getInstance() {
  45. if ( singletonInstance == null ) {
  46. synchronized ( DefaultExecutor.class ) {
  47. if ( singletonInstance == null ) {
  48. singletonInstance = new DefaultExecutor();
  49. }
  50. }
  51. }
  52. return singletonInstance;
  53. }
  54. /** The logger. */
  55. private final Logger logger = ESAPI.getLogger("Executor");
  56. private Codec codec = null;
  57. //private final int MAX_SYSTEM_COMMAND_LENGTH = 2500;
  58. /**
  59. * Instantiate a new Executor
  60. */
  61. private DefaultExecutor() {
  62. if ( System.getProperty("os.name").indexOf("Windows") != -1 ) {
  63. logger.warning( Logger.SECURITY_SUCCESS, "Using WindowsCodec for Executor. If this is not running on Windows this could allow injection" );
  64. codec = new WindowsCodec();
  65. } else {
  66. logger.warning( Logger.SECURITY_SUCCESS, "Using UnixCodec for Executor. If this is not running on Unix this could allow injection" );
  67. codec = new UnixCodec();
  68. }
  69. }
  70. /**
  71. * {@inheritDoc}
  72. */
  73. public ExecuteResult executeSystemCommand(File executable, List params) throws ExecutorException {
  74. File workdir = ESAPI.securityConfiguration().getWorkingDirectory();
  75. boolean logParams = false;
  76. boolean redirectErrorStream = false;
  77. return executeSystemCommand( executable, params, workdir, codec, logParams, redirectErrorStream );
  78. }
  79. /**
  80. * {@inheritDoc}
  81. *
  82. * The reference implementation sets the work directory, escapes the parameters as per the Codec in use,
  83. * and then executes the command without using concatenation. The exact, absolute, canonical path of each
  84. * executable must be listed as an approved executable in the ESAPI properties. The executable must also
  85. * exist on the disk. All failures will be logged, along with parameters if specified. Set the logParams to false if
  86. * you are going to invoke this interface with confidential information.
  87. */
  88. public ExecuteResult executeSystemCommand(File executable, List params, File workdir, Codec codec, boolean logParams, boolean redirectErrorStream ) throws ExecutorException {
  89. try {
  90. // executable must exist
  91. if (!executable.exists()) {
  92. throw new ExecutorException("Execution failure", "No such executable: " + executable);
  93. }
  94. // executable must use canonical path
  95. if ( !executable.isAbsolute() ) {
  96. throw new ExecutorException("Execution failure", "Attempt to invoke an executable using a non-absolute path: " + executable);
  97. }
  98. // executable must use canonical path
  99. if ( !executable.getPath().equals( executable.getCanonicalPath() ) ) {
  100. throw new ExecutorException("Execution failure", "Attempt to invoke an executable using a non-canonical path: " + executable);
  101. }
  102. // exact, absolute, canonical path to executable must be listed in ESAPI configuration
  103. List approved = ESAPI.securityConfiguration().getAllowedExecutables();
  104. if (!approved.contains(executable.getPath())) {
  105. throw new ExecutorException("Execution failure", "Attempt to invoke executable that is not listed as an approved executable in ESAPI configuration: " + executable.getPath() + " not listed in " + approved );
  106. }
  107. // escape any special characters in the parameters
  108. for ( int i = 0; i < params.size(); i++ ) {
  109. String param = (String)params.get(i);
  110. params.set( i, ESAPI.encoder().encodeForOS(codec, param));
  111. }
  112. // working directory must exist
  113. if (!workdir.exists()) {
  114. throw new ExecutorException("Execution failure", "No such working directory for running executable: " + workdir.getPath());
  115. }
  116. // set the command into the list and create command array
  117. params.add(0, executable.getCanonicalPath());
  118. // Legacy - this is how to implement in Java 1.4
  119. // String[] command = (String[])params.toArray( new String[0] );
  120. // Process process = Runtime.getRuntime().exec(command, new String[0], workdir);
  121. // The following is host to implement in Java 1.5+
  122. ProcessBuilder pb = new ProcessBuilder(params);
  123. Map env = pb.environment();
  124. env.clear(); // Security check - clear environment variables!
  125. pb.directory(workdir);
  126. pb.redirectErrorStream(redirectErrorStream);
  127. if ( logParams ) {
  128. logger.warning(Logger.SECURITY_SUCCESS, "Initiating executable: " + executable + " " + params + " in " + workdir);
  129. } else {
  130. logger.warning(Logger.SECURITY_SUCCESS, "Initiating executable: " + executable + " [sensitive parameters obscured] in " + workdir);
  131. }
  132. final StringBuilder outputBuffer = new StringBuilder();
  133. final StringBuilder errorsBuffer = new StringBuilder();
  134. final Process process = pb.start();
  135. try {
  136. ReadThread errorReader;
  137. if (!redirectErrorStream) {
  138. errorReader = new ReadThread(process.getErrorStream(), errorsBuffer);
  139. errorReader.start();
  140. } else {
  141. errorReader = null;
  142. }
  143. readStream( process.getInputStream(), outputBuffer );
  144. if (errorReader != null) {
  145. errorReader.join();
  146. if (errorReader.exception != null) {
  147. throw errorReader.exception;
  148. }
  149. }
  150. process.waitFor();
  151. } catch (Throwable e) {
  152. process.destroy();
  153. throw new ExecutorException("Execution failure", "Exception thrown during execution of system command: " + e.getMessage(), e);
  154. }
  155. String output = outputBuffer.toString();
  156. String errors = errorsBuffer.toString();
  157. int exitValue = process.exitValue();
  158. if ( errors != null && errors.length() > 0 ) {
  159. String logErrors = errors;
  160. final int MAX_LEN = 256;
  161. if (logErrors.length() > MAX_LEN) {
  162. logErrors = logErrors.substring(0, MAX_LEN) + "(truncated at "+MAX_LEN+" characters)";
  163. }
  164. logger.warning( Logger.SECURITY_SUCCESS, "Error during system command: " + logErrors );
  165. }
  166. if ( exitValue != 0 ) {
  167. logger.warning( Logger.EVENT_FAILURE, "System command exited with non-zero status: " + exitValue );
  168. }
  169. logger.warning(Logger.SECURITY_SUCCESS, "System command complete");
  170. return new ExecuteResult(exitValue, output, errors);
  171. } catch (IOException e) {
  172. throw new ExecutorException("Execution failure", "Exception thrown during execution of system command: " + e.getMessage(), e);
  173. }
  174. }
  175. /**
  176. * readStream reads lines from an input stream and returns all of them in a single string
  177. *
  178. * @param is
  179. * input stream to read from
  180. * @throws IOException
  181. */
  182. private static void readStream( InputStream is, StringBuilder sb ) throws IOException {
  183. InputStreamReader isr = new InputStreamReader(is);
  184. BufferedReader br = new BufferedReader(isr);
  185. String line;
  186. while ((line = br.readLine()) != null) {
  187. sb.append(line).append('\n');
  188. }
  189. }
  190. private static class ReadThread extends Thread {
  191. volatile IOException exception;
  192. private final InputStream stream;
  193. private final StringBuilder buffer;
  194. ReadThread(InputStream stream, StringBuilder buffer) {
  195. this.stream = stream;
  196. this.buffer = buffer;
  197. }
  198. @Override
  199. public void run() {
  200. try {
  201. readStream(stream, buffer);
  202. } catch (IOException e) {
  203. exception = e;
  204. }
  205. }
  206. }
  207. }