In this week's exercises, your group will try out the various tasks for code summarization and reverse engineering using LLMs. Begin by completing the initial two parts of the codelab. Then, attempt the exercise your group has been assigned in the following Google Slide presentation:

Add screenshots that you can use to walkthrough how you performed the exercise. Your group will present your results for the exercise during the last hour of class. After completing the exercise you've been assigned, continue to the rest of the exercises in order to prepare for the week's homework assignment.

Using an LLM such as ChatGPT, Gemini , or Copilot to aid in analyzing vulnerabilities in source code and running services is one potential use for generative AI. To leverage this capability, one must be able to understand what tasks the models are reliably capable of performing. In this lab, you will utilize LLMs to automate the detection of vulnerable services and source code examples, then determine whether the results are accurate. To begin with, change into the code directory for the exercises and install the packages.

cd cs410g-src
git pull
cd 06*
virtualenv -p python3 env
source env/bin/activate
pip install -r requirements.txt

LLM agents have the ability to make decisions about what tools to use based on the context they receive. In this exercise an agent will utilize two tools to solve a Portswigger password authentication level. Go to: https://portswigger.net/web-security/authentication/password-based/lab-username-enumeration-via-different-responses

This is the first level in the password based authentication challenges found on Portswigger Academy. Click "Access the Lab" to start the lab.

To run the program:

python3 01_auth_breaker.py

The program will display the two tools that are running. Now, to use the tool, fill in the prompt below with the URL of your level.

I want to login to the website with a base url of <YOUR_LEVEL_URL> 

The program will first call the find login tool

@tool("find_login_page", return_direct=False)
def find_login_page(base_url):
    """The function will try to find the login page url"""
    loader = RecursiveUrlLoader(
        url = base_url,
        max_depth = 2,
    )
    docs = loader.load()

    login_page = None

    for doc in docs:
        login_page = doc.metadata["source"]
        if login_url(login_page):
            break 

This tool is utilizing the RecursiveUrlLoader found in the RAG section of the course to locate a webpage that has a url with the string "login" contained in it. It will also check to see if any of the returned links yield redirects:

def check_redirects(url):
    """
    Checks each URL in a list for redirects and returns if it contains login
    """
    try:
        # Send a request to the URL with allow_redirects set to True
        response = requests.get(url, allow_redirects=True)
        # Check if any redirection has occurred
        if response.history:
            # If redirected, check the url 
            if login_url(response.url):
                return response.url

If redirects are returned it will also check those. After checking the website's pages for a login page, it will return the URL it finds.

Then the ReAct agent will call the next tool, called get_creds

@tool("get_creds", return_direct=False)
def get_creds(login_url):
    """Given the login page url the function will find the credentials needed to login"""

This function loads a password list and a username list that were provided by the Portswigger level.

password_lines = open("./data/auth-lab-passwords","r").readlines()
username_lines = open("./data/auth-lab-usernames","r").readlines()

It then uses the Python requests library to check if the correct username password pair is found.

If the correct pair is found, it will login to the website and solve the level. The agent will then produce a final answer of the correct username and password pair.

Consider the PHP program below from Level 6 of the natas Overthewire CTF. The code includes a secret from the file system and uses it in the web application to authenticate access. If one can directly access the includes/secret.inc file, the level can be solved.

<html><body>
<?
    include "includes/secret.inc";

    if(array_key_exists("submit", $_POST)) {
        if($secret == $_POST['secret']) {
            print "Access granted. The password for natas7 is <censored>";
        } else {
            print "Wrong secret";
        }
    }
?>

<form method=post>
Input secret: <input name=secret><br>
<input type=submit name=submit>
</form>

</body></html>

Consider the PHP program below from Level 8 of the natas Overthewire CTF. The code includes an encoded secret within the script itself. If one can decode the secret, the level can be solved.

<html><body>
<?

$encodedSecret = "3d3d516343746d4d6d6c315669563362";

function encodeSecret($secret) {
    return bin2hex(strrev(base64_encode($secret)));
}

if(array_key_exists("submit", $_POST)) {
    if(encodeSecret($_POST['secret']) == $encodedSecret) {
        print "Access granted. The password for natas9 is <censored>";
    } else {
        print "Wrong secret";
    }
}
?>

