向量数据库和异常数据
书接上文:https://www.cnblogs.com/k4n5ha0/p/18314781
一、最近学习机器学习期间,了解到了向量数据库:

1)可以将文本向量化存储(如上图,将不同语句向量化)
2)在 检索向量 上的时间复杂 和 对比向量相似度的时间复杂度(例如余弦相似度)充分调优
3)可以调用TPU、GPU等硬件加速运算
4)高度适配向量所以性能明显好于某些插件化支持向量的数据库

二、对上文的设计可以进一步的优化:
1)构建一组安全正则表达式,用户可以根据业务场景对String类型的入参选择选择适配业务且安全的正则表达式,此类入参不再进行存储以及自学习
2)定期搜集、构造、维护各类攻击代码并进行预处理(降噪):
2.1)SQL注入的payload进行语义处理,去掉注释、异常ASCII、多余的空白字符等并进行格式化后再存储为向量
2.2)XSS的payload进行语义处理,需分析html标签、标签属性等内容后再进行格式化,最终存储为向量
2.3)其他log4j、spring4shell、fastjson等恶意payload也进行单独向量化存储
2.4)此类向量记录为denyVector
3)可以对String类型的入参选择“向量判断”选项
3.1)在学习期间,网关会对此类入参进行降噪后存储
3.2)在防护期间,网关对该入参的风险Rank进行判定高于一定阈值则进行向量化判定
备注:结合(1)其实正则表达式可以看成可解释性极高、安全阈值控制到只有0或1两者的向量距离算法
4)在网关1~2周学习期间:
3.1)启动waf产品将所有业务数据进行安全过滤
3.2)降噪(上一条(2)中的预处理过程)
3.3)去重
3.4)最后向量化存储,此类向量记录为allowVector
5)在防护期间,
5.1)如“向量判断”的入参通过正则计算风险Rank高于一定阈值(参考modsecurity给出的风险Rank),则进行下一步向量计算
5.2)对该入参计算与 denyVector 和对应的 allowVector 的向量相似性,判断是否为恶意请求
三、扩展1:
正则表达式我们可以看成是一维的、可解释性强、速度快的模型,如有正则处理不了的入参再使用向量数据库处理
参考: https://coreruleset.org/docs/concepts/anomaly_scoring/
CRS提供了一系列识别风险的反向正则表达式,当1条rule可以match命中后会Add分数,达到一定阈值就会触发拦截

1)利用历史入参数据进行降维后的数据集推算出正向正则表达式(扣风险分)
2)结合负面正则(加风险分)进行可信分计算最终风险分
3)当风险分高于一定的值则拦截,这种算法与使用向量数据库对入参计算正样本和负样本距离没有本质区别
四、扩展2:
在libinjection-go之中的sqli_token.go里有一处maxTokens 这是词法解析后Token序列的最大长度,我们将其改成64

同步的将sqli.go里的tokenVec [8]sqliToken改为tokenVec [maxTokens + 1]sqliToken

如此之后词法解析的长度上限就达到了64,当我们在sqli_test.go中添加以下代码后
func (s *sqliState) check2() bool {
// no input? not SQLi
if s.length == 0 {
return false
}
// test input "as-is"
s.sqliFingerprint(sqliFlagQuoteNone | sqliFlagSQLAnsi)
fmt.Println("sqliFlagQuoteNone | sqliFlagSQLAnsi ", s.fingerprint)
if s.reparseAsMySQL() {
s.sqliFingerprint(sqliFlagQuoteNone | sqliFlagSQLMysql)
fmt.Println("sqliFlagQuoteNone | sqliFlagSQLMysql", s.fingerprint)
}
// if input has a single quote, then
// test as if input was actually '
// example: if input if "1' = 1", then pretend it's "'1' = 1"
if strings.IndexByte(s.input, byteSingle) != -1 {
s.sqliFingerprint(sqliFlagQuoteSingle | sqliFlagSQLAnsi)
fmt.Println("sqliFlagQuoteSingle | sqliFlagSQLAnsi", s.fingerprint)
if s.reparseAsMySQL() {
s.sqliFingerprint(sqliFlagQuoteSingle | sqliFlagSQLMysql)
fmt.Println("sqliFlagQuoteSingle | sqliFlagSQLMysql", s.fingerprint)
}
}
// same as above but with a double quote
if strings.IndexByte(s.input, byteDouble) != -1 {
s.sqliFingerprint(sqliFlagQuoteDouble | sqliFlagSQLMysql)
fmt.Println("sqliFlagQuoteDouble | sqliFlagSQLMysql", s.fingerprint)
}
// Hurry, input is not SQLi
return false
}
func TestIsSQLi2(t *testing.T) {
input := "/*12312*/and/*12312*/(select 1/*12312*/from (select count(),concat(0x3a,0x3a,(select concat(0x3a,0x3a, name,0x3a,0x3a,passwd,0x3a,0x3a) from message.users limit 0,1),0x3a,0x3a, floor(rand(0)2)) a from information_schema.columns group by a)s) -- "
state := new(sqliState)
sqliInit(state, input, 0)
state.check2()
}
执行TestIsSQLi2这个单元测试可以得到sqli的一个完整的Token序列(左边未选中内容为解析模式,右边选中部分为词法字符串)

参照上述设计,每次入参都使用同样算法得到Token序列:
1)对libinjection-go项目中的test内容形成一套恶意Token序列
2)利用历史入参数据词法分析并去重后得到一组善意Token序列
3)通过各种文本聚类算法对每次入参的Token序列分别计算与恶意Token序列和善意Token序列的相似性(文本聚类)
4)结合(3)得到的两个相似性,最终得到风险值
五、题外话
其实我们除了对入参做以上的分析,我们还能将入参和运行时信息一起进行关联计算:
1)例如请求入参和关键函数入参
2)运行时上下文尤其是堆栈信息
3)将(1)和(2)进行预处理之后在sink点触发时进行向量计算
备注:可参考openrasp对上下文的校验函数 validate_stack_java
https://packages.baidu.com/app/openrasp/release/latest/plugin.js
在敏感节点触发向量对比,剩下的工作完全就是统计学的一次应用。
说穿了,网络安全就是一种数学的应用场景,仅此而已。
五、参考:
https://auto.gluon.ai/stable/tutorials/multimodal/semantic_matching/text_semantic_search.html

浙公网安备 33010602011771号