awdp小waf合集

发布于 2025-03-18  45 次阅读


awdp小waf合集

php

收集来源或启发

https://5ime.cn/awdp.html

https://github.com/dr0op/k4l0ng_WAF

https://github.com/Drun1baby/AWD-AWDP_SecFilters

https://github.com/leohearts/awd-watchbird

xss过滤

function wafxss($str){
    return !preg_match("/\'|http|\"|\`|cookie|<|>|script/i", $str);
}

SQL 注入

function wafsqli($str){
    return !preg_match("/select|and|\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\x26|\x7c|or|into|from|where|join|sleexml|extractvalue|+|regex|copy|read|file|create|grand|dir|insert|link|server|drop|=|>|<|;|\"|\'|\^|\|/i", $str);
}

RCE执行

function wafrce($str){
    return !preg_match("/openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|scandir|assert|pcntl_exec|fwrite|curl|system|eval|assert|flag|passthru|exec|chroot|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore/i", $str);
}

LFI文件包含路径遍历

function wafLFI($str){
    return !preg_match("/\.\.|\.php[2357]?|\.phtml/i", $str);
}

反序列化(phar)防御

function wafphar($str){
    return !preg_match("/phar|zip|compress\.(bzip2|zlib)/i", $str);
}

特殊字符防御

function wafDefense($str){
    for($i=0; $i<strlen($str); $i++){
        $ascii = ord($str[$i]);
        // <32 或 >126 属于不可见/特殊字符(这里未考虑中文,具体可自行修改)
        if($ascii < 32 || $ascii > 126){
            return false;
        }
        // 常见可疑符号
        if (preg_match("/[\|\`;,'\"<>]/", $str[$i])) {
            return false;
        }
    }
    return true
}

文件上传防御

function uploadDefense($files) {
    // $files 通常是 $_FILES
    foreach ($files as $key => $fileInfo) {// 遍历所有上传文件
        // 检测上传是否成功
        if ($fileInfo['error'] === UPLOAD_ERR_OK) {
            $filename = $fileInfo['name'];
            $tmpName  = $fileInfo['tmp_name'];
            $ext      = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

            // 1) 扩展名白名单检查
            if (!preg_match("/^(jpg|jpeg|png|gif|pdf|txt|docx|xlsx)$/i", $ext)) {
                return false;
            }

            // 2) 检查文件内容,防止 PHP 代码执行
            $content = file_get_contents($tmpName);
            if (stripos($content, '<?php') !== false) {
                return false;
            }

            // 3) 检测是否为伪装的图片
            if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
                if (!getimagesize($tmpName)) {
                    return false;
                }
            }

            // 4) 仅允许文件大小在 5MB 以内(可根据需求调整)
            if (filesize($tmpName) > 5 * 1024 * 1024) {
                return false;
            }
        }
    }
    return true;
}

多次解码

function multiLayerDecode($str) {
    $old = $str;
    do {
        $new = $old;

        // 1) 处理 URL 编码
        $newDecoded = urldecodeExtended($new);
        if ($newDecoded !== $new) {
            $new = $newDecoded;
        }

        // 2) 处理 Hex 和 Unicode 转义
        $new = parseHex($new);
        $new = parseUnicode($new);

        // 3) 尝试 Base64 解码
        if (isLikelyBase64($new)) {
            $tmp = base64_decode($new, true);
            if ($tmp !== false && isLikelyText($tmp)) {
                $new = $tmp;
            }
        }

        // 4) 检测 HTML 实体编码
        $new = html_entity_decode($new, ENT_QUOTES | ENT_HTML5, 'UTF-8');

        // 循环,直到字符串不再变化
        if ($new === $old) {
            break;
        }
        $old = $new;
    } while (true);

    return $new;
}

function urldecodeExtended($str) {
    $old = $str;
    while (strpos($old, '%25') !== false) {
        $old = str_replace('%25', '%', $old);
    }
    return urldecode($old);
}

function parseHex($str) {
    return preg_replace_callback('/\\\x([0-9A-Fa-f]{2})/', function($m){
        return chr(hexdec($m[1]));
    }, $str);
}

function parseUnicode($str) {
    return preg_replace_callback('/\\\u([0-9A-Fa-f]{4})/', function($m){
        return mb_convert_encoding("&#".hexdec($m[1]).";", 'UTF-8', 'HTML-ENTITIES');
    }, $str);
}

