Rob van der Woude's Scripting Pages

Batch How To ...

Validate Input

The recent (September 2014) disclosure of a command insertion vulnerability for command-shell scripts (batch files), by The Security Factory, made it painfully clear that batch files can be vulnerable to exploits.

Batch files are extremely "weakly typed" (to use the understatement of the millennium): everything is a string, and a string can be everything, command as well as data, or even both, and there is no way to distinguish between the two.
This makes batch code insertion more than a potential threat.

It all comes down to input validation, and the bad news is: input validation is a lot of work and in batch files it will never be 100% fool-proof.

Three methods of passing input to batch files will be discussed here:

  1. Command line arguments
  2. Parameter files as an alternative to command line arguments
  3. SET /P

Though neither of these is 100% fool-proof, parameter files do come close.

 

Command Line Validation

Unquoted command line arguments %1, %~n1, %*, etcetera can pose a risk, running "unexpected" code, though I cannot imagine any way (yet) to make it run code with elevated privileges.

To start with the bad news: as far as I know, there is no fool-proof command line input validation.

Some generally used input validation methods and their strong and weak points:

 

# Method Successfully handles: Score
(0..7)
Recommended?
empty string white­space delimiters ampersands (double) equal signs double­quoted quotes within
1 IF /I %1==TEST 0
2 IF /I X%1==XTEST 1
3 IF /I "%~1"=="TEST" 5
4 ECHO %1| FIND /I "TEST" 0
5 ECHO.%1| FIND /I "TEST" 1
6 ECHO "%1"| FIND /I "TEST" 5
7 ECHO "%~1"| FIND /I "TEST" 6 ✔️
8 ECHO "%~1"| FIND /I """TEST""" ➕➖ 6.5 ✔️
9 ECHO %1| FINDSTR /L /X /I "TEST" 0
10 ECHO.%1| FINDSTR /L /X /I "TEST" 1
11 ECHO "%1"| FINDSTR /L /X /I "TEST" 5
12 ECHO "%~1"| FINDSTR /L /X /I "TEST" 5
13 ECHO "%~1"| FINDSTR /L /X /I """TEST""" ➕➖ 6.5 ✔️

 

Notes: 1: The validation methods shown in the table are all case insensitive.
To make them case sensitive, remove the /I switch for IF or FIND.
  2: The examples all use %1 or %~1, but the same techniques can be applied to %2..%9 or %~2..%~9 as well.
  3: FIND returns an ErrorLevel 1 if "test string" isn't found in the input, or 0 if it is (if the test string is equal to the input, but also if the test string is part of the input).
FINDSTR /L /X (or FINDSTR /R /X) returns Errorlevel 0 for exact matches only, or 1 otherwise.
  4: Characters that may be "misinterpreted" for delimiters are, besides whitespace: commas, semicolons and equal signs.
  5: For FINDSTR /L as well as for FIND, a doublequote in the "filter string" is "escaped" by an extra doublequote (see methods 8 and 13 in the table); for FINDSTR /R however, use a backspace (\") to "escape" a doublequote in the "filter string".

 

As the table above shows, the weak point of input validation is with "quotes within", a.k.a. "randomly" placed unterminated doublequotes in the command line arguments. Combining these with ampersands is a recipe for disaster.
Though the command line interpreter (CMD.EXE) does its best to try and interpret doublequotes, it can easily be fooled.
While this is not an issue in %CD% (folder names can be quoted, but cannot contain doublequotes themselves), it could be in command line arguments. Even if it doesn't allow cross-scripting so far, it may still cause the batch file to terminate with an error.

 

A simple demonstration: create the following batch file, and run it with command line arguments test1"&test2=

@ECHO OFF
CLS
SETLOCAL ENABLEDELAYEDEXPANSION
FOR /L %%A IN (1,1,9) DO (
	SET Count=%%A
	CALL ECHO %%%%!Count!=%%!Count!
	CALL ECHO %%%%~!Count!=%%~!Count!
	CALL ECHO "%%%%!Count!"="%%!Count!"
	CALL ECHO "%%%%~!Count!"="%%~!Count!"
	ECHO.
)
ENDLOCAL

The resulting output will look like this:

%1=test1"&test2
%~1=test1"&test2

%2=
%~2=
"%2"=""
"%~2"=""

...

Try again, with the entire line in doublequotes, i.e. "test1"&test2="

The resulting output will look like this:

%1="test1"
%~1=test1
"%1"=""test1""
"%~1"="test1"

%2=
%~2=
"%2"=""
"%~2"=""

...

%9=
%~9=
"%9"=""
"%~9"=""

'test2' is not recognized as an internal or external command,
operable program or batch file.

The text 'test2' is not recognized as an internal or external command,operable program or batch file indicates successful code insertion — or at least, it might have been successfull...

Try other "randomly" inserted doublequotes, use more arguments, combine them with ampersands, and see if you can successfully predict the results...

Note: Try test1 "test2; if an opening doublequote is not terminated by a closing doublequote ("test2 in this example), CMD.EXE will just assume a doublequote at the end of the command line.

 

