Level 5 of the Stripe CTF revolved around a design issue in an OpenID like protocol.
def authenticated?(body)
body =~ /[^\w]AUTHENTICATED[^\w]*$/
end
...
if authenticated?(body)
session[:auth_user] = username
session[:auth_host] = host
return "Remote server responded with: #{body}." \
" Authenticated as #{username}@#{host}!"
This level is an implementation of a federated identity protocol. You give it an endpoint URI and a username and password, it posts the username and password to the endpoint URI, and if the response is 'AUTHENTICATED' then access is allowed. It is easy to be authenticated on a server you control, but this level requires you to authenticate from the server running the level. This level only talks to stripe CTF servers so the first step is to upload a document to the level 2 server containing the text 'AUTHENTICATED' and we can now authenticate on a level 2 server. Notice that the level 5 server will dump out the content of the endpoint URI and that the regexp it uses to detect the text 'AUTHENTICATED' can match on that dump. Accordingly I uploaded an authenticated file to
https://level02-2.stripe-ctf.com/user-ajvivlehdt/uploads/authenticated
Using that as my endpoint URI means authenticating as level 2. I can then choose the following endpoint
URI to authenticate as level 5.
https://level05-1.stripe-ctf.com/user-qtoyekwrod/?pingback=https%3A%2F%2Flevel02-2.stripe-ctf.com%2Fuser-ajvivlehdt%2Fuploads%2Fauthenticated&username=a&password=a
Navigating
to that URI results in the level 5 server telling me I'm authenticated as level 2 and lists the text of the level 2 file 'AUTHENTICATED'. Feeding this back into the level 5 server as my endpoint
URI means level 5 seeing 'AUTHENTICATED' coming back from a level 5 URI.
I didn't see any particular code review red flags, really the issue here is that the regular expression testing for 'AUTHENTICATED' is too permisive and the protocol itself doesn't do enough. The protocol requires only a set piece of common literal text to be returned which makes it easy for a server to accidentally fall into authenticating. Having the endpoint URI have to return variable text based on the input would make it much harder for a server to accidentally authenticate.
Level 4 and level 6 of the Stripe CTF had solutions around XSS.
> Registered Users
<%= user[:username] %>
(password: <%= user[:password] %>, last active <%= last_active %>)
The level 4 web application lets you transfer karma to another user and in doing so you are also forced to expose your password to that user. The main user page displays a list of users who have transfered karma to you along with their password. The password is not HTML encoded so we can inject HTML into that user's browser. For instance, we could create an account with the following HTML as the password which will result in XSS with that HTML:
This HTML runs script that uses jQuery to post to the transfer URI resulting in a transfer of karma from the attacked user to the attacker user, and also the attacked user's
password.
Code review red flags in this case included lack of encoding when using user controlled content to create HTML content, storing passwords in plain text in the database, and displaying passwords generally. By design the web app shows users passwords which is a very bad idea.
...
def self.safe_insert(table, key_values)
key_values.each do |key, value|
# Just in case people try to exfiltrate
# level07-password-holder's password
if value.kind_of?(String) &&
(value.include?('"') || value.include?("'"))
raise "Value has unsafe characters"
end
end
conn[table].insert(key_values)
end
This web app does a much better job than the level 4 app with HTML injection. They use encoding whenever creating HTML using user controlled data, however they don't use encoding when injecting JSON data into script (see post_data initialization above). This JSON data is the last five most recent messages sent on the app so we get to inject script directly. However, the system also ensures that no strings we write contains single or double quotes so we can't get out of the string in the JSON data directly. As it turns out, HTML lets you jump out of a script block using no matter where you are in script. For instance, in the middle of a value in some JSON data we can jump out of script. But we still want to run script, so we can jump right back in. So the frame so far for the message we're going to post is the following:
Classic 2d space shooter except you are fighting the ghosts of previous players. Your play through is replayed as an enemy against future players. Also, written in HTML/JS
Stripe's web security CTF's Level 1 and level 2 of the Stripe CTF had issues with missing input validation solutions described below.
$filename = 'secret-combination.txt';
extract($_GET);
if (isset($attempt)) {
$combination = trim(file_get_contents($filename));
if ($attempt === $combination) {
The issue here is the usage of the extract php method which extracts name value pairs from the map input parameter and creates corresponding local variables. However this code uses $_GET which contains a map of name value pairs passed in the query of the URI. The expected behavior is to get an attempt variable out, but since no input validation is done I can provide a filename variable and overwrite the value of $filename. Providing an empty string gives an empty string $combination which I can match with an empty string $attempt. So without knowing the combination I can get past the combination check.
Code review red flag in this case was the direct use of $_GET with no validation. Instead of using extract the developer could try to extract specifically the attempt variable manually without using extract.
$dest_dir = "uploads/";
$dest = $dest_dir . basename($_FILES["dispic"]["name"]);
$src = $_FILES["dispic"]["tmp_name"];
if (move_uploaded_file($src, $dest)) {
$_SESSION["dispic_url"] = $dest;
chmod($dest, 0644);
echo "Successfully uploaded your display picture.
";
}
This code accepts POST uploads of images but with no validation to ensure it is not an arbitrary file. And even though it uses chmod to ensure the file is not executable, things like PHP don't require a file to be executable in order to run them. Accordingly, one can upload a PHP script, then navigate to that script to run it. My PHP script dumped out the contents of the file we're interested in for this level:
Code review red flags include manual file management, chmod, and use of file and filename inputs without any kind of validation. If this code controlled the filename and ensured that the extension was one of a set of image extensions, this would solve this issue. Due to browser mime sniffing its additionally a good idea to serve a content-type that starts with "image/" for these uploads to ensure browsers treat these as images and not sniff for script or HTML.
Stripe's web security CTF's level 0 and level 3 had SQL injection solutions described below.
app.get('/*', function(req, res) {
var namespace = req.param('namespace');
if (namespace) {
var query = 'SELECT * FROM secrets WHERE key LIKE ? || ".%"';
db.all(query, namespace, function(err, secrets) {
There's no input validation on the namespace parameter and it is injected into the SQL query with no encoding applied. This means you can use the '%' character as the namespace which is the wildcard character matching all secrets.
Code review red flag was using strings to query the database. Additional levels made this harder to exploit by using an API with objects to construct a query rather than strings and by running a query that only returned a single row, only ran a single command, and didn't just dump out the results of the query to the caller.
@app.route('/login', methods=['POST'])
def login():
username = flask.request.form.get('username')
password = flask.request.form.get('password')
if not username:
return "Must provide username\n"
if not password:
return "Must provide password\n"
conn = sqlite3.connect(os.path.join(data_dir, 'users.db'))
cursor = conn.cursor()
query = """SELECT id, password_hash, salt FROM users
WHERE username = '{0}' LIMIT 1""".format(username)
cursor.execute(query)
res = cursor.fetchone()
if not res:
return "There's no such user {0}!\n".format(username)
user_id, password_hash, salt = res
calculated_hash = hashlib.sha256(password + salt)
if calculated_hash.hexdigest() != password_hash:
return "That's not the password for {0}!\n".format(username)
There's little input validation on username before it is used to constrcut a SQL query. There's no encoding applied when constructing the SQL query string which is used to, given a username, produce the hashed password and the associated salt. Accordingly one can make username a part of a SQL query command which ensures the original select returns nothing and provide a new SELECT via a UNION that returns some literal values for the hash and salt. For instance the following in blue is the query template and the red is the username injected SQL code:
SELECT id, password_hash, salt FROM users WHERE username = 'doesntexist' UNION SELECT id, ('5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8') AS password_hash, ('word') AS salt FROM users WHERE username = 'bob' LIMIT 1
In the above I've supplied my own salt and hash such that my salt (word) plus my password (pass) hashed produce the hash I provided above. Accordingly, by
providing the above long and interesting looking username and password as 'pass' I can login as any user.
Code review red flag is again using strings to query the database. Although this level was made more difficult by using an API that returns only a single row and by using the execute method which only runs one command. I was forced to (as a SQL noob) learn the syntax of SELECT in order to figure out UNION and how to return my own literal values.
When they went to the Moon, they received the same per diem compensation as they would have for being away from base in Bakersfield: eight dollars a day, before various deductions (like for accommodation, because the government was providing the bed in the spaceship).
Apollo 11’s Astronauts Received an $8 Per Diem for the Mission to the Moon
The astronauts of Apollo 11: Intrepid explorers. Inspirational heroes. Government employees.
Read more. [Image: Reuters]
Stripe is running a web security capture the flag - a series of increasingly difficult web security exploit challenges. I've finished it and had a lot of fun. Working on a web browser I knew the theory of these various web based attacks, but this was my first chance to put theory into practice with:
Here's a blog post on the CTF behind the scenes setup which has many impressive features including phantom users that can be XSS/CSRF'ed.
I'll have another post on my difficulties and answers for the CTF levels after the contest is over on Wed, but if you're looking for hints, try out the CTF chatroom or the level specific CTF chatroom.
From: David Risney
Views: 75
0 ratings
|
||
Time: 00:43 | More in People & Blogs |
Winterton, a senior entomologist at the California Department of Food and Agriculture, has seen a lot of bugs. But he hadn’t seen this species before.
There’s no off switch when you’re the senior entomologist. If you’re browsing the web you find your way to Flickr photos of insects or start correcting Wikipedia articles on insects.
Meet Sarcastic Mars Rover, now on Twitter, doing a science all over your everything.
Meet your new twitter friend.
From: David Risney
Views: 69
0 ratings
|
||
Time: 00:53 | More in People & Blogs |
(via Pareidoloop)
“Phil McCarthy’s Pareidoloop overlays randomly generated polygons on top of one another until facial recognition software recognizes a human face. Can’t sleep, at SIGGRAPH! [via @Brandonn]”
Chaos Monkey randomly kills your Amazon Web Service VMs increasing the failure rate forcing your web service to deal with it.
Paul Scheer and Adam Scott trolling Comic ConYup…This Happened!
And the video:
Getting closer to the day I resume my Netflix subscription…