function isLikelyBase64($str) {
    return (bool)preg_match('/^[A-Za-z0-9\/+]+=*$/', $str);
}

function isLikelyText($str) {
    $printable = preg_replace('/[\x20-\x7E]/', '', $str); 
    return (strlen($str) > 0) ? (strlen($printable)/strlen($str) < 0.3) : false;
}

关键函数的反写检查

function detectReverseInjection($str) {
    $dangerous_keywords = ['select', 'union', 'drop', 'insert', 'update', 'delete', 'eval', 'system', 'exec'];

    foreach ($dangerous_keywords as $keyword) {
        if (stripos($str, $keyword) !== false || stripos(strrev($str), $keyword) !== false) {
            return false;
        }
    }
    return true;
}

java

打包流程

反编译成Maven项目

对比于手动编译打包,反编译成Maven项目更加方便快捷(Maven其实也就是干这个事)

手动仅使用原始Java进行反编译并打包可以参考:AWD离线-Jar文件冷补丁

下面记录反编译成Maven项目并进行修复的步骤

  1. 反编译jar包,使用IDEA反编译插件还原源代码,反编译出来还是jar包,直接解压即可
/Library/Java/JavaVirtualMachines/jdk-17.0.11.jdk/Contents/Home/bin/java -cp "/Applications/IntelliJ IDEA.app/Contents/plugins/java-decompiler/lib/java-decompiler.jar" org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true <jar_path> <output_path>

最新版IDEA的反编译插件需要java17

  1. 导入源代码,创建一个空的Maven项目,然后将反编译后/BOOT-INF/classes下的源代码复制到/src/main/java 路径下。视check情况选择是否将resources 下资源文件一起导入

image

  1. 导入依赖库,将BOOT-INF/lib下的依赖复制到项目的lib文件文件夹下(没有这个文件夹,需要自己新建,一般在项目的第一级目录),在文件-项目结构-项目设置-库中将lib文件夹添加为库文件夹,此步骤是解决在离线情况下缺少依赖的问题
  2. 用反编译出的pom.xml 文件覆盖创建的Maven项目中的pom.xml 文件
  3. 修复代码并打包,修改Java源代码,或者根据漏洞点直接修改pom.xml 文件中依赖的版本号,然后使用Maven进行打包,当然也可以直接在在IDEA中点点点。Springboot项目在pom.xml 内置了打包插件,所以选择Maven打包而不是构建成工件的jar或者war包的形式,需要根据实际情况选择如何打包
mvn clean package

image

以上展示的是打包一个完整的jar包的步骤,成功完成后会在/target 目录输出修复好的jar包文件,直接-jar 运行即可,当然也可以直接选择使用压缩软件对编译好的class文件进行替换,看文章说Bandizip可以保证替换前后jar包和war包的可用性,使用Maczip测试确实出现如下报错

image

jar uf更新class文件

那么除了使用Bandizip还有其他方法可以更新class文件而不影响jar包和war包的可用性呢,答案是肯定的,可以使用jar uf命令来更新class文件

需要注意在jar包内class文件的真实包名,如下图,这里需要在包名前添加BOOT-INF/classes/ 前缀

image

同时注意根据包名创建文件夹,IDEA中直接把target目录改名为BOOT-INF 即可

命令示例

jar uf vulnspringboot-1.0-SNAPSHOT.jar BOOT-INF/classes/org/example/controller/SCtfController.class

Javassist修改jar包字节码

可以用以下项目作为脚手架:

非常便捷,在main函数中指定要修改的jar包路径,然后在ExamplePatch 类的patch 方法中编写Javassist修改字节码逻辑即可,同样因为jar包结构问题,需要注意BOOT-INF/classes/ 路径问题

image

修改jar包中class内容

CtClass c1 = new PatchClass("org.example.controller.SCtfController", "BOOT-INF/classes/").getCtClass();
CtMethod write1 = c1.getDeclaredMethod("index");
write1.insertBefore("System.out.println(\"Sakura\");");

修改jar包中依赖的class

PatchLibrary patchLibrary = new PatchLibrary("hessian-4.0.4.jar",  "BOOT-INF/lib/");
CtClass c4 = patchLibrary.getCtClass("com.alipay.hessian.NameBlackListFilter");
CtMethod write4 = c4.getDeclaredMethod("resolve");
write4.insertBefore("System.out.println(\"Sakura\");");

