Blogs & Stories

SpiderLabs Blog

Attracting more than a half-million annual readers – is the security community’s go-to destination for technical breakdowns of the latest threats, critical vulnerability disclosures and cutting-edge research.

JSON Crypto Helper a Ruby-based Burp Extension for JSON Encryption/Decryption - Part I

Burp Suite is one of my favorite tools when performing security assessments of web applications. Although it can handle almost all the basic situations by itself, corner case applications need some extra Burp customizations to maximize your assessment capabilities. For this purpose, Burp provides an extension API that enables you to extend its functionality. Extensions can be written in Java, Python or Ruby.

This is the first post in a three-part series about how to write a simple Ruby extension that helps deal with encrypted JSON messages (read part two here). You can download the complete extension code here. It was inspired by the JSON Decoder extension by Michal Melewski, which is available here.

Context

The last mobile application I tested used encrypted JSON messages to communicate with the server and any tampering or fuzzing was almost impossible. Requesting URLs in a browser looked like this:

01 - request

As you can see, only the value string was encrypted and encoded with base64:

{
  "hello":"9DpW68SsC5nHi5PeyXEHEA==",
  "test":{ 
    "input1":"irTKsV484FzLRuH2A1r42Q==",
    "input2":"fFAUsaRu8W47lfusAtzV88ewPj9etXuoPtbCUUMzQHs="
  }
}

From a security perspective, the additional encryption step is not really efficient because the encryption/decryption is performed locally on the device; it is not difficult to retrieve all the information needed to decrypt the message directly from the application binary. And that's what I did. So, after reversing the binary (an iOS application), I managed to extract the following information (NOTE: the encryption key and the IV have been changed for demonstration purposes):

Encryption Key (hex digits): 0cc91185341d6a27c380e97fed30b4a1dcafd7f5044f09cc7fa6b3fa0df290b

IV (hex digits): abdad4a94d544b52f4782e2856f82874

Encryption Algorithm: AES 128bit CBC

You can verify this using OpenSSL from the terminal:

$ echo "9DpW68SsC5nHi5PeyXEHEA==" | openssl enc -aes-128-cbc -base64 -d -K a0cc91185341d6a27c380e97fed30b4a1dcafd7f5044f09cc7fa6b3fa0df290b -iv abdad4a94d544b52f4782e2856f82874
   => world!

So, now you have all you need to get rid of the encryption and use Burp the same way you do with unencrypted content. The end goal is to automatically decrypt the value anywhere in the Burp interface, automatically encrypt the value you want to send to the server (using the Repeater tool for example) and fuzz the JSON parameters with on-the-fly encryption (Intruder tool).

Setup the environment

Because we will use Ruby to implement the extension, you need to setup the environment with JRuby. The easiest way to do it is using RVM. Just install it using the RVM install guide and run the following commands in the directory you will create your Ruby extension:

$ rvm install jruby
$ rvm --ruby-version use jruby@burp --create

This will install JRuby (if you don't have it yet) and create the gemset burp. It also adds .ruby-version and .ruby-gemset files to tell RVM which ruby version and which gemset to use when entering the directory.

Then just run Burp like this:

JRUBY_HOME=$MY_RUBY_HOME java -XX:MaxPermSize=1G -Xmx1g -Xms1g -jar [burp_install_dir]/burpsuite_pro_v1.6.09.jar

It is important to setup the JRUBY_HOME environment variable to tell Burp where the JRuby environment is located.

IBurpExtender interface

The first thing to do is to implement the IBurpExtender interface with the method #registerExtenderCallbacks.

require 'java'
java_import 'burp.IBurpExtender'

class BurpExtender
  include IBurpExtender

  def registerExtenderCallbacks(callbacks)
    callbacks.setExtensionName("JSON Crypto Helper")
  end
end

This method is called when the extension is loaded; passing a callbacks instance that implements the IBurpExtenderCallbacks interface. This instance will be used to perform many actions, for example here, #setExtensionName is used to inform the extension name that will be displayed in Burp.

To load the extension you need first to configure Burp Suite Extender tool (Options tab) and specify the path where the JRuby jar is installed. If you're using RVM, it should be in your RVM project path, commonly installed here: $HOME/.rvm/rubies/jruby-x.x.x/lib/jruby.jar.

02 - jruby path

Then select Add in the Extensions tab, select the extension type Ruby and your .rb file. You should see your new extension showing up in the list:

03 - load extension

Creating a Custom Tab

In order to interact with any Burp tools, you will need to create a specific tab that will display the decrypted JSON content. To do so, you need to implement the IMessageEditorTabFactory interface and register your extension with #registerMessageEditorTabFactory. Then you create a separate class that will take care of it (I called it JSONDecryptorTab) and instantiate it in the #createNewInstance method. Because you will use the callbacks a lot, it is a good idea to create an instance variable to easily access it inside JSONDecryptorTab instances.

require 'java'
java_import 'burp.IBurpExtender'
java_import 'burp.IMessageEditorTabFactory'

class BurpExtender
  include IBurpExtender
  include IMessageEditorTabFactory
  attr_reader :callbacks

  def registerExtenderCallbacks(callbacks)
    @callbacks = callbacks
    callbacks.setExtensionName("JSON Crypto Helper")
    callbacks.registerMessageEditorTabFactory(self)
  end

  def createNewInstance(controller, editable)
    JSONDecryptorTab.new(@callbacks, editable)
  end
end

Let's see how the JSONDecryptorTab class looks like. First it needs to implement the IMessageEditorTab interface:

class JSONDecryptorTab
  include IMessageEditorTab
  ...[SNIP]...

and the following methods:

  • #initialize: the constructor, which setups some instance variable:
def initialize(callbacks, editable)
  # Burp Suite useful helpers:
  @helper = callbacks.get_helpers()
  # Create a Burp's plain text editor to use with this extension:
  @txt_input = callbacks.create_text_editor()
  # Indicates if the text editor is read-only or not:
  @editable = editable
end
  • #getTabCaption: the tab name that will be displayed by Burp
def getTabCaption
  return "JSON Crypto Helper"
end
  • #isEnabled: this method is invoked each time Burp displays a new message to check if the new custom tab should be displayed. It should return a Boolean (see below).
  • #setMessage: this method is invoked each time a new message is displayed in your custom tab. This method will take care of processing the message, decrypting the JSON and displaying it (see below).
  • #getMessage: this method is invoked each time you leave the custom tab. It returns an array of bytes that will be used by Burp (see below).
  • #isModified: this method is invoked after calling #getMessage and if the editor tab is editable (in the Repeater tool for example). It should return true if the message has been edited. You simply use the value returned by #text_modified? of the text editor object:
def isModified
  return @txt_input.text_modified?
end
  • #getSelectedData: not in use, but still need to be defined.

Decrypting JSON

Burp invokes the #isEnabled method to know if the custom tab has to be displayed for a given message (request or response). In this method, you will simply analyze the content and check if it is a proper JSON:

def isEnabled(content, is_request)
  return false if content.nil? or content.empty?
  if is_request
    info = @helper.analyze_request(content)
  else
    info = @helper.analyze_response(content)
  end
  return json?(info, is_request)
end

The method #json? uses the information already gathered by Burp:

def json?(info, is_request)
  if is_request
    return info.content_type == IRequestInfo::CONTENT_TYPE_JSON
  end
  return (info.stated_mime_type == "JSON" or info.inferred_mime_type == "JSON")
end

Don't forget to import the java interface for the constant CONTENT_TYPE_JSON:

java_import 'burp.IRequestInfo'

Now the tab should be displayed on every JSON request or response. Next step, you need to fill it with the decrypted JSON. The #setMessage method will take care of this:

def setMessage(content, is_request)
  return if content.nil? or content.empty? or @txt_input.text_modified?
  if is_request
    info = @helper.analyze_request(content)
  else
    info = @helper.analyze_response(content)
  end
  headers = content[ 0..(info.get_body_offset - 1) ].to_s
  body = content[ info.get_body_offset..-1 ].to_s
  body = process_json(body, :decrypt) if json?(info, is_request)
  @txt_input.text = (headers + body).to_java_bytes
  @txt_input.editable = @editable
end

Basically, the method analyses the message content, extracts the body, processes it and fills the editor.

#process_json will take care of decrypting/encrypting the JSON message. First, it will parse the content using the JSON Ruby library, decrypt/encrypt it and finally generate a well formatted JSON using #pretty_generate from the same library:

def process_json(json, mode = :no_encryption)
  message = ""
  begin
    json_tmp = JSON.parse(json)
    if mode == :decrypt
      json_tmp = decrypt_json(json_tmp)
    elsif mode == :encrypt
      json_tmp = encrypt_json(json_tmp)
    end
    message << JSON.pretty_generate(json_tmp)
  rescue OpenSSL::Cipher::CipherError => e
    # not encrypted, ignore and return the original message
    puts "process_json: Cryptography error: #{e.message}"
    message << json
  rescue JSON::ParserError => e
    puts "process_json: Parsing error: #{e.message}"
    message << json
  end
  message
end

Note that in case of decryption/encryption or parsing errors, the method rolls-back to the unmodified JSON message.

Decryption and Encryption Routines

I created a separate module for all the cryptographic methods. You will need to include it in the JSONDecryptorTab class.

module EncryptionHelper
  # Key and IV retrieved from the application binary
  KEY = "\xa0\xcc\x91\x18\x53\x41\xd6\xa2\x7c\x38\x0e\x97\xfe\xd3\x0b\x4a\x1d\xca\xfd\x7f\x50\x44\xf0\x9c\xc7\xfa\x6b\x3f\xa0\xdf\x29\x0b"
  IV = "\xab\xda\xd4\xa9\x4d\x54\x4b\x52\xf4\x78\x2e\x28\x56\xf8\x28\x74"
  ALGO = "aes-128-cbc"

  def encrypt text
      cipher = OpenSSL::Cipher::Cipher.new(ALGO)
      cipher.encrypt
      cipher.key = KEY
      cipher.iv = IV
      ciphertext = cipher.update(text)
      ciphertext << cipher.final
  end

  def decrypt ciphertext
      cipher = OpenSSL::Cipher::Cipher.new(ALGO)
      cipher.decrypt
      cipher.key = KEY
      cipher.iv = IV
      text = cipher.update(ciphertext)
      text << cipher.final
  end

  def encode_b64 text
      [text].pack('m0')
  end

  def decode_b64 encoded_text
      encoded_text.unpack('m')[0]
  end

  # This method will base64-decode and decrypt each value recursively
  def decrypt_json(json)
    json.each do |key, value|
      if value.is_a?(Hash)
        json[key] = decrypt_json(value)
      else
        value_tmp = decode_b64(value)
        if value_tmp.empty?
          json[key] = value
        else
          json[key] = decrypt(value_tmp)
        end
      end
    end
    json
  end

  # This method will encrypt and base64-encode each value recursively
  def encrypt_json(json)
    json.each do |key, value|
      if value.is_a?(Hash)
        json[key] = encrypt_json(value)
      else
        if value.empty?
          json[key] = value
        else
          json[key] = encode_b64(encrypt(value))
        end
      end
    end
    json
  end
end

Also, don't forget to require openssl and json in the file:

require "openssl"
require "json"

You are now able to decrypt JSON messages in the new custom tab, which is available in any tool. For example, in the Proxy tool:

04 - decrypt proxy

Parting Thoughts

With this first post we covered the basics of Burp Extender tool and learned how to write a simple ruby-based extension. We took an encrypted JSON as an example but this also works with any content you want to process before displaying. For example, it won't be too complicated to implement a parser for an in-house communication protocol used by the application.

In the next post I will explain how to automatically encrypt the JSON values using the Repeater tool. This will enable you to write or modify a plaintext JSON and send it encrypted to the remote server.

Stay tuned!

Recent SpiderLabs Blog Posts