awdp小waf合集
php
收集来源或启发
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
打包流程
- Post author: Str3am
- Post link: https://jlkl.github.io/2024/07/23/Java-11/
- Copyright Notice: All articles in this blog are licensed under BY-NC-SA unless stating additionally.
反编译成Maven项目
对比于手动编译打包,反编译成Maven项目更加方便快捷(Maven其实也就是干这个事)
手动仅使用原始Java进行反编译并打包可以参考:AWD离线-Jar文件冷补丁
下面记录反编译成Maven项目并进行修复的步骤
- 反编译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
- 导入源代码,创建一个空的Maven项目,然后将反编译后
/BOOT-INF/classes
下的源代码复制到/src/main/java
路径下。视check情况选择是否将resources
下资源文件一起导入
- 导入依赖库,将
BOOT-INF/lib
下的依赖复制到项目的lib文件文件夹下(没有这个文件夹,需要自己新建,一般在项目的第一级目录),在文件-项目结构-项目设置-库
中将lib文件夹添加为库文件夹,此步骤是解决在离线情况下缺少依赖的问题 - 用反编译出的
pom.xml
文件覆盖创建的Maven项目中的pom.xml
文件 - 修复代码并打包,修改Java源代码,或者根据漏洞点直接修改
pom.xml
文件中依赖的版本号,然后使用Maven进行打包,当然也可以直接在在IDEA中点点点。Springboot项目在pom.xml
内置了打包插件,所以选择Maven打包而不是构建成工件的jar
或者war
包的形式,需要根据实际情况选择如何打包
mvn clean package
以上展示的是打包一个完整的jar包的步骤,成功完成后会在/target
目录输出修复好的jar
包文件,直接-jar
运行即可,当然也可以直接选择使用压缩软件对编译好的class文件进行替换,看文章说Bandizip可以保证替换前后jar包和war包的可用性,使用Maczip测试确实出现如下报错
jar uf更新class文件
那么除了使用Bandizip还有其他方法可以更新class文件而不影响jar包和war包的可用性呢,答案是肯定的,可以使用jar uf
命令来更新class文件
需要注意在jar包内class文件的真实包名,如下图,这里需要在包名前添加BOOT-INF/classes/
前缀
同时注意根据包名创建文件夹,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/
路径问题
修改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
,就可以直接修改了
修改完成后,点击Save(Compile)
,编译并保存当前修改的java内容,最后点击Build Jar
,将编译保存的类文件写入Jar包中
经测试,目前好像没有修改未在jar包中class的功能,对于tomcat环境,可以将classes
文件夹压缩并修改为jar后缀即可修改
总结
几种方法优先推荐JarEditor修改jar包,其他思路比如JByteMod直接修改字节码因为普适性不高并没有做演示
同时也测试了arthas用于AWD修复jar包,但由于arthas采用agent原理,有些class并没有被jvm加载导致内存编译功能错误,还是更适用于应急响应以及bug诊断场景
Refference
- AWD离线-Jar文件冷补丁
- Jlan45/AWDJavaWebPatch: 通过jar包快速生成patch模版
- silentEAG/java-patch: awd/awdp 小工具,使用 javassist 对 jar 包进行 patch
- JarEditor插件:可直接编辑jar包的IDEA插件
- Liubsyy/JarEditor: IDEA plugin for directly editing and modifying files in jar without decompression. (一款无需解压直接编辑修改jar包内文件的IDEA插件)
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 多层编码
"漢字", # 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)
Comments 1 条评论
666