同时也支持直接修改class,主要针对tomcat环境

// we want to change /usr/local/tomcat/webapps/ROOT/WEB-INF/classes/com/ctf/BoardServlet.class

// addClassRootPath
patch.addClassRootPath("/usr/local/tomcat/webapps/ROOT/WEB-INF/classes/");

// rewrite it with Javassist
CtClass c2 = new PatchClass("com.ctf.BoardServlet", "").getCtClass();
CtMethod write2 = c2.getDeclaredMethod("index");
write2.insertBefore("System.out.println(\"Sakura\");");

// make sure you set `cleanAfterPatch` = false
patch.setCleanAfterPatch(false);

// then you'll find it in `patch/`

JarEditor修改jar包

比较推荐的方法,IDE插件直接修改jar包内容。对于外部包,右键jar包,Add Library ,就可以直接修改了

image

修改完成后,点击Save(Compile),编译并保存当前修改的java内容,最后点击Build Jar,将编译保存的类文件写入Jar包中

经测试,目前好像没有修改未在jar包中class的功能,对于tomcat环境,可以将classes文件夹压缩并修改为jar后缀即可修改

总结

几种方法优先推荐JarEditor修改jar包,其他思路比如JByteMod直接修改字节码因为普适性不高并没有做演示

同时也测试了arthas用于AWD修复jar包,但由于arthas采用agent原理,有些class并没有被jvm加载导致内存编译功能错误,还是更适用于应急响应以及bug诊断场景

image

Refference

waf

来自 https://github.com/Drun1baby/AWD-AWDP_SecFilters

Fastjson 防护

  • 文件: Fastjson1224Filter.java
  • 作用: 防止 Fastjson 远程调用漏洞 (rmi, ldap, jndi)

若针对其他版本直接在 Java 代码中:

ParserConfig.getGlobalInstance().setAutoTypeSupport(false);

示例:

Fastjson1224Filter filter = new Fastjson1224Filter();
String result = filter.Unserjson("someSecret", "{\"key\": \"rmi://malicious-server\"}");
System.out.println(result); // "Hacker get out!!!"

文件上传防护

  • 文件: FileUploadMap.java, FileUploadUtils.java

  • 作用: 限制文件类型和大小,防止恶意文件上传

文件存储路径默认设置在 System.getProperty("user.dir") + "/tmp"

接收securityUpload传来的文件

SQL 注入防护

  • 文件: SqliFilter.java
  • 作用: 过滤 SQL 关键字,防止 SQL 注入

urlPatterns = "/system/role/list"应用路由

web.xml 配置

如果使用 web.xml 方式,添加以下过滤器:

<filter>
    <filter-name>sqlInjectFilter</filter-name>
    <filter-class>com.example.filters.sqlFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>sqlInjectFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Log4j2 防护

  • 文件: Log4j2Filter.java
  • 作用: 拦截 JNDI 相关字符串,防止远程代码执行

urlPatterns = "/system/role/list"应用路由

目录遍历防护

  • 文件: PathFilter.java
  • 作用: 过滤路径中的 ../,防止访问敏感文件

XSS 防护

  • 文件: XSSFilter.java
  • 作用: 过滤 HTML & JavaScript 代码,防止 XSS 攻击

    private List urlExclusion = null; 白名单 URL

SSRF 防护

  • 文件: SSRFilter.java
  • 作用: 拦截危险协议,防止服务器端请求伪造

Node.js / JavaScript

反序列化漏洞

