AWDP-FIX 1 2 3 4 5 #!/bin/sh cat app.py > /app/app.py ps -ef | grep app|grep -v grep |awk '{print $2}' |xargs kill -9 python3 /app/app.py
通防
1 2 3 4 5 6 7 https://gi thub.com/leohearts/ awd-watchbird https://gi thub.com/sharpleung/ CTF-WAF https://gi thub.com/NonupleBroken/ AWD_PHP_WAF https://gi thub.com/DasSecurity-HatLab/ AoiAWD https://gi thub.com/dr0op/ k4l0ng_WAF https://gi thub.com/Drun1baby/ AWD-AWDP_SecFilters
php rce 黑名单 1 preg_match preg_replace、
1 2 3 4 5 6 7 8 9 10 11 12 13 $s tr1 =; foreach ($_ POST as $k ey => $v alue) { $s tr1.=$k ey; $s tr1.=$v alue; }$s tr2 =; foreach ($_ GET as $k ey => $v alue) { $s tr2.=$k ey; $s tr2.=$v alue; } if (preg_match(|\<|\{|\}|exec|base64|phpinfo|<\?|\, $s tr1)||preg_match(|\<|\{|\}|exec|base64|phpinfo|<\?|\, $s tr2)) { die('no!' ); }
1 2 3 4 5 6 7 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 ); }if (preg_match ('/system|tail|flag|exec|base64/i' , $_SERVER ['REQUEST_URI' ])) { die ('no!' ); }
无字母数字命令执行 这类 payload 不一定直接出现 cat、bash、sh 这些单词,更多是依赖反引号、$()、${}、重定向、管道、换行、通配符等符号去拼接和触发命令。
黑名单 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function wafrce_no_alnum_blacklist ($str ) { $value = rawurldecode (rawurldecode ((string )$str )); $blacklist = [ '`' , '$(' , '${' , '$$' , '$IFS' , '${IFS}' , '|' , '&' , ';' , '>' , '<' , '>>' , '<<' , '<<<' , "\n" , "\r" , "\t" , '*' , '?' , '[' , ']' , '{' , '}' , '\\' ]; foreach ($blacklist as $item ) { if (stripos ($value , $item ) !== false ) { return false ; } } return true ; }
正则 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function wafrce_no_alnum_regex($str) { $value = rawurldecode(rawurldecode((string)$str)); $pattern = '/ \$\(| \$\{[^}]+\} | `[^`]* `| [ |;&<>`] |\r |\n |\t | (?:^|[^a-z0-9])[~*?\[\]{}\\\/]{3,}(?:[^a-z0-9] |$) | ^(?=.* [`$(){}|&;<>~*?\[\]\\\/])[^a-z0-9]{4,}$ /ix'; return !preg_match($pattern, $value); }
用法 1 2 3 4 5 6 7 8 9 foreach ($_REQUEST as $key => $value ) { if (!wafrce_no_alnum_blacklist ($key ) || !wafrce_no_alnum_blacklist ($value )) { die ('no!' ); } if (!wafrce_no_alnum_regex ($key ) || !wafrce_no_alnum_regex ($value )) { die ('no!' ); } }
这种拦截误杀会比普通关键字黑名单更高,适合先顶在存在命令执行风险的参数入口前面做应急防护。
xss 黑名单 1 2 3 function wafxss ($str ) { return !preg_match ("/\'|http|\"|\`|cookie|<|>|script/i" , $str ); }
CSP策略 1 2 3 4 <?php header ("Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';" );
特殊字符转义 1 2 3 4 <?php function e ($value ) { return htmlspecialchars ((string )$value , ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); }
sql注入 黑名单 1 2 3 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 ); }
mysqli 预处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php $id = $_GET ['id' ] ?? '' ;$stmt = mysqli_prepare ($conn , "select * from users where id = ?" );mysqli_stmt_bind_param ($stmt , "s" , $id ); mysqli_stmt_execute ($stmt );$result = mysqli_stmt_get_result ($stmt );<?php $id = isset ($_GET ['id' ]) ? (int )$_GET ['id' ] : 0 ;$stmt = mysqli_prepare ($conn , "select * from users where id = ?" );mysqli_stmt_bind_param ($stmt , "i" , $id ); mysqli_stmt_execute ($stmt );$result = mysqli_stmt_get_result ($stmt );$stmt = $mysqli ->prepare ("select * from users where id = ?" );$stmt ->bind_param ("i" , $id );$stmt ->execute ();$result = $stmt ->get_result ();
PDO预处理 1 2 3 4 5 6 7 8 9 10 <?php $username = $_POST ['username' ] ?? '' ;$password = $_POST ['password' ] ?? '' ;$stmt = $pdo ->prepare ("select * from users where username = :username and password = :password" );$stmt ->execute ([ ':username' => $username , ':password' => $password ]);$user = $stmt ->fetch (PDO::FETCH_ASSOC );
addslashes() 函数
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。 预定义字符是:
单引号(’)
双引号(”)
反斜杠(\)
NULL
该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。
1 2 3 4 5 6 7 8 $username = $_GET ['username' ];$password = $_GET ['password' ];$username = addslashes ($username );$password = addslashes ($password );if (isset ($_GET ['username' ]) && isset ($_GET ['password' ])) { $sql = "SELECT * FROM users WHERE username = '$username ' AND password = '$password '" ;
放到漏洞点的最前面
1 2 3 4 5 foreach ($_REQUEST as $key =>$value ) { $_POST [$key ] = addslashes ($value ); $_GET [$key ] = addslashes ($value ); $_REQUEST [$key ] = addslashes ($value ); }
文件上传 黑名单 1 2 3 4 5 6 7 8 9 10 11 <?php $filename = $_FILES ['file' ]['name' ];if (preg_match ('/\.(php|php3|php4|php5|phtml|pht|phar|asp|aspx|jsp|jspx|htaccess|ini|conf)$/i' , $filename )) { die ('forbidden file' ); }<?php $content = file_get_contents ($_FILES ['file' ]['tmp_name' ]);if (preg_match ('/<\?(php|=)?|eval\s*\(|assert\s*\(|system\s*\(|exec\s*\(/i' , $content )) { die ('malicious file' ); }
白名单 1 2 3 4 5 6 7 8 9 10 <?php $x =explode ('.' ,$_FILES ['myfile' ]['name' ]);if ($x [sizeof ($x )-1 ]!='png' ){ die ("只允许png哦!<br>" ); }$allowTypes = [IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_GIF];$type = $_FILES ["myfile" ]["type" ];if (!in_array ($type , $allow_content_type )) { die ("只允许png哦!<br>" ); }
.htaccess (apache) 1 2 3 4 5 6 7 8 php_flag engine off Options -ExecCGIAddType text/plain .php .phtml .php3 .php4 .php5 .phar<IfModule mod_rewrite.c> RewriteEngine On RewriteRule \.ph.*$ - [F] </IfModule>
thinkphp框架防护 把这个waf直接上到public/index.php最前面
可以防住所有的tp框架漏洞
1 2 3 4 foreach ($_REQUEST as $key =>$value ) { $_POST [$key ] = preg_replace ("/construct|get|call_user_func|load|invokefunction|Session|phpinfo|param1|Runtime|assert|input|dump|checkcode|union|select|updatexml|@/i" ,'' ,$value ); $_GET [$key ] = preg_replace ("/construct|get|call_user_func|load|invokefunction|Session|phpinfo|param1|Runtime|assert|input|dump|checkcode|union|select|updatexml|@/i" ,'' ,$value ); }
python 1 2 3 4 5 6 7 8 9 10 11 12 13 def wafsqli (value ): blacklist = [ "select" , "union" , "and" , "or" , "from" , "where" , "join" , "sleep" , "benchmark" , "extractvalue" , "updatexml" , "insert" , "delete" , "update" , "drop" , ";" , "--" , "#" , "/*" , "*/" , "\"" , "'" , "=" , ">" , "<" ] value = str (value).lower() for item in blacklist: if item in value: return False return True
rce 黑名单 主要危险点一般出现在:
os.system
os.popen
subprocess.Popen
subprocess.call
eval
exec
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import refrom flask import requestdef wafrce (value ): pattern = r"system|exec|eval|subprocess|popen|os\.|cat|ls|flag|bash|sh|curl|wget|nc|bash -c|`|\$\(.*\)|\.\./" return not re.search(pattern, str (value), re.I)for key, value in request.args.items(): if not wafrce(key) or not wafrce(value): exit("no!" )for key, value in request.form.items(): if not wafrce(key) or not wafrce(value): exit("no!" )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import refrom flask import request str1 = "" for key, value in request.form.items(): str1 += str (key) str1 += str (value) str2 = "" for key, value in request.args.items(): str2 += str (key) str2 += str (value)if re.search(r"system|tail|flag|exec|base64|bash|sh|curl|wget|<\?|{|}" , str1, re.I) or \ re.search(r"system|tail|flag|exec|base64|bash|sh|curl|wget|<\?|{|}" , str2, re.I): exit("no!" )
xss 黑名单 1 2 3 4 import redef wafxss (value ): return not re.search(r"http|script|cookie|onerror|onload|alert|<|>|'|\"|`" , str (value), re.I)
Flask 中快速拦截:
1 2 3 4 5 6 7 8 9 from flask import requestfor key, value in request.args.items(): if not wafxss(key) or not wafxss(value): exit("blocked" )for key, value in request.form.items(): if not wafxss(key) or not wafxss(value): exit("blocked" )
CSP 策略 Flask:
1 2 3 4 5 6 7 8 from flask import Flask app = Flask(__name__)@app.after_request def add_csp (response ): response.headers["Content-Security-Policy" ] = "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';" return response
特殊字符转义 Python 里推荐直接用模板引擎自动转义,或者手动调用 html.escape。
1 2 3 4 import htmldef e (value ): return html.escape(str (value), quote=True )
使用:
1 2 name = request.args.get("name" , "" ) safe_name = e(name)
如果是 Flask + Jinja2,默认模板渲染会自动转义:
1 2 3 4 5 6 from flask import render_template_string, request@app.route("/" ) def index (): name = request.args.get("name" , "" ) return render_template_string("<div>{{ name }}</div>" , name=name)
sql注入 黑名单 1 2 3 4 5 import redef wafsqli (value ): pattern = r"select|union|and|or|from|where|join|sleep|benchmark|extractvalue|updatexml|insert|delete|update|drop|;|--|#|/\*|\*/|\"|'|=|>|<" return not re.search(pattern, str (value), re.I)
快速拦截:
1 2 3 4 5 6 7 8 9 from flask import requestfor key, value in request.args.items(): if not wafsqli(key) or not wafsqli(value): exit("blocked" )for key, value in request.form.items(): if not wafsqli(key) or not wafsqli(value): exit("blocked" )
pymysql 预处理 错误写法:
1 2 3 username = request.args.get("username" ) sql = "select * from users where username = '%s'" % username cursor.execute(sql)
安全写法:
1 2 3 4 5 6 import pymysql username = request.args.get("username" , "" ) sql = "select * from users where username = %s" cursor.execute(sql, (username,)) row = cursor.fetchone()
多个参数:
1 2 3 4 5 username = request.form.get("username" , "" ) password = request.form.get("password" , "" ) sql = "select * from users where username = %s and password = %s" cursor.execute(sql, (username, password)) user = cursor.fetchone()
sqlite3 预处理 1 2 3 4 5 6 7 8 import sqlite3 conn = sqlite3.connect("test.db" ) cursor = conn.cursor() username = "admin" cursor.execute("select * from users where username = ?" , (username,)) row = cursor.fetchone()
SQLAlchemy 预处理 1 2 3 4 5 6 from sqlalchemy import text username = request.form.get("username" , "" ) stmt = text("select * from users where username = :username" ) result = db.session.execute(stmt, {"username" : username}) row = result.fetchone()
强制类型转换 如果是数字参数:
1 2 id = int (request.args.get("id" , 0 )) cursor.execute("select * from users where id = %s" , (id ,))
SSTI (模板注入) 根本修复 禁止将用户输入直接拼接进模板字符串。
错误写法(危险):
1 2 content = request.args.get('content' )return render_template_string(content)
或者:
1 2 name = request.args.get('name' )return render_template_string('Hello ' + name)
安全写法:
1 2 name = request.args.get('name' )return render_template_string('Hello {{ name }}' , name=name)
或者使用 render_template 加载静态模板文件。
黑名单 如果不得不使用 render_template_string,可以使用黑名单过滤常见的 SSTI 关键字。
1 2 3 4 5 6 7 8 9 10 11 12 13 import refrom flask import abortdef wafssti (value ): pattern = r"\{\{|\}\}|\{%|%\}|\[|\]|class|mro|base|subclasses|init|globals|getitem|popen|os|sys|import|eval|exec|config|request" return not re.search(pattern, str (value), re.I)@app.before_request def check_ssti (): for key, value in request.values.items(): if not wafssti(key) or not wafssti(value): abort(403 )
node 1 2 3 4 5 6 7 const keywords = ["__proto__" , "constructor" , "prototype" , "insert" , "update" , "truncate" , "drop" , "create" , "\"" , "'" , " " , "{{" , "}}" , "union" , "select" , "delete" , "\"" , "'" , " " , "{{" , "}}" , "." , "{" , "}" , "flag" ]; input = input.toLowerCase ();for (const i of keywords) { if (input.includes (i)) { console .log ("Hacker!" ); } }
rce 黑名单 主要危险点一般出现在:
child_process.exec
child_process.execSync
child_process.spawn
eval
Function
1 2 3 4 5 const dangerousPattern = /exec|spawn|eval|bash|sh|curl|wget|nc|cat|ls|flag|\$\(|`|\.\.\//i ;function wafrce (value ) { return !dangerousPattern.test (String (value)); }
Express 中快速拦截:
1 2 3 4 5 6 7 8 9 10 11 function wafrce (value ) { return !/exec|spawn|eval|bash|sh|curl|wget|nc|cat|ls|flag|\$\(|`|\.\.\//i .test (String (value)); } app.use ((req, res, next ) => { const input = JSON .stringify (req.query ) + JSON .stringify (req.body ); if (!wafrce (input)) { return res.status (403 ).send ("blocked" ); } next (); });
如果必须执行系统命令,尽量不用拼接字符串:
1 2 3 4 5 6 7 8 const { execFile } = require ("child_process" );execFile ("/usr/bin/id" , ["www-data" ], (err, stdout ) => { if (err) { return ; } console .log (stdout); });
xss 黑名单 1 2 3 function wafxss (value ) { return !/script|onerror|onload|alert|cookie|<|>|'|"|`|javascript:/i .test (String (value)); }
Express 中快速拦截:
1 2 3 4 5 6 7 app.use ((req, res, next ) => { const input = JSON .stringify (req.query ) + JSON .stringify (req.body ); if (!wafxss (input)) { return res.status (403 ).send ("blocked" ); } next (); });
CSP策略 不依赖库:
1 2 3 4 5 6 7 app.use ((req, res, next ) => { res.setHeader ( "Content-Security-Policy" , "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';" ); next (); });
如果项目里已经用了 helmet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const helmet = require ("helmet" ); app.use ( helmet ({ contentSecurityPolicy : { directives : { defaultSrc : ["'self'" ], scriptSrc : ["'self'" ], objectSrc : ["'none'" ], baseUri : ["'self'" ] } } }) );
特殊字符转义 1 2 3 4 5 6 7 8 function e (value ) { return String (value) .replace (/&/g , "&" ) .replace (/</g , "<" ) .replace (/>/g , ">" ) .replace (/"/g , """ ) .replace (/'/g , "'" ); }
使用:
1 2 3 4 app.get ("/" , (req, res ) => { const name = e (req.query .name || "" ); res.send (`<div>${name} </div>` ); });
sql注入 黑名单 1 2 3 function wafsqli (value ) { return !/select|union|and|or|from|where|join|sleep|benchmark|extractvalue|updatexml|insert|delete|update|drop|;|--|#|\/\*|\*\/|'|"|=|>|</i .test (String (value)); }
快速拦截:
1 2 3 4 5 6 7 app.use ((req, res, next ) => { const input = JSON .stringify (req.query ) + JSON .stringify (req.body ); if (!wafsqli (input)) { return res.status (403 ).send ("blocked" ); } next (); });
mysql2 预处理 错误写法:
1 2 3 const username = req.query .username || "" ;const sql = `select * from users where username = '${username} '` ;const [rows] = await db.query (sql);
安全写法:
1 2 3 4 5 const username = req.query .username || "" ;const [rows] = await db.execute ( "select * from users where username = ?" , [username] );
多个参数:
1 2 3 4 5 6 const username = req.body .username || "" ;const password = req.body .password || "" ;const [rows] = await db.execute ( "select * from users where username = ? and password = ?" , [username, password] );
Sequelize 参数化查询 1 2 3 4 5 6 7 8 9 const { QueryTypes } = require ("sequelize" );const users = await sequelize.query ( "select * from users where username = :username" , { replacements : { username : req.query .username || "" }, type : QueryTypes .SELECT } );
强制类型转换 如果是数字参数:
1 2 const id = Number .parseInt (req.query .id || "0" , 10 );const [rows] = await db.execute ("select * from users where id = ?" , [id]);
原型链污染 拦截中间件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function wafPrototypePollution (req, res, next ) { const isObject = (obj ) => obj && typeof obj === 'object' && !Array .isArray (obj); const visited = new WeakSet (); const MAX_DEPTH = 20 ; const check = (obj, depth = 0 ) => { if (depth > MAX_DEPTH ) { return true ; } if (!isObject (obj)) return false ; if (visited.has (obj)) return false ; visited.add (obj); for (const key in obj) { if (key.includes ('__proto__' ) || key.includes ('constructor' ) || key.includes ('prototype' )) { return true ; } if (check (obj[key], depth + 1 )) { return true ; } } return false ; }; if (check (req.body ) || check (req.query ) || check (req.params )) { return res.status (403 ).send ('Forbidden' ); } next (); } app.use (wafPrototypePollution);
冻结 Object.prototype 1 Object .freeze (Object .prototype );
使用 Map 1 2 const map = new Map (); map.set ('key' , 'value' );
golang 1 2 3 4 5 6 7 8 9 10 11 12 import ( "fmt" "strings" ) filterList := []string {"union" , "select" , "delete" , "insert" , "update" , "truncate" , "drop" , "create" , "\"" , "'" , " " , "{{" , "}}" , "." , "{" , "}" , "flag" } input = strings.ToLower(input)for _, s := range filterList { if strings.Contains( input,s) { fmt.Println("Hacker!" ) } }
不依赖库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import ( "fmt" )func main () { var input string fmt.Print("请输入一个字符串:" ) fmt.Scanln(&input) maliciousStrings := []string {"union" , "select" , "delete" , "insert" , "update" , "truncate" , "drop" , "create" , "\"" , "'" , " " , "{{" , "}}" , "." , "{" , "}" , "flag" } if isMalicious(input, maliciousStrings) { return } }func isMalicious (input string , maliciousStrings []string ) bool { input = stringToLower(input) for _, s := range maliciousStrings { if stringContains(input, s) { return true } } return false }func stringToLower (str string ) string { runes := []rune (str) for i, r := range runes { if r >= 'A' && r <= 'Z' { runes[i] = r + ('a' - 'A' ) } } return string (runes) }func stringContains (str string , substr string ) bool { strRunes := []rune (str) substrRunes := []rune (substr) for i := 0 ; i <= len (strRunes)-len (substrRunes); i++ { found := true for j := 0 ; j < len (substrRunes); j++ { if strRunes[i+j] != substrRunes[j] { found = false break } } if found { return true } } return false }
rce 黑名单 主要危险点一般出现在:
os/exec.Command
exec.CommandContext
syscall.Exec
任何把用户输入直接拼到 shell 的场景
1 2 3 4 5 6 import "regexp" func wafrce (value string ) bool { pattern := regexp.MustCompile("(?i)exec|bash|sh|curl|wget|nc|cat|ls|flag|\\$\\(|`|\\.\\./" ) return !pattern.MatchString(value) }
net/http 中快速拦截:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import ( "net/http" "regexp" )func wafrce (value string ) bool { pattern := regexp.MustCompile("(?i)exec|bash|sh|curl|wget|nc|cat|ls|flag|\\$\\(|`|\\.\\./" ) return !pattern.MatchString(value) }func rceMiddleware (next http.Handler) http.Handler { return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { input := r.URL.RawQuery if !wafrce(input) { http.Error(w, "blocked" , http.StatusForbidden) return } next.ServeHTTP(w, r) }) }
如果必须执行命令,不要把用户输入直接拼进 shell:
1 2 3 4 5 6 cmd := exec.Command("/usr/bin/id" , "www-data" ) output, err := cmd.Output()if err != nil { return } fmt.Println(string (output))
xss 黑名单 1 2 3 4 5 6 import "regexp" func wafxss (value string ) bool { pattern := regexp.MustCompile(`(?i)script|onerror|onload|alert|cookie|<|>|'|"|javascript:` ) return !pattern.MatchString(value) }
CSP策略 1 2 3 4 5 6 func cspMiddleware (next http.Handler) http.Handler { return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Security-Policy" , "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';" ) next.ServeHTTP(w, r) }) }
特殊字符转义 Go 里优先使用 html/template,它默认会转义:
1 2 3 4 5 6 7 8 9 10 11 12 13 import ( "html/template" "net/http" )var tpl = template.Must(template.New("index" ).Parse(`<div>{{.Name}}</div>` ))func index (w http.ResponseWriter, r *http.Request) { data := map [string ]string { "Name" : r.URL.Query().Get("name" ), } tpl.Execute(w, data) }
如果是手动转义:
1 2 3 4 5 import "html" func e (value string ) string { return html.EscapeString(value) }
sql注入 黑名单 1 2 3 4 5 6 import "regexp" func wafsqli (value string ) bool { pattern := regexp.MustCompile(`(?i)select|union|and|or|from|where|join|sleep|benchmark|extractvalue|updatexml|insert|delete|update|drop|;|--|#|/\*|\*/|'|"|=|>|<` ) return !pattern.MatchString(value) }
快速拦截:
1 2 3 4 5 6 7 8 9 func sqliMiddleware (next http.Handler) http.Handler { return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) { if !wafsqli(r.URL.RawQuery) { http.Error(w, "blocked" , http.StatusForbidden) return } next.ServeHTTP(w, r) }) }
database/sql 预处理 错误写法:
1 2 3 username := r.URL.Query().Get("username" ) query := "select * from users where username = '" + username + "'" rows, _ := db.Query(query)
安全写法:
1 2 3 4 5 6 username := r.URL.Query().Get("username" ) rows, err := db.Query("select * from users where username = ?" , username)if err != nil { return }defer rows.Close()
多个参数:
1 2 3 4 5 6 7 username := r.FormValue("username" ) password := r.FormValue("password" ) row := db.QueryRow( "select * from users where username = ? and password = ?" , username, password, )
GORM 预处理 1 2 3 4 5 var user User err := db.Where("username = ?" , r.URL.Query().Get("username" )).First(&user).Errorif err != nil { return }
强制类型转换 如果是数字参数:
1 2 id, _ := strconv.Atoi(r.URL.Query().Get("id" )) row := db.QueryRow("select * from users where id = ?" , id)
java 改jar包
idea jareditor直接改
1 2 3 4 jar -xvf your_jar_name.jar javac -cp "..\lib\*" org\ota\MyApp.java jar -uvf util.jar MyApp.classjar -tvf util.jar
或者zip直接解压压缩
1 2 3 4 5 6 7 8 String [] filterList = {"apple" , "banana" , "cherry" };String str = "ana" ;for (String s : filterList) { if (s.contains (str )) { System.out .println ("Hacker!" ); } }
rce 黑名单 1 2 3 4 5 6 7 import java.util.regex.Pattern;public static boolean wafRce (String value) { String pattern = "(?i)exec|bash|sh|cmd|powershell|curl|wget|nc|whoami|cat|ls|flag|runtime|processbuilder" ; if (value == null ) return true ; return !Pattern.compile(pattern).matcher(value).find(); }
xss 黑名单 1 2 3 4 5 6 7 import java.util.regex.Pattern;public static boolean wafXss (String value) { String pattern = "(?i)script|onerror|onload|alert|cookie|<|>|'|\"|javascript:" ; if (value == null ) return true ; return !Pattern.compile(pattern).matcher(value).find(); }
特殊字符转义 Spring Boot / Thymeleaf 等框架默认会对输出进行 HTML 转义。
如果手动转义,可使用 StringEscapeUtils (需引入 commons-text):
1 2 3 4 5 import org.apache.commons.text.StringEscapeUtils;public String safe (String value) { return StringEscapeUtils.escapeHtml4(value); }
或者简单替换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static String htmlEncode (String s) { StringBuilder sb = new StringBuilder (); for (int i = 0 ; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case '<' : sb.append("<" ); break ; case '>' : sb.append(">" ); break ; case '&' : sb.append("&" ); break ; case '"' : sb.append(""" ); break ; case '\'' : sb.append("'" ); break ; default : sb.append(c); } } return sb.toString(); }
sql注入 黑名单 1 2 3 4 5 6 7 import java.util.regex.Pattern;public static boolean wafSqli (String value) { String pattern = "(?i)select|union|and|or|from|where|join|sleep|benchmark|extractvalue|updatexml|insert|delete|update|drop|;|--|#|/\\*|\\*/|'|\"|=|>|<" ; if (value == null ) return true ; return !Pattern.compile(pattern).matcher(value).find(); }
JDBC 预处理 错误写法:
1 2 3 4 String username = request.getParameter("username" );String sql = "select * from users where username = '" + username + "'" ;Statement stmt = connection.createStatement();ResultSet rs = stmt.executeQuery(sql);
安全写法:
1 2 3 4 5 String username = request.getParameter("username" );String sql = "select * from users where username = ?" ;PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setString(1 , username);ResultSet rs = pstmt.executeQuery();
MyBatis 预处理 使用 #{} 而不是 ${}。
错误写法:
1 2 3 <select id ="getUser" resultType ="User" > select * from users where username = ${username}</select >
安全写法:
1 2 3 <select id ="getUser" resultType ="User" > select * from users where username = #{username}</select >
java反序列化 Java 9+ (或 Java 8u121+) 支持。
1 2 3 4 5 6 7 8 import java.io.*;ObjectInputFilter filter = ObjectInputFilter.Config.createFilter( "java.util.HashMap;java.lang.*;!*" ); objectInputStream.setObjectInputFilter(filter);
重写 resolveClass 如果不支持 JEP 290,可以重写 ObjectInputStream 的 resolveClass 方法进行黑白名单校验。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import java.io.*;public class SafeObjectInputStream extends ObjectInputStream { public SafeObjectInputStream (InputStream in) throws IOException { super (in); } @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { String name = desc.getName(); if (name.contains("org.apache.commons.collections.functors" ) || name.contains("org.apache.xalan" ) || name.contains("java.lang.Runtime" ) || name.contains("java.lang.ProcessBuilder" )) { throw new InvalidClassException ("Unauthorized deserialization attempt" , name); } return super .resolveClass(desc); } }
使用:
1 2 3 FileInputStream fis = new FileInputStream ("object.ser" );SafeObjectInputStream ois = new SafeObjectInputStream (fis);Object obj = ois.readObject();