It may be clear from the demonstration above that it is hard, if possible at all, to completely prevent code insertion in batch files.

 

Parameter Files

Instead of passing arguments on the command line, you can present them in a parameter file instead.
A parameter file is a plain text file, with each parameter/argument specified on a single line, e.g.:

SourceDir=D:\MyFiles
TargetDir=Z:\MyFiles
FileSpec=*.mp3

For those of you who have been around for quite a while: yes, like INI files, but without the section headers.

Though not as flexible as command line arguments, text files can be tested for unwanted content much easier.
Unfortunately, the FOR /F loop required to "read" the parameters introduces another vulnerability that has to be addressed first.

Characters that you want to avoid in a FOR /F loop are parenthesis and singlequotes (besides the usual suspects & < > |).
You can escape them if hard coded in the batch file, but for "free" input this is a pain in the ...
Singlequoutes can be used freely in a FOR /F loop by using usebackq, but in that case backquotes are unwanted in the parameter file.

For parameter file validation FINDSTR usually is the best choice:

FINDSTR /R /C:"[()&'`\"]" "parameterfile"
IF ERRORLEVEL 1 (
	ECHO Parameter file does not contain unwanted characters
)

Of course we want to parse the file if it is safe, instead of showing a message; this is where FOR /F is put to work.
But we may want to add an extra test if the lines are in Parameter=Value format:

FINDSTR /R "( ) & ' ` \"" "parameterfile" > NUL
IF NOT ERRORLEVEL 1 (
	ECHO Sorry, the parameter file contains unwanted characters, and cannot be parsed.
	ECHO Aborting the script...
	GOTO:EOF
)
REM Only parse the file if no unwanted characters were found
FOR /F "tokens=* delims==" %%A IN ('FINDSTR /R /X /C:"[^=][^=]*=.*" "parameterfile"') DO (
	SET Parameter.%%A="%%~B"
)
SET Parameter.
Note: I did not use the /C: switch for the "filter" string here, nor did I use a character class ([...] or [^...]).
By omitting the /C: switch, FINDSTR will look if any of the space delimited "filter parts" is found.
Each part is itself still a regular expression, due to the /R command line switch, hence the escaped doublequote (\").
For FINDSTR the filter string "( ) & ' ` \"" is like "[\(\)&'`\"]" in "regular" regular expressions; or, more accurately, like "(\(|\)|&|'|`|\")".
The reason to choose this notation instead of a character class? When redirecting the first example, FINDSTR may trip on doublequotes that surround the file name and write directly to the console — which is exactly what we wanted to prevent using redirection.

 

Using a parameter file instead of command line arguments may be feasible for configuration parameters, but it may prove to be rather impractical for "free" user input.

If you really do need those ampersands, singlequotes and doublequotes, or if you must ask for user input, consider using "stronger" scripting languages like VBScript or, even safer (because it has stronger typing), PowerShell.
You could even consider to have the batch file create the (temporary) VBScript or PowerShell scripts.

 

Related Stuff:

Safely using SET /P to prompt for input

 


page last modified: 2022-10-23; loaded in 0.0012 seconds