function wafJSDeserialize(str) {
  // 常见可疑标记:
  // 1) node-serialize: _$$ND_FUNC$$_
  // 2) serialize-to-js: $$ND_FUNC$$
  // 3) funcster: __js_function
  // 4) 可能的 IIFE 方式: 立即调用 function () {...}()
  const pattern = /(\_?\$\$ND_FUNC\$\$|__js_function|\(\s*function\s*\(\)\s*\{|\)\(\))/i;
  return !pattern.test(str);
}

原型链污染

function wafProtoPollution(obj) {
  function recursiveCheck(target) {
    if (target && typeof target === 'object') {
      for (let key in target) {
        // 关键检查
        if (['__proto__','prototype','constructor'].includes(key)) {
          return false; // 不安全
        }
        if (!recursiveCheck(target[key])) {
          return false; // 递归子对象
        }
      }
    }
    return true;
  }
  return recursiveCheck(obj);
}

Python

多层解码

import re
import base64
import html
import urllib.parse

def multi_layer_decode(input_str, max_iterations=10):
    """
    对输入字符串进行多层解码,包括:
    1. URL 编码 (%xx)
    2. Base64 编码
    3. HTML 实体编码
    4. Unicode 转义 (\uXXXX)
    5. Hex (\xXX)

    直到字符串不再变化,或达到最大迭代次数 max_iterations 以防死循环。
    """
    old_str = input_str
    for _ in range(max_iterations):
        new_str = old_str

        # 1) URL 解码(多次%25 -> %)
        while "%25" in new_str:
            new_str = new_str.replace("%25", "%")
        new_str = urllib.parse.unquote(new_str)

        # 2) HTML 实体解码
        new_str = html.unescape(new_str)

        # 3) Unicode 转义解码(\uXXXX)
        new_str = decode_unicode_escape(new_str)

        # 4) Hex 转义解码(\xXX)
        new_str = decode_hex_escape(new_str)

        # 5) Base64 解码
        if is_likely_base64(new_str):
            try:
                decoded_b64 = base64.b64decode(new_str, validate=True).decode('utf-8', errors='ignore')
                if is_likely_text(decoded_b64):  # 只保留解码后仍像文本的结果
                    new_str = decoded_b64
            except Exception:
                pass  # Base64 解码失败则跳过

        # 终止条件:如果本次循环解码后,字符串没有变化,则停止
        if new_str == old_str:
            break

        old_str = new_str  # 更新字符串,继续下一轮解码

    return old_str

def decode_unicode_escape(s):
    """ 解析 \uXXXX 形式的 Unicode 转义 """
    return re.sub(r'\\u([0-9A-Fa-f]{4})', lambda m: chr(int(m.group(1), 16)), s)

def decode_hex_escape(s):
    """ 解析 \xXX 形式的十六进制转义 """
    return re.sub(r'\\x([0-9A-Fa-f]{2})', lambda m: chr(int(m.group(1), 16)), s)

def is_likely_base64(s):
    """
    判断一个字符串是否可能是 Base64 编码:
    1. 只能包含 a-z, A-Z, 0-9, +, /, =
    2. 长度必须是 4 的倍数
    """
    return bool(re.fullmatch(r'^[A-Za-z0-9+/=]+$', s)) and len(s) % 4 == 0

def is_likely_text(s):
    """ 判断字符串是否主要由可见字符组成 """
    visible_chars = sum(1 for c in s if 32 <= ord(c) <= 126)
    return (visible_chars / max(1, len(s))) > 0.7  # 至少 70% 是可见字符

# 示例测试
if __name__ == "__main__":
    test_cases = [
        "hello%20world",                  # URL 编码
        "%25E6%259C%2589%25E5%25B0%25A7", # URL 多层编码
        "&#x6F22;&#x5B57;",               # HTML 实体编码
        "\\u4F60\\u597D",                 # Unicode 转义
        "\\x48\\x65\\x6C\\x6C\\x6F",      # Hex 转义
        "aGVsbG8gd29ybGQ=",               # Base64 编码
        "JTI1RTYlMjU5QyUyNTg5JTI1RTUlMjVBMCUyNTUw"  # 多层 URL 编码 + Base64
    ]

    for case in test_cases:
        print(f"原始字符串: {case}")
        print(f"解码结果: {multi_layer_decode(case)}\n")

SSTI

import re

def waf_ssti_check(input_str: str) -> bool:
    """
    若检测到常见 Jinja2 / Mako / Django 模板注入符号,则返回 False 表示危险。
    """
    # 常见可疑语法:{{ ... }}, {%, %}, <%, ...
    pattern = r"(\{\{|\{%|<%|__globals__|os\.system|popen|config\.items\(\)|cycler\.__init__)"
    return not re.search(pattern, input_str, re.IGNORECASE)

反序列化

import re

def waf_pickle_check(input_bytes: bytes) -> bool:
    """
    简单示例:若 pickle payload 中出现诸如 __reduce__, os.system, exec 等关键字,就认为可疑。
    """
    pattern = b'__reduce__|os\.system|eval|exec|subprocess'
    return not re.search(pattern, input_bytes)
QQ:2219349024
最后更新于 2025-03-18