<form method=post>
Input secret: <input name=secret><br>
<input type=submit name=submit>
</form>

</body></html>

Command injection is a catastrophic vulnerability to have in web applications. Consider the PHP program below from Level 9 of the natas Overthewire CTF. The code allows one to search a file on the server using a given string.

<html><body>
<form>
Find words containing: <input name=needle><input type=submit name=submit value=Search><br><br>
</form>

Output:
<pre>
<?
$key = "";

if(array_key_exists("needle", $_REQUEST)) {
    $key = $_REQUEST["needle"];
}

if($key != "") {
    passthru("grep -i $key dictionary.txt");
}
?>
</pre>
</body>
</html>

Any remedy that filters characters to prevent the prior command injection vulnerability must have a complete understanding of how commands can be included in the shell. Consider the PHP program below from Level 16 of the natas Overthewire CTF. A regular expression is now used to filter the characters ;, |, &, `, ', and " from the input since they can be used to inject rogue commands in the command line.

<html><body>
For security reasons, we now filter even more on certain characters<br/><br/>
<form>
Find words containing: <input name=needle><input type=submit name=submit value=Search><br><br>
</form>

Output:
<pre>
<?
$key = "";

if(array_key_exists("needle", $_REQUEST)) {
    $key = $_REQUEST["needle"];
}

if($key != "") {
    if(preg_match('/[;|&`\'"]/',$key)) {
        print "Input contains an illegal character!";
    } else {
        passthru("grep -i \"$key\" dictionary.txt");
    }
}
?>
</pre>
</body></html>

The inappropriate use of cryptographic mechanisms can lead to applications being easily compromised. Consider the PHP program below from Level 11 of the natas Overthewire CTF. The code implements a session cookie mechanism to encode a background color preference and a key that enables a password to be shown.

<html><head>
<?
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
function xor_encrypt($in) {
    $key = '<censored>';
    $text = $in;
    $outText = '';
    for($i=0;$i<strlen($text);$i++) {
        $outText .= $text[$i] ^ $key[$i % strlen($key)];
    }
    return $outText;
}
function loadData($def) {
    global $_COOKIE;
    $mydata = $def;
    if(array_key_exists("data", $_COOKIE)) {
        $tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true);
        if(is_array($tempdata) && array_key_exists("showpassword", $tempdata) && array_key_exists("bgcolor", $tempdata)) {
            if (preg_match('/^#(?:[a-f\d]{6})$/i', $tempdata['bgcolor'])) {
                $mydata['showpassword'] = $tempdata['showpassword'];
                $mydata['bgcolor'] = $tempdata['bgcolor'];
            }
        }
    }
    return $mydata;
}
function saveData($d) {
    setcookie("data", base64_encode(xor_encrypt(json_encode($d))));
}
$data = loadData($defaultdata);
if(array_key_exists("bgcolor",$_REQUEST)) {
    if (preg_match('/^#(?:[a-f\d]{6})$/i', $_REQUEST['bgcolor'])) {
        $data['bgcolor'] = $_REQUEST['bgcolor'];
    }
}
saveData($data);
?>
<body style="background: <?=$data['bgcolor']?>;">
Cookies are protected with XOR encryption<br/><br/>
<?
if($data["showpassword"] == "yes") {
    print "The password for natas12 is <censored><br>";
}
?>
<form>
Background color: <input name=bgcolor value="<?=$data['bgcolor']?>">
<input type=submit value="Set color">
</form>
</body></html>

The inappropriate use of cryptographic mechanisms can lead to applications being easily compromised. Consider the PHP program below from Level 12 of the natas Overthewire CTF. The code implements a file upload function that allows a user to upload a JPEG image. Unfortunately, while the form sent to the user sets the extension to .jpg, the extension is not validated at the server which uses pathinfo() in PHP to pull out the extension sent by the client.

<html><body>
<?php

function genRandomString() {
    $length = 10;
    $characters = "0123456789abcdefghijklmnopqrstuvwxyz";
    $string = "";
    for ($p = 0; $p < $length; $p++) {
        $string .= $characters[mt_rand(0, strlen($characters)-1)];
    }
    return $string;
}

function makeRandomPath($dir, $ext) {
    do {
        $path = $dir."/".genRandomString().".".$ext;
    } while(file_exists($path));
    return $path;
}

