OnlyBypassMe
题目描述:OnlyBypassMe
开题
先扫一下后台吧
/swagger-ui/index.html
/v3/api-docs
/swagger-ui/index.html
是一个管理api的UI界面,这边标题下面有一个登录接口文档/v3/api-docs/login
(应该是扫出来第二个敏感路由的一部分)
用于更新用户头像有两个接口。updateAvatarV1
版本已经被标记为废弃,参数是图片url,总感觉这边配合头像读取接口可以任意文件读取,但是没读出来。
/v3/api-docs
是个api文档。里面有UI那边没有的登录接口/api/v1/auth/login
。还有个更新用户权限的接口/api/v1/users/updatePermission
(后面都要用)
先注册一个账号/api/v1/users/register
{
"username": "Jay17",
"email": "3539306573@qq.com",
"password": "@Jsj123456",
"confirmPassword": "@Jsj123456"
}
然后登录/api/v1/auth/login
{
"username": "Jay17",
"password": "@Jsj123456",
"rememberMe": true
}
登录之后先尝试通过接口/api/v1/secret/flag/flag/
去读一下flag
发现权限不足
还有一个看用户信息的接口/api/v1/users/info
(是没写好吗?换一个号登录后info中还是最开始登录的用户的信息)
可以看到这边"roleId" : 4
应该标志着用户权限
咱们去之前提到的修改权限的接口修改下试试/api/v1/users/updatePermission
:
{
"userId": "bbpvfzlqaeoq",
"roleId": "3"
}
roleId
修改成3、2都可以,唯独1不行。(/api/v1/users/info
接口中的用户信息内容也不会更新,一开始还以为修改不了。。。。还好重新登录后可以看到权限)
这里大概能猜出,roleId
为1的时候就可以读取flag了,同时这里也就是需要bypass的地方。
1
不行的话使用1.
或者1.0
或者1.x
接口处得到flag
ezoldbuddy
题目描述:百万富翁的梦
开题,是一个登录框
这个登录没有用,纯前端的。
注释部分应该是个hint,告诉我们有个路由是/shopbytedancesdhjkf
直接访问是403无法访问,后端服务是nginx
题目提示了解析差异绕过
,比较容易找到这篇文章:
nginx deny限制路径绕过 - 先知社区 (aliyun.com)
85
和A0
都可以用,后端应该是python
那么现在问题就是,只有500元,但是需要买10000元的flag
先随便试试,但是返回了404
?????
看源码应该是访问了/cart/checkout
路由
有种无力感。。。这里卡了挺久的
但是,没有脑洞怎么当百万富翁?走投无路就要瞎蒙
直接访问/shopbytedancesdhjkf/cart/checkout
。好消息是路由存在,坏消息是又403了
和之前一样绕过,成功
但是买不起怎么办呢。想想怎么bypass。qty这个参数换成1.0
。成功但是没有给我flag
继续瞎蒙。想到题目描述是百万富翁,一个flag一万,那我们至少买100个以上。
美好的0元购
Bash Game*
题目描述:A easy bash game
放一下附件:
cmd.go
package cmd
// 导入需要的包
import (
"context" // 用于处理上下文(context),可以控制超时等操作
"os/exec" // 提供执行外部命令的功能
"time" // 用于时间相关操作,设置超时时间
)
// 定义一个常量,表示命令的超时时间为5秒
const ExecTimeout = 5 * time.Second
// 定义 Exec 函数,用于执行外部命令
// name: 要执行的命令名称
// arg: 命令的参数,使用变长参数以支持多个参数
// 返回值: ret 是命令执行的输出,err 是命令执行中的错误(如果有)
func Exec(name string, arg ...string) (ret string, err error) {
// 定义一个空的字节切片,用来存储命令的输出
var stdout = make([]byte, 0)
// 使用 context.WithTimeout 创建一个上下文,设置超时时间为5秒
// ctx: 代表上下文,用于控制命令执行
// cancel: 函数,用于在超时或任务完成后释放资源
ctx, cancel := context.WithTimeout(context.Background(), ExecTimeout)
// 确保函数返回前调用 cancel(),以释放相关资源
defer cancel()
// 使用 exec.CommandContext 来创建一个命令对象
// cmd: 代表要执行的外部命令
// name: 命令名称
// arg...: 命令的参数
cmd := exec.CommandContext(ctx, name, arg...)
// 执行命令,并将命令的输出存储在 stdout 中
// 如果执行过程中产生错误,err 会保存该错误
stdout, err = cmd.Output()
// 如果有错误发生,直接返回错误和空的输出
if err != nil {
return
}
// 将命令输出从字节切片转换为字符串
ret = string(stdout)
// 返回命令的输出(字符串)和可能的错误(如果有)
return
}
main.go
package main
import (
// 引入项目中的 cmd 包,该包可能包含了自定义的命令执行工具函数
"ByteCTF/lib/cmd"
// 引入 Gin Web 框架,用于处理 HTTP 请求
"github.com/gin-gonic/gin"
)
func main() {
// 初始化 Gin 实例,使用默认的日志和恢复中间件
r := gin.Default()
// 定义 POST 请求的处理函数,路径为 "/update"
r.POST("/update", func(c *gin.Context) {
// 调用 Update 函数处理请求,获取结果
result := Update(c)
// 返回结果给客户端,状态码为 200
c.String(200, result)
})
// 定义 GET 请求的处理函数,路径为 "/"
r.GET("/", func(c *gin.Context) {
// 返回 "Welcome to BashGame" 字符串给客户端,状态码为 200
c.String(200, "Welcome to BashGame")
})
// 监听并启动 HTTP 服务器,端口为 23333
r.Run(":23333")
}
// 定义两个常量,OpsPath 和 CtfPath,它们是 shell 脚本文件的路径
const OpsPath = "/opt/challenge/ops.sh"
const CtfPath = "/opt/challenge/ctf.sh"
// Update 函数处理 POST 请求中的数据并执行相关的 shell 脚本
func Update(c *gin.Context) string {
// 从 POST 请求中获取字段 "name" 的值,存储到 username 变量
username := c.PostForm("name")
// 如果 username 的长度小于 6,则执行 OpsPath 对应的 shell 脚本
if len(username) < 6 {
// 使用 cmd.Exec 函数执行 shell 脚本,并传递 username 参数
_, err := cmd.Exec("/bin/bash", OpsPath, username)
// 如果脚本执行出错,返回错误信息
if err != nil {
return err.Error()
}
}
// 无论 username 长度如何,都会执行 CtfPath 对应的 shell 脚本
ret, err := cmd.Exec("/bin/bash", CtfPath)
// 如果脚本执行出错,返回错误信息
if err != nil {
return err.Error()
}
// 将脚本的执行结果转换为字符串并返回
return string(ret)
}
ops.sh
#!/bin/bash
# -----------params------------
name=$1
switch_domain() {
conf_file="/opt/challenge/ctf.sh"
sed -i "s/Bytectf.*!/Bytectf, $name!/" "$conf_file"
}
# 调用函数
switch_domain
ctf.sh
#!/bin/bash
echo welcome to Bytectf, username!
简而言之就是在/update
路由处可以输入参数name
,长度限制<6。
其中name可以命令注入,比如$HOME
返回环境变量$HOME
的值,反引号+env+反引号
执行env命令。
当前目录就是根目录,没有flag文件。主要麻烦在长度限制太小了
linux只能3字符以内的。
$_
:在 Linux shell 中,$_
是一个特殊的变量,表示上一个命令的最后一个参数或输出。它常用于快速引用之前的命令中的最后一个参数。例如:
- 如果你执行
echo Hello
,那么$_
会变成Hello
。 - 如果你执行
ls /var/log
,接着输入echo $_
,它会输出/var/log
,因为这是上一条命令中的最后一个参数。
<
:重定向操作符,通常用来将一个文件的内容作为输入传递给一个命令。例如:
-
cat < file.txt
会将file.txt
的内容传递给cat
命令进行显示。
name=`<$_`
得到一个elf文件。看开头应该是和flag有关了
ezauth*
题目描述:please check in~
先看下附件,是go
package main
import (
"crypto/rand"
"errors"
"net/http"
"os"
"time"
"github.com/RobotsAndPencils/go-saml"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
)
// 定义自定义的JWT声明结构体,包含标准的声明和用户名
type MyCustomClaims struct {
jwt.StandardClaims
Username string `json:"username"`
}
// 生成随机字符串函数,n是字符串长度
func generateRandomString(n int) string {
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
bytes := make([]byte, n) // 创建长度为n的字节数组
if _, err := rand.Read(bytes); err != nil {
return "" // 如果生成随机字节失败,则返回空字符串
}
for i, b := range bytes {
bytes[i] = letters[b%byte(len(letters))] // 将字节映射到字母表中
}
return string(bytes) // 返回生成的随机字符串
}
// 定义全局变量secret_key(用于JWT签名的密钥)和random_code(随机验证码)
var secret_key = generateRandomString(20)
var random_code = generateRandomString(10)
func main() {
r := gin.Default() // 创建默认的Gin路由引擎
// 定义一个POST路由处理SAML断言请求
r.POST("/acs", func(c *gin.Context) {
// 配置SAML服务提供商的设置
sp := saml.ServiceProviderSettings{
PublicCertPath: "/usr/src/app/cert/default.crt", // 公钥证书路径
PrivateKeyPath: "/usr/src/app/cert/default.key", // 私钥路径
IDPSSOURL: "http://idp/saml2", // 身份提供商的SSO URL
IDPSSODescriptorURL: "http://idp/issuer", // 身份提供商的描述符URL
IDPPublicCertPath: "/usr/src/app/cert/idpcert.crt", // 身份提供商的公钥证书路径
SPSignRequest: true, // 是否签署请求
AssertionConsumerServiceURL: "http://localhost:8000/saml_consume", // SP的断言消费者服务URL
}
sp.Init() // 初始化SAML服务提供商设置
// 从POST表单中获取SAML响应
encodedXML := c.PostForm("SAMLResponse")
if encodedXML == "" {
// 如果SAML响应为空,返回错误
c.JSON(http.StatusBadRequest, gin.H{"error": "SAMLResponse form value missing"})
return
}
// 解析SAML响应
response, err := saml.ParseEncodedResponse(encodedXML)
if err != nil {
// 如果解析失败,返回错误
c.JSON(http.StatusBadRequest, gin.H{"error": "SAMLResponse parse: " + err.Error()})
return
}
// 验证SAML响应
err = response.Validate(&sp)
if err != nil {
// 如果验证失败,返回错误
c.JSON(http.StatusBadRequest, gin.H{"error": "SAMLResponse validation: " + err.Error()})
return
}
// 获取SAML响应中的用户标识符uid
samlID := response.GetAttribute("uid")
if samlID == "" {
// 如果uid缺失,返回错误
c.JSON(http.StatusBadRequest, gin.H{"error": "SAML attribute identifier uid missing"})
return
}
// 定义JWT声明,设置过期时间为7天
claims := MyCustomClaims{
jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Hour * 24 * 7).Unix(), // 过期时间
Issuer: "bytectf", // 发行者
},
"guest", // 用户名
}
// 使用HS256签名方法生成JWT令牌
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, _ := token.SignedString([]byte(secret_key)) // 使用密钥签名生成token字符串
// 返回生成的JWT令牌和随机验证码
c.JSON(http.StatusOK, gin.H{
"token": tokenString,
"code": random_code,
})
})
// 定义受JWT认证保护的路由组
auth := r.Group("/")
auth.Use(jwtauth()) // 使用JWT认证中间件
{
// 受保护的/admin路由
auth.GET("/admin", func(c *gin.Context) {
username, _ := c.Get("username") // 获取JWT中的用户名
code := c.Query("code") // 获取请求中的code参数
if username == "admin" && code == random_code {
// 如果用户名为admin且code匹配,返回flag文件内容
flag, _ := os.ReadFile("/flag.txt")
c.JSON(http.StatusOK, gin.H{
"message": "Welcome, admin! This is flag:" + string(flag),
})
} else {
// 否则返回未授权的错误
c.JSON(http.StatusUnauthorized, gin.H{
"message": "Unauthorized",
})
}
})
}
// 定义根路由,返回欢迎消息
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello ByteCTF 2024! Please check in.")
})
// 启动服务器,监听58080端口
r.Run(":58080")
}
// JWT认证中间件函数
func jwtauth() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization") // 获取请求头中的Authorization字段
if token == "" {
// 如果没有提供token,返回未授权
c.JSON(401, gin.H{
"message": "Unauthorized",
})
c.Abort() // 中断请求
return
}
claims := &MyCustomClaims{} // 初始化自定义声明结构
tk, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return []byte(secret_key), nil // 使用密钥解析JWT
})
if err != nil && !errors.Is(err, jwt.ErrTokenExpired) {
// 如果解析失败并且不是因为过期错误,返回错误信息
c.JSON(401, gin.H{
"message": err.Error(),
})
c.Abort()
return
}
// 获取当前时间和JWT过期时间
currentTime := time.Now().Unix()
expirationTime := claims.ExpiresAt
timeSinceExpiration := currentTime - expirationTime
// 如果token过期且在24小时内,重新生成token
if err != nil && errors.Is(err, jwt.ErrTokenExpired) {
if timeSinceExpiration >= 0 && timeSinceExpiration <= 86400 {
claims.ExpiresAt = time.Now().Add(time.Hour * 24 * 7).Unix() // 续签7天
tk = jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 重新生成JWT
newToken, _ := tk.SignedString([]byte(secret_key)) // 使用密钥签名新的token
c.Header("Authorization", newToken) // 将新的token添加到响应头
c.Abort()
return
} else {
// 如果token过期时间超过24小时,返回错误
c.JSON(401, gin.H{
"message": err.Error(),
})
c.Abort()
return
}
}
// 将用户名设置到Gin的上下文中供后续路由使用
c.Set("username", claims.Username)
c.Next() // 继续处理请求
}
}
ezobj*
题目描述:ezobj
开题,直接给了源码:
<?php
ini_set("display_errors", "On");
include_once("config.php");
if (isset($_GET['so']) && isset($_GET['key'])) {
if (is_numeric($_GET['so']) && $_GET['key'] === $secret) {
array_map(function($file) { echo $file . "\n"; }, glob('/tmp/*'));
putenv("LD_PRELOAD=/tmp/".$_GET['so'].".so");
}
}
if (isset($_GET['byte']) && isset($_GET['ctf'])) {
$a = new ReflectionClass($_GET['byte']);
$b = $a->newInstanceArgs($_GET['ctf']);
// echo $b;
} elseif (isset($_GET['clean'])){
array_map('unlink', glob('/tmp/*'));
} else {
highlight_file(__FILE__);
echo 'Hello ByteCTF2024!';
}
// phpinfo.html
CrossVue*
题目描述:小e同学刚学会vue后写了个简单的网页,并在管理员用户上放了你想要的flag,try to get it. 注:请选手尽可能先尝试本地环境做题,能成功拿到flag后再尝试远程靶机,以免因为远程环境可能存在的不稳定因素干扰解题。