Burp Python Scripter scripts
import sys | |
# Provides introspection into the Python Scripter API. | |
apis = ('extender', 'callbacks', 'helpers', 'toolFlag', 'messageIsRequest', 'messageInfo') | |
funcs = (type, dir) | |
if messageIsRequest: | |
for api in apis: | |
print('\n{}:\n{}'.format(api, '='*len(api))) | |
for func in funcs: | |
print('\n{}:\n'.format(func.__name__)) | |
try: | |
print(func(locals()[api])) | |
except Exception as e: | |
print(func(globals()[api])) | |
# *********************************************** | |
from pyscripter_utils import CustomIssue | |
import re | |
import sys | |
# Adds custom passive audit checks. | |
# Requires pyscripter_utils.py to be loaded with Burp. | |
if not messageIsRequest: | |
if toolFlag in (callbacks.TOOL_PROXY,): | |
if callbacks.isInScope(messageInfo.getUrl()): | |
response = messageInfo.getResponse() | |
# Checks for autocomplete on text form fields. | |
results = re.findall(r'(<input [^>]*>)', response) | |
for result in results: | |
if re.search(r'''type=['"]text['"]''', result) and not re.search(r'autocomplete', result): | |
issue = CustomIssue( | |
BasePair=messageInfo, | |
IssueName='Text field with autocomplete enabled', | |
IssueDetail='The following text field has autocomplete enabled:\n\n<ul><li>' + result.replace('<', '<').replace('>', '>') + '</li></ul>', | |
Severity='Low', | |
) | |
callbacks.addScanIssue(issue) | |
# Checks for verbose headers. | |
bad_headers = ('server', 'x-powered-by', 'x-aspnet-version') | |
headers = helpers.analyzeResponse(messageInfo.getResponse()).getHeaders() | |
for header in headers: | |
name = header.split(':')[0] | |
if name.lower() in bad_headers: | |
issue = CustomIssue( | |
BasePair=messageInfo, | |
IssueName='Verbose header', | |
IssueDetail='The following HTTP response header may disclose sensitive information:\n\n<ul><li>' + header + '</li></ul>', | |
Severity='Low', | |
) | |
callbacks.addScanIssue(issue) | |
# *********************************************** | |
import re | |
import sys | |
# Extracts multiple instances of a REGEX capture group from responses. | |
pattern = r'<regex>' | |
if not messageIsRequest: | |
response = messageInfo.getResponse() | |
matches = re.findall(pattern, response) | |
for match in matches: | |
print(match) | |
# *********************************************** | |
import re | |
# Replaces the body of a response from a matched URL. | |
# Great for swapping SPA UI build definitions between user roles. | |
url_pattern = r'<regex for response URL>' | |
body = r'''<new body>''' | |
if not messageIsRequest: | |
url = messageInfo.url.toString() | |
if re.search(url_pattern, url): | |
response = messageInfo.getResponse() | |
headers = helpers.analyzeResponse(response).getHeaders() | |
new_response = helpers.buildHttpMessage(headers, helpers.stringToBytes(body)) | |
messageInfo.setResponse(new_response) | |
print('Response replaced from: {}'.format(url)) | |
# *********************************************** | |
import sys | |
import re | |
from hashlib import md5 | |
# Overwrites a previously attempted password signature to bypass client-side anti-automation logic. | |
# Not sure why anyone would do this, but they did, or this wouldn't be a thing. | |
if messageIsRequest: | |
if toolFlag in (callbacks.TOOL_INTRUDER,): | |
request = helpers.bytesToString(messageInfo.getRequest()) | |
if '&nonce=' in request: | |
nonce = re.search(r'&nonce=([^&]*)', request).group(1) | |
password = re.search(r'&password=([^&]*)', request).group(1) | |
token = md5(password+nonce).hexdigest() | |
orig_token = re.search(r'&token=([^\s]*)', request).group(1) | |
request = request.replace(orig_token, token) | |
messageInfo.setRequest(helpers.stringToBytes(request)) | |
# *********************************************** | |
# Fetches and replaces a Bearer token in the current request. | |
def get_new_token(): | |
url = '<url>' | |
username = '<username>' | |
password = '<password>' | |
import urllib2 | |
import json | |
data = { | |
'username': username, | |
'password': password, | |
} | |
req = urllib2.Request(url) | |
req.add_header('Content-Type', 'application/json') | |
response = urllib2.urlopen(req, json.dumps(data)) | |
data = json.load(response) | |
token = data.get('token', '') | |
print('New token obtained.') | |
return token | |
# only apply to repeater | |
if toolFlag == callbacks.TOOL_REPEATER: | |
# only apply to requests | |
if messageIsRequest: | |
# obtain a new token | |
new_token = get_new_token() | |
# remove any existing Authorization header | |
request = helpers.analyzeRequest(messageInfo) | |
headers = request.getHeaders() | |
for header in headers: | |
if header.startswith('Authorization'): | |
headers.remove(header) | |
break | |
# add a new Authorization header with the new token | |
headers.add('Authorization: Bearer {}'.format(new_token)) | |
body = messageInfo.getRequest()[request.getBodyOffset():] | |
new_request = helpers.buildHttpMessage(headers, body) | |
messageInfo.setRequest(new_request) | |
print('Token replaced.') | |
# *********************************************** | |
# Removes authentication information from the current request. | |
header_names = ['Cookie', 'Authorization'] | |
# only apply to target | |
if toolFlag == callbacks.TOOL_TARGET: | |
# only apply to requests | |
if messageIsRequest: | |
request = helpers.analyzeRequest(messageInfo) | |
headers = request.getHeaders() | |
for header_name in header_names: | |
for header in headers: | |
if header.startswith(header_name): | |
headers.remove(header) | |
break | |
body = messageInfo.getRequest()[request.getBodyOffset():] | |
new_request = helpers.buildHttpMessage(headers, body) | |
messageInfo.setRequest(new_request) | |
print('Headers removed: {}'.format(', '.join(header_names))) |
from burp import IScanIssue | |
class CustomIssue(IScanIssue): | |
def __init__(self, BasePair, Confidence='Certain', IssueBackground=None, IssueDetail=None, IssueName='Python Scripter generated issue', RemediationBackground=None, RemediationDetail=None, Severity='High'): | |
self.HttpMessages=[BasePair] # list of HTTP Messages | |
self.HttpService=BasePair.getHttpService() # HTTP Service | |
self.Url=BasePair.getUrl() # Java URL | |
self.Confidence = Confidence # "Certain", "Firm" or "Tentative" | |
self.IssueBackground = IssueBackground # String or None | |
self.IssueDetail = IssueDetail # String or None | |
self.IssueName = IssueName # String | |
self.IssueType = 134217728 # always "extension generated" | |
self.RemediationBackground = RemediationBackground # String or None | |
self.RemediationDetail = RemediationDetail # String or None | |
self.Severity = Severity # "High", "Medium", "Low", "Information" or "False positive" | |
def getHttpMessages(self): | |
return self.HttpMessages | |
def getHttpService(self): | |
return self.HttpService | |
def getUrl(self): | |
return self.Url | |
def getConfidence(self): | |
return self.Confidence | |
def getIssueBackground(self): | |
return self.IssueBackground | |
def getIssueDetail(self): | |
return self.IssueDetail | |
def getIssueName(self): | |
return self.IssueName | |
def getIssueType(self): | |
return self.IssueType | |
def getRemediationBackground(self): | |
return self.RemediationBackground | |
def getRemediationDetail(self): | |
return self.RemediationDetail | |
def getSeverity(self): | |
return self.Severity |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment