package org.owasp.webgoat.lessons; import java.io.IOException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEParameterSpec; import org.apache.ecs.Element; import org.apache.ecs.ElementContainer; import org.apache.ecs.html.B; import org.apache.ecs.html.Input; import org.apache.ecs.html.P; import org.apache.ecs.html.TD; import org.apache.ecs.html.TR; import org.apache.ecs.html.Table; import org.owasp.webgoat.session.ECSFactory; import org.owasp.webgoat.session.WebSession; import org.owasp.webgoat.util.HtmlEncoder; /** * Copyright (c) 2002 Free Software Foundation developed under the custody of the Open Web * Application Security Project (http://www.owasp.org) This software package org.owasp.webgoat.is published by OWASP * under the GPL. You should read and accept the LICENSE before you use, modify and/or redistribute * this software. * * @author Jeff Williams Aspect Security * @created October 28, 2003 */ public class Encoding extends LessonAdapter { private final static String INPUT = "input"; private final static String KEY = "key"; // local encoders private static sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder(); static Map e2i = new HashMap(); private static sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder(); // html entity list private static Object[][] entities = { {"quot", new Integer( 34 )}, // " - double-quote {"amp", new Integer( 38 )}, // & - ampersand {"lt", new Integer( 60 )}, // < - less-than {"gt", new Integer( 62 )}, // > - greater-than {"nbsp", new Integer( 160 )}, // non-breaking space {"copy", new Integer( 169 )}, // © - copyright {"reg", new Integer( 174 )}, // ® - registered trademark {"Agrave", new Integer( 192 )}, // À - uppercase A, grave accent {"Aacute", new Integer( 193 )}, // Á - uppercase A, acute accent {"Acirc", new Integer( 194 )}, // Â - uppercase A, circumflex accent {"Atilde", new Integer( 195 )}, // Ã - uppercase A, tilde {"Auml", new Integer( 196 )}, // Ä - uppercase A, umlaut {"Aring", new Integer( 197 )}, // Å - uppercase A, ring {"AElig", new Integer( 198 )}, // Æ - uppercase AE {"Ccedil", new Integer( 199 )}, // Ç - uppercase C, cedilla {"Egrave", new Integer( 200 )}, // È - uppercase E, grave accent {"Eacute", new Integer( 201 )}, // É - uppercase E, acute accent {"Ecirc", new Integer( 202 )}, // Ê - uppercase E, circumflex accent {"Euml", new Integer( 203 )}, // Ë - uppercase E, umlaut {"Igrave", new Integer( 204 )}, // Ì - uppercase I, grave accent {"Iacute", new Integer( 205 )}, // Í - uppercase I, acute accent {"Icirc", new Integer( 206 )}, // Î - uppercase I, circumflex accent {"Iuml", new Integer( 207 )}, // Ï - uppercase I, umlaut {"ETH", new Integer( 208 )}, // Ð - uppercase Eth, Icelandic {"Ntilde", new Integer( 209 )}, // Ñ - uppercase N, tilde {"Ograve", new Integer( 210 )}, // Ò - uppercase O, grave accent {"Oacute", new Integer( 211 )}, // Ó - uppercase O, acute accent {"Ocirc", new Integer( 212 )}, // Ô - uppercase O, circumflex accent {"Otilde", new Integer( 213 )}, // Õ - uppercase O, tilde {"Ouml", new Integer( 214 )}, // Ö - uppercase O, umlaut {"Oslash", new Integer( 216 )}, // Ø - uppercase O, slash {"Ugrave", new Integer( 217 )}, // Ù - uppercase U, grave accent {"Uacute", new Integer( 218 )}, // Ú - uppercase U, acute accent {"Ucirc", new Integer( 219 )}, // Û - uppercase U, circumflex accent {"Uuml", new Integer( 220 )}, // Ü - uppercase U, umlaut {"Yacute", new Integer( 221 )}, // Ý - uppercase Y, acute accent {"THORN", new Integer( 222 )}, // Þ - uppercase THORN, Icelandic {"szlig", new Integer( 223 )}, // ß - lowercase sharps, German {"agrave", new Integer( 224 )}, // à - lowercase a, grave accent {"aacute", new Integer( 225 )}, // á - lowercase a, acute accent {"acirc", new Integer( 226 )}, // â - lowercase a, circumflex accent {"atilde", new Integer( 227 )}, // ã - lowercase a, tilde {"auml", new Integer( 228 )}, // ä - lowercase a, umlaut {"aring", new Integer( 229 )}, // å - lowercase a, ring {"aelig", new Integer( 230 )}, // æ - lowercase ae {"ccedil", new Integer( 231 )}, // ç - lowercase c, cedilla {"egrave", new Integer( 232 )}, // è - lowercase e, grave accent {"eacute", new Integer( 233 )}, // é - lowercase e, acute accent {"ecirc", new Integer( 234 )}, // ê - lowercase e, circumflex accent {"euml", new Integer( 235 )}, // ë - lowercase e, umlaut {"igrave", new Integer( 236 )}, // ì - lowercase i, grave accent {"iacute", new Integer( 237 )}, // í - lowercase i, acute accent {"icirc", new Integer( 238 )}, // î - lowercase i, circumflex accent {"iuml", new Integer( 239 )}, // ï - lowercase i, umlaut {"igrave", new Integer( 236 )}, // ì - lowercase i, grave accent {"iacute", new Integer( 237 )}, // í - lowercase i, acute accent {"icirc", new Integer( 238 )}, // î - lowercase i, circumflex accent {"iuml", new Integer( 239 )}, // ï - lowercase i, umlaut {"eth", new Integer( 240 )}, // ð - lowercase eth, Icelandic {"ntilde", new Integer( 241 )}, // ñ - lowercase n, tilde {"ograve", new Integer( 242 )}, // ò - lowercase o, grave accent {"oacute", new Integer( 243 )}, // ó - lowercase o, acute accent {"ocirc", new Integer( 244 )}, // ô - lowercase o, circumflex accent {"otilde", new Integer( 245 )}, // õ - lowercase o, tilde {"ouml", new Integer( 246 )}, // ö - lowercase o, umlaut {"oslash", new Integer( 248 )}, // ø - lowercase o, slash {"ugrave", new Integer( 249 )}, // ù - lowercase u, grave accent {"uacute", new Integer( 250 )}, // ú - lowercase u, acute accent {"ucirc", new Integer( 251 )}, // û - lowercase u, circumflex accent {"uuml", new Integer( 252 )}, // ü - lowercase u, umlaut {"yacute", new Integer( 253 )}, // ý - lowercase y, acute accent {"thorn", new Integer( 254 )}, // þ - lowercase thorn, Icelandic {"yuml", new Integer( 255 )}, // ÿ - lowercase y, umlaut {"euro", new Integer( 8364 )},// Euro symbol }; static Map i2e = new HashMap(); // encryption constant private static byte[] salt = { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; /** * Returns the base 64 decoding of a string. * * @param str Description of the Parameter * @return Description of the Return Value * @exception IOException Description of the Exception */ public static String base64Decode( String str ) throws IOException { byte[] b = decoder.decodeBuffer( str ); return ( new String( b ) ); } /** * Description of the Method * * @param c Description of the Parameter * @return Description of the Return Value * @exception IOException Description of the Exception */ public static String base64Decode( char[] c ) throws IOException { return base64Decode( new String( c ) ); } /** * Description of the Method * * @param c Description of the Parameter * @return Description of the Return Value */ public static String base64Encode( char[] c ) { return base64Encode( new String( c ) ); } /** * Returns the base 64 encoding of a string. * * @param str Description of the Parameter * @return Description of the Return Value */ public static String base64Encode( String str ) { byte[] b = str.getBytes(); return ( encoder.encode( b ) ); } /** * Description of the Method * * @param b Description of the Parameter * @return Description of the Return Value */ public static String base64Encode( byte[] b ) { return ( encoder.encode( b ) ); } /** * Description of the Method * * @param s Description of the Parameter * @return Description of the Return Value */ protected Element createContent( WebSession s ) { ElementContainer ec = new ElementContainer(); try { String userInput = s.getParser().getRawParameter( INPUT, "" ); String userKey = s.getParser().getStringParameter( KEY, "" ); Table table = new Table(); TR tr = new TR(); tr.addElement( new TD( "Enter a string: " ) ); Input input = new Input( Input.TEXT, INPUT, userInput ); tr.addElement( new TD().addElement( input ) ); table.addElement( tr ); tr = new TR(); tr.addElement( new TD( "Enter a password (optional): " ) ); Input key = new Input( Input.TEXT, KEY, userKey ); tr.addElement( new TD().addElement( key ) ); table.addElement( tr ); tr = new TR(); Element b = ECSFactory.makeButton( "Go!" ); tr.addElement( new TD().setAlign( "center" ).setColSpan( 2 ).addElement( b ) ); table.addElement( tr ); ec.addElement( table ); ec.addElement( new P() ); Table t = new Table(); t.setWidth( "100%" ); t.setBorder( 0 ); t.setCellSpacing( 1 ); t.setCellPadding( 4 ); String description; t.addElement( makeTitleRow( "Description", "Encoded", "Decoded" ) ); description = "Base64 encoding is a simple reversable encoding used to encode bytes into ASCII characters. Useful for making bytes into a printable string, but provides no security."; // t.addElement( makeDescriptionRow( description ) ); t.addElement( makeRow( description, base64Encode( userInput ), base64Decode( userInput ) ) ); // t.addElement( makeSpacerRow() ); description = "Entity encoding uses special sequences like &amp; for special characters. This prevents these characters from being interpreted by most interpreters."; t.addElement( makeRow( description, HtmlEncoder.encode( userInput ), HtmlEncoder.decode( userInput ) ) ); description = "Password based encryption (PBE) is strong encryption with a text password. Cannot be decrypted without the password"; t.addElement( makeRow( description, encryptString( userInput, userKey ), decryptString( userInput, userKey ) ) ); description = "MD5 hash is a checksum that can be used to validate a string or byte array, but cannot be reversed to find the original string or bytes. For obscure cryptographic reasons, it is better to use SHA-256 if you have a choice."; t.addElement( makeRow( description, hashMD5( userInput ), "Cannot reverse a hash" ) ); description = "SHA-256 hash is a checksum that can be used to validate a string or byte array, but cannot be reversed to find the original string or bytes."; t.addElement( makeRow( description, hashSHA( userInput ), "N/A" ) ); description = "Unicode encoding is..."; t.addElement( makeRow( description, "Not Implemented", "Not Implemented" ) ); description = "URL encoding is..."; t.addElement( makeRow( description, urlEncode( userInput ), urlDecode( userInput ) ) ); description = "Hex encoding simply encodes bytes into %xx format."; t.addElement( makeRow( description, hexEncode( userInput ), hexDecode( userInput ) ) ); description = "Rot13 encoding is a way to make text unreadable, but is easily reversed and provides no security."; t.addElement( makeRow( description, rot13( userInput ), rot13( userInput ) ) ); description = "XOR with password encoding is a weak encryption scheme that mixes a password into data."; t.addElement( makeRow( description, xorEncode( userInput, userKey ), xorDecode( userInput, userKey ) ) ); description = "Double unicode encoding is..."; t.addElement( makeRow( description, "Not Implemented", "Not Implemented" ) ); description = "Double URL encoding is..."; t.addElement( makeRow( description, urlEncode( urlEncode( userInput ) ), urlDecode( urlDecode( userInput ) ) ) ); ec.addElement( t ); } catch ( Exception e ) { s.setMessage( "Error generating " + this.getClass().getName() ); e.printStackTrace(); } if ( getLessonTracker( s ).getNumVisits() > 3 ) { makeSuccess( s ); } return ( ec ); } /** * Convenience method for encrypting a string. * * @param str Description of the Parameter * @param pw Description of the Parameter * @return String the encrypted string. */ public static synchronized String decryptString( String str, String pw ) { try { PBEParameterSpec ps = new javax.crypto.spec.PBEParameterSpec( salt, 20 ); SecretKeyFactory kf = SecretKeyFactory.getInstance( "PBEWithMD5AndDES" ); Cipher passwordDecryptCipher = Cipher.getInstance( "PBEWithMD5AndDES/CBC/PKCS5Padding" ); char[] pass = pw.toCharArray(); SecretKey k = kf.generateSecret( new javax.crypto.spec.PBEKeySpec( pass ) ); passwordDecryptCipher.init( Cipher.DECRYPT_MODE, k, ps ); byte[] dec = decoder.decodeBuffer( str ); byte[] utf8 = passwordDecryptCipher.doFinal( dec ); return new String( utf8, "UTF-8" ); } catch ( Exception e ) { return ( "This is not an encrypted string" ); } } /** * Convenience method for encrypting a string. * * @param str Description of the Parameter * @param pw Description of the Parameter * @return String the encrypted string. * @exception SecurityException Description of the Exception */ public static synchronized String encryptString( String str, String pw ) throws SecurityException { try { PBEParameterSpec ps = new javax.crypto.spec.PBEParameterSpec( salt, 20 ); SecretKeyFactory kf = SecretKeyFactory.getInstance( "PBEWithMD5AndDES" ); Cipher passwordEncryptCipher = Cipher.getInstance( "PBEWithMD5AndDES/CBC/PKCS5Padding" ); char[] pass = pw.toCharArray(); SecretKey k = kf.generateSecret( new javax.crypto.spec.PBEKeySpec( pass ) ); passwordEncryptCipher.init( Cipher.ENCRYPT_MODE, k, ps ); byte[] utf8 = str.getBytes( "UTF-8" ); byte[] enc = passwordEncryptCipher.doFinal( utf8 ); return encoder.encode( enc ); } catch ( Exception e ) { return ( "Encryption error" ); } } /** * Gets the category attribute of the Encoding object * * @return The category value */ protected Category getDefaultCategory() { return AbstractLesson.A8; } /** * Gets the hints attribute of the HelloScreen object * * @return The hints value */ public List getHints() { List hints = new ArrayList(); hints.add( "Enter a string and press 'go'" ); hints.add( "Enter 'abc' and notice the rot13 encoding is 'nop' ( increase each letter by 13 characters )." ); hints.add( "Enter 'a c' and notice the url encoding is 'a+c' ( ' ' is converted to '+' )." ); return hints; } /** * Gets the instructions attribute of the Encoding object * * @return The instructions value */ public String getInstructions(WebSession s) { return "This lesson will familiarize the user with different encoding schemes. "; } private final static Integer DEFAULT_RANKING = new Integer(15); protected Integer getDefaultRanking() { return DEFAULT_RANKING; } /** * Gets the title attribute of the HelloScreen object * * @return The title value */ public String getTitle() { return ( "Encoding Basics" ); } /** * Returns the MD5 hash of a String. * * @param str Description of the Parameter * @return Description of the Return Value */ public static String hashMD5( String str ) { byte[] b = str.getBytes(); MessageDigest md = null; try { md = MessageDigest.getInstance( "MD5" ); md.update( b ); } catch ( NoSuchAlgorithmException e ) { // it's got to be there e.printStackTrace(); } return ( base64Encode( md.digest() ) ); } /** * Returns the SHA hash of a String. * * @param str Description of the Parameter * @return Description of the Return Value */ public static String hashSHA( String str ) { byte[] b = str.getBytes(); MessageDigest md = null; try { md = MessageDigest.getInstance( "SHA-256" ); md.update( b ); } catch ( NoSuchAlgorithmException e ) { // it's got to be there e.printStackTrace(); } return ( base64Encode( md.digest() ) ); } /** * Description of the Method * * @param hexString Description of the Parameter * @return Description of the Return Value */ public static String hexDecode( String hexString ) { try { if ( ( hexString.length() % 3 ) != 0 ) { return ( "String not comprised of Hex digit pairs." ); } char[] chars = new char[hexString.length()]; char[] convChars = new char[hexString.length() / 3]; hexString.getChars( 0, hexString.length(), chars, 0 ); for ( int i = 1; i < hexString.length(); i += 3 ) { String hexToken = new String( chars, i, 2 ); convChars[i / 3] = (char) Integer.parseInt( hexToken, 16 ); } return new String( convChars ); } catch ( NumberFormatException nfe ) { return ( "String not comprised of Hex digits" ); } } /** * Description of the Method * * @param asciiString Description of the Parameter * @return Description of the Return Value */ public static String hexEncode( String asciiString ) { char[] ascii = new char[asciiString.length()]; asciiString.getChars( 0, asciiString.length(), ascii, 0 ); StringBuffer hexBuff = new StringBuffer(); for ( int i = 0; i < asciiString.length(); i++ ) { hexBuff.append( "%" ); hexBuff.append( Integer.toHexString( ascii[i] ) ); } return hexBuff.toString().toUpperCase(); } /** * The main program for the Encoding class * * @param args The command line arguments */ public static void main( String[] args ) { try { String userInput = args[0]; String userKey = args[1]; System.out.println( "Working with: " + userInput ); System.out.print( "Base64 encoding: " ); System.out.println( base64Encode( userInput ) + " : " + base64Decode( userInput ) ); System.out.print( "Entity encoding: " ); System.out.println( HtmlEncoder.encode( userInput ) + " : " + HtmlEncoder.decode( userInput ) ); System.out.print( "Password based encryption (PBE): " ); System.out.println( encryptString( userInput, userKey ) + " : " + decryptString( userInput, userKey ) ); System.out.print( "MD5 hash: " ); System.out.println( hashMD5( userInput ) + " : " + "Cannot reverse a hash" ); System.out.print( "SHA-256 hash: " ); System.out.println( hashSHA( userInput ) + " : " + "Cannot reverse a hash" ); System.out.print( "Unicode encoding: " ); System.out.println( "Not Implemented" + " : " + "Not Implemented" ); System.out.print( "URL encoding: " ); System.out.println( urlEncode( userInput ) + " : " + urlDecode( userInput ) ); System.out.print( "Hex encoding: " ); System.out.println( hexEncode( userInput ) + " : " + hexDecode( userInput ) ); System.out.print( "Rot13 encoding: " ); System.out.println( rot13( userInput ) + " : " + rot13( userInput ) ); System.out.print( "XOR with password: " ); System.out.println( xorEncode( userInput, userKey ) + " : " + xorDecode( userInput, userKey ) ); System.out.print( "Double unicode encoding is..." ); System.out.println( "Not Implemented" + " : " + "Not Implemented" ); System.out.print( "Double URL encoding: " ); System.out.println( urlEncode( urlEncode( userInput ) ) + " : " + urlDecode( urlDecode( userInput ) ) ); } catch ( Exception e ) { e.printStackTrace(); } } /** * Description of the Method * * @param value1 Description of the Parameter * @param value2 Description of the Parameter * @param description Description of the Parameter * @return Description of the Return Value */ private TR makeRow( String description, String value1, String value2 ) { TD desc = new TD().addElement( description ).setBgColor( "#bbbbbb" ); TD val1 = new TD().addElement( value1 ).setBgColor( "#dddddd" ); TD val2 = new TD().addElement( value2 ).setBgColor( "#dddddd" ); TR tr = new TR(); tr.addElement( desc ); tr.addElement( val1 ); tr.addElement( val2 ); return tr; } /** * Description of the Method * * @param value1 Description of the Parameter * @param value2 Description of the Parameter * @param description Description of the Parameter * @return Description of the Return Value */ private TR makeTitleRow( String description, String value1, String value2 ) { TD desc = new TD().addElement( new B().addElement( description ) ); TD val1 = new TD().addElement( new B().addElement( value1 ) ); TD val2 = new TD().addElement( new B().addElement( value2 ) ); desc.setAlign( "center" ); val1.setAlign( "center" ); val2.setAlign( "center" ); TR tr = new TR(); tr.addElement( desc ); tr.addElement( val1 ); tr.addElement( val2 ); return ( tr ); } /** * Description of the Method * * @param input Description of the Parameter * @return Description of the Return Value */ public static synchronized String rot13( String input ) { StringBuffer output = new StringBuffer(); if ( input != null ) { for ( int i = 0; i < input.length(); i++ ) { char inChar = input.charAt( i ); if ( ( inChar >= 'A' ) & ( inChar <= 'Z' ) ) { inChar += 13; if ( inChar > 'Z' ) { inChar -= 26; } } if ( ( inChar >= 'a' ) & ( inChar <= 'z' ) ) { inChar += 13; if ( inChar > 'z' ) { inChar -= 26; } } output.append( inChar ); } } return output.toString(); } /** * Description of the Method * * @param str Description of the Parameter * @return Description of the Return Value */ public static String unicodeDecode( String str ) { // FIXME: TOTALLY EXPERIMENTAL try { ByteBuffer bbuf = ByteBuffer.allocate( str.length() ); bbuf.put( str.getBytes() ); Charset charset = Charset.forName( "ISO-8859-1" ); CharsetDecoder decoder = charset.newDecoder(); CharBuffer cbuf = decoder.decode( bbuf ); return ( cbuf.toString() ); } catch ( Exception e ) { return ( "Encoding problem" ); } } /** * Description of the Method * * @param str Description of the Parameter * @return Description of the Return Value */ public static String unicodeEncode( String str ) { // FIXME: TOTALLY EXPERIMENTAL try { Charset charset = Charset.forName( "ISO-8859-1" ); CharsetEncoder encoder = charset.newEncoder(); ByteBuffer bbuf = encoder.encode( CharBuffer.wrap( str ) ); return ( new String( bbuf.array() ) ); } catch ( Exception e ) { return ( "Encoding problem" ); } } /** * Description of the Method * * @param str Description of the Parameter * @return Description of the Return Value */ public static String urlDecode( String str ) { try { return ( URLDecoder.decode( str, "UTF-8" ) ); } catch ( Exception e ) { return ( "Decoding error" ); } } /** * Description of the Method * * @param str Description of the Parameter * @return Description of the Return Value */ public static String urlEncode( String str ) { try { return ( URLEncoder.encode( str, "UTF-8" ) ); } catch ( Exception e ) { return ( "Encoding error" ); } } /** * Description of the Method * * @param input Description of the Parameter * @param userKey Description of the Parameter * @return Description of the Return Value */ public static synchronized char[] xor( String input, String userKey ) { if ( ( userKey == null ) || ( userKey.trim().length() == 0 ) ) { userKey = "Goober"; } char[] xorChars = userKey.toCharArray(); int keyLen = xorChars.length; char[] inputChars = null; char[] outputChars = null; if ( input != null ) { inputChars = input.toCharArray(); outputChars = new char[inputChars.length]; for ( int i = 0; i < inputChars.length; i++ ) { outputChars[i] = (char) ( inputChars[i] ^ xorChars[i % keyLen] ); } } return outputChars; } /** * Description of the Method * * @param input Description of the Parameter * @param userKey Description of the Parameter * @return Description of the Return Value */ public static synchronized String xorDecode( String input, String userKey ) { try { String decoded = base64Decode( input ); return new String( xor( decoded, userKey ) ); } catch ( Exception e ) { return "String not XOR encoded."; } } /** * Description of the Method * * @param input Description of the Parameter * @param userKey Description of the Parameter * @return Description of the Return Value */ public static synchronized String xorEncode( String input, String userKey ) { return base64Encode( xor( input, userKey ) ); } static { for ( int i = 0; i < entities.length; ++i ) { e2i.put( entities[i][0], entities[i][1] ); i2e.put( entities[i][1], entities[i][0] ); } } }