Sunday, June 1, 2014

VulnHub: SecOS 1

Recently @PaulSec posted an entry level web exploitation VM to vulnhub. It seemed interesting enough and not too time consuming (I'm sitting for my CISSP exam next month so I'm a bit busy memorizing random ISO numbers) so I decided to do it.

After firing up the VM, we're not really given any indication of where it's at. We'll use nmap to find it really quickly.

We see it sitting at, but a quick check shows it's not running anything on port 80. So we'll run nmap again to see where the webserver is listening.

And find that it's running our service on port 8081, which is a fairly simple web page.

We'll check the source really quickly to see if there's any interesting scripts or remnants from development and find a commented out nav link.

When we navigate to, we're met with what's essentially a blank page. However, checking the source again gives a (far too helpful, booo) hint.

The first hint tells us the admin will probably read our messages. Second tells us how he accesses it. Third says CSRF will work here. Alright, no big deal. Lets register a user and see what we find after logging in. The app gives a user list which tells us who the admin is.

And there's also a change password function. The form on that page doesn't contain any CSRF mitigations, nor does the cookie seem to have anything there. We'll check the post data just in case and find that it's probably vulnerable.

So we'll just copy that form into our own html file, populate it with data we want, and have javascript automatically submit it. I also added a mail function to let me know when the admin has taken the bait.

Nothing special. I toss it up on my sketchy free web host and send the link to the admin. I pretty quickly receive the email and log in as spiderman.

Despite being an admin, the user interface is exactly the same. Poking around, we see in one of his messages that another user has found his password and notified him of the issue. Looking back, the VM is running an SSH server so maybe he reuses his passwords and lets us into his box.

It works, and now we can see the source of the node.js app. Looking around at different configuration files, we notice that there's actually a hidden admin interface that spiderman runs locally (defined in internalServer.js). This interface seems to offer a web-based ping, which is pretty clearly vulnerable to command injection.

We can't access this directly from the outside, so we have two options. We could edit the configuration to make it outward facing, but this is easily noticed (and we don't have rights to reboot the server or reload configs). Instead, we'll utilize ssh to tunnel us to port 9000.

Now if we navigate to http://localhost:1337 on our own machine, it gets tunneled to the loopback interface on the VM at port 9000, where the internalServer app is running. It is in fact a web based ping.

Let's try to inject whoami to see who the server is running as. Inputting ; whoami gives us clean output letting us know we're running as root.

From here, it's pretty easy to set up and run a reverse shell. Unfortunately, the nc package installed on this VM doesn't have the -e option compiled in, so we'll have to take a different route. The Reverse Shell Cheat Sheet I like to use has a number of them, so we'll give the python one a try. After setting up a nc listener on port 31337, we'll input the that shell into the ping command and get our reverse shell.

Now we just navigate to /root and read the flag.txt file to win the game.

Monday, May 19, 2014

IRISSCON 2012 CTF: Security Challenge II

So after a good long break from this challenge (and posting writeups in general), I've been helping out a friend start doing things with different wargames/CTF/hacking challenges and we came back to the MiniCTF challenges. We got to IRISSCON 2012 Challenge II where I told him I had admitted defeat quite a few months ago. He began searching for a writeup (being curious about what went on) and informed me that there didn't seem to be one that directly addressed a proper solution (only some google-fu ones which wouldn't have been available during the CTF most likely). So I decided to revisit it and took to Twitter to see if anyone had mentioned it and, curiously, found one with my exact feelings from a few months before.
This is a pretty concise article on how to properly use LFI, which totally opened my eyes to how simple this problem really was: we had a very clear way to get at the data we needed. So without any more rambling, I'll get into the challenge.

To start out with, we're given a page that describes the challenge, gives a hint to use one of the other challenge pages to solve this one, and links to a "Restricted Area."

Not that exciting. So looking at the restricted page, it's a simple PHP-based login screen which POSTs to secure.php.

Since we want to know what's going on behind the scenes, seeing the source of secure.php would be great. However, using the most basic LFI we found in Challenge I won't work - the server will process the PHP before serving it up to us - we'd just see the login screen again. The trick is the "reading a file" section from the LFI post Damo pointed us to: if we get the server to encode that PHP code into base64 (or something else like it, but that's the example they provide), there's nothing to process and we get the plaintext source with only the extra trivial step of decoding the b64. So we navigate to which gives us a b64 encoded secure.php.

If we decode that we get the source for secure.php with an interesting bit:

Which reads the contents of admin_key.log, decodes it from base64, then decodes the result from uuencode and uses that as the password. We can do that pretty easily by first reading admin_key.log via Challenge I again at which gives some nice base64.

If we decode that from both the base64 and uuencode we get "f763hcn348vh489mj7c45" as the password for secure.php. If we go back and enter that we're done. 6ish months later.

Wednesday, December 11, 2013

IRISSCON 2012 CTF: Security Challenge VIII

Now we're going to work on Security Challenge VIII. I've actually been sitting on this solution for a while - Java reversing is pretty easy so it was one of the first ones I did. Challenges 2 and 7 are still evading me, but I'll get there eventually.

