书接上文《安全中间件的设计思路和简单实践》: https://my.oschina.net/9199771/blog/5417127
当时写完之后感觉意犹未尽,内心觉得很多实现方案其实可以进一步强化。
之后又遇到了中国X科院的安全测试工作,仔细研究了下其中的安全标准,其中就要求应用系统内部能抵抗sqli、xss、恶意上传。
去年开始不间断的研究 ModSecurity 这个出名的waf,感觉设计的很好(还提交了几个策略都被官方采纳了)
今年又发现了一个java集成ModSecurity的项目: https://github.com/bytedeco/javacpp-presets/tree/master/modsecurity
这么多事情积攒在一起,所以简单的实现下,集成ModSecurity的应用内部防护设计
1)准备java环境
目前建议使用java8+maven的配置,使用java11会有个报错
之后我们要看看当前的最新版本
目前只支持到ModSecurity 3.0.6
<!-- https://mvnrepository.com/artifact/org.bytedeco/modsecurity -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>modsecurity</artifactId>
<version>3.0.6-1.5.7</version>
</dependency>
所以我们编译ModSecurity的时候版本要切换到3.0.6
2)编译ModSecurity
ModSecurity3目前只能在linux下编译使用,所以本文在linux上实现。
官方给出了在不同环境下的编译命令(直接复制粘贴就可以了):
https://github.com/SpiderLabs/ModSecurity/wiki/Compilation-recipes-for-v3.x
编译难度几乎为0,我就不多说了,其中不要忘记git clone下代码后先执行 git checkout v3.0.6 把代码切换到3.0.6分支,后续版本更新就见机行事
3)执行防护
官方的样例代码: https://github.com/bytedeco/javacpp-presets/tree/master/modsecurity/samples
一个是pom文件一个java源码,我们都要做一下修改。
3.1)其中因为我们采用了3.0.6版本,所以先修改一下pom文件的<version>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.bytedeco.modsecurity</groupId>
<artifactId>samples</artifactId>
<version>1.5.8-SNAPSHOT</version>
<properties>
<exec.mainClass>ModSecuritySimpleIntervention</exec.mainClass>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>modsecurity-platform</artifactId>
<version>3.0.6-1.5.7</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>.</sourceDirectory>
</build>
</project>
3.2)java源码中我们主要关注的是检测方式
ModSecurity只是一个引擎,它的防护规则来自于: https://github.com/coreruleset/coreruleset
我们先使用sqli和xss防护策略。顺便说一句他们更新的挺勤快的,测试用例同步的也很好
@detectXSS 和 @detectSQLi 这两句是使用内置的libinjection语义引擎进行sql和xss探测的语句
所以我们可以把 ModSecuritySimpleIntervention.java 做如下修改:
import java.util.Optional;
import org.bytedeco.javacpp.*;
import org.bytedeco.modsecurity.*;
public class ModSecuritySimpleIntervention {
private static final String BASIC_RULE =
"SecRuleEngine On\n" +
"SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_HEADERS:User-Agent|REQUEST_HEADERS:Referer|ARGS_NAMES|ARGS|XML:/* \"@detectSQLi\" \"id:942100, phase:2, block, capture, t:none,t:utf8toUnicode,t:urlDecodeUni,t:removeNulls, msg:'SQL Injection Attack Detected via libinjection', logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}', tag:'application-multi', tag:'language-multi', tag:'platform-multi', tag:'attack-sqli', tag:'paranoia-level/1', tag:'OWASP_CRS', tag:'capec/1000/152/248/66', tag:'PCI/6.5.2', ver:'OWASP_CRS/4.0.0-rc1', severity:'CRITICAL', multiMatch, setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}', setvar:'tx.sql_injection_score=+%{tx.critical_anomaly_score}'\""
+ "\n" +
"SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_HEADERS:User-Agent|ARGS_NAMES|ARGS|XML:/* \"@detectXSS\" \"id:941100, phase:2, block, t:none,t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls, msg:'XSS Attack Detected via libinjection', logdata:'Matched Data: XSS data found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}', tag:'application-multi', tag:'language-multi', tag:'platform-multi', tag:'attack-xss', tag:'paranoia-level/1', tag:'OWASP_CRS', tag:'capec/1000/152/242', ctl:auditLogParts=+E, ver:'OWASP_CRS/4.0.0-rc1', severity:'CRITICAL', setvar:'tx.xss_score=+%{tx.critical_anomaly_score}', setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'\"";
public static void main(String[]args){
ModSecurity modSecurity = new ModSecurity();
RulesSet rulesSet = new RulesSet();
rulesSet.load(BASIC_RULE);
Transaction transaction = new Transaction(modSecurity, rulesSet, null);
transaction.processConnection("127.0.0.1", 4455, "", 80);
transaction.processURI("https://modsecurity.org/attack?uid=<script>alert(1)</script>&id=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--", "GET", "1.0");
transaction.addResponseHeader("HTTP/1.1", "200 OK");
transaction.processResponseHeaders(200, "HTTP/1.1");
transaction.processRequestBody();
transaction.processRequestHeaders();
ModSecurityIntervention modSecurityIntervention = new ModSecurityIntervention();
boolean isIntervention = transaction.intervention(modSecurityIntervention);
if (isIntervention){
System.out.println("!!!!!!There is intervention !!!!!!!!!");
logRuleMessages(transaction.m_rulesMessages());
}
}
private static void logRuleMessages(RuleMessageList messageList){
if (messageList != null && !messageList.isNull() && !messageList.empty()) {
long size = messageList.size();
System.out.println("MessageRuleSize " + size);
RuleMessageList.Iterator iterator = messageList.begin();
for (int i = 0; i < size; i++) {
logRuleMessage(iterator.get());
iterator.increment();
}
}
}
private static void logRuleMessage(RuleMessage ruleMessage){
System.out.println("RuleMessage id = "+ ruleMessage.m_ruleId()+ " message = " + Optional.ofNullable(ruleMessage.m_message()).map(BytePointer::getString).orElse("NO_MESSAGE"));
}
}
其中规则部分的三句话我增加了注释:
private static final String BASIC_RULE =
//启动引擎
"SecRuleEngine On\n" +
//使用@detectSQLi检测sqli
"SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_HEADERS:User-Agent|REQUEST_HEADERS:Referer|ARGS_NAMES|ARGS|XML:/* \"@detectSQLi\" \"id:942100, phase:2, block, capture, t:none,t:utf8toUnicode,t:urlDecodeUni,t:removeNulls, msg:'SQL Injection Attack Detected via libinjection', logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}', tag:'application-multi', tag:'language-multi', tag:'platform-multi', tag:'attack-sqli', tag:'paranoia-level/1', tag:'OWASP_CRS', tag:'capec/1000/152/248/66', tag:'PCI/6.5.2', ver:'OWASP_CRS/4.0.0-rc1', severity:'CRITICAL', multiMatch, setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}', setvar:'tx.sql_injection_score=+%{tx.critical_anomaly_score}'\""
//注意要增加一个换行
+ "\n" +
//使用@detectXSS检测xss攻击
"SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_HEADERS:User-Agent|ARGS_NAMES|ARGS|XML:/* \"@detectXSS\" \"id:941100, phase:2, block, t:none,t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls, msg:'XSS Attack Detected via libinjection', logdata:'Matched Data: XSS data found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}', tag:'application-multi', tag:'language-multi', tag:'platform-multi', tag:'attack-xss', tag:'paranoia-level/1', tag:'OWASP_CRS', tag:'capec/1000/152/242', ctl:auditLogParts=+E, ver:'OWASP_CRS/4.0.0-rc1', severity:'CRITICAL', setvar:'tx.xss_score=+%{tx.critical_anomaly_score}', setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'\"";
并构造了一个sqli和xss:
transaction.processURI("https://modsecurity.org/attack?uid=<script>alert(1)</script>&id=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--", "GET", "1.0");
使用 mvn compile exec:java 执行
成功探测到了sqli和xss。
3.3)略微修改策略,做到防护探测
语法参照: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-%28v3.x%29#deny
我们把sql注入的语法增加 deny 拦截语法
"SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_HEADERS:User-Agent|REQUEST_HEADERS:Referer|ARGS_NAMES|ARGS|XML:/* \"@detectSQLi\" \"log,deny,id:942100, phase:2, block, capture, t:none,t:utf8toUnicode,t:urlDecodeUni,t:removeNulls, msg:'SQL Injection Attack Detected via libinjection', logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}', tag:'application-multi', tag:'language-multi', tag:'platform-multi', tag:'attack-sqli', tag:'paranoia-level/1', tag:'OWASP_CRS', tag:'capec/1000/152/248/66', tag:'PCI/6.5.2', ver:'OWASP_CRS/4.0.0-rc1', severity:'CRITICAL', multiMatch, setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}', setvar:'tx.sql_injection_score=+%{tx.critical_anomaly_score}'\""
新代码如下:
import java.util.Optional;
import org.bytedeco.javacpp.*;
import org.bytedeco.modsecurity.*;
public class ModSecuritySimpleIntervention {
private static final String BASIC_RULE =
"SecRuleEngine On\n"
+ "\n" +
"SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_HEADERS:User-Agent|ARGS_NAMES|ARGS|XML:/* \"@detectXSS\" \"log,deny,id:941100, phase:2, block, t:none,t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls, msg:'XSS Attack Detected via libinjection', logdata:'Matched Data: XSS data found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}', tag:'application-multi', tag:'language-multi', tag:'platform-multi', tag:'attack-xss', tag:'paranoia-level/1', tag:'OWASP_CRS', tag:'capec/1000/152/242', ctl:auditLogParts=+E, ver:'OWASP_CRS/4.0.0-rc1', severity:'CRITICAL', setvar:'tx.xss_score=+%{tx.critical_anomaly_score}', setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'\""
+ "\n" +
"SecRule REQUEST_COOKIES|!REQUEST_COOKIES:/__utm/|REQUEST_COOKIES_NAMES|REQUEST_HEADERS:User-Agent|ARGS_NAMES|ARGS|XML:/* \"@detectSQLi\" \"log,deny,id:942100, phase:2, block, t:none,t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:removeNulls, msg:'SQLi Attack Detected via libinjection', logdata:'Matched Data: SQLi found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}', tag:'application-multi', tag:'language-multi', tag:'platform-multi', tag:'attack-SQLi', tag:'paranoia-level/1', tag:'OWASP_CRS', tag:'capec/1000/152/242', ctl:auditLogParts=+E, ver:'OWASP_CRS/4.0.0-rc1', severity:'CRITICAL', setvar:'tx.SQLi_score=+%{tx.critical_anomaly_score}', setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'\"";
public static void main(String[]args){
ModSecurity modSecurity = new ModSecurity();
RulesSet rulesSet = new RulesSet();
rulesSet.load(BASIC_RULE);
Transaction transaction = new Transaction(modSecurity, rulesSet, null);
transaction.processConnection("127.0.0.1", 4455, "", 80);
transaction.processURI("https://modsecurity.org/attack?id=-1' and 1=1 union/* foo */select load_file('/etc/passwd')-- &uid=<script>alert(1)</script>", "GET", "1.0");
transaction.addResponseHeader("HTTP/1.1", "200 OK");
transaction.processResponseHeaders(200, "HTTP/1.1");
transaction.processRequestBody();
transaction.processRequestHeaders();
ModSecurityIntervention modSecurityIntervention = new ModSecurityIntervention();
boolean isIntervention = transaction.intervention(modSecurityIntervention);
if (isIntervention){
System.out.println("!!!!!!There is intervention !!!!!!!!!");
logRuleMessages(transaction.m_rulesMessages());
}
}
private static void logRuleMessages(RuleMessageList messageList){
if (messageList != null && !messageList.isNull() && !messageList.empty()) {
long size = messageList.size();
System.out.println("MessageRuleSize " + size);
RuleMessageList.Iterator iterator = messageList.begin();
for (int i = 0; i < size; i++) {
logRuleMessage(iterator.get());
iterator.increment();
}
}
}
private static void logRuleMessage(RuleMessage ruleMessage){
System.out.println("RuleMessage id = "+ ruleMessage.m_ruleId()+ " message = " + Optional.ofNullable(ruleMessage.m_message()).map(BytePointer::getString).orElse("NO_MESSAGE"));
}
}
再次执行,因为xss策略放在sqli前面,又开启了拦截
又因为有了干预行为 isIntervention 为真,所以只有部分提示
当我把xss的deny标记删掉后,就能提示xss和sqli两种问题了
后续在代码中我们只要判断 isIntervention 为真,则说明:策略成功拦截并做出对应的处理
4)后续拓展
其实做到这里思路就很清晰了,我们内置waf的主要工作就是将ModSecurity的策略进行拆解内置到应用中。
不过后续的工作定制化程度就非常高了,都需要和架构师达成技术一致才能开展,这里捋一下定制化思路:
4.1)过滤原则
所有的request(输入)和response(输出)都需要进行过滤
4.2)输入过滤
用户输入的入参包括字符串、xml、json、multipart中的数据逐个进行正则+语义的校验
4.2)输出过滤
系统输出的内容,最重要的就是接口都要进行敏感信息判断,一旦包含就应当禁止输出,如下图:
4.3)策略定制
依据系统实际情况进行策略增减,尤其是 multipart 上传文件的文件内容应进行全局白名单校验
4.4)策略裁剪
对输入输出内容还能使用AntiSamy这个库进行xss过滤,AntiSamy的过滤定制化简单并主要使用白名单对输入和输出的标签和标签属性过滤,是主流的防xss的设计
4.5)黑白名单
对某些需要输入sql或者后台需要定制复杂html模板的功能,则需要可配置白名单。
对某些策略可以被绕过的情况,暂时无法得到强壮策略时,应可以配置紧急黑名单。
5)总结:
第一代rasp就是使用上面的思路,尤其是sql注入问题,由应用内置防护可以避免url转义、编码等实现差异所导致的waf和应用获取的入参不一致问题。
第二代rasp使用函数hook,保证恶意行为在底层拦截,增加了对未知漏洞的抵御能力
第三代rasp将操作系统的函数调用和应用的函数调用进行关联,加强了未知漏洞的分析能力。
杀毒软件的演进过程: 静态文件特征码-》内存特征码-》主动防御-》云沙箱
应用防护的演进过程: 静态流量特征码-》语义引擎-》上下文行为分析-》内存马云查杀
都从静态特征码-》动态行为特征-》主动防御和rasp的上下文都是针对api函数调用关系进行分析-》云查杀
历史真是出奇的相似:-)
禁止转载
谢谢