APPSEC USA CTF #3 Writeup: ============================= Write up by: Michael Matonis -twitter: @matonis  -web: http://mike-matonis.com A special shout-out goes to Adnan Baykal for his wisdom. ============================= For the challenge, a java applet was decompiled using JD-GUI (because I like it) and then subsequently analyzed.  Getting further through the applet required certain credentials which were later determined.   As the application demonstrated, credentials were made up of a name and a 6-digit pin number. Testing the application demonstrated that passing other non-digit characters were not allowed, so, it was pretty obvious that obtaining the pin number would be trivial using brute force methods… (10^6 = 10,000,000 different combinations (0-9), but nothing java couldn't handle in a short period of time!) While this is easy to handle from a programming perspective, I really didn't want to go too deep into writing something that would immediately take the actions I needed on the form to determine a correct password, so I decided to look at the application at a much lower level for either authentication bypass, or a function that might handle the credentials in a way that would allow one to use brute-force techniques. Nothing by way of obvious authentication bypass vulnerabilities were apparent, but the codes could be “intelligently” guessed. Decompiling the java revealed that authentications were handled by hash-comparisons as following the code logic demonstrates to be handled by the MD5HashCalculator function compare(String, String): "return getHash(text).equals(hash);" Following the code, and after unzipping the *.jar archive, the xml document "Data.xml" was discovered that contained user names and corresponding hashes: Larry:  520540d155fdde616031d546cd5c747a Curly:  edbf0f9d73cd984fae64c44c618f3c33 Moe:  b2512d38c5702ce5108585d3c730f657 Analysis of this file revealed that a special event might occur when the user with the id of 3, was found- but I'll save that for later!  Looking at the hashes, it was pretty obvious that they could be determined leveraging inherent functionality. Initially, I threw the hashes into md5crack.com. Big whoop, looks like this hash hasn't been analyzed, or this is a some sort of custom function.   To determine the hash, I decided to leverage the functions within MD5HashCalculator to show me what's going on under the hood and to find the decrypted passcode (and it's hashed equivalent).  To achieve this, I developed the following addition which was ran independently from the java applet using the same routines within MD5HashCalculator: import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; //Import for JOPTION Pane import javax.swing.JOptionPane; import exc.EncryptionException; public class MD5HashCalculator {  public static void main(String[] args) throws EncryptionException  {  try{      //Generate a quick range of 6 digit pin codes      for ( int n = 0; n < 999999; n++ )      {          //convert integer to string for operational type consistencies          String text = Integer.toString(n);          MessageDigest m = MessageDigest.getInstance("MD5");          m.update("BuildYourOwnRainbow-AppSecUSA:".getBytes(), 0, "BuildYourOwnRainbow AppSecUSA:".length());          m.update(text.getBytes(), 0, text.length());          byte[] digest = m.digest();          BigInteger bigInt = new BigInteger(1, digest);          String hashtext = bigInt.toString(16);          while (hashtext.length() < 32) {              hashtext = "0" + hashtext;            }            //By this point, the code has been generated and hashed, //now we compare alerting on the current number generated within this iteration (text)            if (hashtext.equals("520540d155fdde616031d546cd5c747a"))            {                JOptionPane.showMessageDialog(null,"Larry:  " + text);            }            else if (hashtext.equals("edbf0f9d73cd984fae64c44c618f3c33"))            {                JOptionPane.showMessageDialog(null,"Curly:  " + text);            }            else if (hashtext.equals("b2512d38c5702ce5108585d3c730f657"))            {                JOptionPane.showMessageDialog(null,"Moe:  " + text);            }        }    }catch (NoSuchAlgorithmException ex) {    throw new EncryptionException("Failed to digest:" + ex.getMessage(), ex);      }  } } If you can't tell, this routine found the code by counting up from 0 to 999999 and hashing the generated number by sending it through the facilities and functions provided. It then compares the hash to the hashes stored in Data.xml and then alerts on it. (see annotations in code above). Now, this was lucky, and many of you will see how this is a "flawed" brute forcer. Based on how I constructed this here it will test from 0-999999, so, if a passcode were 000000 (or even 010000), this routine would not have discovered it. In this case, a nested for loop could be developed which would be much more accurate to this specific business requirement. There's POC code out there for that I'm just not getting in to it right now. I was trying to be the first done, and I just assumed that someone would have made use of all the place holders with a number > 0. Sweet, so, I enter in the credentials and then they magically work. When we put in Moe’s code, we get a special error message which warrants further investigation. With the message of "displayFlag" begin set to false, I immediately looked at the code and found the following check after Moe being authenticated within the Login class which explained the messages when logging in as the other users: if (this.dataTest.booleanValue() == true)            displayFlag(Boolean.valueOf(false));          else            this.myWindow.response_label.setText("There are no encrypted flags stored for this user."); Here, the displayFlag function is called, but its main bool check on whether or not to display it is set to false.  What would have been cool is that if the true/false value could have been set based on some set logic that could have been subverted, but, it didn't look like it could have been gotten around.  So, in this case, using the same method as before, I kept the displayFlag function as is, running it independently with all dependencies only this time, chose to write the BufferedImage to a file, statically providing parameters to the BASE64Decoder variable de (the encoded value within Data.xml) and the actual  Code is as follows:  public static void displayFlag(Boolean displayFlag) throws IOException, EncryptionException {    BASE64Decoder de = new BASE64Decoder();    AES128Encryptor instance = new AES128Encryptor();        //Added encoded value    InputStream in = new ByteArrayInputStream(de.decodeBuffer("LCNIk2NvX1JdVcwIoWBxyewfKIFexhCeGnepwYK63RmSrEmAbEg/CHLRA7sK0mjAB/ZJzbM113Mv3YXg2d46age+gNFoERSce9u2/up8xcpjfLdKd9RilU1hqpkZ0vcXkgZSn8XTnecHVYSRG45gXuWAPzWGvNdy396WivrEKZo5B3kGUOJh8XIBW90aA9RWN0thyHoJvjaiu7alKuv/wtKwMk0ZTuZol5LerY7iJJdaEPthqeso3bHg0xd3xKmD2X09PdEa1U8ZuOYH2Z3ZjN4fj6gm5EBjDNferc7rSWYcbNkF064jnQHO73XQAURMXl+vaXWMX3i7uqWinm/eSc1BUspXaB3BBZChcoMqK0+gdsmzd1GGalVqL86fxBL7T5yVS7RJe0r45HtCYXroecR1aaO6xzHUwcAqGAD2Km2n0DrmRz9MoVO0yR2/KipiAfjFzPd+7wjbzF5M8BcfLNRtBR9fTK86fkeIO+19+IFwnlewYFzVIWtTebm3zOHJCbQtBttMfwp8YaM+UWxZsA=="));    ByteArrayOutputStream out = new ByteArrayOutputStream();        //Added working pin code for uid 3    instance.decrypt("495823", in, out);    ByteArrayInputStream flagData = new ByteArrayInputStream(out.toByteArray());    BufferedImage image = ImageIO.read(flagData);    ImageIcon icon = new ImageIcon(image, "Our precious flag.");        //not essential, but I kept it for nastalgia    if (displayFlag.booleanValue() == true)        //write the file to the local file system    ImageIO.write(image,"jpg",new File("image.jpg")); } This wrote an image to the local file system with an md5 of: MD5 (image.jpg) = 0c211111a323b01c3c8dfee9c7dae7a1 This image was a QR code that, when scanned, sends one to the following URI: http://www.appsecusa.org/ctf.html