In this challenge, you're presented with a "Government File Store" system. When you first run the provided .jar file, you get prompted for a login. 

Some real quick cheesey brute force attempts didn't work, so I'll move on to a Java decompiler. I used DJ Java Decompiler which got the job done, but I'd prefer to find a free option if anyone wants to chime in with one. Inside the classes of this program, there's one called That's probably what we're interested in, and it's a pretty simple SwingUI with two buttons and a couple of text fields. So going straight to the only ActionListener.actionPerformed() implementation I can find, there's a branch of the method which handles the login button.
Well that pretty clearly gives us the login credentials - we actually probably could have found this using strings against the unzipped jar file. Whatever though, we got the answer so we'll move on. After logging in, we're presented with this screen, which is an instance of the SecureFileStore class, which we know from above.
In this, we can browse to a file, tell it to be uploaded, and get some status updates out of it. Looking at the source, there's a function  upload() in the class which references a SubmitFile class in the same package.
So going to look at the function we can see some interesting things. First off, we notice that it's using a ZipOutputStream, indicating that it's created an encrypted .zip file. Next, there's another line near the beginning of this function which sets the password of this zip to the SHA1 hash of the file's name: String password = getSHA1Hash(inputfile.getName());. Then it uses some standard Java classes to create an HTTP connection, but starts referencing a class Config to retrieve a URL, username, and password. So we'll go to the Config class and see what's up with getURL(), getUsername(), and getPassword(). All of these reference a function called uncipher() which they pass some constant byte arrays to.
Examining the unCipher() function, it's clearly a really simple XOR function. I wrote a quick Python script to run the uncipher for myself which yielded a url, a username, and a password.
So navigating to that URL and hoping that it implements a GET so we don't have to try to figure out where the admin panel is is the next step. Luckily we're met with the familiar HTTP based login prompt, into which we'll input those credentials we just found.
Damn. All we get out of that is a nice little string that says "gfs:1". Maybe there's an index.php there though. So we'll try to navigate to that and we're met with the list of uploaded files and some instructions.
As they say, it's pretty obvious which file we want as there's only one that seems to be interesting whatsoever. So we'll download the memo to the admin and try to open it up with something like 7zip (since they claim it won't work with Explorer's unzip). If you notice the filenames, all have a timestamp appended after the original extension of the file and before the .zip extension. It's probably a good bet that we don't want the hash of the whole .zip filename, because who names their files like that? So we'll hash just "memo_to_hof_admin.txt" and that gives us "293b663b729409237c28c2ff5659b0ba22caf50b". Type that into 7-zip when we go to extract this and we get a text file with the link to a hall of fame and credentials to edit it...why would we want that?
But whatever we'll go there and login. Oh, it gives us the key lolololol.

IRISSCON 2012 CTF: Security Challenge VI

So after a long hiatus I'm back to working on the IRISSCON 2012 Security Challenges. This one is going to be for Security Challenge VI which presents us with a website where we can register an account, go to a members only page, and then try to access an admin page.
So first I tried some SQL injection on the login page and registration page and didn't get much. So we'll register an account and poke around. Going to the members area and click the admin only link gives us a little error message.
There's not much else in the UI that seems exploitable, so let's fire up Burp to see what's going on in the communication end. I logged out, and then started with the login page. During the login process, I noted that some cookies are being set for deletion by marking their values as "deleted" and also setting their expiry date a year in the past.

But I don't really care what the server has to say about that so I'll change it and see what happens.

Then we're met with a nice little error - very simply we just get a response that says "SQL Error" (I'll theorize on why later). So these fields are obviously used for something, which means we may be on the right track. I changed the usernamesch6 cookie value to "admin" to see what happens, but no dice. I actually struggled with this part for a long time and went to read about cookie based SQLi (semicolons have to be encoded) and in the example I found at The Infosec Institute I noticed something I hadn't thought of - they decode one of the values from Base64 into plaintext. Web developers love that crap, so I decided to base64 "admin" to "YWRtaW4=" to see if we got any different results. The error went away (still going to explain the SQL Error before, don't worry), but we still get no access. So maybe some SQL injection is in order! Let's try just adding a single quote as base64 ("Jw==") and see what happens.

SQL Error! That's a good sign. The same problem exists in the password cookie. I tried using my login credentials using in B64, but that didn't work. So I went on the assumption that the username is being matched to "admin" and worked from there. I tried using ' or '1'='1';-- in the password field, but didn't get anywhere. So let's see if the SQL query checks the username first and the password second, such that we can cut off the password verification. admin';-- is encoded in B64 as "YWRtaW4nOy0t" and used in the username cookie which works.
Now, all this base64 into SQL business is probably why "jpaglier" wouldn't work. It actually broke down at "jpaglie". Each letter in base64 represents an octet triple and I'm assuming that inserted a control character or something that SQL couldn't handle causing the crash. It helped us though, so cheers to that.

Tuesday, December 10, 2013

Reflected and Stored XSS on

