PHP-Audit-Labs-day14
从变量覆盖到getshell
题目源码
//content.php
<?php
include './global.php';
extract($_REQUEST);
$sql = "select * from test.content where id=$message_id";
$arr = select($sql);
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="./css/body.css" rel='stylesheet' type='text/css' />
<script src="main.js"></script>
</head>
<body>
<center>
<h1><?php echo $arr['0']['title']; ?></h1>
<?php echo $arr['0']['content']; ?>
</center>
</body>
</html>
//waf.php
<?php
foreach ($_GET as $key => $value) {
if ($key != "username"&&strstr($key, "password") == false) {
$_GET[$key] = filtering($value);
}
}
foreach ($_POST as $key => $value) {
if ($key != "username"&&strstr($key, "password") == false) {
$_POST[$key] = filtering($value);
}
}
foreach ($_REQUEST as $key => $value) {
if ($key != "username"&&strstr($key, "password") == false) {
$_REQUEST[$key] = ($value);
}
}
$request_uri = explode("?", $_SERVER['REQUEST_URI']);
if (isset($request_uri[1])) {
$rewrite_url = explode("&", $request_uri[1]);
foreach ($rewrite_url as $key => $value) {
$_value = explode("=", $value);
if (isset($_value[1])) {
$_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
}
}
}
foreach ($_POST as $key => $value) {
$_POST[$key] = safe_str($value);
$_GET[$key] = dhtmlspecialchars($value);
}
foreach ($_COOKIE as $key => $value) {
$_COOKIE[$key] = safe_str($value);
$_GET[$key] = dhtmlspecialchars($value);
}
?>
//function.php
<?php
include './config.php';
function select($sql){
$re = mysql_query($sql);
$arr = array();
while ($row = mysql_fetch_array($re)) {
$arr[] = $row;
}
return $arr;
}
function filtering($str) {
$check= eregi('select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile', $str);
if($check)
{
echo "非法字符!";
exit();
}
$newstr="";
while($newstr!=$str){
$newstr=$str;
$str = str_replace("script", "", $str);
$str = str_replace("execute", "", $str);
$str = str_replace("update", "", $str);
$str = str_replace("master", "", $str);
$str = str_replace("truncate", "", $str);
$str = str_replace("declare", "", $str);
$str = str_replace("select", "", $str);
$str = str_replace("create", "", $str);
$str = str_replace("delete", "", $str);
$str = str_replace("insert", "", $str);
$str = str_replace("\'", "", $str);
}
return $str;
}
function safe_str($str){
if(!get_magic_quotes_gpc()) {
if( is_array($str) ) {
foreach($str as $key => $value) {
$str[$key] = safe_str($value);
}
}else{
$str = addslashes($str);
}
}
return $str;
}
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;
}
?>
源码分析:
在content.php里,$message_id被直接拼接进sql语句,不过之前会过waf.php,在waf.php里,接受GET,POST,COOKIE三种传值方式,并且对各类变量的过滤方式不完全相同,COOKIE方式传值,只用了safe_str和dhtmlspecialchars两种方式过滤,而我们的sql注入语句并不会受到影响,所以这里可以直接注入
利用方法:
1.ereg函数的截断漏洞:
ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。ereg函数如果遇到%00则默认为字符串的结束,该函数在php5.3以上版本已经被弃用
2._REQUEST特性:
超全局数组 $_REQUEST 中的数据,是 $_GET 、 $_POST 、 $_COOKIE 的合集,而且数据是复制过去的,并不是引用。所以对 $_GET 、 $_POST 处理并不会影响 $_REQUEST 中的数据
payload:
message_id=0 union select 1,2,flag,4 from flag
使用cookie传递参数
还有一种解法,用的就是上面两个利用方法
可以通过 GET 或 POST 向 content.php 文件传递如下 payload 获取flag:
message_id=-1/*%00*/union/**/select/**/1,flag,3,4/**/from/**/flag
如果是 GET 方式传递数据的话,数据会经过 filtering 函数过滤,而在 filtering 函数中,开头的 eregi 检测,我们又可以使用 %00 截断绕过,但是下方还有循环替换恶意字符的代码,这里无法绕过。 filtering 函数代码如下:
也就是说我们的 payload 经过 filtering 函数处理后变成了下面这样( select 被过滤掉了):
-1/*%00*/union/**//**/1,flag,3,4/**/from/**/flag
当我们继续看代码时,会发现下面的代码又把 message_id 变量的值还原了。因为 content.php 文件中有代码:
extract($_REQUEST)
,所以这里也就造成了注入。那如果是 POST 方式传送数据,会先经过 filtering 函数处理,然后经过 safe_str 函数。 safe_str 函数主要用了 addslashes 函数过滤数据,可以发现对我们的 payload 并没有影响。 safe_str 函数代码如下:
这里能进行注入的原因,主要是因为超全局数组 $_REQUEST 中的数据,是 $_GET 、 $_POST 、 $_COOKIE 的合集,而且数据是复制过去的,并不是引用。所以对 $_GET 、 $_POST 处理并不会影响 $_REQUEST 中的数据。
——来自七月火师傅
拓展内容:
[红日安全]代码审计Day14 - 从变量覆盖到getshell:https://xz.aliyun.com/t/2911