function makeRandomPathFromFilename($dir, $fn) {
    $ext = pathinfo($fn, PATHINFO_EXTENSION);
    return makeRandomPath($dir, $ext);
}

if(array_key_exists("filename", $_POST)) {
    $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]);

    if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {
        echo "File is too big";
    } else {
        if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
            echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded";
        } else {
            echo "There was an error uploading the file, please try again!";
        }
    }
} else {
?>

<form enctype="multipart/form-data" action="index.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="1000" />
<input type="hidden" name="filename" value="<?php print genRandomString(); ?>.jpg" />
Choose a JPEG to upload (max 1KB):<br/>
<input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>
<?php } ?>
</body></html>

The next level, Level 13, is identical, but the exif_imagetype() call is used to validate that the image uploaded contains EXIF data at the beginning. Unfortunately, this check is not foolproof. Modify the previous level to add the following check. Then repeat the analysis.

    if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {
        echo "File is too big";
    } else if (! exif_imagetype($_FILES['uploadedfile']['tmp_name'])) {
        echo "File is not an image";
    } else {
        if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
            echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded";
        } else{
            echo "There was an error uploading the file, please try again!";
        }
    }

SQL injection is one of the most impactful vulnerabilities in web applications. Code that is written should never include such a vulnerability and LLMs should be able to identify and to automatically fix vulnerable code. Consider the PHP program below from Level 14 of the natas Overthewire CTF. The code implements a simple login application by taking the username and password fields that the user submits and then performing a SQL query to determine whether or not they match what's in the database. It is vulnerable to the canonical SQL injection attack.

