心血来潮学习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