org.owasp.esapi.codecs.MySQLCodec Maven / Gradle / Ivy
/**
* OWASP Enterprise Security API (ESAPI)
*
* This file is part of the Open Web Application Security Project (OWASP)
* Enterprise Security API (ESAPI) project. For details, please see
* http://www.owasp.org/index.php/ESAPI.
*
* Copyright (c) 2007 - The OWASP Foundation
*
* The ESAPI is published by OWASP under the BSD license. You should read and accept the
* LICENSE before you use, modify, and/or redistribute this software.
*
* @author Jeff Williams Aspect Security
* @created 2007
*/
package org.owasp.esapi.codecs;
/**
* Codec implementation which can be used to escape string literals in MySQL.
*
* Implementation accepts 2 Modes as identified by the OWASP Recommended
* escaping strategies:
*
* - ANSI
* Simply encode all ' (single tick) characters with '' (two single ticks)
*
* - Standard
*
*
* NUL (0x00) --> \0 [This is a zero, not the letter O]
* BS (0x08) --> \b
* TAB (0x09) --> \t
* LF (0x0a) --> \n
* CR (0x0d) --> \r
* SUB (0x1a) --> \Z
* " (0x22) --> \"
* % (0x25) --> \%
* ' (0x27) --> \'
* \ (0x5c) --> \\
* _ (0x5f) --> \_
*
* all other non-alphanumeric characters with ASCII values less than 256 --> \c
* where 'c' is the original non-alphanumeric character.
*
*
*
*
*
*
* @author Jeff Williams (jeff.williams .at. aspectsecurity.com)
* Aspect Security
* @since June 1, 2007
*
* MySQL 8.0 String Literals
* OWASP
* SQL_Injection_Prevention_Cheat_Sheet#MySQL_Escaping
*/
public class MySQLCodec extends AbstractCharacterCodec {
/**
* Specifies the SQL Mode the target MySQL Server is running with. For details about MySQL Server Modes
* please see the Manual at
* @link http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_ansi
*
* Currently the only supported modes are:
* ANSI
* STANDARD
*/
public static enum Mode {
ANSI(1),STANDARD(0);
private int key;
private Mode(int key) { this.key = key; }
static Mode findByKey(int key) {
for ( Mode m : values() ) {
if ( m.key == key )
return m;
}
String message = String.format("No Mode for %s. Valid references are MySQLStandard: %s or ANSI: %s", key, MYSQL_MODE, ANSI_MODE);
throw new IllegalArgumentException(message);
}
}
/** Target MySQL Server is running in Standard MySQL (Default) mode. */
public static final int MYSQL_MODE = 0;
/**
* Target MySQL Server is running in ANSI_QUOTES Mode
*
* @see
* https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sqlmode_ansi_quotes
*/
public static final int ANSI_MODE = 1;
//private int mode = 0;
private Mode mode;
/**
* Instantiate the MySQL codec
*
* @param mode
* Mode has to be one of {MYSQL_MODE|ANSI_MODE} to allow correct encoding
* @deprecated
* @see #MySQLCodec(org.owasp.esapi.codecs.MySQLCodec.Mode)
*/
@Deprecated
public MySQLCodec( int mode ) {
this.mode = Mode.findByKey(mode);
}
/**
* Instantiate the MySQL Codec with the given SQL {@link Mode}.
* @param mode The mode the target server is running in
*/
public MySQLCodec( Mode mode ) {
this.mode = mode;
}
/**
* {@inheritDoc}
*/
public String encodeCharacter( char[] immune, Character c ) {
char ch = c.charValue();
// check for immune characters
if ( containsCharacter( ch, immune ) ) {
return ""+ch;
}
// check for alphanumeric characters
String hex = super.getHexForNonAlphanumeric( ch );
if ( hex == null ) {
return ""+ch;
}
switch( mode ) {
case ANSI: return encodeCharacterANSI( c );
case STANDARD: return encodeCharacterMySQL( c );
default: return null;
}
}
/**
* encodeCharacterANSI encodes for ANSI SQL.
*
* Apostrophe is encoded
*
* Bug ###: In ANSI Mode Strings can also be passed in using the quotation. In ANSI_QUOTES mode a quotation
* is considered to be an identifier, thus cannot be used at all in a value and will be dropped completely.
*
* @param c
* character to encode
* @return
* String encoded to standards of MySQL running in ANSI mode
*/
private String encodeCharacterANSI( Character c ) {
if ( c == '\'' )
return "\'\'";
return ""+c;
}
/**
* Encode a character suitable for MySQL
*
* @param c
* Character to encode
* @return
* Encoded Character
*/
private String encodeCharacterMySQL( Character c ) {
char ch = c.charValue();
if ( ch == 0x00 ) return "\\0";
if ( ch == 0x08 ) return "\\b";
if ( ch == 0x09 ) return "\\t";
if ( ch == 0x0a ) return "\\n";
if ( ch == 0x0d ) return "\\r";
if ( ch == 0x1a ) return "\\Z";
if ( ch == 0x22 ) return "\\\"";
if ( ch == 0x25 ) return "\\%";
if ( ch == 0x27 ) return "\\'";
if ( ch == 0x5c ) return "\\\\";
if ( ch == 0x5f ) return "\\_";
return "\\" + c;
}
/**
* {@inheritDoc}
*
* Returns the decoded version of the character starting at index, or
* null if no decoding is possible.
*
* Formats all are legal (case sensitive)
* In ANSI_MODE '' decodes to '
* In MYSQL_MODE \x decodes to x (or a small list of specials)
*/
public Character decodeCharacter( PushbackSequence input ) {
switch( mode ) {
case ANSI: return decodeCharacterANSI( input );
case STANDARD: return decodeCharacterMySQL( input );
default: return null;
}
}
/**
* decodeCharacterANSI decodes the next character from ANSI SQL escaping
*
* @param input
* A PushBackString containing characters you'd like decoded
* @return
* A single character, decoded
*/
private Character decodeCharacterANSI( PushbackSequence input ) {
input.mark();
Character first = input.next();
if ( first == null ) {
input.reset();
return null;
}
// if this is not an encoded character, return null
if ( first.charValue() != '\'' ) {
input.reset();
return null;
}
Character second = input.next();
if ( second == null ) {
input.reset();
return null;
}
// if this is not an encoded character, return null
if ( second.charValue() != '\'' ) {
input.reset();
return null;
}
return( Character.valueOf( '\'' ) );
}
/**
* decodeCharacterMySQL decodes all the potential escaped characters that MySQL is prepared to escape
*
* @param input
* A string you'd like to be decoded
* @return
* A single character from that string, decoded.
*/
private Character decodeCharacterMySQL( PushbackSequence input ) {
input.mark();
Character first = input.next();
if ( first == null ) {
input.reset();
return null;
}
// if this is not an encoded character, return null
if ( first.charValue() != '\\' ) {
input.reset();
return null;
}
Character second = input.next();
if ( second == null ) {
input.reset();
return null;
}
if ( second.charValue() == '0' ) {
return Character.valueOf( (char)0x00 );
} else if ( second.charValue() == 'b' ) {
return Character.valueOf( (char)0x08 );
} else if ( second.charValue() == 't' ) {
return Character.valueOf( (char)0x09 );
} else if ( second.charValue() == 'n' ) {
return Character.valueOf( (char)0x0a );
} else if ( second.charValue() == 'r' ) {
return Character.valueOf( (char)0x0d );
} else if ( second.charValue() == 'Z' ) {
return Character.valueOf( (char)0x1a );
} else if ( second.charValue() == '\"' ) {
return Character.valueOf( (char)0x22 );
} else if ( second.charValue() == '%' ) {
return Character.valueOf( (char)0x25 );
} else if ( second.charValue() == '\'' ) {
return Character.valueOf( (char)0x27 );
} else if ( second.charValue() == '\\' ) {
return Character.valueOf( (char)0x5c );
} else if ( second.charValue() == '_' ) {
return Character.valueOf( (char)0x5f );
} else {
return second;
}
}
}