<html><body>
<?php
if(array_key_exists("username", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas14', '<censored>');
    mysqli_select_db($link, 'natas14');

    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"";
    if(array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }

    if(mysqli_num_rows(mysqli_query($link, $query)) > 0) {
            echo "Successful login! The password for natas15 is <censored><br>";
    } else {
            echo "Access denied!<br>";
    }
    mysqli_close($link);
} else {
?>

<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password"><br>
<input type="submit" value="Login" />
</form>
<?php } ?>
</body></html>

The next level (natas Level 15) still contains the vulnerability, but the output of the script is either the user exists or doesn't.

<html><body>
<?php
/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/

if(array_key_exists("username", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas15', '<censored>');
    mysqli_select_db($link, 'natas15');

    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
    if(array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }

    $res = mysqli_query($link, $query);
    if($res) {
    if(mysqli_num_rows($res) > 0) {
        echo "This user exists.<br>";
    } else {
        echo "This user doesn't exist.<br>";
    }
    } else {
        echo "Error in query.<br>";
    }

    mysqli_close($link);
} else {
?>

<form action="index.php" method="POST">
Username: <input name="username"><br>
<input type="submit" value="Check existence" />
</form>
<?php } ?>
</body></html>

When a web application uses redirection to send a user off to another location, it is important that the execution of the script immediately stops. If it does not, then the user may see the results of the execution of the script in an unauthorized manner. Consider the PHP program below from Level 22 of the natas Overthewire CTF. The initial code checks that the user has an authenticated session that contains a key named admin, indicating a prior authentication has occurred as the admin user. Without an admin session, one is then redirected back to the root page. Unfortunately, rather than stop execution after the redirection has been decided, execution is allowed to continue to the next part of the script which emits the password.

<?php
session_start();

if(array_key_exists("revelio", $_GET)) {
    // only admins can reveal the password
    if(!($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1)) {
        header("Location: /");
    }
}
?>

<html><body>
<?php
    if(array_key_exists("revelio", $_GET)) {
        print "You are an admin. The credentials for the next level are:<br>";
        print "<pre>Username: natas23\n";
        print "Password: <censored></pre>";
    }
?>
</body></html>

A dynamic and weakly typed programming language can lead to developers making unintended errors unless they understand the rules in which data is coerced from one type to another. Consider the PHP program below from Level 23 of the natas Overthewire CTF. The program checks for a passwd key in the request. The key's value must match the string 'iloveyou' and be greater than 10 characters. Can this ever happen?

<html><body>
Password:
<form name="input" method="get">
    <input type="text" name="passwd" size=20>
    <input type="submit" value="Login">
</form>

<?php
    if(array_key_exists("passwd",$_REQUEST)){
        if(strstr($_REQUEST["passwd"],"iloveyou") && ($_REQUEST["passwd"] > 10 )){
            echo "<br>The credentials for the next level are:<br>";
            echo "<pre>Username: natas24 Password: <censored></pre>";
        }
        else{
            echo "<br>Wrong!<br>";
        }
    }
?>
</body></html>

Another common problem with a dynamic and weakly typed programming language is type juggling where comparisons are made between values of disparate data types. When this happens, rules for performing type coercion to convert one value's data type to another's is performed. Consider the PHP program below from Level 24 of the natas Overthewire CTF. The program checks that a password matches a particular string that is kept secret at the server using the strcmp() call in PHP.

<html><body>
Password:
<form name="input" method="get">
    <input type="text" name="passwd" size=20>
    <input type="submit" value="Login">
</form>

<?php
    if(array_key_exists("passwd",$_REQUEST)){
        if(!strcmp($_REQUEST["passwd"],"<censored>")){
            echo "<br>The credentials for the next level are:<br>";
            echo "<pre>Username: natas25 Password: <censored></pre>";
        }
        else{
            echo "<br>Wrong!<br>";
        }
    }
?>
</body></html>

Cross-site scripting (XSS) is another impactful vulnerability in web applications. Consider a vulnerable Javascript snippet below.

<script type="text/javascript">
    document.write('<body');
    var color = unescape(document.location.search.substring(1));
    if (color != '') {
        document.write(' style="background-color:' + color + '"');
    }
    document.write('>');
</script>

Consider a vulnerable Javascript snippet below.

<h1>
<span>Search results for '</span><span id="searchMessage"></span><span>'</span>
</h1>
 <script>
   function doSearchQuery(query) {
      document.getElementById('searchMessage').innerHTML = query;
   }
   var query = (new URLSearchParams(window.location.search)).get('search');
   if(query) {
      doSearchQuery(query);
   }
</script>

Buffer overflow

Buffer overflows in C are a class of vulnerabilities that should have long been eliminated from our codebases. Consider the code below for a CTF level. It performs an unconstrained read from the user (via the gets() call) and stores the result in a 32 character buffer (buf). In doing so, a user writing input that is more than 32 characters will overwrite stack locations that include the return address of main(). For the CTF exercise, the goal would be to write the address of print_flag() onto the stack where the return address of main() is located to cause the program to execute print_flag() when the main() function returns.

void print_flag() {
  char flagbuf[256];
  int fd = open("flag.txt", O_RDONLY);
  read(fd, flagbuf, 256);
  write(1, flagbuf, 256);
  close(fd);
}

int main() {
  char buf[32];
  gets(buf);
  printf("Hi, %s\n", buf);
  return 0;
}

The following program has three source code errors, one for each command it implements.

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
        char input[100] = {0};
        char *out;

        // Slurp input
        if (read(STDIN_FILENO, input, 100) < 0) {
                fprintf(stderr, "Couldn't read stdin.\n");
        }
        if(input[0] == 'c') {
                // count characters
                out = malloc(sizeof(input) - 1 + 3); // enough space for 2 digits + a space + input-1 chars
                sprintf(out, "%lu ", strlen(input) - 1);
                strcat(out, input+1);
                printf("%s", out);
                free(out);
        } else if ((input[0] == 'e') && (input[1] == 'c')) {
                // echo input
                printf(input + 2);
        } else if (strncmp(input, "head", 4) == 0) {
                // head
                if (strlen(input) > 5) {
                        input[input[4]] = '\0'; // truncate string at specified position
                        printf("%s", input+4);
                } else {
                        fprintf(stderr, "head input was too small\n");
                }
        }
        else {
                fprintf(stderr, "Usage: %s\nText utility - accepts commands on stdin and prints results to stdout:\n", argv[0]);
                fprintf(stderr, "\tInput           | Output\n");
                fprintf(stderr, "\t----------------+-----------------------\n");
                fprintf(stderr, "\tec<string>      | <string> (simple echo)\n");
                fprintf(stderr, "\thead<N><string> | The first <N> bytes of <string>\n");
                fprintf(stderr, "\tc<string>       | The length of <string>, followed by <string>\n");
        }
}