HackerOne CTF Postbook (Spoilers)
Understanding the App
The Postbook website has much greater functionality than Micro-CMS:
- Arbitrary user sign ups with no verification.
- "Usernames should be lower case characters only", hinting at SQL Injection?
- If you enter a username with an upper case letter, number, or symbol it greys out the submit button. This appears to be due to the validate() method attached to onkeyup event tag of the input:
<input type="text" id="username" placeholder="Username" onkeyup="validate()" name="username">
. The function validate is defined just above.
function validate() {
var username = document.getElementById('username');
var submit = document.getElementById('submit');
if(/^[a-z]+$/.test(username.value)) {
submit.disabled = false;
} else {
submit.disabled = true;
}
}
- Sign in with a user just created works. Getting past the auth screen is a possibility this time! The sign in page has no similar JavaScript validation methods.
- All of these pages are being rendered by index.php, and differing based on the ?page= GET parameter passed:
- /index.php?page=sign_in.php
- /index.php?page=sign_up.php
- Once signed in, there's a post timeline with two public posts by users "user" and "admin". Creating a public and private post is very straight forward:
Welcome!
With this amazing tool you can write and publish your own posts. It'll allow you to write public and private posts. Public posts can be read by anyone that signs up. Private posts can only be read by you. See it as your own diary. We'll make sure that your private posts are safe with us.
Post timeline
- There's a write post page that looks like a duplication of the write post div on the main page:
New post
The source of this page is especially interesting, because the form has a hidden field:
<input type="hidden" name="user_id" value="3" />
- There's a profile page that shows some some stats and some quick links to my posts. Interestingly, the edit link appears to be a simple GET request to
index.php?page=view.php&id=6
where id appears to be the id of the record in the database.
My profile
Username: testPost count: 2
Posts
Hello! (edit delete)SECRET BLOG: Secret Hello! (edit delete)
- Finally, there's a settings page where the username and password can be updated via a POST request to
index.php?page=account.php
.
My account
- If I try to login as another user, I'm greeted by this hilarious warning:
Sign in
You've entered a wrong username/password combination. Please do not hack our system because it is insanely illegal. We will report you to PETA if you continue. Nothing is logged, but we ask you kindly not to try anything malicious.
The front end of this app appears to be all HTML and JavaScript, while the backend appears to be written entirely in PHP. Sure seems like there's a lot to exploit to me!
XSS
Most of the user inputs across the site properly escape the JavaScript and deisplay it as raw plain text. But persistence pays off and the settings page allows an XSS attack against the timeline by setting the username to <script>alert(1);</alert>
which is rendered as functioning code. Unfortunately no flags are awarded for this. I felt really great about this one, and a bit dejected when it turned out to be unrewarded.
Viewing Secret Posts
There are two existing users, each having posted one public message. I joined and posted a public message with id=5
and a secret message with id=6
. The existing posts have id=1
and id=3
This suggests the potential for two private messages, assuming they aren't simply two deleted posts. Let's see if we can just GET request our way to editing another user's posts by passing the id:
https://***.ctf.hacker101.com/index.php?page=edit.php&id=2
Edit post
So I can definitely view another user's private posts this way, but there's no flag yet. Perhaps this post should be public?
^FLAG^****************************************************************$FLAG$
I am so glad that I am on Postbook. I can finally write down my thoughts and no one can see them. See you tomorrow. Yours truly, admin
Author: admin
What about that other user's post? I can toggle that one to private with the same trick:
^FLAG^****************************************************************$FLAG$
Posting as Another User
That user_id field in the create post form is definitely a problem. If I switch this value to 1 in browser console and then submit a post, I get to post as the admin user:
Welcome!
With this amazing tool you can write and publish your own posts. It'll allow you to write public and private posts. Public posts can be read by anyone that signs up. Private posts can only be read by you. See it as your own diary. We'll make sure that your private posts are safe with us.
Post timeline
^FLAG^****************************************************************FLAG
I am so glad that I am on Postbook. I can finally write down my thoughts and no one can see them. See you tomorrow. Yours truly, admin
Author: admin
Impersonating Another User with Cookies
Creating a new user yields id="e", id="f", then id="g", id="h", id="i", id="j", then... id="ba"? That's strange. The letter j is the 10th of the alphabet, which suggests they're using ids in a base 10 number system converted to the letters a = 0 through j = 9. Why do that? Just to obfuscate the true integer id?
Each user also appears to have a unique cookie id sent in all request headers. For example, my "testd" user has Cookie: id=eccbc87e4b5ce2fe28308fd9f2a7baf3
. That sure looks like a hash of some sort, probably md5 .
Let's take a look at all these accounts and their Cookie ids:
- Username: "testd", user_id=3, id=d, cookie: eccbc87e4b5ce2fe28308fd9f2a7baf3
- Username: "teste", user_id=4, id=e, cookie: a87ff679a2f3e71d9181a67b7542122c
- Username: "testf", user_id=5, id=f, cookie: e4da3b7fbbce2345d7772b0674a318d5
Time to check each of these user values with echo -n testd | md5sum
, etc. With the values in th user_id, all of these hashes match. The admin user has id=1, that means they have a cookie value of "c4ca4238a0b923820dcc509a6f75849b". What happens if I just swap my cookie id to that?
Running document.cookie = "id=c4ca4238a0b923820dcc509a6f75849b";
in the JavaScript console and reloading the home page has granted me admin account access, and a new flag!
Welcome!
With this amazing tool you can write and publish your own posts. It'll allow you to write public and private posts. Public posts can be read by anyone that signs up. Private posts can only be read by you. See it as your own diary. We'll make sure that your private posts are safe with us.
Post timeline
^FLAG^****************************************************************FLAG
I am so glad that I am on Postbook. I can finally write down my thoughts and no one can see them. See you tomorrow. Yours truly, admin
Author: admin
Deleting Another User's Posts
The request to delete a post also appears to use an md5 hash. Is that a similar exploit? Post id=3 would have md5 hash eccbc87e4b5ce2fe28308fd9f2a7baf3
, so I can just re-use that one for simplicity. Dropping it into the following URL yields another flag:
https://7bc17eeba53b2a6f1ca09a96e52ebc7d.ctf.hacker101.com/index.php?page=delete.php&id=eccbc87e4b5ce2fe28308fd9f2a7baf3
When in Doubt, Just Guess?
I suppose it should be obvious when a user has username "user" that might be a default value. And their password might also be the default value of "password" as well. Kind of funny that it took me so long to find this flag...
945
Eventually I capitulated and had to look at the hint for this one. A post with id 945 wasn't exactly something I was on the lookout for. I guess with some automation, I could have just tried an overwhelming number of ids just in case. Maybe I just missed some subtle hint, but honestly, this flag was not that great.
Lessons
This website has more pages and more functionality to test, making the allocation of time and effort a little more challenging. There were also some major red herrings that can eat up a fair amount of time if one is too stubborn. The remark about lower case letters only when signing up really made me suspect a SQL injection, so I spent a fair amount of time looking around for those, to no avail. The vectors for XSS were also very tempting, but mostly well escaped so that raw JavaScript was never injected into the timeline. When I finally did find the XSS with the username, it didn't yield a flag. In the real world, that XSS could be super valuable for cookie stealing, especially if the id wasn't just the md5 hash of the user id. While it may not have been a flag, that was definitely a win, and something that could be exploited for full account takeover of many accounts.