AWVS-SQL注入部分源码分析
**PS:**在文章中我会对一些句式进行缩进,文字默认定格,输入缩进对应着代码缩进,所以请注意我的段落缩进。
classErrorBasedSQLInjection.inc
classSQLErrorMessages()
this.plainArray
存储常见的sql注入报错信息,如
'Microsoft OLE DB Provider for ODBC Drivers',
'Error Executing Database Query',
'Microsoft OLE DB Provider for SQL Server',
'ODBC Microsoft Access Driver',
this.regexArray
存储常见的sql注入的正则匹配信息,如
/(Incorrect\ssyntax\snear\s'[^']*')/,
/(Syntax error: Missing operand after '[^']*' operator)/,
/Syntax error near\s.*?\sin the full-text search condition\s/,
/column "\w{5}" does not exist/,
/near\s[^:]+?:\ssyntax\serror/,
/(pg_query\(\)[:]*\squery\sfailed:\serror:\s)/,
/('[^']*'\sis\snull\sor\snot\san\sobject)/,
/(ORA-\d{4,5}:\s)/,
**PS:**这里解释一下this.plainArray与this.regexArray的区别,二者都是对字符串进行匹配,但是this.plainArray直接将数组中的内容与字符串进行匹配,要求有完全吻合的变量才符合匹配要求,而this.regexArray则强调了一种正则匹配的规则,只要字符串中有符合匹配规则的内容,就视为符合匹配要求。
this.FalsePositivesPlainArray
存储sql注入报错时的明显特征,如
"Connection Timeout",
"(0x80131904)",
"org.apache.commons.dbcp.SQLNestedException: Cannot get a connection, pool error Timeout waiting for idle object"
this.FalsePositivesRegexArray
暂时为空,根据数组名判断是存储sql注入报错时的这则表达特征的。
classSQLErrorMessages.prototype.isFalsePositive = function (text)
功能
用于判断是否存在sql报错信息明显特征的函数。
内容
将获取的数据依次与this.FalsePositivesPlainArray中的数据进行匹配,如果存在返回true。
将获取的数据依次与this.FalsePositivesRegexArray中的数据进行匹配,如果存在返回true。
如果都没有返回false。
classSQLErrorMessages.prototype.searchOnText = function (text)
功能
通过字符串匹配的方式,判断字符串属于哪一种sql注入的特征。
内容
先声明一个变量 _in
,默认值为body
将获取的数据进行匹配,如果识别到"HTTP/1."或"HTTP/0."则获取的信息识别为响应包,_in
赋值为response。
将获取的数据依次与this.plainArray中的数据进行匹配,如果存在返回highlightFromTextSearch(this.plainArray[i], text, _in),这个函数的作用是将text中匹配到的this.plainArray[i]值返回给_in。
将获取的数据依次与this.regexArray中的数据进行匹配,如果存在返回highlightFromRegexMatch(m, _in),这个函数是将m中的正则匹配结果返回给**_in**,其中m=this.regexArray[i].exec(text)
如果都没有返回false。
InjectionResult(data, adItem)
功能
为类属性this.data和this.adItem赋值
内容
this.data = data;
this.adItem = adItem;
classErrorBasedSQLInjection(targetUrl, errorMessages, scheme, inputIndex, variationIndex, reflectionPoint)
功能
为类属性进行赋值,尤其是对输入内容(变化值)this.variations进行保存,并初始化部分类属性。
内容
先对一些类变量赋值
this.scheme = scheme;
this.targetUrl = targetUrl;
this.errorMessages = errorMessages;
this.inputIndex = inputIndex;
this.reflectionPoint = reflectionPoint;
this.foundVulnOnVariation = false;
this.lastJob = null;
this.lastJobConfirm = null;
this.disableSensorBased = false;
如果scheme不为空,则初始化this.currentVariation和this.origValue = this.getOrigValue();
如果scheme不为空且inputIndex不为空,创建数组this.variations,将inputIndex赋值给this.variations。
如果scheme不为空但inputIndex为空,创建数组this.variations,将this.scheme.selectVariationsForInput(inputIndex)赋值给this.variations。
classErrorBasedSQLInjection.prototype.isSQLInjection = function (sensorData, start, end)
功能
根据指纹特征判断数据库类型,并存储尝试注入的结果。
内容
先初始化items,items = sensorData.getItems(“SQL_Query”),这里的sensorData.getItems大家可以理解为一个对http请求包进行抓取分析的函数。
如果items为空null。
从items中依次取出值item。
如果item不为空,初始化dbType = “mysql” addData = item.additional
如果addData长度大于等于2,db = addData[1]
如果在db中匹配到database=
,db = db.substr(9) dbType = db.toLowerCase()
从item.dataList中依次取出值entry
如果在entry中匹配到变量start字符串
若dbType == 'mysql’且ax.util.testForInjection(entry, ax.util.InjectionType.MySQL, start, end) 不等于空(尝试注入函数返回值不为空),则返回InjectionResult(entry, item)
若dbType == 'mssql’或dbType == 'mssql_or_access’且ax.util.testForInjection(entry, ax.util.InjectionType.MySQL, start, end) 不等于空,则返回InjectionResult(entry, item)
若dbType == 'pg’且ax.util.testForInjection(entry, ax.util.InjectionType.MySQL, start, end) 不等于空,则返回InjectionResult(entry, item)
若dbType == 'sqlite’且ax.util.testForInjection(entry, ax.util.InjectionType.MySQL, start, end) 不等于空,则返回InjectionResult(entry, item)
若dbType == 'oracle’且ax.util.testForInjection(entry, ax.util.InjectionType.MySQL, start, end) 不等于空,则返回InjectionResult(entry, item)
若dbType == 'sybase’且ax.util.testForInjection(entry, ax.util.InjectionType.MySQL, start, end) 不等于空,则返回InjectionResult(entry, item)
若dbType不符合上述情况,且ax.util.testForInjection(entry, ax.util.InjectionType.MySQL, start, end) 不等于空,则返回InjectionResult(entry, item)
否则返回false
classErrorBasedSQLInjection.prototype.getOrigValue = function ()
功能
匹配输入数据(this.inputIndex)是否已经输入过。
内容
初始化变量value,初始值为空。
从this.variations(此数组为classErrorBasedSQLInjection中存储的输入内容)中依次取值,将输入的数据(this.inputIndex)与之前存储的数据(this.variations)进行匹配,匹配结果为varValue,如果存在包含关系(variations包含输入的数据this.inputIndex)。若value为空且varValue不为空,则将value赋值为varValue,并退出循环,返回value。
classErrorBasedSQLInjection.prototype.request = function (value)
功能
为当前变体(variation)附一个值value并发送HTTP请求。
内容
如果存在文件上传(this.scheme.hasFileInput)且输入的数据存在关键标志(this.scheme.getInputFlags(this.inputIndex))且上传的数据是文件(INPUT_FLAG_IS_FILE),则设定上传文件名(setInputFileName)为value,设定上传文件类型(setInputContentType)为image/png,设定上传文件值(setInputValue)为value。否则设定上传文件值为value。
初始化lastJob(this.lastJob = new THTTPJob()),为this.lastJob.url赋值为this.targetUrl
如果targetHasAcuSensor(this.scheme.targetHasAcuSensor)中的值不为空,则 调用this.lastJob.addAspectHeaders()
调用populateRequest,传入lastjob (this.scheme.populateRequest(this.lastJob))
PS:调用populateRequest的作用就是填充完善请求头。
如果请求头中不存在字符串Referer,则调用函数addHeader添加请求头Referer
随后向lastJob发包(this.lastJob.execute())。
初始化变量tmp = false
如果请求结果不报错(wasError))且可以收到响应包,则向reflectionPoint发包,将响应包数据复制到lastJob响应包中,将tmp赋值为wasError的值。
最后返回 !this.lastJob.wasError && !tmp
classFileInclusion.prototype.requestProof = function (value, dontEncode)
功能
提取证明,判断提取证明(Proof)是否存在。
内容
如果存在文件上传(this.scheme.hasFileInput)且输入的数据存在关键标志(this.scheme.getInputFlags(this.inputIndex))且上传的数据是文件(INPUT_FLAG_IS_FILE),则设定上传文件名(setInputFileName)为value,设定上传文件类型(setInputContentType)为image/png。
如果dontEncode == TRUE,调用setEncodedInputValue,设定编码的输入值(this.inputIndex)为value (this.scheme.setEncodedInputValue(this.inputIndex, value)),否则设定输入值(this.inputIndex)为value。
否则
如果dontEncode == TRUE,调用setEncodedInputValue,设定编码的输入值(this.inputIndex)为value (this.scheme.setEncodedInputValue(this.inputIndex, value)),否则设定输入值(this.inputIndex)为value。
初始化this.lastJobProof = new THTTPJob()
初始化this.lastJobProof.url = this.targetUrl
如果targetHasAcuSensor(this.scheme.targetHasAcuSensor)中的值不为空,则 调用this.lastJob.addAspectHeaders()
调用populateRequest,传入lastjob (this.scheme.populateRequest(this.lastJob))
如果请求头中不存在字符串Referer,则调用函数addHeader添加请求头Referer
随后向lastJob发包(this.lastJob.execute())。
返回 !this.lastJobProof.wasError
classFileInclusion.prototype.extractProofOfExploit = function (testValue)
功能
尝试提取漏洞利用证明
内容
初始化proof = false
初始化proof_title = false
初始化proof_contents = false
初始化regex = false
如果testValue以**http://**开头,则
testValue = "http://bxss.me/t/fit.txt?";
regex = /(63c19a6da79816b21429e5bb262daed863c19a6da79816b21429e5bb262daed8)/;
proof_title = "URL - http://bxss.me/t/fit.txt";
否则
testValue = "../../../../../../../../proc/version";
regex = /(Linux\sversion\s\d.*?\s\(.*?\)\s\(gcc\sversion\s\d.*?\(.*?\)\s*\)\s\#.*?[A-Z]{3}\s\d{4})/;
proof_title = "File - /proc/version";
如果regex不为空
如果testValue提取证明结果为flase,则返回false。
将变量match赋值为this.lastJobProof.response.toString().match(regex),这里是将regex(正则匹配规则)对testValue中的请求包的信息进行匹配后返回的匹配结果。
如果match不为空且match(数组)长度大于1,则将proof_contents赋值为match[1]。
如果proof_contents不为空,则返回[proof_title, proof_contents]。
返回 proof。
classFileInclusion.prototype.alert = function (testValue, matchedText, sourceFile, sourceLine, additionalInfo, acuSensor)
功能
为扫描器生成报告项。
内容
初始化this.foundVulnOnVariation = true
初始化flags=[]
如果acuSensor == TRUE,则向flags内写入值verified和acusensor。
如果this.reflectionPoint不为空,则向flags内写入值stored。
初始化proof_title = false
初始化proof_contents = false
初始化常量proof = this.extractProofOfExploit(testValue)
如果proof存在且proof(数组)长度为2,则proof_title = proof[0],proof_contents = proof[1]。
如果proof_title和proof_contents都不为空,则向flags内写入值verified。
初始化newVuln
var newVuln = {
typeId: "File_inclusion.xml",
path: this.scheme.path,
tags: flags,
highlights: [matchedText],
details: {
input_type: this.scheme.getInputTypeStr(this.inputIndex),
input_name: this.scheme.getInputName(this.inputIndex),
proof_title: proof_title ? proof_title : false,
proof_contents: proof_contents ? proof_contents : false,
test_value: testValue,
matched_text: matchedText ? matchedText : false,
reflection_point: this.reflectionPoint ? this.reflectionPoint.url.url : false
},
http: this.lastJob.getNativeObject(),
ssl: scriptArg.target.url.protocol == 'https',
parameter: this.scheme.getInputName(this.inputIndex),
attackVector: testValue
};
如果sourceFile不为空或者additionalInfo不为空,则为newVuln增添值
newVuln.sensor = {
file: sourceFile,
line: sourceLine,
additional: additionalInfo
};
将newVuln添加到scanState (scanState.addVuln(newVuln))
classFileInclusion.prototype.testInjection = function (value, dontEncode)
功能
尝试进行sql注入,如果尝试注入失败就生成报告项。
内容
初始化sensorPayload = value.includes(this.injectionValidator.startMark)
如果**!this.request(value, dontEncode)为true**,则返回false。
初始化job = this.lastJob
创建data = this.disableSensorBased,如果为空则调用getSensorData(this.lastJob)。
如果data和sensorPayload不为空,则初始化变量injRes = this.isFileInclusion(value, data)
如果injRes和injRes.adItem不为空,则为变量additional赋值,additional = "File: " + injRes.data + “\r\n” + injRes.adItem.additional[0],生成报告项this.alert(value, “”, injRes.adItem.fileName, injRes.adItem.fileNo, additional, 1)并返回false。
否则,初始化this.disableSensorBased = true。
如果this.reflectionPoint为空,为变量matchedText赋值,matchedText = this.injectionPatterns.searchOnText(job.response.toString()),这里将job的响应包返回的信息与injectionPatterns中的信息进行匹配,将匹配到的结果赋值给matchedText。
如果matchedText不为空,生成报告项this.alert(value, matchedText)并返回false。
返回true。
classFileInclusion.prototype.testInjectionSelfInclude = function (value)
功能
尝试进行sql注入,如果尝试注入失败就生成报告项。
内容
如果发送http请求失败(!this.request(value, 0)),则返回false。
初始化job = this.lastJob。
为match赋值,match = job.response.toString().match(/(<%@[^%]+?%>)/)
,这里将正则匹配方法<%@[^%]+?%>
与job的响应包进行匹配,如果job.response中有符合匹配规则的字符串,则将其赋值给match。
如果match和match[1]不为空则生成报告项this.alert(value, match[1])并返回false。
返回true。
classFileInclusion.prototype.startTesting = function ()
功能
主函数,用于测试所有输入变化。
内容
初始化inputType = this.scheme.getInputTypeStr(this.inputIndex)
初始化inputName = this.scheme.getInputName(this.inputIndex)
从this.variations中依次取出值
如果this.foundVulnOnVariation == false则退出循环。
赋值变量this.currentVariation = i。
如果targetHasAcuSensor或reflectionPoint不为空
如果reflectionPoint为空,则this.injectionValidator.startMark = rnd.randStrDigits(6),this.injectionValidator.endMark = rnd.randStrDigits(6)
如果尝试注入结果为false,则继续循环。
!this.testInjection(`1${this.injectionValidator.startMark}/../../xxx\\..\\..\\${this.injectionValidator.endMark}`)
否则,this.injectionValidator.startMark = rnd.randStrDigits(6),this.injectionValidator.endMark = rnd.randStrDigits(6)
如果尝试注入结果为false,则继续循环。
!this.testInjection(`1${this.injectionValidator.startMark}/../../xxx\\..\\..\\${this.injectionValidator.endMark}`)
如果this.injectionPatterns不为空,赋值origValue = this.getOrigValue(),初始化extension = “jpg”。
如果origValue不为空且origValue中包含字符点"."
,取最后一个点(.)后面的字符串,赋值给extension。
初始化schemeExtension = ""
初始化schemePath = scheme.path
如果schemePath不为空且chemePath中包含字符点"."
,取最后一个点(.)后面的字符串,赋值给schemeExtension;取最后一个反斜杠(/)后面的字符串,赋值给schemeFileName。
如果尝试注入结果为false,则继续循环。
!this.testInjection("http://some-inexistent-website.acu/some_inexistent_file_with_long_name%3F." + extension, 1)
如果尝试注入结果为false,则继续循环。
!this.testInjection("1some_inexistent_file_with_long_name%00." + extension, 1)
如果尝试注入结果为false,则继续循环。
!this.testInjection("Http://" + AcuMonitor_AMServer + "/t/fit.txt", 1)
如果尝试注入结果为false,则继续循环。
!this.testInjection("http://" + AcuMonitor_AMServer + "/t/fit.txt%3F." + extension, 1)
如果尝试注入结果为false,则继续循环。
!this.testInjection(AcuMonitor_AMServer, 0)
如果schemeExtension == 'jsp’且origValue中包含字符串反斜杠(/)。
如果尝试注入结果为false,则继续循环。
!this.testInjectionSelfInclude(schemePath)
如果尝试注入结果为false,则继续循环。
!this.testInjectionSelfInclude(schemePath.replace(/^\/[^\/]+/, ""))
如果尝试注入结果为false,则继续循环。
!this.testInjectionSelfInclude(schemePath.replace(/^\/[^\/]+/, "").replace(/^\/[^\/]+/, ""))
如果尝试注入结果为false,则继续循环。
!this.testInjectionSelfInclude(schemePath.replace(/^\/[^\/]+/, "").replace(/^\/[^\/]+/, "").replace(/^\/[^\/]+/, ""))
如果schemeExtension == ‘jsp’。
如果尝试注入结果为false,则继续循环。
!this.testInjectionSelfInclude(schemeFileName)
如果尝试注入结果为false,则继续循环。
!this.testInjectionSelfInclude("./" + schemeFileName)
如果尝试注入结果为false,则继续循环。
!this.testInjectionSelfInclude("/" + schemeFileName)
SQL_Injection_In_Basic_Auth.script
功能
先判断访问网站知否符合sql注入的要求,处理好URL,调用至多四次testInjection函数来判断是否存在SQL注入。
尝试用户名
尝试密码
单引号用户名,双引号密码
双引号用户名,单引号密码
包含
#include constants.inc;
#include classErrorBasedSQLInjection.inc;
自定义函数
function alert(path, value, job, matchedText, sourceFile, sourceLine, additionalInfo, acuSensor)
AWVS用于报告漏洞情况的函数,当判断出存在漏洞后,调用此函数用于上报漏洞,回显对应漏洞信息。这个只需要知道大概作用就好,认真分析还要牵扯大量调用函数,所以就先不赘述了。
function request(dir, withAop, path, value)
初始化变量
lastJob = new THTTPJob();
lastJob.url = dir.url;
lastJob.uri = path;
lastJob.autoAuthenticate = false;
在lastJob请求头中添加字头名Authorization,值为value的值。(lastJob.request.addHeader(“Authorization”, value, true))
如果withAop == True则调用添加请求头的函数lastJob.addAspectHeaders()
发包lastJob.execute()
返回**!lastJob.wasError**
function testInjection(dir, withAop, path, value, tester)
为变量sensorPayload赋值
sensorPayload = value.includes("MUFDVVNUQVJUJ1wiKi9cclxuIFx0QUNVRU5EOmFhYWE=") || value.includes("YWFhYToxQUNVU1RBUlQnXCIqL1xyXG4gXHRBQ1VFTkQ=")
MUFDVVNUQVJUJ1wiKi9cclxuIFx0QUNVRU5EOmFhYWE=
BASE64解密结果
1ACUSTART'\"*/\r\n \tACUEND:aaaa
YWFhYToxQUNVU1RBUlQnXCIqL1xyXG4gXHRBQ1VFTkQ=
BASE64解密结果
aaaa:1ACUSTART'\"*/\r\n \tACUEND
如果请求结果为false(request(dir, withAop, path, value)),则返回false。
初始化job = lastJob
data = getSensorData(job)
如果data不为空且sensorPayload判断结果不为空,则赋值变量**injRes = tester.isSQLInjection(data, ACUINJSTART, ACUINJEND)**判断是否存在sql注入。
如果injRes和injRes.adItem不为空则赋值变量additional = "SQL query: " + injRes.data + “\r\n” + injRes.adItem.additional[0]并调用alert函数(alert(path, value, job, “”, injRes.adItem.fileName, injRes.adItem.fileNo, additional, 1))并返回false。
否则初始化变量matchedText = errorMessages.searchOnText(job.response.body)
如果matchedText不为空,则调用alert函数(alert(path, value, job, matchedText))并返回false。
返回true。
主函数
初始化dir = getCurrentDirectory()
如果请求后状态码为401且请求头中变量名WWW-Authenticate的变量值中包含Basic则初始化变量
var lastJob = null;
var errorMessages = new classSQLErrorMessages();
var dirName = dir.fullPath;
如果URL结尾不是反斜杠(/),则在URL结尾添加反斜杠(/) (if (dirName.charAt(dirName.length - 1) != ‘/’) dirName = dirName + ‘/’)
赋值变量**tester = new classErrorBasedSQLInjection(scanURL, errorMessages, null, 0)**功能是为类属性进行赋值(this.variations)。
如果dir.hasAcuSensor不为空
如果testInjection(dir, true, dirName, “Basic MUFDVVNUQVJUJ1wiKi9cclxuIFx0QUNVRU5EOmFhYWE=”, tester)返回结果为true【尝试用户名】,则调用函数testInjection (testInjection(dir, true, dirName, “Basic YWFhYToxQUNVU1RBUlQnXCIqL1xyXG4gXHRBQ1VFTkQ=”, tester))【尝试密码】。
否则
如果testInjection(dir, false, dirName, “Basic Jzoi”)返回结果为true【单引号用户,双引号密码】,则调用函数testInjection (testInjection(dir, false, dirName, ‘Basic Ijon’))【双引号用户,单引号密码】。
SQL_Injection_In_URI.script
功能
先判断访问网站知否符合sql注入的要求,处理好URL,调用至多六次testInjection函数来判断是否存在SQL注入。
包含
#include constants.inc;
#include classErrorBasedSQLInjection.inc;
#include dir_listing_helpers.inc;
自定义函数
function alert(path, value, job, matchedText, sourceFile, sourceLine, additionalInfo, acuSensor)
AWVS用于报告漏洞情况的函数,当判断出存在漏洞后,调用此函数用于上报漏洞,回显对应漏洞信息。
function request(dir, withAop, path)
初始化变量
lastJob = new THTTPJob();
lastJob.url = dir.url;
lastJob.uri = path;
如果withAop == True则调用添加请求头的函数lastJob.addAspectHeaders()
添加请求头
lastJob.request.addHeader('User-Agent', '1\'"2000', true);
lastJob.request.addHeader('referer', '1\'"3000', true);
lastJob.request.addHeader('client-ip', '1\'"4000', true);
lastJob.request.addHeader('x-forwarded-for', '1\'"5000', true);
lastJob.request.addHeader('accept-language', '1\'"6000', true);
lastJob.request.addHeader('via', '1\'"7000', true);
发包lastJob.execute()
返回**!lastJob.wasError**
function testInjection(dir, withAop, path, value, tester)
赋值变量sensorPayload = value.includes(ACUINJSTART)
如果请求结果为false(request(dir, withAop, path, value)),则返回false。
初始化job = lastJob
data = getSensorData(job)
如果data不为空且sensorPayload判断结果不为空,则赋值变量**injRes = tester.isSQLInjection(data, ACUINJSTART, ACUINJEND)**判断是否存在sql注入。
如果injRes和injResinjRes都不为空,则赋值变量additional = "SQL query: " + injRes.data + “\r\n” + injRes.adItem.additional[0]并调用alert函数(alert(path, value, job, “”, injRes.adItem.fileName, injRes.adItem.fileNo, additional, 1))并返回false。
否则初始化变量matchedText = errorMessages.searchOnText(job.response.body)
如果matchedText不为空,则调用alert函数(alert(path, value, job, matchedText))并返回false。
返回true。
主函数
初始化dir = getCurrentDirectory()
如果请求后状态码为200,则初始化变量matches = new classDirListingMatches()
如果**matches.searchOnText(dir.response.body)**为空,则初始化变量
var lastJob = null;
var errorMessages = new classSQLErrorMessages();
var dirName = dir.fullPath;
如果URL结尾不是反斜杠(/),则在URL结尾添加反斜杠(/) (if (dirName.charAt(dirName.length - 1) != ‘/’) dirName = dirName + ‘/’)
赋值变量**tester = new classErrorBasedSQLInjection(scanURL, errorMessages, null, 0)**功能是为类属性进行赋值(this.variations)。
如果dir.hasAcuSensor不为空
如果testInjection(dir, true, dirName, dirName + `1 A C U I N J S T A R T ′ " {ACUINJSTART}'" ACUINJSTART′"{ACUINJEND}`, tester, dirName)返回为false,则返回true。【简单构建】
如果testInjection(dir, true, dirName, dirName + `index/1KaTeX parse error: Can't use function '\'' in math mode at position 14: {ACUINJSTART}\̲'̲"{ACUINJEND}`, tester, dirName)返回为false,则返回true。【index构建】
如果testInjection(dir, true, dirName, dirName + `?id=1KaTeX parse error: Can't use function '\'' in math mode at position 14: {ACUINJSTART}\̲'̲"{ACUINJEND}`, tester, dirName)返回为false,则返回true。【带有参数的构建】
如果matchedText为空
如果testInjection(dir, false, dirName, dirName + "1’\“1000”)返回值为false,则返回true。【简单构建】
如果testInjection(dir, false, dirName, dirName + "index/1’\“1000”)返回值为false,则返回true。【index构建】
如果testInjection(dir, false, dirName, dirName + "?id=1’\“1000”)返回值为false,则返回true。【带有参数的构建】