So this little startup at recently posted a link on my university program's Facebook page asking the computer science students what they think. Of course the first thing I did was start inputting characters into the search bar which might show the presence of XSS or SQLi vulnerabilities. Unfortunately, I didn't think to take screenshots during this process, but it was all pretty simple to follow - I wasn't super thorough during this, so I let them know they're going to want to do a serious audit in the future.

First I tried using a single quote in the search bar (controlled by a URL parameter q), which would reload the page with the text \' in the search box. Looks like improper escaping to me (probably using the PHP addslashes() function). So then I tried to break out of the value attribute of the HTML element by using a double quote instead. Lo and behold, just a single \ appeared in the search box and the search button started rendering weirdly. Next I decided I wanted to include some JS in the page, so I input "> <script> alert(1); </script> into the search box. Looking in Chrome's developer console, the XSS Auditor fired a warning that it was blocking JS from running because it appeared in the URL. Cool. It's important to note that the addslashes() function was breaking the HTML if I tried to input well-formed markup like <img src="" />, but Chrome is nice to developers so  it inserted quotes for me (and rendered the page how I wanted) if I left them out a la <img src= />So for the report I filed, I put in an img tag containing a funny .gif just to show I could muck with things, but didn't stop there, because we can obviously have a little more fun at this point, but nothing too special.

Next I moved on to their posting page. I did the really blind method of tossing a <script> alert(1); </script> into every text field and into the value attribute of every select tag and submitted. It happily chugged along, and my alert boxes were showing up in a number of places across the site.

Following that, I went back and used the same parameter in the URL of the search page to embed an iframe into the page pointing at the ones which ran the stored JS which also got code executing on that page. My memory is fuzzy about whether or not I could get a script that I had hosted off-site running in that page or if Chrome was blocking that too, but I'm sure some more fiddling could have gotten stuff done. In the end, we had the url;%3C/script%3E.

I reported the issue to the administrators and was pleased to receive a call back within 15 or 20 minutes, when we had a conversation about what I had done and how to mitigate it. I fired them off some OWASP reference material and the issue seems to be fixed now (though I only did a cursory check). They expressed a keen interest in learning about the problems (graciously giving me permission to do this writeup) and mitigating them and I'm extremely happy with their response. Keep at it guys.

Saturday, November 2, 2013

Mini CTF: Guess the Key

Once again, we'll take a look at a Mini CTF challenge. This one gives you an executable that lets you input a key and then verifies it, giving you some feedback.

Pretty simple. So we'll fire up ollydbg (IDA might be in order if this was more complex, but it's not and instant feedback is nice) and check out what's going on. Looking at the strings in the executable reveals 4 strings that obviously represent win/lose codepaths.

So we'll jump to where these are referenced in the code. For kicks we'll go straight to the success and see what's going on there.

So there's a condition that is checked and based on that we jump to either success or failure. Right above it is a series of operations followed by checks and similar jumps which happen to look very much like checking a string char by char. So we'll go up to the top of that loop to see what happens.

So at the beginning, we check that the string is 16 (0x10) characters long then check each character in the string for specific properties (additions, subtractions, XORs, rotations, etc.). I'll spare you the details (the work here was more tedious than it was hard), but it's nothing too complex. The only issue I ran into was mixing up the semantics of the ROR instruction, which wrap the register values rather than pad them like a shift.

In the end we get the key "d4e364fe580d793c" and we're done.

Friday, November 1, 2013

IRISSCON 2012 CTF: Security Challenge III

So it's time for IRISSCON 2012's Security Challenge III. Poking around we see a list of members that links us to an information page about each member, a restricted area that we need to login to get to, and a login page. First I tried SQL injection on the login form, but didn't seem to get much. However, the member info page has a GET parameter in the URL (member-info.php?id=1) so let's see if that's broken maybe. Adding a ' to break the query (member-info.php?id=1') seems to work and even gives us a hint.

So lets play around and see if we can get a basic query working - sqlite_version() probably won't tell us much, but we can figure out how much information we can get once we dig deeper. This seems prime for a UNION injection and we know that there's at least 5 columns being returned from the database, so lets give member-info.php?id=1' UNION SELECT sqlite_version(),1,1,1,1;-- a try.

Fantastic, so we know we can at least bring 5 columns in. Let's see if we can find anything about the tables in the database. The SQLite FAQ points us at the sqlite_master table, which just happens to have 5 columns (easy mode). So let's change the query to member-info.php?id=1' UNION SELECT * FROM sqlite_master WHERE type='table';-- Too bad it gives us the same information about let's try it with an ID that doesn't exist so that first query returns nothing. member-info.php?id=10' UNION SELECT * FROM sqlite_master WHERE type='table';-- gives much better results.

Nice! We even got the accounts table right away, though the first time I went through I had to poke around the database by changing the rootpage in the query until I found a table with useful information. Now we can see that this table has 3 columns, so we'll need to pad its output a bit in the query. Changing it to member-info.php?id=10' UNION SELECT *,1,1 FROM accounts;-- gives us some really useful information.

We now have the username "stallone" and password hash, so drop that into CrackStation (which I like to use before I fire up hashcat) and we get the password "fire". Logging in with those credentials lets us get to the members only area and we're done.